diff --git a/src/sqlParser.jison b/src/sqlParser.jison index 40b0631..f322390 100644 --- a/src/sqlParser.jison +++ b/src/sqlParser.jison @@ -89,6 +89,7 @@ SHARE return 'SHARE' MODE return 'MODE' OJ return 'OJ' LIMIT return 'LIMIT' +UNION return 'UNION' "," return ',' "=" return '=' @@ -159,8 +160,32 @@ LIMIT return 'LIMIT' %% /* language grammar */ main - : selectClause EOF { return {nodeType: 'Main', value: $1}; } - | selectClause ';' EOF { return {nodeType: 'Main', value: $1, hasSemicolon: true}; } + : selectClause semicolonOpt EOF { return {nodeType: 'Main', value: $1, hasSemicolon: $2}; } + | unionClause semicolonOpt EOF { return {nodeType: 'Main', value: $1, hasSemicolon: $2}; } + ; + +semicolonOpt + : ';' { $$ = true } + | { $$ = false } + ; + +unionClause + : unionClauseNotParenthesized { $$ = $1 } + | unionClauseParenthesized order_by_opt limit_opt { $$ = $1, $$.orderBy = $2, $$.limit = $3; } + ; + +unionClauseParenthesized + : selectClauseParenthesized UNION distinctOpt selectClauseParenthesized { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4 }; } + | selectClauseParenthesized UNION distinctOpt unionClauseParenthesized { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4 }; } + ; + +selectClauseParenthesized + : '(' selectClause ')' { $$ = { type: 'SelectParenthesized', value: $2 }; } + ; + +unionClauseNotParenthesized + : selectClause UNION distinctOpt selectClause { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4 } } + | selectClause UNION distinctOpt unionClauseNotParenthesized { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4 } } ; selectClause diff --git a/src/stringify.js b/src/stringify.js index fd1d289..338479e 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -1,3 +1,5 @@ +import { timingSafeEqual } from "crypto"; + if (!sqlParser) { sqlParser = {}; } @@ -55,13 +57,13 @@ Sql.prototype.append = function(word, noPrefix, noSuffix) { } } Sql.prototype.travelMain = function(ast) { - this.travelSelect(ast.value); + this.travel(ast.value); if (ast.hasSemicolon) { this.append(';', true); } } Sql.prototype.travelSelect = function(ast) { - this.appendKeyword('select', true); + this.appendKeyword('select'); if (ast.distinctOpt) { this.appendKeyword(ast.distinctOpt); } @@ -537,3 +539,16 @@ Sql.prototype.travelTableFactor = function (ast) { this.travel(ast.indexHintOpt); } } +Sql.prototype.travelUnion = function (ast) { + this.travel(ast.left); + this.appendKeyword('UNION'); + if (ast.distinctOpt) { + this.appendKeyword(ast.distinctOpt) + } + this.travel(ast.right); +} +Sql.prototype.travelSelectParenthesized = function (ast) { + this.appendKeyword('('); + this.travel(ast.value); + this.appendKeyword(')'); +} diff --git a/test/main.test.js b/test/main.test.js index b4114ba..8908c22 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -5,9 +5,13 @@ const parser = require('../'); const testParser = function (sql) { let firstAst = parser.parse(sql); + debug(JSON.stringify(firstAst, null, 2)); let firstSql = parser.stringify(firstAst); + debug(firstSql); let secondAst = parser.parse(firstSql); + debug(parser.stringify(secondAst)); let secondSql = parser.stringify(secondAst); + debug(JSON.stringify(secondAst, null, 2)); if (firstSql !== secondSql) { console.log('firstSql', firstSql); @@ -15,9 +19,6 @@ const testParser = function (sql) { throw 'err firstSql don\'t equals secondSql. '; } - debug(JSON.stringify(secondAst, null, 2)); - debug(parser.stringify(secondAst)); - return secondAst; } @@ -360,5 +361,21 @@ describe('select grammar support', function () { testParser('SELECT COUNT(*) AS total, a b, b as c, c/2 d, d & e an FROM b'); }); + it ('union support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () { + testParser('select a from dual union select a from foo;'); + }); + + it ('union Parenthesized support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () { + testParser('(select a from dual) union (select a from foo) order by a desc limit 100, 100;'); + }); + + it ('union all support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () { + testParser('(select a from dual) union all (select a from foo) order by a limit 100'); + }); + + it ('union distinct support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () { + testParser('select a from dual order by a desc limit 1, 1 union distinct select a from foo order by a limit 1'); + }); + });