Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow sqlify to optionally return an array of SQL commands #1780

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 11 additions & 9 deletions src/sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
15 changes: 10 additions & 5 deletions src/union.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
42 changes: 33 additions & 9 deletions test/ast.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
27 changes: 20 additions & 7 deletions test/cmd.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`']);
});
})
})
})
15 changes: 15 additions & 0 deletions test/postgres.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type WhilteListCheckMode = "table" | "column";
export interface Option {
database?: string;
type?: string;
asArray?: boolean;
}
export interface TableColumnAst {
tableList: string[];
Expand Down