diff --git a/.gitignore b/.gitignore index c1119a3..1a8c027 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ package-lock.json *.log *.swp dist/parser/sqlParser.js +.vscode/ diff --git a/CHANGELOG b/CHANGELOG index 35c0dd0..dc5dd0a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,3 +10,4 @@ 1.3.0 fix tableFactor alias bug. AST changed in tableFactor. #34 1.4.0 fix bug `using ' & " for column alias?` #40 #44 1.4.1 hogfix "support quoted alias: multiple alias and orderby support" +1.5.0 support feature placeholder. diff --git a/Makefile b/Makefile index 51dd70d..ca658ba 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,10 @@ publish: test @npm publish -test: +test: @npm test +test-with-log: + @DEBUG=js-sql-parser npm test + .PHONY: publish test diff --git a/README.md b/README.md index ef48aab..e113d48 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html ## news +- Unicode extended char support for column name or alias & Function call in `table_factor` since v1.6.0 [#58](https://github.com/JavaScriptor/js-sql-parser/pull/58), [#60](https://github.com/JavaScriptor/js-sql-parser/issues/60) +- Support feature `PlaceHolder like ${param}` since v1.5.0 [#43](https://github.com/JavaScriptor/js-sql-parser/pull/43) - Fix bug `using ' & " for column alias?` since v1.4.1 [#40](https://github.com/JavaScriptor/js-sql-parser/issues/40), [#44](https://github.com/JavaScriptor/js-sql-parser/issues/44) - Fix bug tableFactor alias since v1.3.0 [#34](https://github.com/JavaScriptor/js-sql-parser/issues/34) - Add support for "`" quoted alias since v1.2.2. [#33](https://github.com/JavaScriptor/js-sql-parser/issues/33) @@ -35,6 +37,18 @@ console.log(parser.stringify(ast)); // SELECT foo FROM bar ``` +```js +// placeholder test +const parser = require('js-sql-parser'); +const ast = parser.parse('select ${a} as a'); + +ast['value']['selectItems']['value'][0]['value'] = "'value'"; +console.log(parser.stringify(ast)); +// SELECT 'value' AS a +``` + +Note: PlaceHolder is an `literal` value but not an `identifier`. Table_name / column_name / function_name are `identifier` thus should NOT be placed with placeholder. + ## script tag ```js @@ -60,10 +74,6 @@ var sql = sqlParser.stringify(ast); - intervalexpr: Date INTERVAL keyword. // to support - into outfile: INTO OUTFILE keyword. // to support -## TODO - -- ${value} like value place holder support. - ## Build - Run `npm run build` to build the distributable. diff --git a/package.json b/package.json index 23bf5b5..bd3083b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "js-sql-parser", - "version": "1.4.1", + "version": "1.6.0", "description": "", "main": "./dist/parser/sqlParser.js", "scripts": { diff --git a/src/sqlParser.jison b/src/sqlParser.jison index 86e13fc..271c94a 100644 --- a/src/sqlParser.jison +++ b/src/sqlParser.jison @@ -11,9 +11,10 @@ [#]\s.*\n /* skip sql comments */ \s+ /* skip whitespace */ -[`][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*[`] return 'IDENTIFIER' -[\w]+[\u4e00-\u9fa5]+[0-9a-zA-Z_\u4e00-\u9fa5]* return 'IDENTIFIER' -[\u4e00-\u9fa5][0-9a-zA-Z_\u4e00-\u9fa5]* return 'IDENTIFIER' +[$][{](.+?)[}] return 'PLACE_HOLDER' +[`][a-zA-Z0-9_\u0080-\uFFFF]*[`] return 'IDENTIFIER' +[\w]+[\u0080-\uFFFF]+[0-9a-zA-Z_\u0080-\uFFFF]* return 'IDENTIFIER' +[\u0080-\uFFFF][0-9a-zA-Z_\u0080-\uFFFF]* return 'IDENTIFIER' SELECT return 'SELECT' ALL return 'ALL' ANY return 'ANY' @@ -124,7 +125,7 @@ UNION return 'UNION' [-]?[0-9]+(\.[0-9]+)? return 'NUMERIC' [-]?[0-9]+(\.[0-9]+)?[eE][-][0-9]+(\.[0-9]+)? return 'EXPONENT_NUMERIC' -[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]* return 'IDENTIFIER' +[a-zA-Z_\u0080-\uFFFF][a-zA-Z0-9_\u0080-\uFFFF]* return 'IDENTIFIER' \. return 'DOT' ["][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["] return 'STRING' ['][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*['] return 'STRING' @@ -304,6 +305,7 @@ literal | number { $$ = $1 } | boolean { $$ = $1 } | null { $$ = $1 } + | place_holder { $$ = $1 } ; function_call : IDENTIFIER '(' function_call_param_list ')' { $$ = {type: 'FunctionCall', name: $1, params: $3} } @@ -586,4 +588,8 @@ table_factor : identifier partitionOpt aliasOpt index_hint_list_opt { $$ = { type: 'TableFactor', value: $1, partition: $2, alias: $3.alias, hasAs: $3.hasAs, indexHintOpt: $4 } } | '(' selectClause ')' aliasOpt { $$ = { type: 'TableFactor', value: { type: 'SubQuery', value: $2 }, alias: $4.alias, hasAs: $4.hasAs} } | '(' table_references ')' { $$ = $2; $$.hasParentheses = true } + | function_call aliasOpt index_hint_list_opt { $$ = { type: 'TableFactor', value: $1, alias: $2.alias, hasAs: $2.hasAs, indexHintOpt: $3 } } ; +place_holder + : PLACE_HOLDER { $$ = { type: 'PlaceHolder', value: $1, param: $1.slice(2, -1)} } + ; \ No newline at end of file diff --git a/src/stringify.js b/src/stringify.js index 57ea74e..4679850 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -551,3 +551,8 @@ Sql.prototype.travelSelectParenthesized = function(ast) { this.travel(ast.value); this.appendKeyword(')'); }; +Sql.prototype.travelPlaceHolder = function (ast) { + if (ast.value) { + this.travel(ast.value); + } +}; \ No newline at end of file diff --git a/test/main.test.js b/test/main.test.js index 188f57d..4bf50b6 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -2,6 +2,7 @@ const debug = require('debug')('js-sql-parser'); const parser = require('../'); +const assert = require('assert'); const testParser = function (sql) { let firstAst = parser.parse(sql); @@ -411,6 +412,24 @@ describe('select grammar support', function () { it('bugfix table alias2', function () { testParser('select a.* from a t1 join b t2 on t1.a = t2.a') }); + it('place holder support', function() { + testParser( + "select sum(quota_value) value, busi_col2 as sh, ${a} as a, YEAR(now()) from der_quota_summary where table_ename = 'gshmyyszje_derivedidx' and cd = (select id from t1 where a = ${t1})" + ) + }); + it('place holder support2', function() { + testParser( + "select sum(quota_value) b, busi_col2 as sh, '${a}' as a, YEAR(now()) from der_quota_summary where table_ename = 'gshmyyszje_derivedidx' and cd = (select id from t1 where a = '${t1}')" + ) + }); + it('place holder support3', function() { + let firstAst = parser.parse('select ${a} as a'); + firstAst['value']['selectItems']['value'][0]['value'] = "'value'"; + let firstSql = parser.stringify(firstAst); + debug(JSON.stringify(firstAst, null, 2)); + assert.equal(firstSql.trim().toUpperCase(), "select 'value' as a".toUpperCase()); + testParser(firstSql); + }); it('support quoted alias: multiple alias and orderby support', function () { testParser('select a as `A A`, b as `B B` from z'); @@ -431,4 +450,22 @@ describe('select grammar support', function () { it('test IDENTIFIER', function () { testParser('select `aa#sfs`(a) as \'A A\' from z'); }); + + it('Support unicode extended char (U+0080..U+FFFF) as column name or alias', function() { + testParser(`select + país, + MAX(produção) as maior_produção, + Ĉapelo, + Δάσος, + Молоко, + سلام, + かわいい + from table`) + }) + + it('#60 Call function in FROM clause', function() { + testParser(` + SELECT one.name, group_concat(j.value, ', ') FROM one, json_each(one.stringArray) AS j GROUP BY one.id + `) + }) });