diff --git a/src/tokenizer.ts b/src/tokenizer.ts index bc3b10d..9d478a4 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -283,12 +283,7 @@ function scanParameter(state: State, dialect: Dialect): Token { } if (dialect === 'mssql') { - let nextChar: Char; - do { - nextChar = read(state); - } while (!isWhitespace(nextChar) && nextChar !== null); - - if (isWhitespace(nextChar)) unread(state); + while(isAlphaNumeric(peek(state))) read(state); const value = state.input.slice(state.start, state.position + 1); return { @@ -409,6 +404,10 @@ function isWhitespace(ch: Char): boolean { return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r'; } +function isAlphaNumeric(ch: Char): boolean { + return ch !== null && /\w/.test(ch); +} + function isString(ch: Char, dialect: Dialect): boolean { const stringStart: Char[] = dialect === 'mysql' ? ["'", '"'] : ["'"]; return stringStart.includes(ch); diff --git a/test/identifier/single-statement.spec.ts b/test/identifier/single-statement.spec.ts index 71f7fe9..30ec5e0 100644 --- a/test/identifier/single-statement.spec.ts +++ b/test/identifier/single-statement.spec.ts @@ -1368,18 +1368,18 @@ describe('identifier', () => { }); it('Should extract positional Parameters with trailing commas', () => { - const actual = identify('SELECT $1,$2 FROM foo', { + const actual = identify('SELECT $1,$2 FROM foo WHERE foo.id in ($3, $4)', { dialect: 'psql', strict: true, }); const expected = [ { start: 0, - end: 20, - text: 'SELECT $1,$2 FROM foo', + end: 45, + text: 'SELECT $1,$2 FROM foo WHERE foo.id in ($3, $4)', type: 'SELECT', executionType: 'LISTING', - parameters: ['$1', '$2'], + parameters: ['$1', '$2', '$3', '$4'], }, ]; @@ -1405,6 +1405,25 @@ describe('identifier', () => { expect(actual).to.eql(expected); }); + it('Should extract named Parameters with trailing commas', () => { + const actual = identify('SELECT * FROM Persons where x in (:one, :two, :three)', { + dialect: 'mssql', + strict: true, + }); + const expected = [ + { + start: 0, + end: 52, + text: 'SELECT * FROM Persons where x in (:one, :two, :three)', + type: 'SELECT', + executionType: 'LISTING', + parameters: [':one', ':two', ':three'], + }, + ]; + + expect(actual).to.eql(expected); + }); + it('Should extract question mark Parameters', () => { const actual = identify('SELECT * FROM Persons where x = ? and y = ? and a = ?', { dialect: 'mysql', diff --git a/test/tokenizer/index.spec.ts b/test/tokenizer/index.spec.ts index 5c7b4e3..0963189 100644 --- a/test/tokenizer/index.spec.ts +++ b/test/tokenizer/index.spec.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { scanToken } from '../../src/tokenizer'; import type { Dialect } from '../../src/defines'; +import { parse } from '../../src/parser'; describe('scan', () => { const initState = (input: string) => ({ @@ -353,6 +354,29 @@ describe('scan', () => { }; expect(actual).to.eql(expected); }); + + it('should not include trailing non-alphanumerics for mssql', () => { + [ + { + actual: scanToken(initState(':one,'), 'mssql'), + expected: { + type: 'parameter', + value: ':one', + start: 0, + end: 3, + }, + }, + { + actual: scanToken(initState(':two)'), 'mssql'), + expected: { + type: 'parameter', + value: ':two', + start: 0, + end: 3, + }, + }, + ].forEach(({ actual, expected }) => expect(actual).to.eql(expected)); + }); }); }); });