From 00559246a4486fa120162efa24cd4be3357db6a6 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:08:35 +0900 Subject: [PATCH 1/9] Implement statement hints in DMLs --- ast/ast.go | 12 +- ast/sql.go | 9 +- parser.go | 43 ++++-- testdata/input/dml/delete_with_hint.sql | 1 + testdata/input/dml/insert_with_hint.sql | 4 + testdata/input/dml/update_with_hint.sql | 2 + testdata/result/dml/delete_with_hint.sql.txt | 74 ++++++++++ testdata/result/dml/insert_with_hint.sql.txt | 131 ++++++++++++++++++ testdata/result/dml/update_with_hint.sql.txt | 107 ++++++++++++++ .../result/statement/!bad_hint_select.sql.txt | 48 +++---- .../result/statement/delete_with_hint.sql.txt | 74 ++++++++++ .../result/statement/insert_with_hint.sql.txt | 131 ++++++++++++++++++ .../result/statement/update_with_hint.sql.txt | 107 ++++++++++++++ 13 files changed, 698 insertions(+), 45 deletions(-) create mode 100644 testdata/input/dml/delete_with_hint.sql create mode 100644 testdata/input/dml/insert_with_hint.sql create mode 100644 testdata/input/dml/update_with_hint.sql create mode 100644 testdata/result/dml/delete_with_hint.sql.txt create mode 100644 testdata/result/dml/insert_with_hint.sql.txt create mode 100644 testdata/result/dml/update_with_hint.sql.txt create mode 100644 testdata/result/statement/delete_with_hint.sql.txt create mode 100644 testdata/result/statement/insert_with_hint.sql.txt create mode 100644 testdata/result/statement/update_with_hint.sql.txt diff --git a/ast/ast.go b/ast/ast.go index c425d444..07e2821d 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -3957,16 +3957,18 @@ type ThenReturn struct { // Insert is INSERT statement node. // +// {{.Hint | sqlOpt}} // INSERT {{if .InsertOrType}}OR .InsertOrType{{end}}INTO {{.TableName | sql}} ({{.Columns | sqlJoin ","}}) {{.Input | sql}} // {{.ThenReturn | sqlOpt}} type Insert struct { - // pos = Insert + // pos = Hint.pos || Insert // end = (ThenReturn ?? Input).end Insert token.Pos // position of "INSERT" keyword InsertOrType InsertOrType + Hint *Hint // optional TableName *Path Columns []*Ident Input InsertInput @@ -4022,14 +4024,16 @@ type SubQueryInput struct { // Delete is DELETE statement. // +// {{.Hint | sqlOpt}} // DELETE FROM {{.TableName | sql}} {{.As | sqlOpt}} {{.Where | sql}} // {{.ThenReturn | sqlOpt}} type Delete struct { - // pos = Delete + // pos = Hint.pos || Delete // end = (ThenReturn ?? Where).end Delete token.Pos // position of "DELETE" keyword + Hint *Hint // optional TableName *Path As *AsAlias // optional Where *Where @@ -4038,15 +4042,17 @@ type Delete struct { // Update is UPDATE statement. // +// {{.Hint | sqlOpt}} // UPDATE {{.TableName | sql}} {{.As | sqlOpt}} // SET {{.Updates | sqlJoin ","}} {{.Where | sql}} // {{.ThenReturn | sqlOpt}} type Update struct { - // pos = Update + // pos = Hint.pos || Update // end = (ThenReturn ?? Where).end Update token.Pos // position of "UPDATE" keyword + Hint *Hint // optional TableName *Path As *AsAlias // optional Updates []*UpdateItem // len(Updates) > 0 diff --git a/ast/sql.go b/ast/sql.go index f110b0cf..92bb94b9 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -1306,7 +1306,8 @@ func (t *ThenReturn) SQL() string { } func (i *Insert) SQL() string { - return "INSERT " + + return sqlOpt("", i.Hint, " ") + + "INSERT " + strOpt(i.InsertOrType != "", "OR "+string(i.InsertOrType)+" ") + "INTO " + i.TableName.SQL() + " (" + sqlJoin(i.Columns, ", ") + @@ -1335,7 +1336,8 @@ func (s *SubQueryInput) SQL() string { } func (d *Delete) SQL() string { - return "DELETE FROM " + + return sqlOpt("", d.Hint, " ") + + "DELETE FROM " + d.TableName.SQL() + " " + sqlOpt("", d.As, " ") + d.Where.SQL() + @@ -1343,7 +1345,8 @@ func (d *Delete) SQL() string { } func (u *Update) SQL() string { - return "UPDATE " + u.TableName.SQL() + " " + + return sqlOpt("", u.Hint, " ") + + "UPDATE " + u.TableName.SQL() + " " + sqlOpt("", u.As, " ") + "SET " + sqlJoin(u.Updates, ", ") + diff --git a/parser.go b/parser.go index 2d46bdab..ab0085cf 100644 --- a/parser.go +++ b/parser.go @@ -150,6 +150,16 @@ func (p *Parser) ParseDMLs() ([]ast.DML, error) { return dmls, nil } +func (p *Parser) lookaheadTokenAfterOptionalHint() token.Token { + lexer := p.Lexer.Clone() + defer func() { + p.Lexer = lexer + }() + + p.tryParseHint() + return p.Token +} + func (p *Parser) parseStatement() (stmt ast.Statement) { l := p.Lexer.Clone() defer func() { @@ -158,20 +168,21 @@ func (p *Parser) parseStatement() (stmt ast.Statement) { } }() + tok := p.lookaheadTokenAfterOptionalHint() switch { - case p.Token.Kind == "SELECT" || p.Token.Kind == "@" || p.Token.Kind == "WITH" || p.Token.Kind == "(" || p.Token.Kind == "FROM": + case tok.Kind == "SELECT" || tok.Kind == "WITH" || tok.Kind == "(" || tok.Kind == "FROM": return p.parseQueryStatement() - case p.Token.Kind == "CREATE" || p.Token.IsKeywordLike("ALTER") || p.Token.IsKeywordLike("DROP") || - p.Token.IsKeywordLike("RENAME") || p.Token.IsKeywordLike("GRANT") || p.Token.IsKeywordLike("REVOKE") || - p.Token.IsKeywordLike("ANALYZE"): - return p.parseDDL() - case p.Token.IsKeywordLike("INSERT") || p.Token.IsKeywordLike("DELETE") || p.Token.IsKeywordLike("UPDATE"): + case tok.IsKeywordLike("INSERT") || tok.IsKeywordLike("DELETE") || tok.IsKeywordLike("UPDATE"): return p.parseDML() - case p.Token.IsKeywordLike("CALL"): + case tok.Kind == "CREATE" || tok.IsKeywordLike("ALTER") || tok.IsKeywordLike("DROP") || + tok.IsKeywordLike("RENAME") || tok.IsKeywordLike("GRANT") || tok.IsKeywordLike("REVOKE") || + tok.IsKeywordLike("ANALYZE"): + return p.parseDDL() + case tok.IsKeywordLike("CALL"): return p.parseOtherStatement() } - panic(p.errorfAtToken(&p.Token, "unexpected token: %s", p.Token.Kind)) + panic(p.errorfAtToken(&tok, "unexpected token: %s", tok.Kind)) } func (p *Parser) parseOtherStatement() ast.Statement { @@ -4995,15 +5006,16 @@ func (p *Parser) parseDML() (dml ast.DML) { } }() + hint := p.tryParseHint() id := p.expect(token.TokenIdent) pos := id.Pos switch { case id.IsKeywordLike("INSERT"): - return p.parseInsert(pos) + return p.parseInsert(pos, hint) case id.IsKeywordLike("DELETE"): - return p.parseDelete(pos) + return p.parseDelete(pos, hint) case id.IsKeywordLike("UPDATE"): - return p.parseUpdate(pos) + return p.parseUpdate(pos, hint) } panic(p.errorfAtToken(id, "expect pseudo keyword: INSERT, DELETE, UPDATE but: %s", id.AsString)) @@ -5042,7 +5054,7 @@ func (p *Parser) tryParseThenReturn() *ast.ThenReturn { } } -func (p *Parser) parseInsert(pos token.Pos) *ast.Insert { +func (p *Parser) parseInsert(pos token.Pos, hint *ast.Hint) *ast.Insert { var insertOrType ast.InsertOrType if p.Token.Kind == "OR" { p.nextToken() @@ -5087,6 +5099,7 @@ func (p *Parser) parseInsert(pos token.Pos) *ast.Insert { return &ast.Insert{ Insert: pos, + Hint: hint, InsertOrType: insertOrType, TableName: name, Columns: columns, @@ -5151,7 +5164,7 @@ func (p *Parser) parseSubQueryInput() *ast.SubQueryInput { } } -func (p *Parser) parseDelete(pos token.Pos) *ast.Delete { +func (p *Parser) parseDelete(pos token.Pos, hint *ast.Hint) *ast.Delete { if p.Token.Kind == "FROM" { p.nextToken() } @@ -5163,6 +5176,7 @@ func (p *Parser) parseDelete(pos token.Pos) *ast.Delete { return &ast.Delete{ Delete: pos, + Hint: hint, TableName: name, As: as, Where: where, @@ -5170,7 +5184,7 @@ func (p *Parser) parseDelete(pos token.Pos) *ast.Delete { } } -func (p *Parser) parseUpdate(pos token.Pos) *ast.Update { +func (p *Parser) parseUpdate(pos token.Pos, hint *ast.Hint) *ast.Update { name := p.parsePath() as := p.tryParseAsAlias(withOptionalAs) @@ -5183,6 +5197,7 @@ func (p *Parser) parseUpdate(pos token.Pos) *ast.Update { return &ast.Update{ Update: pos, + Hint: hint, TableName: name, As: as, Updates: items, diff --git a/testdata/input/dml/delete_with_hint.sql b/testdata/input/dml/delete_with_hint.sql new file mode 100644 index 00000000..cf4638fc --- /dev/null +++ b/testdata/input/dml/delete_with_hint.sql @@ -0,0 +1 @@ +@{pdml_max_parallelism=1} delete foo where foo = 1 and bar = 2 \ No newline at end of file diff --git a/testdata/input/dml/insert_with_hint.sql b/testdata/input/dml/insert_with_hint.sql new file mode 100644 index 00000000..7f55e0ed --- /dev/null +++ b/testdata/input/dml/insert_with_hint.sql @@ -0,0 +1,4 @@ +@{pdml_max_parallelism=1} +insert into foo (foo, bar, baz) +values (1, 2, 3), + (4, 5, 6) \ No newline at end of file diff --git a/testdata/input/dml/update_with_hint.sql b/testdata/input/dml/update_with_hint.sql new file mode 100644 index 00000000..f12abd84 --- /dev/null +++ b/testdata/input/dml/update_with_hint.sql @@ -0,0 +1,2 @@ +@{pdml_max_parallelism=1} +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 \ No newline at end of file diff --git a/testdata/result/dml/delete_with_hint.sql.txt b/testdata/result/dml/delete_with_hint.sql.txt new file mode 100644 index 00000000..a255d5b1 --- /dev/null +++ b/testdata/result/dml/delete_with_hint.sql.txt @@ -0,0 +1,74 @@ +--- delete_with_hint.sql +@{pdml_max_parallelism=1} delete foo where foo = 1 and bar = 2 +--- AST +&ast.Delete{ + Delete: 26, + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + TableName: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 33, + NameEnd: 36, + Name: "foo", + }, + }, + }, + Where: &ast.Where{ + Where: 37, + Expr: &ast.BinaryExpr{ + Op: "AND", + Left: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 43, + NameEnd: 46, + Name: "foo", + }, + Right: &ast.IntLiteral{ + ValuePos: 49, + ValueEnd: 50, + Base: 10, + Value: "1", + }, + }, + Right: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 55, + NameEnd: 58, + Name: "bar", + }, + Right: &ast.IntLiteral{ + ValuePos: 61, + ValueEnd: 62, + Base: 10, + Value: "2", + }, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} DELETE FROM foo WHERE foo = 1 AND bar = 2 diff --git a/testdata/result/dml/insert_with_hint.sql.txt b/testdata/result/dml/insert_with_hint.sql.txt new file mode 100644 index 00000000..463fac3e --- /dev/null +++ b/testdata/result/dml/insert_with_hint.sql.txt @@ -0,0 +1,131 @@ +--- insert_with_hint.sql +@{pdml_max_parallelism=1} +insert into foo (foo, bar, baz) +values (1, 2, 3), + (4, 5, 6) +--- AST +&ast.Insert{ + Insert: 26, + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + TableName: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 38, + NameEnd: 41, + Name: "foo", + }, + }, + }, + Columns: []*ast.Ident{ + &ast.Ident{ + NamePos: 43, + NameEnd: 46, + Name: "foo", + }, + &ast.Ident{ + NamePos: 48, + NameEnd: 51, + Name: "bar", + }, + &ast.Ident{ + NamePos: 53, + NameEnd: 56, + Name: "baz", + }, + }, + Input: &ast.ValuesInput{ + Values: 58, + Rows: []*ast.ValuesRow{ + &ast.ValuesRow{ + Lparen: 65, + Rparen: 73, + Exprs: []*ast.DefaultExpr{ + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 66, + ValueEnd: 67, + Base: 10, + Value: "1", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 69, + ValueEnd: 70, + Base: 10, + Value: "2", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 72, + ValueEnd: 73, + Base: 10, + Value: "3", + }, + }, + }, + }, + &ast.ValuesRow{ + Lparen: 83, + Rparen: 91, + Exprs: []*ast.DefaultExpr{ + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 84, + ValueEnd: 85, + Base: 10, + Value: "4", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 87, + ValueEnd: 88, + Base: 10, + Value: "5", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 90, + ValueEnd: 91, + Base: 10, + Value: "6", + }, + }, + }, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} INSERT INTO foo (foo, bar, baz) VALUES (1, 2, 3), (4, 5, 6) diff --git a/testdata/result/dml/update_with_hint.sql.txt b/testdata/result/dml/update_with_hint.sql.txt new file mode 100644 index 00000000..a8829eb0 --- /dev/null +++ b/testdata/result/dml/update_with_hint.sql.txt @@ -0,0 +1,107 @@ +--- update_with_hint.sql +@{pdml_max_parallelism=1} +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 +--- AST +&ast.Update{ + Update: 26, + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + TableName: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 33, + NameEnd: 36, + Name: "foo", + }, + }, + }, + Updates: []*ast.UpdateItem{ + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 41, + NameEnd: 44, + Name: "foo", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.Ident{ + NamePos: 47, + NameEnd: 50, + Name: "bar", + }, + }, + }, + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 52, + NameEnd: 55, + Name: "bar", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.Ident{ + NamePos: 58, + NameEnd: 61, + Name: "foo", + }, + }, + }, + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 63, + NameEnd: 66, + Name: "baz", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: 69, + Default: true, + }, + }, + }, + Where: &ast.Where{ + Where: 77, + Expr: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 83, + NameEnd: 86, + Name: "foo", + }, + Right: &ast.IntLiteral{ + ValuePos: 89, + ValueEnd: 90, + Base: 10, + Value: "1", + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} UPDATE foo SET foo = bar, bar = foo, baz = DEFAULT WHERE foo = 1 diff --git a/testdata/result/statement/!bad_hint_select.sql.txt b/testdata/result/statement/!bad_hint_select.sql.txt index 1db96187..320ce6ca 100644 --- a/testdata/result/statement/!bad_hint_select.sql.txt +++ b/testdata/result/statement/!bad_hint_select.sql.txt @@ -8,31 +8,29 @@ syntax error: testdata/input/query/!bad_hint_select.sql:1:3: expected token: {, --- AST -&ast.QueryStatement{ - Query: &ast.BadQueryExpr{ - BadNode: &ast.BadNode{ - NodeEnd: 10, - Tokens: []*token.Token{ - &token.Token{ - Kind: "@", - Raw: "@", - End: 1, - }, - &token.Token{ - Kind: "SELECT", - Space: " ", - Raw: "select", - Pos: 2, - End: 8, - }, - &token.Token{ - Kind: "", - Space: " ", - Raw: "1", - Base: 10, - Pos: 9, - End: 10, - }, +&ast.BadStatement{ + BadNode: &ast.BadNode{ + NodeEnd: 10, + Tokens: []*token.Token{ + &token.Token{ + Kind: "@", + Raw: "@", + End: 1, + }, + &token.Token{ + Kind: "SELECT", + Space: " ", + Raw: "select", + Pos: 2, + End: 8, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 9, + End: 10, }, }, }, diff --git a/testdata/result/statement/delete_with_hint.sql.txt b/testdata/result/statement/delete_with_hint.sql.txt new file mode 100644 index 00000000..a255d5b1 --- /dev/null +++ b/testdata/result/statement/delete_with_hint.sql.txt @@ -0,0 +1,74 @@ +--- delete_with_hint.sql +@{pdml_max_parallelism=1} delete foo where foo = 1 and bar = 2 +--- AST +&ast.Delete{ + Delete: 26, + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + TableName: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 33, + NameEnd: 36, + Name: "foo", + }, + }, + }, + Where: &ast.Where{ + Where: 37, + Expr: &ast.BinaryExpr{ + Op: "AND", + Left: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 43, + NameEnd: 46, + Name: "foo", + }, + Right: &ast.IntLiteral{ + ValuePos: 49, + ValueEnd: 50, + Base: 10, + Value: "1", + }, + }, + Right: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 55, + NameEnd: 58, + Name: "bar", + }, + Right: &ast.IntLiteral{ + ValuePos: 61, + ValueEnd: 62, + Base: 10, + Value: "2", + }, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} DELETE FROM foo WHERE foo = 1 AND bar = 2 diff --git a/testdata/result/statement/insert_with_hint.sql.txt b/testdata/result/statement/insert_with_hint.sql.txt new file mode 100644 index 00000000..463fac3e --- /dev/null +++ b/testdata/result/statement/insert_with_hint.sql.txt @@ -0,0 +1,131 @@ +--- insert_with_hint.sql +@{pdml_max_parallelism=1} +insert into foo (foo, bar, baz) +values (1, 2, 3), + (4, 5, 6) +--- AST +&ast.Insert{ + Insert: 26, + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + TableName: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 38, + NameEnd: 41, + Name: "foo", + }, + }, + }, + Columns: []*ast.Ident{ + &ast.Ident{ + NamePos: 43, + NameEnd: 46, + Name: "foo", + }, + &ast.Ident{ + NamePos: 48, + NameEnd: 51, + Name: "bar", + }, + &ast.Ident{ + NamePos: 53, + NameEnd: 56, + Name: "baz", + }, + }, + Input: &ast.ValuesInput{ + Values: 58, + Rows: []*ast.ValuesRow{ + &ast.ValuesRow{ + Lparen: 65, + Rparen: 73, + Exprs: []*ast.DefaultExpr{ + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 66, + ValueEnd: 67, + Base: 10, + Value: "1", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 69, + ValueEnd: 70, + Base: 10, + Value: "2", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 72, + ValueEnd: 73, + Base: 10, + Value: "3", + }, + }, + }, + }, + &ast.ValuesRow{ + Lparen: 83, + Rparen: 91, + Exprs: []*ast.DefaultExpr{ + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 84, + ValueEnd: 85, + Base: 10, + Value: "4", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 87, + ValueEnd: 88, + Base: 10, + Value: "5", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.IntLiteral{ + ValuePos: 90, + ValueEnd: 91, + Base: 10, + Value: "6", + }, + }, + }, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} INSERT INTO foo (foo, bar, baz) VALUES (1, 2, 3), (4, 5, 6) diff --git a/testdata/result/statement/update_with_hint.sql.txt b/testdata/result/statement/update_with_hint.sql.txt new file mode 100644 index 00000000..a8829eb0 --- /dev/null +++ b/testdata/result/statement/update_with_hint.sql.txt @@ -0,0 +1,107 @@ +--- update_with_hint.sql +@{pdml_max_parallelism=1} +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 +--- AST +&ast.Update{ + Update: 26, + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + TableName: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 33, + NameEnd: 36, + Name: "foo", + }, + }, + }, + Updates: []*ast.UpdateItem{ + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 41, + NameEnd: 44, + Name: "foo", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.Ident{ + NamePos: 47, + NameEnd: 50, + Name: "bar", + }, + }, + }, + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 52, + NameEnd: 55, + Name: "bar", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: -1, + Expr: &ast.Ident{ + NamePos: 58, + NameEnd: 61, + Name: "foo", + }, + }, + }, + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 63, + NameEnd: 66, + Name: "baz", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: 69, + Default: true, + }, + }, + }, + Where: &ast.Where{ + Where: 77, + Expr: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 83, + NameEnd: 86, + Name: "foo", + }, + Right: &ast.IntLiteral{ + ValuePos: 89, + ValueEnd: 90, + Base: 10, + Value: "1", + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} UPDATE foo SET foo = bar, bar = foo, baz = DEFAULT WHERE foo = 1 From fd1976ea58736846a40c07cdbcfe609384992e98 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:24:48 +0900 Subject: [PATCH 2/9] Do make gen --- ast/pos.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ast/pos.go b/ast/pos.go index d370cc48..58ed11be 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -2007,7 +2007,7 @@ func (t *ThenReturn) End() token.Pos { } func (i *Insert) Pos() token.Pos { - return i.Insert + return posChoice(nodePos(wrapNode(i.Hint)), i.Insert) } func (i *Insert) End() token.Pos { @@ -2047,7 +2047,7 @@ func (s *SubQueryInput) End() token.Pos { } func (d *Delete) Pos() token.Pos { - return d.Delete + return posChoice(nodePos(wrapNode(d.Hint)), d.Delete) } func (d *Delete) End() token.Pos { @@ -2055,7 +2055,7 @@ func (d *Delete) End() token.Pos { } func (u *Update) Pos() token.Pos { - return u.Update + return posChoice(nodePos(wrapNode(u.Hint)), u.Update) } func (u *Update) End() token.Pos { From 8fde3422a477a100114214728e9d944db357e402 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:08:20 +0900 Subject: [PATCH 3/9] Add hint to bad nodes --- ast/ast.go | 11 ++++++--- ast/pos.go | 4 +-- ast/sql.go | 4 +-- parser.go | 71 +++++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 07e2821d..f6da0e2c 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -577,11 +577,12 @@ type BadNode struct { // BadStatement is a BadNode for Statement. // -// {{.BadNode | sql}} +// {{.Hint | sqlOpt}} {{.BadNode | sql}} type BadStatement struct { - // pos = BadNode.pos + // pos = (Hint ?? BadNode).pos // end = BadNode.end + Hint *Hint BadNode *BadNode } @@ -592,6 +593,7 @@ type BadQueryExpr struct { // pos = BadNode.pos // end = BadNode.end + Hint *Hint BadNode *BadNode } @@ -627,11 +629,12 @@ type BadDDL struct { // BadDML is a BadNode for DML. // -// {{.BadNode | sql}} +// {{.Hint | sqlOpt}} {{.BadNode | sql}} type BadDML struct { - // pos = BadNode.pos + // pos = (Hint ?? BadNode).pos // end = BadNode.end + Hint *Hint // optional BadNode *BadNode } diff --git a/ast/pos.go b/ast/pos.go index 58ed11be..d53ead76 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -15,7 +15,7 @@ func (b *BadNode) End() token.Pos { } func (b *BadStatement) Pos() token.Pos { - return nodePos(wrapNode(b.BadNode)) + return nodePos(nodeChoice(wrapNode(b.Hint), wrapNode(b.BadNode))) } func (b *BadStatement) End() token.Pos { @@ -55,7 +55,7 @@ func (b *BadDDL) End() token.Pos { } func (b *BadDML) Pos() token.Pos { - return nodePos(wrapNode(b.BadNode)) + return nodePos(nodeChoice(wrapNode(b.Hint), wrapNode(b.BadNode))) } func (b *BadDML) End() token.Pos { diff --git a/ast/sql.go b/ast/sql.go index 92bb94b9..530d84ae 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -172,12 +172,12 @@ func (b *BadNode) SQL() string { return sql } -func (b *BadStatement) SQL() string { return b.BadNode.SQL() } +func (b *BadStatement) SQL() string { return sqlOpt("", b.Hint, " ") + b.BadNode.SQL() } func (b *BadQueryExpr) SQL() string { return b.BadNode.SQL() } func (b *BadExpr) SQL() string { return b.BadNode.SQL() } func (b *BadType) SQL() string { return b.BadNode.SQL() } func (b *BadDDL) SQL() string { return b.BadNode.SQL() } -func (b *BadDML) SQL() string { return b.BadNode.SQL() } +func (b *BadDML) SQL() string { return sqlOpt("", b.Hint, " ") + b.BadNode.SQL() } // ================================================================================ // diff --git a/parser.go b/parser.go index ab0085cf..7322817d 100644 --- a/parser.go +++ b/parser.go @@ -163,26 +163,43 @@ func (p *Parser) lookaheadTokenAfterOptionalHint() token.Token { func (p *Parser) parseStatement() (stmt ast.Statement) { l := p.Lexer.Clone() defer func() { + // Panic on tryParseHint() if r := recover(); r != nil { stmt = &ast.BadStatement{BadNode: p.handleParseStatementError(r, l)} } }() - tok := p.lookaheadTokenAfterOptionalHint() + hint := p.tryParseHint() + return p.parseStatementInternal(hint) +} + +func (p *Parser) parseStatementInternal(hint *ast.Hint) (stmt ast.Statement) { + l := p.Lexer.Clone() + defer func() { + if r := recover(); r != nil { + stmt = &ast.BadStatement{ + Hint: hint, + BadNode: p.handleParseStatementError(r, l), + } + } + }() + switch { - case tok.Kind == "SELECT" || tok.Kind == "WITH" || tok.Kind == "(" || tok.Kind == "FROM": - return p.parseQueryStatement() - case tok.IsKeywordLike("INSERT") || tok.IsKeywordLike("DELETE") || tok.IsKeywordLike("UPDATE"): - return p.parseDML() - case tok.Kind == "CREATE" || tok.IsKeywordLike("ALTER") || tok.IsKeywordLike("DROP") || - tok.IsKeywordLike("RENAME") || tok.IsKeywordLike("GRANT") || tok.IsKeywordLike("REVOKE") || - tok.IsKeywordLike("ANALYZE"): + case p.Token.Kind == "SELECT" || p.Token.Kind == "WITH" || p.Token.Kind == "(" || p.Token.Kind == "FROM": + return p.parseQueryStatementInternal(hint) + case p.Token.IsKeywordLike("INSERT") || p.Token.IsKeywordLike("DELETE") || p.Token.IsKeywordLike("UPDATE"): + return p.parseDMLInternal(hint) + case hint != nil: + panic(p.errorfAtToken(&p.Token, "statement hint is only permitted before query or DML, but got: %s", p.Token.Raw)) + case p.Token.Kind == "CREATE" || p.Token.IsKeywordLike("ALTER") || p.Token.IsKeywordLike("DROP") || + p.Token.IsKeywordLike("RENAME") || p.Token.IsKeywordLike("GRANT") || p.Token.IsKeywordLike("REVOKE") || + p.Token.IsKeywordLike("ANALYZE"): return p.parseDDL() - case tok.IsKeywordLike("CALL"): + case p.Token.IsKeywordLike("CALL"): return p.parseOtherStatement() } - panic(p.errorfAtToken(&tok, "unexpected token: %s", tok.Kind)) + panic(p.errorfAtToken(&p.Token, "unexpected p.Token: %s", p.Token.Kind)) } func (p *Parser) parseOtherStatement() ast.Statement { @@ -241,8 +258,8 @@ func (p *Parser) parseQueryStatement() (stmt *ast.QueryStatement) { l := p.Lexer.Clone() defer func() { if r := recover(); r != nil { - // When parsing is failed on tryParseHint or tryParseWith, the result of these methods are discarded - // becasue they are concrete structs and we cannot fill them with *ast.BadNode. + // When parsing is failed on tryParseHint, the result of these methods are discarded + // because they are concrete structs and we cannot fill them with *ast.BadNode. stmt = &ast.QueryStatement{ Query: &ast.BadQueryExpr{BadNode: p.handleParseStatementError(r, l)}, } @@ -250,6 +267,21 @@ func (p *Parser) parseQueryStatement() (stmt *ast.QueryStatement) { }() hint := p.tryParseHint() + return p.parseQueryStatementInternal(hint) +} + +func (p *Parser) parseQueryStatementInternal(hint *ast.Hint) (stmt *ast.QueryStatement) { + l := p.Lexer.Clone() + defer func() { + if r := recover(); r != nil { + // When parsing is failed on tryParseWith, the result of these methods are discarded + // because they are concrete structs and we cannot fill them with *ast.BadNode. + stmt = &ast.QueryStatement{ + Query: &ast.BadQueryExpr{BadNode: p.handleParseStatementError(r, l)}, + } + } + }() + query := p.parseQueryExpr() return &ast.QueryStatement{ @@ -5001,12 +5033,27 @@ func (p *Parser) parseIfExists() bool { func (p *Parser) parseDML() (dml ast.DML) { l := p.Lexer.Clone() defer func() { + // Panic on tryParseHint() if r := recover(); r != nil { dml = &ast.BadDML{BadNode: p.handleParseStatementError(r, l)} } }() hint := p.tryParseHint() + return p.parseDMLInternal(hint) +} + +func (p *Parser) parseDMLInternal(hint *ast.Hint) (dml ast.DML) { + l := p.Lexer.Clone() + defer func() { + if r := recover(); r != nil { + dml = &ast.BadDML{ + Hint: hint, + BadNode: p.handleParseStatementError(r, l), + } + } + }() + id := p.expect(token.TokenIdent) pos := id.Pos switch { From 4f9034c5c71ce8fd2225576959c0826d523b3182 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:08:57 +0900 Subject: [PATCH 4/9] Update testdata --- .../input/ddl/!bad_create_table_with_hint.sql | 2 + testdata/input/dml/!bad_delete.sql | 1 + .../input/dml/!bad_delete_with_bad_hint.sql | 2 + testdata/input/dml/!bad_delete_with_hint.sql | 2 + .../input/dml/!bad_insert_with_bad_hint.sql | 4 + testdata/input/dml/!bad_insert_with_hint.sql | 4 + testdata/input/dml/!bad_update.sql | 1 + .../input/dml/!bad_update_with_bad_hint.sql | 2 + testdata/input/dml/!bad_update_with_hint.sql | 2 + .../!bad_call_cancel_query_with_hint.sql | 2 + .../ddl/!bad_create_table_with_hint.sql.txt | 123 ++++++++++ testdata/result/dml/!bad_delete.sql.txt | 94 ++++++++ .../dml/!bad_delete_with_bad_hint.sql.txt | 120 ++++++++++ .../result/dml/!bad_delete_with_hint.sql.txt | 120 ++++++++++ .../dml/!bad_insert_with_bad_hint.sql.txt | 220 ++++++++++++++++++ .../result/dml/!bad_insert_with_hint.sql.txt | 186 +++++++++++++++ testdata/result/dml/!bad_update.sql.txt | 78 +++++++ .../dml/!bad_update_with_bad_hint.sql.txt | 178 ++++++++++++++ .../result/dml/!bad_update_with_hint.sql.txt | 104 +++++++++ .../!bad_call_cancel_query_with_hint.sql.txt | 78 +++++++ .../!bad_create_table_with_hint.sql.txt | 109 +++++++++ testdata/result/statement/!bad_delete.sql.txt | 94 ++++++++ .../!bad_delete_with_bad_hint.sql.txt | 120 ++++++++++ .../statement/!bad_delete_with_hint.sql.txt | 120 ++++++++++ .../!bad_insert_with_bad_hint.sql.txt | 220 ++++++++++++++++++ .../statement/!bad_insert_with_hint.sql.txt | 186 +++++++++++++++ testdata/result/statement/!bad_update.sql.txt | 78 +++++++ .../!bad_update_with_bad_hint.sql.txt | 178 ++++++++++++++ .../statement/!bad_update_with_hint.sql.txt | 104 +++++++++ 29 files changed, 2532 insertions(+) create mode 100644 testdata/input/ddl/!bad_create_table_with_hint.sql create mode 100644 testdata/input/dml/!bad_delete.sql create mode 100644 testdata/input/dml/!bad_delete_with_bad_hint.sql create mode 100644 testdata/input/dml/!bad_delete_with_hint.sql create mode 100644 testdata/input/dml/!bad_insert_with_bad_hint.sql create mode 100644 testdata/input/dml/!bad_insert_with_hint.sql create mode 100644 testdata/input/dml/!bad_update.sql create mode 100644 testdata/input/dml/!bad_update_with_bad_hint.sql create mode 100644 testdata/input/dml/!bad_update_with_hint.sql create mode 100644 testdata/input/statement/!bad_call_cancel_query_with_hint.sql create mode 100644 testdata/result/ddl/!bad_create_table_with_hint.sql.txt create mode 100644 testdata/result/dml/!bad_delete.sql.txt create mode 100644 testdata/result/dml/!bad_delete_with_bad_hint.sql.txt create mode 100644 testdata/result/dml/!bad_delete_with_hint.sql.txt create mode 100644 testdata/result/dml/!bad_insert_with_bad_hint.sql.txt create mode 100644 testdata/result/dml/!bad_insert_with_hint.sql.txt create mode 100644 testdata/result/dml/!bad_update.sql.txt create mode 100644 testdata/result/dml/!bad_update_with_bad_hint.sql.txt create mode 100644 testdata/result/dml/!bad_update_with_hint.sql.txt create mode 100644 testdata/result/statement/!bad_call_cancel_query_with_hint.sql.txt create mode 100644 testdata/result/statement/!bad_create_table_with_hint.sql.txt create mode 100644 testdata/result/statement/!bad_delete.sql.txt create mode 100644 testdata/result/statement/!bad_delete_with_bad_hint.sql.txt create mode 100644 testdata/result/statement/!bad_delete_with_hint.sql.txt create mode 100644 testdata/result/statement/!bad_insert_with_bad_hint.sql.txt create mode 100644 testdata/result/statement/!bad_insert_with_hint.sql.txt create mode 100644 testdata/result/statement/!bad_update.sql.txt create mode 100644 testdata/result/statement/!bad_update_with_bad_hint.sql.txt create mode 100644 testdata/result/statement/!bad_update_with_hint.sql.txt diff --git a/testdata/input/ddl/!bad_create_table_with_hint.sql b/testdata/input/ddl/!bad_create_table_with_hint.sql new file mode 100644 index 00000000..4e9bbd8f --- /dev/null +++ b/testdata/input/ddl/!bad_create_table_with_hint.sql @@ -0,0 +1,2 @@ +@{unknown_hint=1} +create table tbl(pk int64 primary key) \ No newline at end of file diff --git a/testdata/input/dml/!bad_delete.sql b/testdata/input/dml/!bad_delete.sql new file mode 100644 index 00000000..19a76ae5 --- /dev/null +++ b/testdata/input/dml/!bad_delete.sql @@ -0,0 +1 @@ +delete foo filter foo = 1 and bar = 2 \ No newline at end of file diff --git a/testdata/input/dml/!bad_delete_with_bad_hint.sql b/testdata/input/dml/!bad_delete_with_bad_hint.sql new file mode 100644 index 00000000..46ff9bcb --- /dev/null +++ b/testdata/input/dml/!bad_delete_with_bad_hint.sql @@ -0,0 +1,2 @@ +@{invalid} +delete foo where foo = 1 and bar = 2 \ No newline at end of file diff --git a/testdata/input/dml/!bad_delete_with_hint.sql b/testdata/input/dml/!bad_delete_with_hint.sql new file mode 100644 index 00000000..f49530be --- /dev/null +++ b/testdata/input/dml/!bad_delete_with_hint.sql @@ -0,0 +1,2 @@ +@{pdml_max_parallelism=1} +delete foo filter foo = 1 and bar = 2 \ No newline at end of file diff --git a/testdata/input/dml/!bad_insert_with_bad_hint.sql b/testdata/input/dml/!bad_insert_with_bad_hint.sql new file mode 100644 index 00000000..29209eb8 --- /dev/null +++ b/testdata/input/dml/!bad_insert_with_bad_hint.sql @@ -0,0 +1,4 @@ +@{invalid} +insert foo (foo, bar, baz) +vales (1, 2, 3), + (4, 5, 6) \ No newline at end of file diff --git a/testdata/input/dml/!bad_insert_with_hint.sql b/testdata/input/dml/!bad_insert_with_hint.sql new file mode 100644 index 00000000..80b91ea6 --- /dev/null +++ b/testdata/input/dml/!bad_insert_with_hint.sql @@ -0,0 +1,4 @@ +@{pdml_max_parallelism=1} +insert foo (foo, bar, baz) +vales (1, 2, 3), + (4, 5, 6) \ No newline at end of file diff --git a/testdata/input/dml/!bad_update.sql b/testdata/input/dml/!bad_update.sql new file mode 100644 index 00000000..c52f6d6c --- /dev/null +++ b/testdata/input/dml/!bad_update.sql @@ -0,0 +1 @@ +update foo set invalid where foo = 1 \ No newline at end of file diff --git a/testdata/input/dml/!bad_update_with_bad_hint.sql b/testdata/input/dml/!bad_update_with_bad_hint.sql new file mode 100644 index 00000000..9e93b734 --- /dev/null +++ b/testdata/input/dml/!bad_update_with_bad_hint.sql @@ -0,0 +1,2 @@ +@{invalid} +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 diff --git a/testdata/input/dml/!bad_update_with_hint.sql b/testdata/input/dml/!bad_update_with_hint.sql new file mode 100644 index 00000000..f82763a3 --- /dev/null +++ b/testdata/input/dml/!bad_update_with_hint.sql @@ -0,0 +1,2 @@ +@{pdml_max_parallelism=1} +update foo set invalid where foo = 1 \ No newline at end of file diff --git a/testdata/input/statement/!bad_call_cancel_query_with_hint.sql b/testdata/input/statement/!bad_call_cancel_query_with_hint.sql new file mode 100644 index 00000000..0f1a6dd2 --- /dev/null +++ b/testdata/input/statement/!bad_call_cancel_query_with_hint.sql @@ -0,0 +1,2 @@ +@{unknown_hint=1} +CALL cancel_query("12345") \ No newline at end of file diff --git a/testdata/result/ddl/!bad_create_table_with_hint.sql.txt b/testdata/result/ddl/!bad_create_table_with_hint.sql.txt new file mode 100644 index 00000000..240fa68a --- /dev/null +++ b/testdata/result/ddl/!bad_create_table_with_hint.sql.txt @@ -0,0 +1,123 @@ +--- !bad_create_table_with_hint.sql +@{unknown_hint=1} +create table tbl(pk int64 primary key) +--- Error +syntax error: testdata/input/ddl/!bad_create_table_with_hint.sql:1:1: expected token: CREATE, , but: @ + 1| @{unknown_hint=1} + | ^ + + +--- AST +&ast.BadDDL{ + BadNode: &ast.BadNode{ + NodeEnd: 56, + Tokens: []*token.Token{ + &token.Token{ + Kind: "@", + Raw: "@", + End: 1, + }, + &token.Token{ + Kind: "{", + Raw: "{", + Pos: 1, + End: 2, + }, + &token.Token{ + Kind: "", + Raw: "unknown_hint", + AsString: "unknown_hint", + Pos: 2, + End: 14, + }, + &token.Token{ + Kind: "=", + Raw: "=", + Pos: 14, + End: 15, + }, + &token.Token{ + Kind: "", + Raw: "1", + Base: 10, + Pos: 15, + End: 16, + }, + &token.Token{ + Kind: "}", + Raw: "}", + Pos: 16, + End: 17, + }, + &token.Token{ + Kind: "CREATE", + Space: "\n", + Raw: "create", + Pos: 18, + End: 24, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "table", + AsString: "table", + Pos: 25, + End: 30, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "tbl", + AsString: "tbl", + Pos: 31, + End: 34, + }, + &token.Token{ + Kind: "(", + Raw: "(", + Pos: 34, + End: 35, + }, + &token.Token{ + Kind: "", + Raw: "pk", + AsString: "pk", + Pos: 35, + End: 37, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "int64", + AsString: "int64", + Pos: 38, + End: 43, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "primary", + AsString: "primary", + Pos: 44, + End: 51, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "key", + AsString: "key", + Pos: 52, + End: 55, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 55, + End: 56, + }, + }, + }, +} + +--- SQL +@{unknown_hint=1} create table tbl(pk int64 primary key) diff --git a/testdata/result/dml/!bad_delete.sql.txt b/testdata/result/dml/!bad_delete.sql.txt new file mode 100644 index 00000000..22d327b7 --- /dev/null +++ b/testdata/result/dml/!bad_delete.sql.txt @@ -0,0 +1,94 @@ +--- !bad_delete.sql +delete foo filter foo = 1 and bar = 2 +--- Error +syntax error: testdata/input/dml/!bad_delete.sql:1:19: expected token: WHERE, but: + 1| delete foo filter foo = 1 and bar = 2 + | ^~~ + + +--- AST +&ast.BadDML{ + BadNode: &ast.BadNode{ + NodeEnd: 37, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Raw: "delete", + AsString: "delete", + End: 6, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 7, + End: 10, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "filter", + AsString: "filter", + Pos: 11, + End: 17, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 18, + End: 21, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 22, + End: 23, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 24, + End: 25, + }, + &token.Token{ + Kind: "AND", + Space: " ", + Raw: "and", + Pos: 26, + End: 29, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 30, + End: 33, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 34, + End: 35, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 36, + End: 37, + }, + }, + }, +} + +--- SQL +delete foo filter foo = 1 and bar = 2 diff --git a/testdata/result/dml/!bad_delete_with_bad_hint.sql.txt b/testdata/result/dml/!bad_delete_with_bad_hint.sql.txt new file mode 100644 index 00000000..4c0191d3 --- /dev/null +++ b/testdata/result/dml/!bad_delete_with_bad_hint.sql.txt @@ -0,0 +1,120 @@ +--- !bad_delete_with_bad_hint.sql +@{invalid} +delete foo where foo = 1 and bar = 2 +--- Error +syntax error: testdata/input/dml/!bad_delete_with_bad_hint.sql:1:10: expected token: =, but: } + 1| @{invalid} + | ^ + + +--- AST +&ast.BadDML{ + BadNode: &ast.BadNode{ + NodeEnd: 47, + Tokens: []*token.Token{ + &token.Token{ + Kind: "@", + Raw: "@", + End: 1, + }, + &token.Token{ + Kind: "{", + Raw: "{", + Pos: 1, + End: 2, + }, + &token.Token{ + Kind: "", + Raw: "invalid", + AsString: "invalid", + Pos: 2, + End: 9, + }, + &token.Token{ + Kind: "}", + Raw: "}", + Pos: 9, + End: 10, + }, + &token.Token{ + Kind: "", + Space: "\n", + Raw: "delete", + AsString: "delete", + Pos: 11, + End: 17, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 18, + End: 21, + }, + &token.Token{ + Kind: "WHERE", + Space: " ", + Raw: "where", + Pos: 22, + End: 27, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 28, + End: 31, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 32, + End: 33, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 34, + End: 35, + }, + &token.Token{ + Kind: "AND", + Space: " ", + Raw: "and", + Pos: 36, + End: 39, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 40, + End: 43, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 44, + End: 45, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 46, + End: 47, + }, + }, + }, +} + +--- SQL +@{invalid} delete foo where foo = 1 and bar = 2 diff --git a/testdata/result/dml/!bad_delete_with_hint.sql.txt b/testdata/result/dml/!bad_delete_with_hint.sql.txt new file mode 100644 index 00000000..25ac513c --- /dev/null +++ b/testdata/result/dml/!bad_delete_with_hint.sql.txt @@ -0,0 +1,120 @@ +--- !bad_delete_with_hint.sql +@{pdml_max_parallelism=1} +delete foo filter foo = 1 and bar = 2 +--- Error +syntax error: testdata/input/dml/!bad_delete_with_hint.sql:2:19: expected token: WHERE, but: + 2| delete foo filter foo = 1 and bar = 2 + | ^~~ + + +--- AST +&ast.BadDML{ + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + BadNode: &ast.BadNode{ + NodePos: 26, + NodeEnd: 63, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Space: "\n", + Raw: "delete", + AsString: "delete", + Pos: 26, + End: 32, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 33, + End: 36, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "filter", + AsString: "filter", + Pos: 37, + End: 43, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 44, + End: 47, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 48, + End: 49, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 50, + End: 51, + }, + &token.Token{ + Kind: "AND", + Space: " ", + Raw: "and", + Pos: 52, + End: 55, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 56, + End: 59, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 60, + End: 61, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 62, + End: 63, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} delete foo filter foo = 1 and bar = 2 diff --git a/testdata/result/dml/!bad_insert_with_bad_hint.sql.txt b/testdata/result/dml/!bad_insert_with_bad_hint.sql.txt new file mode 100644 index 00000000..b1b7f70b --- /dev/null +++ b/testdata/result/dml/!bad_insert_with_bad_hint.sql.txt @@ -0,0 +1,220 @@ +--- !bad_insert_with_bad_hint.sql +@{invalid} +insert foo (foo, bar, baz) +vales (1, 2, 3), + (4, 5, 6) +--- Error +syntax error: testdata/input/dml/!bad_insert_with_bad_hint.sql:1:10: expected token: =, but: } + 1| @{invalid} + | ^ + + +--- AST +&ast.BadDML{ + BadNode: &ast.BadNode{ + NodeEnd: 70, + Tokens: []*token.Token{ + &token.Token{ + Kind: "@", + Raw: "@", + End: 1, + }, + &token.Token{ + Kind: "{", + Raw: "{", + Pos: 1, + End: 2, + }, + &token.Token{ + Kind: "", + Raw: "invalid", + AsString: "invalid", + Pos: 2, + End: 9, + }, + &token.Token{ + Kind: "}", + Raw: "}", + Pos: 9, + End: 10, + }, + &token.Token{ + Kind: "", + Space: "\n", + Raw: "insert", + AsString: "insert", + Pos: 11, + End: 17, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 18, + End: 21, + }, + &token.Token{ + Kind: "(", + Space: " ", + Raw: "(", + Pos: 22, + End: 23, + }, + &token.Token{ + Kind: "", + Raw: "foo", + AsString: "foo", + Pos: 23, + End: 26, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 26, + End: 27, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 28, + End: 31, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 31, + End: 32, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "baz", + AsString: "baz", + Pos: 33, + End: 36, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 36, + End: 37, + }, + &token.Token{ + Kind: "", + Space: "\n", + Raw: "vales", + AsString: "vales", + Pos: 38, + End: 43, + }, + &token.Token{ + Kind: "(", + Space: " ", + Raw: "(", + Pos: 44, + End: 45, + }, + &token.Token{ + Kind: "", + Raw: "1", + Base: 10, + Pos: 45, + End: 46, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 46, + End: 47, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 48, + End: 49, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 49, + End: 50, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "3", + Base: 10, + Pos: 51, + End: 52, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 52, + End: 53, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 53, + End: 54, + }, + &token.Token{ + Kind: "(", + Space: "\n ", + Raw: "(", + Pos: 61, + End: 62, + }, + &token.Token{ + Kind: "", + Raw: "4", + Base: 10, + Pos: 62, + End: 63, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 63, + End: 64, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "5", + Base: 10, + Pos: 65, + End: 66, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 66, + End: 67, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "6", + Base: 10, + Pos: 68, + End: 69, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 69, + End: 70, + }, + }, + }, +} + +--- SQL +@{invalid} insert foo (foo, bar, baz) vales (1, 2, 3), (4, 5, 6) diff --git a/testdata/result/dml/!bad_insert_with_hint.sql.txt b/testdata/result/dml/!bad_insert_with_hint.sql.txt new file mode 100644 index 00000000..8c952fed --- /dev/null +++ b/testdata/result/dml/!bad_insert_with_hint.sql.txt @@ -0,0 +1,186 @@ +--- !bad_insert_with_hint.sql +@{pdml_max_parallelism=1} +insert foo (foo, bar, baz) +vales (1, 2, 3), + (4, 5, 6) +--- Error +syntax error: testdata/input/dml/!bad_insert_with_hint.sql:3:1: expected beginning of simple query "(", SELECT, FROM, but: "vales" + 3| vales (1, 2, 3), + | ^~~~~ + + +--- AST +&ast.Insert{ + Insert: 26, + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + TableName: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 33, + NameEnd: 36, + Name: "foo", + }, + }, + }, + Columns: []*ast.Ident{ + &ast.Ident{ + NamePos: 38, + NameEnd: 41, + Name: "foo", + }, + &ast.Ident{ + NamePos: 43, + NameEnd: 46, + Name: "bar", + }, + &ast.Ident{ + NamePos: 48, + NameEnd: 51, + Name: "baz", + }, + }, + Input: &ast.SubQueryInput{ + Query: &ast.BadQueryExpr{ + BadNode: &ast.BadNode{ + NodePos: 53, + NodeEnd: 85, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Space: "\n", + Raw: "vales", + AsString: "vales", + Pos: 53, + End: 58, + }, + &token.Token{ + Kind: "(", + Space: " ", + Raw: "(", + Pos: 59, + End: 60, + }, + &token.Token{ + Kind: "", + Raw: "1", + Base: 10, + Pos: 60, + End: 61, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 61, + End: 62, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 63, + End: 64, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 64, + End: 65, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "3", + Base: 10, + Pos: 66, + End: 67, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 67, + End: 68, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 68, + End: 69, + }, + &token.Token{ + Kind: "(", + Space: "\n ", + Raw: "(", + Pos: 76, + End: 77, + }, + &token.Token{ + Kind: "", + Raw: "4", + Base: 10, + Pos: 77, + End: 78, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 78, + End: 79, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "5", + Base: 10, + Pos: 80, + End: 81, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 81, + End: 82, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "6", + Base: 10, + Pos: 83, + End: 84, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 84, + End: 85, + }, + }, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} INSERT INTO foo (foo, bar, baz) vales (1, 2, 3), (4, 5, 6) diff --git a/testdata/result/dml/!bad_update.sql.txt b/testdata/result/dml/!bad_update.sql.txt new file mode 100644 index 00000000..7a032392 --- /dev/null +++ b/testdata/result/dml/!bad_update.sql.txt @@ -0,0 +1,78 @@ +--- !bad_update.sql +update foo set invalid where foo = 1 +--- Error +syntax error: testdata/input/dml/!bad_update.sql:1:24: expected token: =, but: WHERE + 1| update foo set invalid where foo = 1 + | ^~~~~ + + +--- AST +&ast.BadDML{ + BadNode: &ast.BadNode{ + NodeEnd: 36, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Raw: "update", + AsString: "update", + End: 6, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 7, + End: 10, + }, + &token.Token{ + Kind: "SET", + Space: " ", + Raw: "set", + Pos: 11, + End: 14, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "invalid", + AsString: "invalid", + Pos: 15, + End: 22, + }, + &token.Token{ + Kind: "WHERE", + Space: " ", + Raw: "where", + Pos: 23, + End: 28, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 29, + End: 32, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 33, + End: 34, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 35, + End: 36, + }, + }, + }, +} + +--- SQL +update foo set invalid where foo = 1 diff --git a/testdata/result/dml/!bad_update_with_bad_hint.sql.txt b/testdata/result/dml/!bad_update_with_bad_hint.sql.txt new file mode 100644 index 00000000..95fb505a --- /dev/null +++ b/testdata/result/dml/!bad_update_with_bad_hint.sql.txt @@ -0,0 +1,178 @@ +--- !bad_update_with_bad_hint.sql +@{invalid} +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 + +--- Error +syntax error: testdata/input/dml/!bad_update_with_bad_hint.sql:1:10: expected token: =, but: } + 1| @{invalid} + | ^ + + +--- AST +&ast.BadDML{ + BadNode: &ast.BadNode{ + NodeEnd: 75, + Tokens: []*token.Token{ + &token.Token{ + Kind: "@", + Raw: "@", + End: 1, + }, + &token.Token{ + Kind: "{", + Raw: "{", + Pos: 1, + End: 2, + }, + &token.Token{ + Kind: "", + Raw: "invalid", + AsString: "invalid", + Pos: 2, + End: 9, + }, + &token.Token{ + Kind: "}", + Raw: "}", + Pos: 9, + End: 10, + }, + &token.Token{ + Kind: "", + Space: "\n", + Raw: "update", + AsString: "update", + Pos: 11, + End: 17, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 18, + End: 21, + }, + &token.Token{ + Kind: "SET", + Space: " ", + Raw: "set", + Pos: 22, + End: 25, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 26, + End: 29, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 30, + End: 31, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 32, + End: 35, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 35, + End: 36, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 37, + End: 40, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 41, + End: 42, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 43, + End: 46, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 46, + End: 47, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "baz", + AsString: "baz", + Pos: 48, + End: 51, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 52, + End: 53, + }, + &token.Token{ + Kind: "DEFAULT", + Space: " ", + Raw: "DEFAULT", + Pos: 54, + End: 61, + }, + &token.Token{ + Kind: "WHERE", + Space: " ", + Raw: "where", + Pos: 62, + End: 67, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 68, + End: 71, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 72, + End: 73, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 74, + End: 75, + }, + }, + }, +} + +--- SQL +@{invalid} update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 diff --git a/testdata/result/dml/!bad_update_with_hint.sql.txt b/testdata/result/dml/!bad_update_with_hint.sql.txt new file mode 100644 index 00000000..8a076018 --- /dev/null +++ b/testdata/result/dml/!bad_update_with_hint.sql.txt @@ -0,0 +1,104 @@ +--- !bad_update_with_hint.sql +@{pdml_max_parallelism=1} +update foo set invalid where foo = 1 +--- Error +syntax error: testdata/input/dml/!bad_update_with_hint.sql:2:24: expected token: =, but: WHERE + 2| update foo set invalid where foo = 1 + | ^~~~~ + + +--- AST +&ast.BadDML{ + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + BadNode: &ast.BadNode{ + NodePos: 26, + NodeEnd: 62, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Space: "\n", + Raw: "update", + AsString: "update", + Pos: 26, + End: 32, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 33, + End: 36, + }, + &token.Token{ + Kind: "SET", + Space: " ", + Raw: "set", + Pos: 37, + End: 40, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "invalid", + AsString: "invalid", + Pos: 41, + End: 48, + }, + &token.Token{ + Kind: "WHERE", + Space: " ", + Raw: "where", + Pos: 49, + End: 54, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 55, + End: 58, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 59, + End: 60, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 61, + End: 62, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} update foo set invalid where foo = 1 diff --git a/testdata/result/statement/!bad_call_cancel_query_with_hint.sql.txt b/testdata/result/statement/!bad_call_cancel_query_with_hint.sql.txt new file mode 100644 index 00000000..d9aaa0e5 --- /dev/null +++ b/testdata/result/statement/!bad_call_cancel_query_with_hint.sql.txt @@ -0,0 +1,78 @@ +--- !bad_call_cancel_query_with_hint.sql +@{unknown_hint=1} +CALL cancel_query("12345") +--- Error +syntax error: testdata/input/statement/!bad_call_cancel_query_with_hint.sql:2:1: statement hint is only permitted before query or DML, but got: CALL + 2| CALL cancel_query("12345") + | ^~~~ + + +--- AST +&ast.BadStatement{ + Hint: &ast.Hint{ + Rbrace: 16, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 14, + Name: "unknown_hint", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 15, + ValueEnd: 16, + Base: 10, + Value: "1", + }, + }, + }, + }, + BadNode: &ast.BadNode{ + NodePos: 18, + NodeEnd: 44, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Space: "\n", + Raw: "CALL", + AsString: "CALL", + Pos: 18, + End: 22, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "cancel_query", + AsString: "cancel_query", + Pos: 23, + End: 35, + }, + &token.Token{ + Kind: "(", + Raw: "(", + Pos: 35, + End: 36, + }, + &token.Token{ + Kind: "", + Raw: "\"12345\"", + AsString: "12345", + Pos: 36, + End: 43, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 43, + End: 44, + }, + }, + }, +} + +--- SQL +@{unknown_hint=1} CALL cancel_query("12345") diff --git a/testdata/result/statement/!bad_create_table_with_hint.sql.txt b/testdata/result/statement/!bad_create_table_with_hint.sql.txt new file mode 100644 index 00000000..deaa1c1c --- /dev/null +++ b/testdata/result/statement/!bad_create_table_with_hint.sql.txt @@ -0,0 +1,109 @@ +--- !bad_create_table_with_hint.sql +@{unknown_hint=1} +create table tbl(pk int64 primary key) +--- Error +syntax error: testdata/input/ddl/!bad_create_table_with_hint.sql:2:1: statement hint is only permitted before query or DML, but got: create + 2| create table tbl(pk int64 primary key) + | ^~~~~~ + + +--- AST +&ast.BadStatement{ + Hint: &ast.Hint{ + Rbrace: 16, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 14, + Name: "unknown_hint", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 15, + ValueEnd: 16, + Base: 10, + Value: "1", + }, + }, + }, + }, + BadNode: &ast.BadNode{ + NodePos: 18, + NodeEnd: 56, + Tokens: []*token.Token{ + &token.Token{ + Kind: "CREATE", + Space: "\n", + Raw: "create", + Pos: 18, + End: 24, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "table", + AsString: "table", + Pos: 25, + End: 30, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "tbl", + AsString: "tbl", + Pos: 31, + End: 34, + }, + &token.Token{ + Kind: "(", + Raw: "(", + Pos: 34, + End: 35, + }, + &token.Token{ + Kind: "", + Raw: "pk", + AsString: "pk", + Pos: 35, + End: 37, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "int64", + AsString: "int64", + Pos: 38, + End: 43, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "primary", + AsString: "primary", + Pos: 44, + End: 51, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "key", + AsString: "key", + Pos: 52, + End: 55, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 55, + End: 56, + }, + }, + }, +} + +--- SQL +@{unknown_hint=1} create table tbl(pk int64 primary key) diff --git a/testdata/result/statement/!bad_delete.sql.txt b/testdata/result/statement/!bad_delete.sql.txt new file mode 100644 index 00000000..22d327b7 --- /dev/null +++ b/testdata/result/statement/!bad_delete.sql.txt @@ -0,0 +1,94 @@ +--- !bad_delete.sql +delete foo filter foo = 1 and bar = 2 +--- Error +syntax error: testdata/input/dml/!bad_delete.sql:1:19: expected token: WHERE, but: + 1| delete foo filter foo = 1 and bar = 2 + | ^~~ + + +--- AST +&ast.BadDML{ + BadNode: &ast.BadNode{ + NodeEnd: 37, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Raw: "delete", + AsString: "delete", + End: 6, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 7, + End: 10, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "filter", + AsString: "filter", + Pos: 11, + End: 17, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 18, + End: 21, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 22, + End: 23, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 24, + End: 25, + }, + &token.Token{ + Kind: "AND", + Space: " ", + Raw: "and", + Pos: 26, + End: 29, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 30, + End: 33, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 34, + End: 35, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 36, + End: 37, + }, + }, + }, +} + +--- SQL +delete foo filter foo = 1 and bar = 2 diff --git a/testdata/result/statement/!bad_delete_with_bad_hint.sql.txt b/testdata/result/statement/!bad_delete_with_bad_hint.sql.txt new file mode 100644 index 00000000..ebe4bc37 --- /dev/null +++ b/testdata/result/statement/!bad_delete_with_bad_hint.sql.txt @@ -0,0 +1,120 @@ +--- !bad_delete_with_bad_hint.sql +@{invalid} +delete foo where foo = 1 and bar = 2 +--- Error +syntax error: testdata/input/dml/!bad_delete_with_bad_hint.sql:1:10: expected token: =, but: } + 1| @{invalid} + | ^ + + +--- AST +&ast.BadStatement{ + BadNode: &ast.BadNode{ + NodeEnd: 47, + Tokens: []*token.Token{ + &token.Token{ + Kind: "@", + Raw: "@", + End: 1, + }, + &token.Token{ + Kind: "{", + Raw: "{", + Pos: 1, + End: 2, + }, + &token.Token{ + Kind: "", + Raw: "invalid", + AsString: "invalid", + Pos: 2, + End: 9, + }, + &token.Token{ + Kind: "}", + Raw: "}", + Pos: 9, + End: 10, + }, + &token.Token{ + Kind: "", + Space: "\n", + Raw: "delete", + AsString: "delete", + Pos: 11, + End: 17, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 18, + End: 21, + }, + &token.Token{ + Kind: "WHERE", + Space: " ", + Raw: "where", + Pos: 22, + End: 27, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 28, + End: 31, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 32, + End: 33, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 34, + End: 35, + }, + &token.Token{ + Kind: "AND", + Space: " ", + Raw: "and", + Pos: 36, + End: 39, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 40, + End: 43, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 44, + End: 45, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 46, + End: 47, + }, + }, + }, +} + +--- SQL +@{invalid} delete foo where foo = 1 and bar = 2 diff --git a/testdata/result/statement/!bad_delete_with_hint.sql.txt b/testdata/result/statement/!bad_delete_with_hint.sql.txt new file mode 100644 index 00000000..25ac513c --- /dev/null +++ b/testdata/result/statement/!bad_delete_with_hint.sql.txt @@ -0,0 +1,120 @@ +--- !bad_delete_with_hint.sql +@{pdml_max_parallelism=1} +delete foo filter foo = 1 and bar = 2 +--- Error +syntax error: testdata/input/dml/!bad_delete_with_hint.sql:2:19: expected token: WHERE, but: + 2| delete foo filter foo = 1 and bar = 2 + | ^~~ + + +--- AST +&ast.BadDML{ + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + BadNode: &ast.BadNode{ + NodePos: 26, + NodeEnd: 63, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Space: "\n", + Raw: "delete", + AsString: "delete", + Pos: 26, + End: 32, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 33, + End: 36, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "filter", + AsString: "filter", + Pos: 37, + End: 43, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 44, + End: 47, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 48, + End: 49, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 50, + End: 51, + }, + &token.Token{ + Kind: "AND", + Space: " ", + Raw: "and", + Pos: 52, + End: 55, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 56, + End: 59, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 60, + End: 61, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 62, + End: 63, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} delete foo filter foo = 1 and bar = 2 diff --git a/testdata/result/statement/!bad_insert_with_bad_hint.sql.txt b/testdata/result/statement/!bad_insert_with_bad_hint.sql.txt new file mode 100644 index 00000000..b5e3e5fc --- /dev/null +++ b/testdata/result/statement/!bad_insert_with_bad_hint.sql.txt @@ -0,0 +1,220 @@ +--- !bad_insert_with_bad_hint.sql +@{invalid} +insert foo (foo, bar, baz) +vales (1, 2, 3), + (4, 5, 6) +--- Error +syntax error: testdata/input/dml/!bad_insert_with_bad_hint.sql:1:10: expected token: =, but: } + 1| @{invalid} + | ^ + + +--- AST +&ast.BadStatement{ + BadNode: &ast.BadNode{ + NodeEnd: 70, + Tokens: []*token.Token{ + &token.Token{ + Kind: "@", + Raw: "@", + End: 1, + }, + &token.Token{ + Kind: "{", + Raw: "{", + Pos: 1, + End: 2, + }, + &token.Token{ + Kind: "", + Raw: "invalid", + AsString: "invalid", + Pos: 2, + End: 9, + }, + &token.Token{ + Kind: "}", + Raw: "}", + Pos: 9, + End: 10, + }, + &token.Token{ + Kind: "", + Space: "\n", + Raw: "insert", + AsString: "insert", + Pos: 11, + End: 17, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 18, + End: 21, + }, + &token.Token{ + Kind: "(", + Space: " ", + Raw: "(", + Pos: 22, + End: 23, + }, + &token.Token{ + Kind: "", + Raw: "foo", + AsString: "foo", + Pos: 23, + End: 26, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 26, + End: 27, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 28, + End: 31, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 31, + End: 32, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "baz", + AsString: "baz", + Pos: 33, + End: 36, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 36, + End: 37, + }, + &token.Token{ + Kind: "", + Space: "\n", + Raw: "vales", + AsString: "vales", + Pos: 38, + End: 43, + }, + &token.Token{ + Kind: "(", + Space: " ", + Raw: "(", + Pos: 44, + End: 45, + }, + &token.Token{ + Kind: "", + Raw: "1", + Base: 10, + Pos: 45, + End: 46, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 46, + End: 47, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 48, + End: 49, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 49, + End: 50, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "3", + Base: 10, + Pos: 51, + End: 52, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 52, + End: 53, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 53, + End: 54, + }, + &token.Token{ + Kind: "(", + Space: "\n ", + Raw: "(", + Pos: 61, + End: 62, + }, + &token.Token{ + Kind: "", + Raw: "4", + Base: 10, + Pos: 62, + End: 63, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 63, + End: 64, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "5", + Base: 10, + Pos: 65, + End: 66, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 66, + End: 67, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "6", + Base: 10, + Pos: 68, + End: 69, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 69, + End: 70, + }, + }, + }, +} + +--- SQL +@{invalid} insert foo (foo, bar, baz) vales (1, 2, 3), (4, 5, 6) diff --git a/testdata/result/statement/!bad_insert_with_hint.sql.txt b/testdata/result/statement/!bad_insert_with_hint.sql.txt new file mode 100644 index 00000000..8c952fed --- /dev/null +++ b/testdata/result/statement/!bad_insert_with_hint.sql.txt @@ -0,0 +1,186 @@ +--- !bad_insert_with_hint.sql +@{pdml_max_parallelism=1} +insert foo (foo, bar, baz) +vales (1, 2, 3), + (4, 5, 6) +--- Error +syntax error: testdata/input/dml/!bad_insert_with_hint.sql:3:1: expected beginning of simple query "(", SELECT, FROM, but: "vales" + 3| vales (1, 2, 3), + | ^~~~~ + + +--- AST +&ast.Insert{ + Insert: 26, + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + TableName: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 33, + NameEnd: 36, + Name: "foo", + }, + }, + }, + Columns: []*ast.Ident{ + &ast.Ident{ + NamePos: 38, + NameEnd: 41, + Name: "foo", + }, + &ast.Ident{ + NamePos: 43, + NameEnd: 46, + Name: "bar", + }, + &ast.Ident{ + NamePos: 48, + NameEnd: 51, + Name: "baz", + }, + }, + Input: &ast.SubQueryInput{ + Query: &ast.BadQueryExpr{ + BadNode: &ast.BadNode{ + NodePos: 53, + NodeEnd: 85, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Space: "\n", + Raw: "vales", + AsString: "vales", + Pos: 53, + End: 58, + }, + &token.Token{ + Kind: "(", + Space: " ", + Raw: "(", + Pos: 59, + End: 60, + }, + &token.Token{ + Kind: "", + Raw: "1", + Base: 10, + Pos: 60, + End: 61, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 61, + End: 62, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "2", + Base: 10, + Pos: 63, + End: 64, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 64, + End: 65, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "3", + Base: 10, + Pos: 66, + End: 67, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 67, + End: 68, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 68, + End: 69, + }, + &token.Token{ + Kind: "(", + Space: "\n ", + Raw: "(", + Pos: 76, + End: 77, + }, + &token.Token{ + Kind: "", + Raw: "4", + Base: 10, + Pos: 77, + End: 78, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 78, + End: 79, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "5", + Base: 10, + Pos: 80, + End: 81, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 81, + End: 82, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "6", + Base: 10, + Pos: 83, + End: 84, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 84, + End: 85, + }, + }, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} INSERT INTO foo (foo, bar, baz) vales (1, 2, 3), (4, 5, 6) diff --git a/testdata/result/statement/!bad_update.sql.txt b/testdata/result/statement/!bad_update.sql.txt new file mode 100644 index 00000000..7a032392 --- /dev/null +++ b/testdata/result/statement/!bad_update.sql.txt @@ -0,0 +1,78 @@ +--- !bad_update.sql +update foo set invalid where foo = 1 +--- Error +syntax error: testdata/input/dml/!bad_update.sql:1:24: expected token: =, but: WHERE + 1| update foo set invalid where foo = 1 + | ^~~~~ + + +--- AST +&ast.BadDML{ + BadNode: &ast.BadNode{ + NodeEnd: 36, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Raw: "update", + AsString: "update", + End: 6, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 7, + End: 10, + }, + &token.Token{ + Kind: "SET", + Space: " ", + Raw: "set", + Pos: 11, + End: 14, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "invalid", + AsString: "invalid", + Pos: 15, + End: 22, + }, + &token.Token{ + Kind: "WHERE", + Space: " ", + Raw: "where", + Pos: 23, + End: 28, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 29, + End: 32, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 33, + End: 34, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 35, + End: 36, + }, + }, + }, +} + +--- SQL +update foo set invalid where foo = 1 diff --git a/testdata/result/statement/!bad_update_with_bad_hint.sql.txt b/testdata/result/statement/!bad_update_with_bad_hint.sql.txt new file mode 100644 index 00000000..649a020a --- /dev/null +++ b/testdata/result/statement/!bad_update_with_bad_hint.sql.txt @@ -0,0 +1,178 @@ +--- !bad_update_with_bad_hint.sql +@{invalid} +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 + +--- Error +syntax error: testdata/input/dml/!bad_update_with_bad_hint.sql:1:10: expected token: =, but: } + 1| @{invalid} + | ^ + + +--- AST +&ast.BadStatement{ + BadNode: &ast.BadNode{ + NodeEnd: 75, + Tokens: []*token.Token{ + &token.Token{ + Kind: "@", + Raw: "@", + End: 1, + }, + &token.Token{ + Kind: "{", + Raw: "{", + Pos: 1, + End: 2, + }, + &token.Token{ + Kind: "", + Raw: "invalid", + AsString: "invalid", + Pos: 2, + End: 9, + }, + &token.Token{ + Kind: "}", + Raw: "}", + Pos: 9, + End: 10, + }, + &token.Token{ + Kind: "", + Space: "\n", + Raw: "update", + AsString: "update", + Pos: 11, + End: 17, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 18, + End: 21, + }, + &token.Token{ + Kind: "SET", + Space: " ", + Raw: "set", + Pos: 22, + End: 25, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 26, + End: 29, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 30, + End: 31, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 32, + End: 35, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 35, + End: 36, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "bar", + AsString: "bar", + Pos: 37, + End: 40, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 41, + End: 42, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 43, + End: 46, + }, + &token.Token{ + Kind: ",", + Raw: ",", + Pos: 46, + End: 47, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "baz", + AsString: "baz", + Pos: 48, + End: 51, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 52, + End: 53, + }, + &token.Token{ + Kind: "DEFAULT", + Space: " ", + Raw: "DEFAULT", + Pos: 54, + End: 61, + }, + &token.Token{ + Kind: "WHERE", + Space: " ", + Raw: "where", + Pos: 62, + End: 67, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 68, + End: 71, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 72, + End: 73, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 74, + End: 75, + }, + }, + }, +} + +--- SQL +@{invalid} update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 diff --git a/testdata/result/statement/!bad_update_with_hint.sql.txt b/testdata/result/statement/!bad_update_with_hint.sql.txt new file mode 100644 index 00000000..8a076018 --- /dev/null +++ b/testdata/result/statement/!bad_update_with_hint.sql.txt @@ -0,0 +1,104 @@ +--- !bad_update_with_hint.sql +@{pdml_max_parallelism=1} +update foo set invalid where foo = 1 +--- Error +syntax error: testdata/input/dml/!bad_update_with_hint.sql:2:24: expected token: =, but: WHERE + 2| update foo set invalid where foo = 1 + | ^~~~~ + + +--- AST +&ast.BadDML{ + Hint: &ast.Hint{ + Rbrace: 24, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 22, + Name: "pdml_max_parallelism", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + }, + }, + BadNode: &ast.BadNode{ + NodePos: 26, + NodeEnd: 62, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Space: "\n", + Raw: "update", + AsString: "update", + Pos: 26, + End: 32, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 33, + End: 36, + }, + &token.Token{ + Kind: "SET", + Space: " ", + Raw: "set", + Pos: 37, + End: 40, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "invalid", + AsString: "invalid", + Pos: 41, + End: 48, + }, + &token.Token{ + Kind: "WHERE", + Space: " ", + Raw: "where", + Pos: 49, + End: 54, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "foo", + AsString: "foo", + Pos: 55, + End: 58, + }, + &token.Token{ + Kind: "=", + Space: " ", + Raw: "=", + Pos: 59, + End: 60, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 61, + End: 62, + }, + }, + }, +} + +--- SQL +@{pdml_max_parallelism=1} update foo set invalid where foo = 1 From 90a8fee1ae51689e91770b172db594527cc5f0e7 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:14:15 +0900 Subject: [PATCH 5/9] Remove unused method --- parser.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/parser.go b/parser.go index 7322817d..b6f8d0cc 100644 --- a/parser.go +++ b/parser.go @@ -150,16 +150,6 @@ func (p *Parser) ParseDMLs() ([]ast.DML, error) { return dmls, nil } -func (p *Parser) lookaheadTokenAfterOptionalHint() token.Token { - lexer := p.Lexer.Clone() - defer func() { - p.Lexer = lexer - }() - - p.tryParseHint() - return p.Token -} - func (p *Parser) parseStatement() (stmt ast.Statement) { l := p.Lexer.Clone() defer func() { From 20fa47bcee50dcc2fa7dc22bc53a6f46e857c27d Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:15:30 +0900 Subject: [PATCH 6/9] Fix error message --- parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser.go b/parser.go index b6f8d0cc..81db7289 100644 --- a/parser.go +++ b/parser.go @@ -189,7 +189,7 @@ func (p *Parser) parseStatementInternal(hint *ast.Hint) (stmt ast.Statement) { return p.parseOtherStatement() } - panic(p.errorfAtToken(&p.Token, "unexpected p.Token: %s", p.Token.Kind)) + panic(p.errorfAtToken(&p.Token, "unexpected token: %s", p.Token.Kind)) } func (p *Parser) parseOtherStatement() ast.Statement { From 178fb7ce93b3650856eecadd54855e0c2dbfa7f3 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:18:38 +0900 Subject: [PATCH 7/9] Simplify parseQueryStatementInternal --- parser.go | 12 +--- .../query/!bad_with_select_with_hint.sql | 1 + .../query/!bad_with_select_with_hint.sql.txt | 66 +++++++++++++++++++ .../!bad_with_select_with_hint.sql.txt | 66 +++++++++++++++++++ 4 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 testdata/input/query/!bad_with_select_with_hint.sql create mode 100644 testdata/result/query/!bad_with_select_with_hint.sql.txt create mode 100644 testdata/result/statement/!bad_with_select_with_hint.sql.txt diff --git a/parser.go b/parser.go index 81db7289..f04f2c80 100644 --- a/parser.go +++ b/parser.go @@ -261,17 +261,7 @@ func (p *Parser) parseQueryStatement() (stmt *ast.QueryStatement) { } func (p *Parser) parseQueryStatementInternal(hint *ast.Hint) (stmt *ast.QueryStatement) { - l := p.Lexer.Clone() - defer func() { - if r := recover(); r != nil { - // When parsing is failed on tryParseWith, the result of these methods are discarded - // because they are concrete structs and we cannot fill them with *ast.BadNode. - stmt = &ast.QueryStatement{ - Query: &ast.BadQueryExpr{BadNode: p.handleParseStatementError(r, l)}, - } - } - }() - + // Can be BadQueryExpr and won't panic query := p.parseQueryExpr() return &ast.QueryStatement{ diff --git a/testdata/input/query/!bad_with_select_with_hint.sql b/testdata/input/query/!bad_with_select_with_hint.sql new file mode 100644 index 00000000..c66ca7ae --- /dev/null +++ b/testdata/input/query/!bad_with_select_with_hint.sql @@ -0,0 +1 @@ +@{hint = 1} WITH SELECT 1 \ No newline at end of file diff --git a/testdata/result/query/!bad_with_select_with_hint.sql.txt b/testdata/result/query/!bad_with_select_with_hint.sql.txt new file mode 100644 index 00000000..f6232635 --- /dev/null +++ b/testdata/result/query/!bad_with_select_with_hint.sql.txt @@ -0,0 +1,66 @@ +--- !bad_with_select_with_hint.sql +@{hint = 1} WITH SELECT 1 +--- Error +syntax error: testdata/input/query/!bad_with_select_with_hint.sql:1:18: expected token: , but: SELECT + 1| @{hint = 1} WITH SELECT 1 + | ^~~~~~ + + +--- AST +&ast.QueryStatement{ + Hint: &ast.Hint{ + Rbrace: 10, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 6, + Name: "hint", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 9, + ValueEnd: 10, + Base: 10, + Value: "1", + }, + }, + }, + }, + Query: &ast.BadQueryExpr{ + BadNode: &ast.BadNode{ + NodePos: 12, + NodeEnd: 25, + Tokens: []*token.Token{ + &token.Token{ + Kind: "WITH", + Space: " ", + Raw: "WITH", + Pos: 12, + End: 16, + }, + &token.Token{ + Kind: "SELECT", + Space: " ", + Raw: "SELECT", + Pos: 17, + End: 23, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 24, + End: 25, + }, + }, + }, + }, +} + +--- SQL +@{hint=1} WITH SELECT 1 diff --git a/testdata/result/statement/!bad_with_select_with_hint.sql.txt b/testdata/result/statement/!bad_with_select_with_hint.sql.txt new file mode 100644 index 00000000..f6232635 --- /dev/null +++ b/testdata/result/statement/!bad_with_select_with_hint.sql.txt @@ -0,0 +1,66 @@ +--- !bad_with_select_with_hint.sql +@{hint = 1} WITH SELECT 1 +--- Error +syntax error: testdata/input/query/!bad_with_select_with_hint.sql:1:18: expected token: , but: SELECT + 1| @{hint = 1} WITH SELECT 1 + | ^~~~~~ + + +--- AST +&ast.QueryStatement{ + Hint: &ast.Hint{ + Rbrace: 10, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 6, + Name: "hint", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 9, + ValueEnd: 10, + Base: 10, + Value: "1", + }, + }, + }, + }, + Query: &ast.BadQueryExpr{ + BadNode: &ast.BadNode{ + NodePos: 12, + NodeEnd: 25, + Tokens: []*token.Token{ + &token.Token{ + Kind: "WITH", + Space: " ", + Raw: "WITH", + Pos: 12, + End: 16, + }, + &token.Token{ + Kind: "SELECT", + Space: " ", + Raw: "SELECT", + Pos: 17, + End: 23, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "1", + Base: 10, + Pos: 24, + End: 25, + }, + }, + }, + }, +} + +--- SQL +@{hint=1} WITH SELECT 1 From 416246886ccff24cdf3ebdd1029ce4c6d651ec0e Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:19:44 +0900 Subject: [PATCH 8/9] Fix comment --- parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser.go b/parser.go index f04f2c80..8f35fdbd 100644 --- a/parser.go +++ b/parser.go @@ -261,7 +261,7 @@ func (p *Parser) parseQueryStatement() (stmt *ast.QueryStatement) { } func (p *Parser) parseQueryStatementInternal(hint *ast.Hint) (stmt *ast.QueryStatement) { - // Can be BadQueryExpr and won't panic + // Can be a *ast.BadQueryExpr and won't panic query := p.parseQueryExpr() return &ast.QueryStatement{ From 2c14f6324fd4904893f61ebcb8804edbe1b4194f Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 12 Jan 2025 22:58:38 +0900 Subject: [PATCH 9/9] Fix error position --- parser.go | 2 +- ...ad_call_cancel_query_with_hint_oneline.sql | 1 + .../!bad_call_cancel_query_with_hint.sql.txt | 4 +- ...all_cancel_query_with_hint_oneline.sql.txt | 77 +++++++++++++++++++ .../!bad_create_table_with_hint.sql.txt | 4 +- 5 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 testdata/input/statement/!bad_call_cancel_query_with_hint_oneline.sql create mode 100644 testdata/result/statement/!bad_call_cancel_query_with_hint_oneline.sql.txt diff --git a/parser.go b/parser.go index 8f35fdbd..5eb33e2e 100644 --- a/parser.go +++ b/parser.go @@ -180,7 +180,7 @@ func (p *Parser) parseStatementInternal(hint *ast.Hint) (stmt ast.Statement) { case p.Token.IsKeywordLike("INSERT") || p.Token.IsKeywordLike("DELETE") || p.Token.IsKeywordLike("UPDATE"): return p.parseDMLInternal(hint) case hint != nil: - panic(p.errorfAtToken(&p.Token, "statement hint is only permitted before query or DML, but got: %s", p.Token.Raw)) + panic(p.errorfAtPosition(hint.Pos(), p.Token.End, "statement hint is only permitted before query or DML, but got: %s", p.Token.Raw)) case p.Token.Kind == "CREATE" || p.Token.IsKeywordLike("ALTER") || p.Token.IsKeywordLike("DROP") || p.Token.IsKeywordLike("RENAME") || p.Token.IsKeywordLike("GRANT") || p.Token.IsKeywordLike("REVOKE") || p.Token.IsKeywordLike("ANALYZE"): diff --git a/testdata/input/statement/!bad_call_cancel_query_with_hint_oneline.sql b/testdata/input/statement/!bad_call_cancel_query_with_hint_oneline.sql new file mode 100644 index 00000000..a1da9559 --- /dev/null +++ b/testdata/input/statement/!bad_call_cancel_query_with_hint_oneline.sql @@ -0,0 +1 @@ +@{unknown_hint=1} CALL cancel_query("12345") \ No newline at end of file diff --git a/testdata/result/statement/!bad_call_cancel_query_with_hint.sql.txt b/testdata/result/statement/!bad_call_cancel_query_with_hint.sql.txt index d9aaa0e5..6ee6efc9 100644 --- a/testdata/result/statement/!bad_call_cancel_query_with_hint.sql.txt +++ b/testdata/result/statement/!bad_call_cancel_query_with_hint.sql.txt @@ -2,9 +2,9 @@ @{unknown_hint=1} CALL cancel_query("12345") --- Error -syntax error: testdata/input/statement/!bad_call_cancel_query_with_hint.sql:2:1: statement hint is only permitted before query or DML, but got: CALL +syntax error: testdata/input/statement/!bad_call_cancel_query_with_hint.sql:1:1: statement hint is only permitted before query or DML, but got: CALL + 1| @{unknown_hint=1} 2| CALL cancel_query("12345") - | ^~~~ --- AST diff --git a/testdata/result/statement/!bad_call_cancel_query_with_hint_oneline.sql.txt b/testdata/result/statement/!bad_call_cancel_query_with_hint_oneline.sql.txt new file mode 100644 index 00000000..6a121e85 --- /dev/null +++ b/testdata/result/statement/!bad_call_cancel_query_with_hint_oneline.sql.txt @@ -0,0 +1,77 @@ +--- !bad_call_cancel_query_with_hint_oneline.sql +@{unknown_hint=1} CALL cancel_query("12345") +--- Error +syntax error: testdata/input/statement/!bad_call_cancel_query_with_hint_oneline.sql:1:1: statement hint is only permitted before query or DML, but got: CALL + 1| @{unknown_hint=1} CALL cancel_query("12345") + | ^~~~~~~~~~~~~~~~~~~~~~ + + +--- AST +&ast.BadStatement{ + Hint: &ast.Hint{ + Rbrace: 16, + Records: []*ast.HintRecord{ + &ast.HintRecord{ + Key: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 2, + NameEnd: 14, + Name: "unknown_hint", + }, + }, + }, + Value: &ast.IntLiteral{ + ValuePos: 15, + ValueEnd: 16, + Base: 10, + Value: "1", + }, + }, + }, + }, + BadNode: &ast.BadNode{ + NodePos: 18, + NodeEnd: 44, + Tokens: []*token.Token{ + &token.Token{ + Kind: "", + Space: " ", + Raw: "CALL", + AsString: "CALL", + Pos: 18, + End: 22, + }, + &token.Token{ + Kind: "", + Space: " ", + Raw: "cancel_query", + AsString: "cancel_query", + Pos: 23, + End: 35, + }, + &token.Token{ + Kind: "(", + Raw: "(", + Pos: 35, + End: 36, + }, + &token.Token{ + Kind: "", + Raw: "\"12345\"", + AsString: "12345", + Pos: 36, + End: 43, + }, + &token.Token{ + Kind: ")", + Raw: ")", + Pos: 43, + End: 44, + }, + }, + }, +} + +--- SQL +@{unknown_hint=1} CALL cancel_query("12345") diff --git a/testdata/result/statement/!bad_create_table_with_hint.sql.txt b/testdata/result/statement/!bad_create_table_with_hint.sql.txt index deaa1c1c..8b6bb9f2 100644 --- a/testdata/result/statement/!bad_create_table_with_hint.sql.txt +++ b/testdata/result/statement/!bad_create_table_with_hint.sql.txt @@ -2,9 +2,9 @@ @{unknown_hint=1} create table tbl(pk int64 primary key) --- Error -syntax error: testdata/input/ddl/!bad_create_table_with_hint.sql:2:1: statement hint is only permitted before query or DML, but got: create +syntax error: testdata/input/ddl/!bad_create_table_with_hint.sql:1:1: statement hint is only permitted before query or DML, but got: create + 1| @{unknown_hint=1} 2| create table tbl(pk int64 primary key) - | ^~~~~~ --- AST