From c4b1c55787d8438016851616eb1ddff60118131a Mon Sep 17 00:00:00 2001 From: George Pittarelli Date: Mon, 9 Apr 2018 17:03:42 -0700 Subject: [PATCH 1/3] Implement UPDATE --- src/sqlParser.jison | 64 +++++++++++++++++++++++++++++++++++++++------ src/stringify.js | 46 +++++++++++++++++++++++++++++--- test/main.test.js | 13 ++++++++- 3 files changed, 111 insertions(+), 12 deletions(-) diff --git a/src/sqlParser.jison b/src/sqlParser.jison index c9e29d0..66820df 100644 --- a/src/sqlParser.jison +++ b/src/sqlParser.jison @@ -10,7 +10,7 @@ [-][-]\s.*\n /* skip sql comments */ [#]\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' @@ -66,6 +66,7 @@ JOIN return 'JOIN' ORDER\s+BY return 'ORDER_BY' GROUP\s+BY return 'GROUP_BY' IGNORE return 'IGNORE' +LOW_PRIORITY return 'LOW_PRIORITY' FORCE return 'FORCE' INNER return 'INNER' CROSS return 'CROSS' @@ -82,6 +83,7 @@ WITH return 'WITH' ROLLUP return 'ROLLUP' HAVING return 'HAVING' OFFSET return 'OFFSET' +SET return 'SET' PROCEDURE return 'PROCEDURE' UPDATE return 'UPDATE' LOCK return 'LOCK' @@ -116,7 +118,7 @@ LIMIT return 'LIMIT' "{" return '{' "}" return '}' ";" return ';' - + ['](\\.|[^'])*['] return 'STRING' ["](\\.|[^"])*["] return 'STRING' [0][x][0-9a-fA-F]+ return 'HEX_NUMERIC' @@ -126,7 +128,7 @@ LIMIT return 'LIMIT' [a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]* return 'IDENTIFIER' \. return 'DOT' ['"][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["'] return 'QUOTED_IDENTIFIER' - + <> return 'EOF' . return 'INVALID' @@ -159,12 +161,58 @@ LIMIT return 'LIMIT' %% /* language grammar */ main - : selectClause EOF { return {nodeType: 'Main', value: $1}; } - | selectClause ';' EOF { return {nodeType: 'Main', value: $1, hasSemicolon: true}; } + : query EOF { return {nodeType: 'Main', value: $1}; } + | query ';' EOF { return {nodeType: 'Main', value: $1, hasSemicolon: true}; } + ; + +query + : selectClause + | updateClause + ; + +updateClause + : UPDATE low_priority_opt ignore_opt + table_refrences + SET + assignmentList + where_opt + order_by_opt + limit_opt + { + $$ = { + type: 'Update', + lowPriority: $2, + ignore: $3, + tables: $4, + assignments: $6, + where: $7, + orderBy: $8, + limit: $9 + } + } + ; + +low_priority_opt + : { $$ = null } + | LOW_PRIORITY { $$ = $1 } + ; + +ignore_opt + : { $$ = null } + | IGNORE { $$ = $1 } + ; + +assignment + : identifier '=' expr { $$ = { type: 'Assignment', left: $1, right: $3 } } + ; + +assignmentList + : assignmentList ',' assignment { $1.value.push($3); } + | assignment { $$ = { type: 'AssignmentList', value: [ $1 ] } } ; selectClause - : SELECT + : SELECT distinctOpt highPriorityOpt maxStateMentTimeOpt @@ -203,7 +251,7 @@ selectClause ; distinctOpt - : ALL { $$ = $1 } + : ALL { $$ = $1 } | DISTINCT { $$ = $1 } | DISTINCTROW { $$ = $1 } | { $$ = null } @@ -336,7 +384,7 @@ simple_expr ; bit_expr : simple_expr { $$ = $1 } - | bit_expr '|' bit_expr { $$ = { type: 'BitExpression', operator: '|', left: $1, right: $3 } } + | bit_expr '|' bit_expr { $$ = { type: 'BitExpression', operator: '|', left: $1, right: $3 } } | bit_expr '&' bit_expr { $$ = { type: 'BitExpression', operator: '&', left: $1, right: $3 } } | bit_expr '<<' bit_expr { $$ = { type: 'BitExpression', operator: '<<', left: $1, right: $3 } } | bit_expr '>>' bit_expr { $$ = { type: 'BitExpression', operator: '>>', left: $1, right: $3 } } diff --git a/src/stringify.js b/src/stringify.js index ac632e4..7e8244e 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -55,7 +55,13 @@ Sql.prototype.append = function(word, noPrefix, noSuffix) { } } Sql.prototype.travelMain = function(ast) { - this.travelSelect(ast.value); + if (ast.value.type === 'Select') { + this.travelSelect(ast.value); + } else if (ast.value.type === 'Update') { + this.travelUpdate(ast.value); + } else { + throw new Error('Unknown query value type'); + } if (ast.hasSemicolon) { this.append(';', true); } @@ -123,6 +129,40 @@ Sql.prototype.travelSelect = function(ast) { this.appendKeyword(ast.updateLockMode); } } +Sql.prototype.travelUpdate = function(ast) { + this.appendKeyword('update', true); + if (ast.lowPriority) { + this.appendKeyword('low_priority'); + } + if (ast.ignore) { + this.appendKeyword('ignore'); + } + this.travelTableRefrences(ast.tables); + this.appendKeyword('set'); + this.travelAssignments(ast.assignments); + if (ast.where) { + this.appendKeyword('where'); + this.travel(ast.where); + } + if (ast.orderBy) { + this.travel(ast.orderBy); + } + if (ast.limit) { + this.travel(ast.limit); + } +} +Sql.prototype.travelAssignments = function(ast) { + for (var i = 0; i < ast.value.length; i++) { + var x = ast.value[i]; + this.travelIdentifier(x.left); + this.travel('='); + this.travelIdentifier(x.right); + + if (i !== ast.value.length - 1) { + this.append(',', true); + } + } +} Sql.prototype.travelSelectExpr = function (ast) { var exprList = ast.value; for (var i = 0; i < exprList.length; i++) { @@ -161,8 +201,8 @@ Sql.prototype.travelXORExpression = function (ast) { this.appendKeyword(ast.operator); this.travel(ast.right); } -Sql.prototype.travelNull = -Sql.prototype.travelBoolean = +Sql.prototype.travelNull = +Sql.prototype.travelBoolean = Sql.prototype.travelBooleanExtra = function (ast) { this.appendKeyword(ast.value); } diff --git a/test/main.test.js b/test/main.test.js index cdad952..df2484e 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -136,5 +136,16 @@ AND (rd.rd_numberofrooms <= (select sum(rn.reservation_numberofrooms) as count_r it ('restore semicolon.', function () { testParser('select a from b limit 2;'); }); -}); + it('update0', function () { + testParser('update A SET b=2 WHERE c=3;'); + }); + + it('update1', function () { + testParser('update LOW_PRIORITY ignore A SET b=2, c=d WHERE e=4 ORDER BY d DESC limit 1;'); + }); + + it('update2', function () { + testParser('update LOW_PRIORITY ignore A, B, C SET b=2, `C`.c=d WHERE e=4 ORDER BY d DESC limit 1;'); + }); +}); From 84f4402011e0ffbad075c2c439246d5696c26122 Mon Sep 17 00:00:00 2001 From: George Pittarelli Date: Mon, 9 Apr 2018 18:28:52 -0700 Subject: [PATCH 2/3] Implement INSERT --- src/sqlParser.jison | 89 +++++++++++++++++++++++++++++++++++++++++++-- src/stringify.js | 64 +++++++++++++++++++++++++++++++- test/main.test.js | 43 ++++++++++++++++++++++ 3 files changed, 190 insertions(+), 6 deletions(-) diff --git a/src/sqlParser.jison b/src/sqlParser.jison index 66820df..704b6e3 100644 --- a/src/sqlParser.jison +++ b/src/sqlParser.jison @@ -15,6 +15,8 @@ [\w]+[\u4e00-\u9fa5]+[0-9a-zA-Z_\u4e00-\u9fa5]* return 'IDENTIFIER' [\u4e00-\u9fa5][0-9a-zA-Z_\u4e00-\u9fa5]* return 'IDENTIFIER' SELECT return 'SELECT' +INSERT return 'INSERT' +DEFAULT return 'DEFAULT' ALL return 'ALL' ANY return 'ANY' DISTINCT return 'DISTINCT' @@ -67,6 +69,8 @@ ORDER\s+BY return 'ORDER_ GROUP\s+BY return 'GROUP_BY' IGNORE return 'IGNORE' LOW_PRIORITY return 'LOW_PRIORITY' +DELAYED return 'DELAYED' +HIGH_PRIORITY return 'HIGH_PRIORITY' FORCE return 'FORCE' INNER return 'INNER' CROSS return 'CROSS' @@ -91,6 +95,12 @@ SHARE return 'SHARE' MODE return 'MODE' OJ return 'OJ' LIMIT return 'LIMIT' +INTO return 'INTO' +VALUE return 'VALUE' +VALUES return 'VALUES' +DUPLICATE return 'DUPLICATE' +KEY return 'KEY' +UPDATE return 'UPDATE' "," return ',' "=" return '=' @@ -168,13 +178,74 @@ main query : selectClause | updateClause + | insertClause + ; + +insertClause + : INSERT priority_opt ignore_opt + into_opt simple_table_factor + partitionOpt + insert_cols + insert_source + on_dup_assigns + { + $$ = { + type: 'Insert', + priority: $2, + ignore: $3, + into: $4, + table: $5, + partitions: $6, + cols: $7, + src: $8, + duplicateAssignments: $9 + } + } + ; + +insert_source + : insert_value value_list_list { $$ = { type: 'Values', keyword: $1, values: $2 } } + | selectClause + ; + +on_dup_assigns + : { $$ = null} + | ON DUPLICATE KEY UPDATE assignment_list { $$ = $5 } + ; + +insert_cols + : { $$ = null} + | '(' identifier_list ')' { $$ = $2 } + ; + +value + : expr | DEFAULT + ; + +value_list + : value_list ',' value { $1.value.push($3); } + | value { $$ = { type: 'ValueList', value: [ $1 ] } } + ; + +value_list_list + : value_list_list ',' '(' value_list ')' { $1.value.push($4); } + | '(' value_list ')' { $$ = { type: 'InsertList', value: [ $2 ] } } + ; + +insert_value + : VALUE | VALUES + ; + +into_opt + : { $$ = null } + | INTO { $$ = $1 } ; updateClause : UPDATE low_priority_opt ignore_opt table_refrences SET - assignmentList + assignment_list where_opt order_by_opt limit_opt @@ -197,6 +268,13 @@ low_priority_opt | LOW_PRIORITY { $$ = $1 } ; +priority_opt + : { $$ = null } + | LOW_PRIORITY { $$ = $1 } + | HIGH_PRIORITY { $$ = $1 } + | DELAYED { $$ = $1 } + ; + ignore_opt : { $$ = null } | IGNORE { $$ = $1 } @@ -206,8 +284,8 @@ assignment : identifier '=' expr { $$ = { type: 'Assignment', left: $1, right: $3 } } ; -assignmentList - : assignmentList ',' assignment { $1.value.push($3); } +assignment_list + : assignment_list ',' assignment { $1.value.push($3); } | assignment { $$ = { type: 'AssignmentList', value: [ $1 ] } } ; @@ -603,8 +681,11 @@ index_hint | IGNORE index_or_key for_opt '(' identifier_list ')' { $$ = { type: 'IgnoreIndexHint', value: $5, forOpt: $3, indexOrKey: $2 } } | FORCE index_or_key for_opt '(' identifier_list ')' { $$ = { type: 'ForceIndexHint', value: $5, forOpt: $3, indexOrKey: $2 } } ; -table_factor +simple_table_factor : identifier partitionOpt aliasOpt index_hint_list_opt { $$ = { type: 'TableFactor', value: $1, partition: $2, alias: $3.alias, hasAs: $3.hasAs, indexHintOpt: $4 } } + ; +table_factor + : simple_table_factor | '(' selectClause ')' aliasOpt { $$ = { type: 'SubQuery', value: $2, alias: $4.alias, hasAs: $4.hasAs } } | '(' table_refrences ')' { $$ = $2; $$.hasParentheses = true } ; diff --git a/src/stringify.js b/src/stringify.js index 7e8244e..9ff7e6c 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -59,6 +59,8 @@ Sql.prototype.travelMain = function(ast) { this.travelSelect(ast.value); } else if (ast.value.type === 'Update') { this.travelUpdate(ast.value); + } else if (ast.value.type === 'Insert') { + this.travelInsert(ast.value); } else { throw new Error('Unknown query value type'); } @@ -129,6 +131,64 @@ Sql.prototype.travelSelect = function(ast) { this.appendKeyword(ast.updateLockMode); } } +Sql.prototype.travelInsert = function(ast) { + this.appendKeyword('insert', true); + + if (ast.lowPriority) { + this.appendKeyword('low_priority'); + } + if (ast.ignore) { + this.appendKeyword('ignore'); + } + if (ast.into) { + this.appendKeyword('into'); + } + this.travelTableRefrence(ast.table); + if (ast.partitions) { + this.travelPartitions(ast.partitions); + } + if (ast.cols) { + this.travel('('); + this.travelIdentifierList(ast.cols); + this.travel(')'); + } + this.travel(ast.value); + if (ast.src.type === 'Select') { + this.travelSelect(ast.src); + } else if (ast.src.type === 'Values') { + this.travel(ast.src.keyword); + this.travelInsertRows(ast.src.values); + } + if (ast.duplicateAssignments) { + this.appendKeyword('ON'); + this.appendKeyword('DUPLICATE'); + this.appendKeyword('KEY'); + this.appendKeyword('UPDATE'); + this.travelAssignments(ast.duplicateAssignments); + } +} +Sql.prototype.travelInsertRows = function(ast) { + for (var i = 0; i < ast.value.length; i++) { + var x = ast.value[i]; + this.travel('('); + this.travelValueList(x.value); + this.travel(')'); + + if (i !== ast.value.length - 1) { + this.append(',', true); + } + } +} +Sql.prototype.travelValueList = function(ast) { + for (var i = 0; i < ast.length; i++) { + var x = ast[i]; + this.travel(x); + + if (i !== ast.length - 1) { + this.append(',', true); + } + } +} Sql.prototype.travelUpdate = function(ast) { this.appendKeyword('update', true); if (ast.lowPriority) { @@ -154,9 +214,9 @@ Sql.prototype.travelUpdate = function(ast) { Sql.prototype.travelAssignments = function(ast) { for (var i = 0; i < ast.value.length; i++) { var x = ast.value[i]; - this.travelIdentifier(x.left); + this.travel(x.left); this.travel('='); - this.travelIdentifier(x.right); + this.travel(x.right); if (i !== ast.value.length - 1) { this.append(',', true); diff --git a/test/main.test.js b/test/main.test.js index df2484e..5856aae 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -137,6 +137,10 @@ AND (rd.rd_numberofrooms <= (select sum(rn.reservation_numberofrooms) as count_r testParser('select a from b limit 2;'); }); + it ('subquery.', function () { + testParser('select a from (select 1 as x) b limit 2;'); + }); + it('update0', function () { testParser('update A SET b=2 WHERE c=3;'); }); @@ -148,4 +152,43 @@ AND (rd.rd_numberofrooms <= (select sum(rn.reservation_numberofrooms) as count_r it('update2', function () { testParser('update LOW_PRIORITY ignore A, B, C SET b=2, `C`.c=d WHERE e=4 ORDER BY d DESC limit 1;'); }); + + it('insert0', function () { + testParser('INSERT INTO A VALUES (a, `b`), (c, 3)'); + }); + + it('insert1', function () { + testParser('INSERT DELAYED IGNORE INTO A VALUE (a, DEFAULT, 3+2)'); + }); + + it('insert2', function () { + testParser('INSERT DELAYED IGNORE INTO A VALUE (a, `b`)'); + }); + + it('insert3', function () { + testParser('INSERT DELAYED IGNORE INTO A (`a`, b, c) VALUE (1, 2+3)'); + }); + + it('insert4', function () { + testParser('INSERT DELAYED IGNORE INTO `s0`.`A` PARTITION (p0, p1) (`a`, b, c) VALUE (1, 2+3)'); + }); + + it('insert5', function () { + testParser(` + INSERT DELAYED IGNORE INTO \`s0\`.\`A\` + PARTITION (p0, p1) (\`a\`, b, c) + VALUE (1, 2+3), (5, '6') + ON DUPLICATE KEY UPDATE q=q+1, c=b +`); + }); + + it('insert6', function () { + testParser(` + INSERT HIGH_PRIORITY \`massdrop\`.\`A\` + PARTITION (p0, p1) (\`a\`, b, c) + SELECT 1+1 as b, d + FROM B + ON DUPLICATE KEY UPDATE q=q+1, c=b +`); + }); }); From 8bc20b0abd378d8f9c0a2cfae94de1c895172e58 Mon Sep 17 00:00:00 2001 From: gpittarelli Date: Thu, 10 May 2018 19:19:44 -0700 Subject: [PATCH 3/3] Handle VALUES in ON DUPLICATE KEY UPDATE queries --- src/sqlParser.jison | 1 + test/main.test.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/sqlParser.jison b/src/sqlParser.jison index 704b6e3..be93081 100644 --- a/src/sqlParser.jison +++ b/src/sqlParser.jison @@ -406,6 +406,7 @@ literal ; function_call : IDENTIFIER '(' function_call_param_list ')' { $$ = {type: 'FunctionCall', name: $1, params: $3} } + | VALUES '(' function_call_param_list ')' { $$ = {type: 'FunctionCall', name: $1, params: $3} } ; function_call_param_list : function_call_param_list ',' function_call_param { $1.push($3); $$ = $1; } diff --git a/test/main.test.js b/test/main.test.js index 5856aae..e9422e4 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -189,6 +189,16 @@ AND (rd.rd_numberofrooms <= (select sum(rn.reservation_numberofrooms) as count_r SELECT 1+1 as b, d FROM B ON DUPLICATE KEY UPDATE q=q+1, c=b +`); + }); + + it('insert values', function () { + testParser(` + INSERT HIGH_PRIORITY \`massdrop\`.\`A\` + PARTITION (p0, p1) (\`a\`, b, c) + SELECT 1+1 as b, d + FROM B + ON DUPLICATE KEY UPDATE q=VALUES(\`q\`)+1 `); }); });