diff --git a/README.md b/README.md index a1fda1b7..cc6a4edd 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,19 @@ const sql = parser.sqlify(ast, opt); console.log(sql); // SELECT * FROM `t` ``` +OR you can pass a options object to the parser, and request an array of SQL statements to be returned instead of a string. This can be useful when your SQL might contain a semicolon causing a `split(';')` function to return invalid commands. + +```javascript +const opt = { + asArray: true +} +const { Parser } = require('node-sql-parser') +const parser = new Parser() +// opt only affect sqlify() but it doesn't hurt to pass it to astify() as well +const ast = parser.astify("SELECT * FROM t; SELECT x AS has_semis FROM y WHERE notes LIKE '%;%'", opt) +const sql = parser.sqlify(ast, opt) +console.log(JSON.stringify(sql)); // ["SELECT * FROM `t`","SELECT `x` AS `has_semis` FROM `y` WHERE `notes` LIKE '%;%'"] +``` ### Parse specified Database There two ways to parser the specified database. diff --git a/src/parser.js b/src/parser.js index 21a6440a..12da8986 100644 --- a/src/parser.js +++ b/src/parser.js @@ -10,8 +10,9 @@ class Parser { } sqlify(ast, opt = DEFAULT_OPT) { + const asArray = opt.asArray || false setParserOpt(opt) - return astToSQL(ast, opt) + return astToSQL(ast, asArray) } exprToSQL(expr, opt = DEFAULT_OPT) { diff --git a/src/sql.js b/src/sql.js index b5aa786f..da2e35dd 100644 --- a/src/sql.js +++ b/src/sql.js @@ -7,23 +7,25 @@ function checkSupported(expr) { if (!supportedTypes.includes(ast.type)) throw new Error(`${ast.type} statements not supported at the moment`) } -function toSQL(ast) { +function toSQL(ast, asArray) { if (Array.isArray(ast)) { ast.forEach(checkSupported) - return multipleToSQL(ast) + return multipleToSQL(ast, asArray) } checkSupported(ast) - return unionToSQL(ast) + const sql = unionToSQL(ast) + return asArray ? [sql] : sql } -function goToSQL(stmt) { +function goToSQL(stmt, asArray) { if (!stmt || stmt.length === 0) return '' - const res = [toSQL(stmt.ast)] - if (stmt.go_next) res.push(stmt.go.toUpperCase(), goToSQL(stmt.go_next)) - return res.filter(sqlItem => sqlItem).join(' ') + const res = [toSQL(stmt.ast, asArray)] + if (stmt.go_next) res.push(stmt.go.toUpperCase(), goToSQL(stmt.go_next, asArray)) + const filteredRes = res.filter(sqlItem => sqlItem).flat(Infinity) + return asArray ? filteredRes : filteredRes.join(' ') } -export default function astToSQL(ast) { - const sql = ast.go === 'go' ? goToSQL(ast) : toSQL(ast) +export default function astToSQL(ast, asArray = false) { + const sql = ast.go === 'go' ? goToSQL(ast, asArray) : toSQL(ast, asArray) return sql } diff --git a/src/union.js b/src/union.js index a286b993..0e7d0b35 100644 --- a/src/union.js +++ b/src/union.js @@ -77,15 +77,20 @@ function unionToSQL(stmt) { return res.filter(hasVal).join(' ') } -function multipleToSQL(stmt) { +function processStatement(stmt, atEnd) { + const astInfo = stmt && stmt.ast ? stmt.ast : stmt + let sql = unionToSQL(astInfo) + if (atEnd && astInfo.type === 'transaction') sql = `${sql} ;` + return sql +} + +function multipleToSQL(stmt, asArray = false) { const res = [] for (let i = 0, len = stmt.length; i < len; ++i) { - const astInfo = stmt[i] && stmt[i].ast ? stmt[i].ast : stmt[i] - let sql = unionToSQL(astInfo) - if (i === len - 1 && astInfo.type === 'transaction') sql = `${sql} ;` + const sql = processStatement(stmt[i], i === len - 1) res.push(sql) } - return res.join(' ; ') + return asArray ? res : res.join(' ; ') } export { diff --git a/test/ast.spec.js b/test/ast.spec.js index 3623058c..876547a8 100644 --- a/test/ast.spec.js +++ b/test/ast.spec.js @@ -888,21 +888,45 @@ describe('AST', () => { }); describe('multiple statements', () => { + describe('return string', () => { it('should parser simple multiple statements', () => { - const sql = 'SELECT * FROM a;SELECT id FROM b' - const expectSQL = 'SELECT * FROM `a` ; SELECT `id` FROM `b`' - expect(getParsedSql(sql)).to.equal(expectSQL); + const sql = 'SELECT * FROM a;SELECT id FROM b' + const expectSQL = 'SELECT * FROM `a` ; SELECT `id` FROM `b`' + expect(getParsedSql(sql)).to.equal(expectSQL); }) it('should parser simple multiple statements with same type', () => { - const sql = 'SELECT * FROM a;SELECT id FROM b UNION SELECT id FROM c' - const expectSQL = 'SELECT * FROM `a` ; SELECT `id` FROM `b` UNION SELECT `id` FROM `c`' - expect(getParsedSql(sql)).to.equal(expectSQL); + const sql = 'SELECT * FROM a;SELECT id FROM b UNION SELECT id FROM c' + const expectSQL = 'SELECT * FROM `a` ; SELECT `id` FROM `b` UNION SELECT `id` FROM `c`' + expect(getParsedSql(sql)).to.equal(expectSQL); }) it('should parser simple multiple statements with different types', () => { - const sql = 'SELECT * FROM a;UPDATE b SET id = 1' - const expectSQL = 'SELECT * FROM `a` ; UPDATE `b` SET `id` = 1' - expect(getParsedSql(sql)).to.equal(expectSQL); + const sql = 'SELECT * FROM a;UPDATE b SET id = 1' + const expectSQL = 'SELECT * FROM `a` ; UPDATE `b` SET `id` = 1' + expect(getParsedSql(sql)).to.equal(expectSQL); }) + }) + describe('return array', () => { + it('should parser simple single statement', () => { + const sql = 'SELECT * FROM a' + const expectSQL = ['SELECT * FROM `a`'] + expect(getParsedSql(sql, {asArray: true})).deep.to.equal(expectSQL); + }) + it('should parser simple multiple statements', () => { + const sql = 'SELECT * FROM a;SELECT id FROM b' + const expectSQL = ['SELECT * FROM `a`', 'SELECT `id` FROM `b`'] + expect(getParsedSql(sql, {asArray: true})).deep.to.equal(expectSQL); + }) + it('should parser simple multiple statements with same type', () => { + const sql = 'SELECT * FROM a;SELECT id FROM b UNION SELECT id FROM c' + const expectSQL = ['SELECT * FROM `a`', 'SELECT `id` FROM `b` UNION SELECT `id` FROM `c`'] + expect(getParsedSql(sql, {asArray: true})).deep.to.equal(expectSQL); + }) + it('should parser simple multiple statements with different types', () => { + const sql = 'SELECT * FROM a;UPDATE b SET id = 1' + const expectSQL = ['SELECT * FROM `a`', 'UPDATE `b` SET `id` = 1'] + expect(getParsedSql(sql, {asArray: true})).deep.to.equal(expectSQL); + }) + }) }) describe('delete statements', () => { diff --git a/test/cmd.spec.js b/test/cmd.spec.js index 00ba33e6..a4120adc 100644 --- a/test/cmd.spec.js +++ b/test/cmd.spec.js @@ -464,13 +464,26 @@ describe('Command SQL', () => { }) describe('go', () => { - it(`should support go`, () => { - expect(getParsedSql('use abc go')).to.equal('USE `abc` GO'); - expect(getParsedSql('use abc; select * from abc go update abc set id = 1')).to.equal('USE `abc` ; SELECT * FROM `abc` GO UPDATE `abc` SET `id` = 1'); - }); + describe('return strings', () => { + it(`should support go`, () => { + expect(getParsedSql('use abc go')).to.equal('USE `abc` GO'); + expect(getParsedSql('use abc; select * from abc go update abc set id = 1')).to.equal('USE `abc` ; SELECT * FROM `abc` GO UPDATE `abc` SET `id` = 1'); + }); - it(`should support multiple go`, () => { - expect(getParsedSql('use abc; select * from abc go update abc set id = 1 go select id from abc')).to.equal('USE `abc` ; SELECT * FROM `abc` GO UPDATE `abc` SET `id` = 1 GO SELECT `id` FROM `abc`'); - }); + it(`should support multiple go`, () => { + expect(getParsedSql('use abc; select * from abc go update abc set id = 1 go select id from abc')).to.equal('USE `abc` ; SELECT * FROM `abc` GO UPDATE `abc` SET `id` = 1 GO SELECT `id` FROM `abc`'); + }); + }) + describe('return arrays', () => { + const opt = { asArray: true } + it(`should support go`, () => { + expect(getParsedSql('use abc go', opt)).deep.to.equal(['USE `abc`', 'GO']); + expect(getParsedSql('use abc; select * from abc go update abc set id = 1', opt)).deep.to.equal(['USE `abc`', 'SELECT * FROM `abc`', 'GO', 'UPDATE `abc` SET `id` = 1']); + }); + + it(`should support multiple go`, () => { + expect(getParsedSql('use abc; select * from abc go update abc set id = 1 go select id from abc', opt)).deep.to.equal(['USE `abc`', 'SELECT * FROM `abc`', 'GO', 'UPDATE `abc` SET `id` = 1', 'GO', 'SELECT `id` FROM `abc`']); + }); + }) }) }) diff --git a/test/postgres.spec.js b/test/postgres.spec.js index c4f3ca97..cffb15e5 100644 --- a/test/postgres.spec.js +++ b/test/postgres.spec.js @@ -1412,6 +1412,21 @@ describe('Postgres', () => { } neatlyNestTestedSQL(SQL_LIST) + it('transaction arrays', () => { + const sql = `SELECT search_path FROM y; + BEGIN; SET search_path TO ht_hyt; COMMIT; + SELECT search_path FROM y;` + const expected = [ + 'SELECT "search_path" FROM "y"', + 'BEGIN', + 'SET search_path TO ht_hyt', + 'COMMIT', + 'SELECT "search_path" FROM "y"' + ] + const result = getParsedSql(sql, {asArray: true, ...opt}) + expect(result).deep.to.equal(expected) + }) + describe('tables to sql', () => { it('should parse object tables', () => { const ast = parser.astify(SQL_LIST[100].sql[0], opt) diff --git a/types.d.ts b/types.d.ts index ef3bb88e..ea6c68f5 100644 --- a/types.d.ts +++ b/types.d.ts @@ -13,6 +13,7 @@ export type WhilteListCheckMode = "table" | "column"; export interface Option { database?: string; type?: string; + asArray?: boolean; } export interface TableColumnAst { tableList: string[];