diff --git a/src/formatter/index.ts b/src/formatter/index.ts index 427f3c34..fafaa3f5 100644 --- a/src/formatter/index.ts +++ b/src/formatter/index.ts @@ -4,9 +4,6 @@ import { INVALID_PARAMETERS } from "../common/errors"; const CHARS_GLOBAL_REGEXP = /[\0\b\t\n\r\x1a"'\\]/g; // eslint-disable-line no-control-regex -const COMMENTS_REGEXP = - /("(""|[^"])*")|('(''|[^'])*')|(--[^\n\r]*)|(\/\*[\w\W]*?(?=\*\/)\*\/)/gm; - const CHARS_ESCAPE_MAP: Record = { "\0": "\\0", "\b": "\\b", @@ -19,19 +16,6 @@ const CHARS_ESCAPE_MAP: Record = { "\\": "\\\\" }; -const removeComments = (query: string) => { - query = query.replace(COMMENTS_REGEXP, match => { - if ( - (match[0] === '"' && match[match.length - 1] === '"') || - (match[0] === "'" && match[match.length - 1] === "'") - ) - return match; - - return ""; - }); - return query; -}; - const zeroPad = (param: number, length: number, direction = "left") => { let paded = param.toString(); while (paded.length < length) { @@ -73,52 +57,40 @@ export class QueryFormatter { namedParams: Record ) { params = [...params]; - const regex = /''|""|``|\\\\|\\'|\\"|'|"|`|\?|::|:(\w+)/g; - - const STATE = { - WHITESPACE: 0, - SINGLE_QUOTE: 1, - DOUBLE_QUOTE: 2, - BACKTICK: 3 - }; - - const stateSwitches: Record = { - "'": STATE.SINGLE_QUOTE, - '"': STATE.DOUBLE_QUOTE, - "`": STATE.BACKTICK - }; - - let state = STATE.WHITESPACE; - - query = query.replace(regex, (str, paramName: string | undefined) => { - if (str in stateSwitches) { - if (state === STATE.WHITESPACE) { - state = stateSwitches[str]; - } else if (state === stateSwitches[str]) { - state = STATE.WHITESPACE; - } - } - - if (str === "?") { - if (state !== STATE.WHITESPACE) return str; - if (params.length == 0) { - throw new Error("Too few parameters given"); + // Matches: + // - ' strings with \ escapes + // - " strings with \ escapes + // - /* */ comments + // - -- comments + // - ? parameters + // - :: operator + // - :named parameters + const tokenizer = + /'(?:[^'\\]+|\\.)*'|"(?:[^"\\]+|\\.)*"|\/\*[\s\S]*\*\/|--.*|(\?)|::|:(\w+)/g; + + query = query.replace( + tokenizer, + (str, param: string | undefined, paramName: string | undefined) => { + if (param) { + if (params.length == 0) { + throw new Error("Too few parameters given"); + } + + return this.escape(params.shift()); } - return this.escape(params.shift()); - } else if (paramName) { - if (state !== STATE.WHITESPACE) return str; + if (paramName) { + if (!Object.prototype.hasOwnProperty.call(namedParams, paramName)) { + throw new Error(`Parameter named "${paramName}" not given`); + } - if (!Object.prototype.hasOwnProperty.call(namedParams, paramName)) { - throw new Error(`Parameter named "${paramName}" not given`); + return this.escape(namedParams[paramName]); } - return this.escape(namedParams[paramName]); - } else { return str; } - }); + ); if (params.length) { throw new Error("Too many parameters given"); @@ -270,7 +242,6 @@ export class QueryFormatter { parameters?: unknown[], namedParameters?: Record ): string { - query = removeComments(query); if (parameters || namedParameters) { if (parameters) { checkArgumentValid(Array.isArray(parameters), INVALID_PARAMETERS); diff --git a/test/unit/statement.test.ts b/test/unit/statement.test.ts index d47630c9..0337d8a3 100644 --- a/test/unit/statement.test.ts +++ b/test/unit/statement.test.ts @@ -22,14 +22,6 @@ describe("format query", () => { `"select 1 from table where bar = 2"` ); }); - it("format 3", () => { - const queryFormatter = new QueryFormatter(); - const query = "select * from table where bar = ? and foo = `some'?`"; - const formattedQuery = queryFormatter.formatQuery(query, [1]); - expect(formattedQuery).toMatchInlineSnapshot( - `"select * from table where bar = 1 and foo = \`some'?\`"` - ); - }); it("escape boolean", () => { const queryFormatter = new QueryFormatter(); const query = "select * from table where bar = ?;"; @@ -128,6 +120,15 @@ describe("format query", () => { expect(true).toEqual(true); } }); + it("format with comments in strings", () => { + const queryFormatter = new QueryFormatter(); + const query = + "SELECT 'str \\' ? -- not comment', /* comment? */ ? -- comment?"; + const formattedQuery = queryFormatter.formatQuery(query, ["foo"]); + expect(formattedQuery).toMatchInlineSnapshot( + `"SELECT 'str \\\\' ? -- not comment', /* comment? */ 'foo' -- comment?"` + ); + }); it("format tuple", () => { const queryFormatter = new QueryFormatter(); const query = "select foo from bar where foo in ?";