From bb8d1011708705e9032803a2886e7c8cffd62cdb Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:38:51 +0900 Subject: [PATCH 1/5] Fix JoinMethod handling (#213) --- ast/const.go | 6 +- parser.go | 8 +- .../query/select_singer_with_hash_join.sql | 8 +- .../select_singer_with_hash_join.sql.txt | 183 +++--------------- .../select_singer_with_hash_join.sql.txt | 183 +++--------------- 5 files changed, 70 insertions(+), 318 deletions(-) diff --git a/ast/const.go b/ast/const.go index eff6d893..9ea9a7d9 100644 --- a/ast/const.go +++ b/ast/const.go @@ -14,12 +14,12 @@ const ( JoinTypeJoinHint JoinHintKey = "JOIN_TYPE" ) +// JoinMethod is used for prefix of JOIN, not for hint. type JoinMethod string const ( - HashJoinMethod JoinMethod = "HASH" - ApplyJoinMethod JoinMethod = "APPLY" - LoopJoinMethod JoinMethod = "LOOP" // Undocumented, but the Spanner accept this value at least. + HashJoinMethod JoinMethod = "HASH" + LookupJoinMethod JoinMethod = "LOOKUP" // Undocumented, but the GoogleSQL can parse this value. ) type PositionKeyword string diff --git a/parser.go b/parser.go index c2a2595f..0655fc52 100644 --- a/parser.go +++ b/parser.go @@ -737,13 +737,9 @@ func (p *Parser) parseTableExpr(toplevel bool) ast.TableExpr { p.nextToken() method = ast.HashJoinMethod needJoin = true - case p.Token.IsKeywordLike("APPLY"): + case p.Token.IsKeywordLike("LOOKUP"): p.nextToken() - method = ast.ApplyJoinMethod - needJoin = true - case p.Token.IsKeywordLike("LOOP"): - p.nextToken() - method = ast.LoopJoinMethod + method = ast.LookupJoinMethod needJoin = true } } diff --git a/testdata/input/query/select_singer_with_hash_join.sql b/testdata/input/query/select_singer_with_hash_join.sql index cd77034d..e5d1e7e4 100644 --- a/testdata/input/query/select_singer_with_hash_join.sql +++ b/testdata/input/query/select_singer_with_hash_join.sql @@ -4,10 +4,4 @@ FROM Singers A HASH JOIN Singers B - ON A.SingerID = B.SingerID - APPLY JOIN - Singer C - ON B.SingerID = C.SingerID - LOOP JOIN - Singer D - ON C.SingerID = D.SingerID + ON A.SingerID = B.SingerID \ No newline at end of file diff --git a/testdata/result/query/select_singer_with_hash_join.sql.txt b/testdata/result/query/select_singer_with_hash_join.sql.txt index 6a385ba4..51741f24 100644 --- a/testdata/result/query/select_singer_with_hash_join.sql.txt +++ b/testdata/result/query/select_singer_with_hash_join.sql.txt @@ -6,13 +6,6 @@ FROM HASH JOIN Singers B ON A.SingerID = B.SingerID - APPLY JOIN - Singer C - ON B.SingerID = C.SingerID - LOOP JOIN - Singer D - ON C.SingerID = D.SingerID - --- AST &ast.QueryStatement{ Hint: (*ast.Hint)(nil), @@ -30,168 +23,56 @@ FROM From: 11, Source: &ast.Join{ Op: "INNER JOIN", - Method: "LOOP", + Method: "HASH", Hint: (*ast.Hint)(nil), - Left: &ast.Join{ - Op: "INNER JOIN", - Method: "APPLY", - Hint: (*ast.Hint)(nil), - Left: &ast.Join{ - Op: "INNER JOIN", - Method: "HASH", - Hint: (*ast.Hint)(nil), - Left: &ast.TableName{ - Table: &ast.Ident{ - NamePos: 18, - NameEnd: 25, - Name: "Singers", - }, - Hint: (*ast.Hint)(nil), - As: &ast.AsAlias{ - As: -1, - Alias: &ast.Ident{ - NamePos: 26, - NameEnd: 27, - Name: "A", - }, - }, - Sample: (*ast.TableSample)(nil), - }, - Right: &ast.TableName{ - Table: &ast.Ident{ - NamePos: 42, - NameEnd: 49, - Name: "Singers", - }, - Hint: (*ast.Hint)(nil), - As: &ast.AsAlias{ - As: -1, - Alias: &ast.Ident{ - NamePos: 50, - NameEnd: 51, - Name: "B", - }, - }, - Sample: (*ast.TableSample)(nil), - }, - Cond: &ast.On{ - On: 54, - Expr: &ast.BinaryExpr{ - Op: "=", - Left: &ast.Path{ - Idents: []*ast.Ident{ - &ast.Ident{ - NamePos: 57, - NameEnd: 58, - Name: "A", - }, - &ast.Ident{ - NamePos: 59, - NameEnd: 67, - Name: "SingerID", - }, - }, - }, - Right: &ast.Path{ - Idents: []*ast.Ident{ - &ast.Ident{ - NamePos: 70, - NameEnd: 71, - Name: "B", - }, - &ast.Ident{ - NamePos: 72, - NameEnd: 80, - Name: "SingerID", - }, - }, - }, - }, - }, - }, - Right: &ast.TableName{ - Table: &ast.Ident{ - NamePos: 96, - NameEnd: 102, - Name: "Singer", - }, - Hint: (*ast.Hint)(nil), - As: &ast.AsAlias{ - As: -1, - Alias: &ast.Ident{ - NamePos: 103, - NameEnd: 104, - Name: "C", - }, - }, - Sample: (*ast.TableSample)(nil), + Left: &ast.TableName{ + Table: &ast.Ident{ + NamePos: 18, + NameEnd: 25, + Name: "Singers", }, - Cond: &ast.On{ - On: 107, - Expr: &ast.BinaryExpr{ - Op: "=", - Left: &ast.Path{ - Idents: []*ast.Ident{ - &ast.Ident{ - NamePos: 110, - NameEnd: 111, - Name: "B", - }, - &ast.Ident{ - NamePos: 112, - NameEnd: 120, - Name: "SingerID", - }, - }, - }, - Right: &ast.Path{ - Idents: []*ast.Ident{ - &ast.Ident{ - NamePos: 123, - NameEnd: 124, - Name: "C", - }, - &ast.Ident{ - NamePos: 125, - NameEnd: 133, - Name: "SingerID", - }, - }, - }, + Hint: (*ast.Hint)(nil), + As: &ast.AsAlias{ + As: -1, + Alias: &ast.Ident{ + NamePos: 26, + NameEnd: 27, + Name: "A", }, }, + Sample: (*ast.TableSample)(nil), }, Right: &ast.TableName{ Table: &ast.Ident{ - NamePos: 148, - NameEnd: 154, - Name: "Singer", + NamePos: 42, + NameEnd: 49, + Name: "Singers", }, Hint: (*ast.Hint)(nil), As: &ast.AsAlias{ As: -1, Alias: &ast.Ident{ - NamePos: 155, - NameEnd: 156, - Name: "D", + NamePos: 50, + NameEnd: 51, + Name: "B", }, }, Sample: (*ast.TableSample)(nil), }, Cond: &ast.On{ - On: 159, + On: 54, Expr: &ast.BinaryExpr{ Op: "=", Left: &ast.Path{ Idents: []*ast.Ident{ &ast.Ident{ - NamePos: 162, - NameEnd: 163, - Name: "C", + NamePos: 57, + NameEnd: 58, + Name: "A", }, &ast.Ident{ - NamePos: 164, - NameEnd: 172, + NamePos: 59, + NameEnd: 67, Name: "SingerID", }, }, @@ -199,13 +80,13 @@ FROM Right: &ast.Path{ Idents: []*ast.Ident{ &ast.Ident{ - NamePos: 175, - NameEnd: 176, - Name: "D", + NamePos: 70, + NameEnd: 71, + Name: "B", }, &ast.Ident{ - NamePos: 177, - NameEnd: 185, + NamePos: 72, + NameEnd: 80, Name: "SingerID", }, }, @@ -223,4 +104,4 @@ FROM } --- SQL -SELECT * FROM Singers A INNER JOIN Singers B ON A.SingerID = B.SingerID INNER JOIN Singer C ON B.SingerID = C.SingerID INNER JOIN Singer D ON C.SingerID = D.SingerID +SELECT * FROM Singers A INNER JOIN Singers B ON A.SingerID = B.SingerID diff --git a/testdata/result/statement/select_singer_with_hash_join.sql.txt b/testdata/result/statement/select_singer_with_hash_join.sql.txt index 6a385ba4..51741f24 100644 --- a/testdata/result/statement/select_singer_with_hash_join.sql.txt +++ b/testdata/result/statement/select_singer_with_hash_join.sql.txt @@ -6,13 +6,6 @@ FROM HASH JOIN Singers B ON A.SingerID = B.SingerID - APPLY JOIN - Singer C - ON B.SingerID = C.SingerID - LOOP JOIN - Singer D - ON C.SingerID = D.SingerID - --- AST &ast.QueryStatement{ Hint: (*ast.Hint)(nil), @@ -30,168 +23,56 @@ FROM From: 11, Source: &ast.Join{ Op: "INNER JOIN", - Method: "LOOP", + Method: "HASH", Hint: (*ast.Hint)(nil), - Left: &ast.Join{ - Op: "INNER JOIN", - Method: "APPLY", - Hint: (*ast.Hint)(nil), - Left: &ast.Join{ - Op: "INNER JOIN", - Method: "HASH", - Hint: (*ast.Hint)(nil), - Left: &ast.TableName{ - Table: &ast.Ident{ - NamePos: 18, - NameEnd: 25, - Name: "Singers", - }, - Hint: (*ast.Hint)(nil), - As: &ast.AsAlias{ - As: -1, - Alias: &ast.Ident{ - NamePos: 26, - NameEnd: 27, - Name: "A", - }, - }, - Sample: (*ast.TableSample)(nil), - }, - Right: &ast.TableName{ - Table: &ast.Ident{ - NamePos: 42, - NameEnd: 49, - Name: "Singers", - }, - Hint: (*ast.Hint)(nil), - As: &ast.AsAlias{ - As: -1, - Alias: &ast.Ident{ - NamePos: 50, - NameEnd: 51, - Name: "B", - }, - }, - Sample: (*ast.TableSample)(nil), - }, - Cond: &ast.On{ - On: 54, - Expr: &ast.BinaryExpr{ - Op: "=", - Left: &ast.Path{ - Idents: []*ast.Ident{ - &ast.Ident{ - NamePos: 57, - NameEnd: 58, - Name: "A", - }, - &ast.Ident{ - NamePos: 59, - NameEnd: 67, - Name: "SingerID", - }, - }, - }, - Right: &ast.Path{ - Idents: []*ast.Ident{ - &ast.Ident{ - NamePos: 70, - NameEnd: 71, - Name: "B", - }, - &ast.Ident{ - NamePos: 72, - NameEnd: 80, - Name: "SingerID", - }, - }, - }, - }, - }, - }, - Right: &ast.TableName{ - Table: &ast.Ident{ - NamePos: 96, - NameEnd: 102, - Name: "Singer", - }, - Hint: (*ast.Hint)(nil), - As: &ast.AsAlias{ - As: -1, - Alias: &ast.Ident{ - NamePos: 103, - NameEnd: 104, - Name: "C", - }, - }, - Sample: (*ast.TableSample)(nil), + Left: &ast.TableName{ + Table: &ast.Ident{ + NamePos: 18, + NameEnd: 25, + Name: "Singers", }, - Cond: &ast.On{ - On: 107, - Expr: &ast.BinaryExpr{ - Op: "=", - Left: &ast.Path{ - Idents: []*ast.Ident{ - &ast.Ident{ - NamePos: 110, - NameEnd: 111, - Name: "B", - }, - &ast.Ident{ - NamePos: 112, - NameEnd: 120, - Name: "SingerID", - }, - }, - }, - Right: &ast.Path{ - Idents: []*ast.Ident{ - &ast.Ident{ - NamePos: 123, - NameEnd: 124, - Name: "C", - }, - &ast.Ident{ - NamePos: 125, - NameEnd: 133, - Name: "SingerID", - }, - }, - }, + Hint: (*ast.Hint)(nil), + As: &ast.AsAlias{ + As: -1, + Alias: &ast.Ident{ + NamePos: 26, + NameEnd: 27, + Name: "A", }, }, + Sample: (*ast.TableSample)(nil), }, Right: &ast.TableName{ Table: &ast.Ident{ - NamePos: 148, - NameEnd: 154, - Name: "Singer", + NamePos: 42, + NameEnd: 49, + Name: "Singers", }, Hint: (*ast.Hint)(nil), As: &ast.AsAlias{ As: -1, Alias: &ast.Ident{ - NamePos: 155, - NameEnd: 156, - Name: "D", + NamePos: 50, + NameEnd: 51, + Name: "B", }, }, Sample: (*ast.TableSample)(nil), }, Cond: &ast.On{ - On: 159, + On: 54, Expr: &ast.BinaryExpr{ Op: "=", Left: &ast.Path{ Idents: []*ast.Ident{ &ast.Ident{ - NamePos: 162, - NameEnd: 163, - Name: "C", + NamePos: 57, + NameEnd: 58, + Name: "A", }, &ast.Ident{ - NamePos: 164, - NameEnd: 172, + NamePos: 59, + NameEnd: 67, Name: "SingerID", }, }, @@ -199,13 +80,13 @@ FROM Right: &ast.Path{ Idents: []*ast.Ident{ &ast.Ident{ - NamePos: 175, - NameEnd: 176, - Name: "D", + NamePos: 70, + NameEnd: 71, + Name: "B", }, &ast.Ident{ - NamePos: 177, - NameEnd: 185, + NamePos: 72, + NameEnd: 80, Name: "SingerID", }, }, @@ -223,4 +104,4 @@ FROM } --- SQL -SELECT * FROM Singers A INNER JOIN Singers B ON A.SingerID = B.SingerID INNER JOIN Singer C ON B.SingerID = C.SingerID INNER JOIN Singer D ON C.SingerID = D.SingerID +SELECT * FROM Singers A INNER JOIN Singers B ON A.SingerID = B.SingerID From 202999ac38e7e067eaedac14e0054e7618a495b0 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:06:45 +0900 Subject: [PATCH 2/5] Implement REPLACE_FIELDS (#220) * Implement REPLACE_FIELDS * Update testdata * Add blank line * Fix doc comment and receiver --- ast/ast.go | 26 ++++ ast/pos.go | 16 +++ ast/sql.go | 6 + parser.go | 29 ++++ ...otocol_buffer_functions_replace_fields.sql | 6 + ...ol_buffer_functions_replace_fields.sql.txt | 126 ++++++++++++++++++ 6 files changed, 209 insertions(+) create mode 100644 testdata/input/expr/protocol_buffer_functions_replace_fields.sql create mode 100644 testdata/result/expr/protocol_buffer_functions_replace_fields.sql.txt diff --git a/ast/ast.go b/ast/ast.go index 1de8aac0..a51cf9e4 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -172,6 +172,7 @@ func (CallExpr) isExpr() {} func (CountStarExpr) isExpr() {} func (CastExpr) isExpr() {} func (ExtractExpr) isExpr() {} +func (ReplaceFieldsExpr) isExpr() {} func (CaseExpr) isExpr() {} func (IfExpr) isExpr() {} func (ParenExpr) isExpr() {} @@ -1273,6 +1274,31 @@ type ExtractExpr struct { AtTimeZone *AtTimeZone // optional } +// ReplaceFieldsArg is value AS field_path node in ReplaceFieldsExpr. +// +// {{.Expr | sql}} AS {{.Field | sql}} +type ReplaceFieldsArg struct { + // pos = Expr.pos + // end = Field.end + + Expr Expr + Field *Path +} + +// ReplaceFieldsExpr is REPLACE_FIELDS call expression node. +// +// REPLACE_FIELDS({{.Expr.| sql}}, {{.Fields | sqlJoin ", "}}) +type ReplaceFieldsExpr struct { + // pos = ReplaceFields + // end = Rparen + 1 + + ReplaceFields token.Pos // position of "REPLACE_FIELDS" keyword + Rparen token.Pos // position of ")" + + Expr Expr + Fields []*ReplaceFieldsArg +} + // AtTimeZone is AT TIME ZONE clause in EXTRACT call. // // AT TIME ZONE {{.Expr | sql}} diff --git a/ast/pos.go b/ast/pos.go index dd0cd882..8bcac94f 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -478,6 +478,22 @@ func (e *ExtractExpr) End() token.Pos { return posAdd(e.Rparen, 1) } +func (r *ReplaceFieldsArg) Pos() token.Pos { + return nodePos(wrapNode(r.Expr)) +} + +func (r *ReplaceFieldsArg) End() token.Pos { + return nodeEnd(wrapNode(r.Field)) +} + +func (r *ReplaceFieldsExpr) Pos() token.Pos { + return r.ReplaceFields +} + +func (r *ReplaceFieldsExpr) End() token.Pos { + return posAdd(r.Rparen, 1) +} + func (a *AtTimeZone) Pos() token.Pos { return a.At } diff --git a/ast/sql.go b/ast/sql.go index 12f864db..e7a2ec76 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -549,6 +549,12 @@ func (a *AtTimeZone) SQL() string { return "AT TIME ZONE " + a.Expr.SQL() } +func (r *ReplaceFieldsArg) SQL() string { return r.Expr.SQL() + " AS " + r.Field.SQL() } + +func (r *ReplaceFieldsExpr) SQL() string { + return "REPLACE_FIELDS(" + r.Expr.SQL() + ", " + sqlJoin(r.Fields, ", ") + ")" +} + func (c *CastExpr) SQL() string { return strOpt(c.Safe, "SAFE_") + "CAST(" + c.Expr.SQL() + " AS " + c.Type.SQL() + ")" } diff --git a/parser.go b/parser.go index 0655fc52..ad6bf959 100644 --- a/parser.go +++ b/parser.go @@ -1469,6 +1469,8 @@ func (p *Parser) parseLit() ast.Expr { switch { case id.IsKeywordLike("SAFE_CAST"): return p.parseCastExpr() + case id.IsKeywordLike("REPLACE_FIELDS"): + return p.parseReplaceFieldsExpr() } p.nextToken() switch p.Token.Kind { @@ -1754,6 +1756,33 @@ func (p *Parser) parseIfExpr() *ast.IfExpr { } } +func (p *Parser) parseReplaceFieldsArg() *ast.ReplaceFieldsArg { + expr := p.parseExpr() + p.expect("AS") + field := p.parsePath() + + return &ast.ReplaceFieldsArg{ + Expr: expr, + Field: field, + } +} + +func (p *Parser) parseReplaceFieldsExpr() *ast.ReplaceFieldsExpr { + replaceFields := p.expectKeywordLike("REPLACE_FIELDS").Pos + p.expect("(") + expr := p.parseExpr() + p.expect(",") + fields := parseCommaSeparatedList(p, p.parseReplaceFieldsArg) + rparen := p.expect(")").Pos + + return &ast.ReplaceFieldsExpr{ + ReplaceFields: replaceFields, + Rparen: rparen, + Expr: expr, + Fields: fields, + } +} + func (p *Parser) parseCastExpr() *ast.CastExpr { if p.Token.Kind != "CAST" && !p.Token.IsKeywordLike("SAFE_CAST") { panic(p.errorfAtToken(&p.Token, `expected CAST keyword or SAFE_CAST pseudo keyword, but: %v`, p.Token.Kind)) diff --git a/testdata/input/expr/protocol_buffer_functions_replace_fields.sql b/testdata/input/expr/protocol_buffer_functions_replace_fields.sql new file mode 100644 index 00000000..64caec01 --- /dev/null +++ b/testdata/input/expr/protocol_buffer_functions_replace_fields.sql @@ -0,0 +1,6 @@ +REPLACE_FIELDS( + NEW Book( + "The Hummingbird" AS title, + NEW BookDetails(10 AS chapters) AS details), + "The Hummingbird II" AS title, + 11 AS details.chapters) \ No newline at end of file diff --git a/testdata/result/expr/protocol_buffer_functions_replace_fields.sql.txt b/testdata/result/expr/protocol_buffer_functions_replace_fields.sql.txt new file mode 100644 index 00000000..5587e16a --- /dev/null +++ b/testdata/result/expr/protocol_buffer_functions_replace_fields.sql.txt @@ -0,0 +1,126 @@ +--- protocol_buffer_functions_replace_fields.sql +REPLACE_FIELDS( + NEW Book( + "The Hummingbird" AS title, + NEW BookDetails(10 AS chapters) AS details), + "The Hummingbird II" AS title, + 11 AS details.chapters) +--- AST +&ast.ReplaceFieldsExpr{ + ReplaceFields: 0, + Rparen: 166, + Expr: &ast.NewConstructor{ + New: 18, + Type: &ast.NamedType{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 22, + NameEnd: 26, + Name: "Book", + }, + }, + }, + Args: []ast.NewConstructorArg{ + &ast.Alias{ + Expr: &ast.StringLiteral{ + ValuePos: 32, + ValueEnd: 49, + Value: "The Hummingbird", + }, + As: &ast.AsAlias{ + As: 50, + Alias: &ast.Ident{ + NamePos: 53, + NameEnd: 58, + Name: "title", + }, + }, + }, + &ast.Alias{ + Expr: &ast.NewConstructor{ + New: 64, + Type: &ast.NamedType{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 68, + NameEnd: 79, + Name: "BookDetails", + }, + }, + }, + Args: []ast.NewConstructorArg{ + &ast.Alias{ + Expr: &ast.IntLiteral{ + ValuePos: 80, + ValueEnd: 82, + Base: 10, + Value: "10", + }, + As: &ast.AsAlias{ + As: 83, + Alias: &ast.Ident{ + NamePos: 86, + NameEnd: 94, + Name: "chapters", + }, + }, + }, + }, + Rparen: 94, + }, + As: &ast.AsAlias{ + As: 96, + Alias: &ast.Ident{ + NamePos: 99, + NameEnd: 106, + Name: "details", + }, + }, + }, + }, + Rparen: 106, + }, + Fields: []*ast.ReplaceFieldsArg{ + &ast.ReplaceFieldsArg{ + Expr: &ast.StringLiteral{ + ValuePos: 111, + ValueEnd: 131, + Value: "The Hummingbird II", + }, + Field: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 135, + NameEnd: 140, + Name: "title", + }, + }, + }, + }, + &ast.ReplaceFieldsArg{ + Expr: &ast.IntLiteral{ + ValuePos: 144, + ValueEnd: 146, + Base: 10, + Value: "11", + }, + Field: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 150, + NameEnd: 157, + Name: "details", + }, + &ast.Ident{ + NamePos: 158, + NameEnd: 166, + Name: "chapters", + }, + }, + }, + }, + }, +} + +--- SQL +REPLACE_FIELDS(NEW Book("The Hummingbird" AS title, NEW BookDetails(10 AS chapters) AS details), "The Hummingbird II" AS title, 11 AS details.chapters) From 77fc9f82dccd12ca7fe48d71a06d95954a8ac365 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:43:41 +0900 Subject: [PATCH 3/5] Implement lambda expressions (#219) * Implement LambdaArg * Update testdata * Fix to use token.InvalidPos * Replace useless use of parseCommaSeparatedList * Add comment to LambdaArg.Args * Update LambdaArg comments --- ast/ast.go | 16 ++++ ast/pos.go | 8 ++ ast/sql.go | 9 +++ parser.go | 39 ++++++++++ ...unctions_array_filter_parenless_lambda.sql | 1 + ...functions_array_filter_two_args_lambda.sql | 1 + ...ions_array_filter_parenless_lambda.sql.txt | 72 ++++++++++++++++++ ...tions_array_filter_two_args_lambda.sql.txt | 76 +++++++++++++++++++ 8 files changed, 222 insertions(+) create mode 100644 testdata/input/expr/array_functions_array_filter_parenless_lambda.sql create mode 100644 testdata/input/expr/array_functions_array_filter_two_args_lambda.sql create mode 100644 testdata/result/expr/array_functions_array_filter_parenless_lambda.sql.txt create mode 100644 testdata/result/expr/array_functions_array_filter_two_args_lambda.sql.txt diff --git a/ast/ast.go b/ast/ast.go index a51cf9e4..e13a9791 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -218,6 +218,7 @@ type Arg interface { func (ExprArg) isArg() {} func (IntervalArg) isArg() {} func (SequenceArg) isArg() {} +func (LambdaArg) isArg() {} // NullHandlingModifier represents IGNORE/RESPECT NULLS of aggregate function calls type NullHandlingModifier interface { @@ -1193,6 +1194,21 @@ type SequenceArg struct { Expr Expr } +// LambdaArg is lambda expression argument of the generic function call. +// +// {{if .Lparen.Invalid}}{{.Args | sqlJoin ", "}}{{else}}({{.Args | sqlJoin ", "}}) -> {{.Expr | sql}} +// +// Note: Args won't be empty. If Lparen is not appeared, Args have exactly one element. +type LambdaArg struct { + // pos = Lparen || Args[0].pos + // end = Expr.end + + Lparen token.Pos // optional + + Args []*Ident // if Lparen.Invalid() then len(Args) = 1 else len(Args) > 0 + Expr Expr +} + // NamedArg represents a name and value pair in named arguments // // {{.Name | sql}} => {{.Value | sql}} diff --git a/ast/pos.go b/ast/pos.go index 8bcac94f..c6cc1f4b 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -422,6 +422,14 @@ func (s *SequenceArg) End() token.Pos { return nodeEnd(wrapNode(s.Expr)) } +func (l *LambdaArg) Pos() token.Pos { + return posChoice(l.Lparen, nodePos(nodeSliceIndex(l.Args, 0))) +} + +func (l *LambdaArg) End() token.Pos { + return nodeEnd(wrapNode(l.Expr)) +} + func (n *NamedArg) Pos() token.Pos { return nodePos(wrapNode(n.Name)) } diff --git a/ast/sql.go b/ast/sql.go index e7a2ec76..fa8caaf2 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -506,6 +506,15 @@ func (c *CallExpr) SQL() string { ")" } +func (l *LambdaArg) SQL() string { + // This implementation is not exactly matched with the doc comment for simplicity. + return strOpt(!l.Lparen.Invalid(), "(") + + sqlJoin(l.Args, ", ") + + strOpt(!l.Lparen.Invalid(), ")") + + " -> " + + l.Expr.SQL() +} + func (n *NamedArg) SQL() string { return n.Name.SQL() + " => " + n.Value.SQL() } func (i *IgnoreNulls) SQL() string { return "IGNORE NULLS" } diff --git a/parser.go b/parser.go index ad6bf959..f7c30ddb 100644 --- a/parser.go +++ b/parser.go @@ -1590,6 +1590,42 @@ func (p *Parser) tryParseNamedArg() *ast.NamedArg { } } +func (p *Parser) lookaheadLambdaArg() bool { + lexer := p.Lexer.Clone() + defer func() { + p.Lexer = lexer + }() + + if p.Token.Kind != "(" && p.Token.Kind != token.TokenIdent { + return false + } + + // Note: all lambda patterns can be parsed as expr -> expr. + p.parseExpr() + return p.Token.Kind == "->" +} + +func (p *Parser) parseLambdaArg() *ast.LambdaArg { + lparen := token.InvalidPos + var args []*ast.Ident + if p.Token.Kind == "(" { + lparen = p.expect("(").Pos + args = parseCommaSeparatedList(p, p.parseIdent) + p.expect(")") + } else { + args = []*ast.Ident{p.parseIdent()} + } + + p.expect("->") + expr := p.parseExpr() + + return &ast.LambdaArg{ + Lparen: lparen, + Args: args, + Expr: expr, + } +} + func (p *Parser) parseArg() ast.Arg { if i := p.tryParseIntervalArg(); i != nil { return i @@ -1597,6 +1633,9 @@ func (p *Parser) parseArg() ast.Arg { if s := p.tryParseSequenceArg(); s != nil { return s } + if p.lookaheadLambdaArg() { + return p.parseLambdaArg() + } return p.parseExprArg() } diff --git a/testdata/input/expr/array_functions_array_filter_parenless_lambda.sql b/testdata/input/expr/array_functions_array_filter_parenless_lambda.sql new file mode 100644 index 00000000..e00d2ea4 --- /dev/null +++ b/testdata/input/expr/array_functions_array_filter_parenless_lambda.sql @@ -0,0 +1 @@ +ARRAY_FILTER([1 ,2, 3], e -> e > 1) \ No newline at end of file diff --git a/testdata/input/expr/array_functions_array_filter_two_args_lambda.sql b/testdata/input/expr/array_functions_array_filter_two_args_lambda.sql new file mode 100644 index 00000000..205e21f0 --- /dev/null +++ b/testdata/input/expr/array_functions_array_filter_two_args_lambda.sql @@ -0,0 +1 @@ +ARRAY_FILTER([0, 2, 3], (e, i) -> e > i) \ No newline at end of file diff --git a/testdata/result/expr/array_functions_array_filter_parenless_lambda.sql.txt b/testdata/result/expr/array_functions_array_filter_parenless_lambda.sql.txt new file mode 100644 index 00000000..982a7bab --- /dev/null +++ b/testdata/result/expr/array_functions_array_filter_parenless_lambda.sql.txt @@ -0,0 +1,72 @@ +--- array_functions_array_filter_parenless_lambda.sql +ARRAY_FILTER([1 ,2, 3], e -> e > 1) +--- AST +&ast.CallExpr{ + Rparen: 34, + Func: &ast.Ident{ + NamePos: 0, + NameEnd: 12, + Name: "ARRAY_FILTER", + }, + Distinct: false, + Args: []ast.Arg{ + &ast.ExprArg{ + Expr: &ast.ArrayLiteral{ + Array: -1, + Lbrack: 13, + Rbrack: 21, + Type: nil, + Values: []ast.Expr{ + &ast.IntLiteral{ + ValuePos: 14, + ValueEnd: 15, + Base: 10, + Value: "1", + }, + &ast.IntLiteral{ + ValuePos: 17, + ValueEnd: 18, + Base: 10, + Value: "2", + }, + &ast.IntLiteral{ + ValuePos: 20, + ValueEnd: 21, + Base: 10, + Value: "3", + }, + }, + }, + }, + &ast.LambdaArg{ + Lparen: -1, + Args: []*ast.Ident{ + &ast.Ident{ + NamePos: 24, + NameEnd: 25, + Name: "e", + }, + }, + Expr: &ast.BinaryExpr{ + Op: ">", + Left: &ast.Ident{ + NamePos: 29, + NameEnd: 30, + Name: "e", + }, + Right: &ast.IntLiteral{ + ValuePos: 33, + ValueEnd: 34, + Base: 10, + Value: "1", + }, + }, + }, + }, + NamedArgs: []*ast.NamedArg(nil), + NullHandling: nil, + Having: nil, +} + +--- SQL +ARRAY_FILTER([1, 2, 3], e -> e > 1) diff --git a/testdata/result/expr/array_functions_array_filter_two_args_lambda.sql.txt b/testdata/result/expr/array_functions_array_filter_two_args_lambda.sql.txt new file mode 100644 index 00000000..7d83c243 --- /dev/null +++ b/testdata/result/expr/array_functions_array_filter_two_args_lambda.sql.txt @@ -0,0 +1,76 @@ +--- array_functions_array_filter_two_args_lambda.sql +ARRAY_FILTER([0, 2, 3], (e, i) -> e > i) +--- AST +&ast.CallExpr{ + Rparen: 39, + Func: &ast.Ident{ + NamePos: 0, + NameEnd: 12, + Name: "ARRAY_FILTER", + }, + Distinct: false, + Args: []ast.Arg{ + &ast.ExprArg{ + Expr: &ast.ArrayLiteral{ + Array: -1, + Lbrack: 13, + Rbrack: 21, + Type: nil, + Values: []ast.Expr{ + &ast.IntLiteral{ + ValuePos: 14, + ValueEnd: 15, + Base: 10, + Value: "0", + }, + &ast.IntLiteral{ + ValuePos: 17, + ValueEnd: 18, + Base: 10, + Value: "2", + }, + &ast.IntLiteral{ + ValuePos: 20, + ValueEnd: 21, + Base: 10, + Value: "3", + }, + }, + }, + }, + &ast.LambdaArg{ + Lparen: 24, + Args: []*ast.Ident{ + &ast.Ident{ + NamePos: 25, + NameEnd: 26, + Name: "e", + }, + &ast.Ident{ + NamePos: 28, + NameEnd: 29, + Name: "i", + }, + }, + Expr: &ast.BinaryExpr{ + Op: ">", + Left: &ast.Ident{ + NamePos: 34, + NameEnd: 35, + Name: "e", + }, + Right: &ast.Ident{ + NamePos: 38, + NameEnd: 39, + Name: "i", + }, + }, + }, + }, + NamedArgs: []*ast.NamedArg(nil), + NullHandling: nil, + Having: nil, +} + +--- SQL +ARRAY_FILTER([0, 2, 3], (e, i) -> e > i) From 82ce77ae7985bd0ccd29d264d61ed4f65b8ff98d Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:36:09 +0900 Subject: [PATCH 4/5] Fix lookaheadLambdaArg not to use parseExpr (#221) * Fix lookaheadLambdaArg * Remove unreachable code --- parser.go | 31 ++++++-- ...array_filter_one_arg_with_paren_lambda.sql | 1 + ...y_filter_one_arg_with_paren_lambda.sql.txt | 72 +++++++++++++++++++ 3 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 testdata/input/expr/array_functions_array_filter_one_arg_with_paren_lambda.sql create mode 100644 testdata/result/expr/array_functions_array_filter_one_arg_with_paren_lambda.sql.txt diff --git a/parser.go b/parser.go index f7c30ddb..72f17544 100644 --- a/parser.go +++ b/parser.go @@ -1596,13 +1596,34 @@ func (p *Parser) lookaheadLambdaArg() bool { p.Lexer = lexer }() - if p.Token.Kind != "(" && p.Token.Kind != token.TokenIdent { + switch p.Token.Kind { + case "(": + p.nextToken() + if p.Token.Kind != token.TokenIdent { + return false + } + p.nextToken() + + for p.Token.Kind != ")" { + if p.Token.Kind != "," { + return false + } + p.nextToken() + + if p.Token.Kind != token.TokenIdent { + return false + } + p.nextToken() + } + p.nextToken() + + return p.Token.Kind == "->" + case token.TokenIdent: + p.nextToken() + return p.Token.Kind == "->" + default: return false } - - // Note: all lambda patterns can be parsed as expr -> expr. - p.parseExpr() - return p.Token.Kind == "->" } func (p *Parser) parseLambdaArg() *ast.LambdaArg { diff --git a/testdata/input/expr/array_functions_array_filter_one_arg_with_paren_lambda.sql b/testdata/input/expr/array_functions_array_filter_one_arg_with_paren_lambda.sql new file mode 100644 index 00000000..c303120d --- /dev/null +++ b/testdata/input/expr/array_functions_array_filter_one_arg_with_paren_lambda.sql @@ -0,0 +1 @@ +ARRAY_FILTER([1 ,2, 3], (e) -> e > 1) \ No newline at end of file diff --git a/testdata/result/expr/array_functions_array_filter_one_arg_with_paren_lambda.sql.txt b/testdata/result/expr/array_functions_array_filter_one_arg_with_paren_lambda.sql.txt new file mode 100644 index 00000000..a2037132 --- /dev/null +++ b/testdata/result/expr/array_functions_array_filter_one_arg_with_paren_lambda.sql.txt @@ -0,0 +1,72 @@ +--- array_functions_array_filter_one_arg_with_paren_lambda.sql +ARRAY_FILTER([1 ,2, 3], (e) -> e > 1) +--- AST +&ast.CallExpr{ + Rparen: 36, + Func: &ast.Ident{ + NamePos: 0, + NameEnd: 12, + Name: "ARRAY_FILTER", + }, + Distinct: false, + Args: []ast.Arg{ + &ast.ExprArg{ + Expr: &ast.ArrayLiteral{ + Array: -1, + Lbrack: 13, + Rbrack: 21, + Type: nil, + Values: []ast.Expr{ + &ast.IntLiteral{ + ValuePos: 14, + ValueEnd: 15, + Base: 10, + Value: "1", + }, + &ast.IntLiteral{ + ValuePos: 17, + ValueEnd: 18, + Base: 10, + Value: "2", + }, + &ast.IntLiteral{ + ValuePos: 20, + ValueEnd: 21, + Base: 10, + Value: "3", + }, + }, + }, + }, + &ast.LambdaArg{ + Lparen: 24, + Args: []*ast.Ident{ + &ast.Ident{ + NamePos: 25, + NameEnd: 26, + Name: "e", + }, + }, + Expr: &ast.BinaryExpr{ + Op: ">", + Left: &ast.Ident{ + NamePos: 31, + NameEnd: 32, + Name: "e", + }, + Right: &ast.IntLiteral{ + ValuePos: 35, + ValueEnd: 36, + Base: 10, + Value: "1", + }, + }, + }, + }, + NamedArgs: []*ast.NamedArg(nil), + NullHandling: nil, + Having: nil, +} + +--- SQL +ARRAY_FILTER([1, 2, 3], (e) -> e > 1) From 66dfc61aa2dd037f5528e524f24732894f2f6cff Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:42:41 +0900 Subject: [PATCH 5/5] Implement THEN RETURN clause in DML statements (#214) * Implement THEN RETURN * Update testdata * Fix Update.SQL() * Fix Update.SQL() * Fix needless sqlOpt --- ast/ast.go | 58 ++++++++--- ast/pos.go | 22 ++++- ast/sql.go | 55 +++++------ parser.go | 58 +++++++++-- testdata/input/dml/delete_then_return.sql | 1 + ...t_or_update_then_return_with_action_as.sql | 3 + .../dml/update_then_return_with_action.sql | 1 + testdata/result/dml/delete _from.sql.txt | 1 + testdata/result/dml/delete.sql.txt | 1 + testdata/result/dml/delete_as.sql.txt | 1 + .../result/dml/delete_then_return.sql.txt | 58 +++++++++++ .../result/dml/insert_into_values.sql.txt | 1 + testdata/result/dml/insert_or_ignore.sql.txt | 1 + testdata/result/dml/insert_or_update.sql.txt | 1 + ..._update_then_return_with_action_as.sql.txt | 80 +++++++++++++++ testdata/result/dml/insert_select.sql.txt | 1 + testdata/result/dml/insert_values.sql.txt | 1 + .../result/dml/insert_values_default.sql.txt | 1 + .../dml/insert_with_sequence_function.sql.txt | 1 + testdata/result/dml/update.sql.txt | 1 + testdata/result/dml/update_as.sql.txt | 1 + .../update_then_return_with_action.sql.txt | 97 +++++++++++++++++++ .../result/statement/delete _from.sql.txt | 1 + testdata/result/statement/delete.sql.txt | 1 + testdata/result/statement/delete_as.sql.txt | 1 + .../statement/delete_then_return.sql.txt | 58 +++++++++++ .../statement/insert_into_values.sql.txt | 1 + .../result/statement/insert_or_ignore.sql.txt | 1 + .../result/statement/insert_or_update.sql.txt | 1 + ..._update_then_return_with_action_as.sql.txt | 80 +++++++++++++++ .../result/statement/insert_select.sql.txt | 1 + .../result/statement/insert_values.sql.txt | 1 + .../statement/insert_values_default.sql.txt | 1 + .../insert_with_sequence_function.sql.txt | 1 + testdata/result/statement/update.sql.txt | 1 + testdata/result/statement/update_as.sql.txt | 1 + .../update_then_return_with_action.sql.txt | 97 +++++++++++++++++++ 37 files changed, 638 insertions(+), 54 deletions(-) create mode 100644 testdata/input/dml/delete_then_return.sql create mode 100644 testdata/input/dml/insert_or_update_then_return_with_action_as.sql create mode 100644 testdata/input/dml/update_then_return_with_action.sql create mode 100644 testdata/result/dml/delete_then_return.sql.txt create mode 100644 testdata/result/dml/insert_or_update_then_return_with_action_as.sql.txt create mode 100644 testdata/result/dml/update_then_return_with_action.sql.txt create mode 100644 testdata/result/statement/delete_then_return.sql.txt create mode 100644 testdata/result/statement/insert_or_update_then_return_with_action_as.sql.txt create mode 100644 testdata/result/statement/update_then_return_with_action.sql.txt diff --git a/ast/ast.go b/ast/ast.go index e13a9791..35718abc 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -3165,20 +3165,48 @@ type AlterSearchIndex struct { // // ================================================================================ +// WithAction is WITH ACTION clause in ThenReturn. +// +// WITH ACTION {{.Alias | sqlOpt}} +type WithAction struct { + // pos = With + // end = Alias.end || Action + 6 + + With token.Pos // position of "WITH" keyword + Action token.Pos // position of "ACTION" keyword + + Alias *AsAlias // optional +} + +// ThenReturn is THEN RETURN clause in DML. +// +// THEN RETURN {{.WithAction | sqlOpt}} {{.Items | sqlJoin ", "}} +type ThenReturn struct { + // pos = Then + // end = Items[$].end + + Then token.Pos // position of "THEN" keyword + + WithAction *WithAction // optional + Items []SelectItem +} + // Insert is INSERT statement node. // // INSERT {{if .InsertOrType}}OR .InsertOrType{{end}}INTO {{.TableName | sql}} ({{.Columns | sqlJoin ","}}) {{.Input | sql}} +// {{.ThenReturn | sqlOpt}} type Insert struct { // pos = Insert - // end = Input.end + // end = (ThenReturn ?? Input).end Insert token.Pos // position of "INSERT" keyword InsertOrType InsertOrType - TableName *Ident - Columns []*Ident - Input InsertInput + TableName *Ident + Columns []*Ident + Input InsertInput + ThenReturn *ThenReturn // optional } // ValuesInput is VALUES clause in INSERT. @@ -3231,31 +3259,35 @@ type SubQueryInput struct { // Delete is DELETE statement. // // DELETE FROM {{.TableName | sql}} {{.As | sqlOpt}} {{.Where | sql}} +// {{.ThenReturn | sqlOpt}} type Delete struct { // pos = Delete - // end = Where.end + // end = (ThenReturn ?? Where).end Delete token.Pos // position of "DELETE" keyword - TableName *Ident - As *AsAlias // optional - Where *Where + TableName *Ident + As *AsAlias // optional + Where *Where + ThenReturn *ThenReturn // optional } // Update is UPDATE statement. // // UPDATE {{.TableName | sql}} {{.As | sqlOpt}} // SET {{.Updates | sqlJoin ","}} {{.Where | sql}} +// {{.ThenReturn | sqlOpt}} type Update struct { // pos = Update - // end = Where.end + // end = (ThenReturn ?? Where).end Update token.Pos // position of "UPDATE" keyword - TableName *Ident - As *AsAlias // optional - Updates []*UpdateItem // len(Updates) > 0 - Where *Where + TableName *Ident + As *AsAlias // optional + Updates []*UpdateItem // len(Updates) > 0 + Where *Where + ThenReturn *ThenReturn // optional } // UpdateItem is SET clause items in UPDATE. diff --git a/ast/pos.go b/ast/pos.go index c6cc1f4b..7b7a108c 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -1590,12 +1590,28 @@ func (a *AlterSearchIndex) End() token.Pos { return nodeEnd(wrapNode(a.IndexAlteration)) } +func (w *WithAction) Pos() token.Pos { + return w.With +} + +func (w *WithAction) End() token.Pos { + return posChoice(nodeEnd(wrapNode(w.Alias)), posAdd(w.Action, 6)) +} + +func (t *ThenReturn) Pos() token.Pos { + return t.Then +} + +func (t *ThenReturn) End() token.Pos { + return nodeEnd(nodeSliceLast(t.Items)) +} + func (i *Insert) Pos() token.Pos { return i.Insert } func (i *Insert) End() token.Pos { - return nodeEnd(wrapNode(i.Input)) + return nodeEnd(nodeChoice(wrapNode(i.ThenReturn), wrapNode(i.Input))) } func (v *ValuesInput) Pos() token.Pos { @@ -1635,7 +1651,7 @@ func (d *Delete) Pos() token.Pos { } func (d *Delete) End() token.Pos { - return nodeEnd(wrapNode(d.Where)) + return nodeEnd(nodeChoice(wrapNode(d.ThenReturn), wrapNode(d.Where))) } func (u *Update) Pos() token.Pos { @@ -1643,7 +1659,7 @@ func (u *Update) Pos() token.Pos { } func (u *Update) End() token.Pos { - return nodeEnd(wrapNode(u.Where)) + return nodeEnd(nodeChoice(wrapNode(u.ThenReturn), wrapNode(u.Where))) } func (u *UpdateItem) Pos() token.Pos { diff --git a/ast/sql.go b/ast/sql.go index fa8caaf2..eedd404a 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -1385,20 +1385,22 @@ func (a *AlterSearchIndex) SQL() string { // // ================================================================================ +func (w *WithAction) SQL() string { + return "WITH ACTION" + sqlOpt(" ", w.Alias, "") +} + +func (t *ThenReturn) SQL() string { + return "THEN RETURN " + sqlOpt("", t.WithAction, " ") + sqlJoin(t.Items, ", ") +} + func (i *Insert) SQL() string { - sql := "INSERT " - if i.InsertOrType != "" { - sql += "OR " + string(i.InsertOrType) + " " - } - sql += "INTO " + i.TableName.SQL() + " (" - for i, c := range i.Columns { - if i != 0 { - sql += ", " - } - sql += c.SQL() - } - sql += ") " + i.Input.SQL() - return sql + return "INSERT " + + strOpt(i.InsertOrType != "", "OR "+string(i.InsertOrType)+" ") + + "INTO " + i.TableName.SQL() + " (" + + sqlJoin(i.Columns, ", ") + + ") " + + i.Input.SQL() + + sqlOpt(" ", i.ThenReturn, "") } func (v *ValuesInput) SQL() string { @@ -1436,25 +1438,20 @@ func (s *SubQueryInput) SQL() string { } func (d *Delete) SQL() string { - sql := "DELETE FROM " + d.TableName.SQL() - if d.As != nil { - sql += " " + d.As.SQL() - } - sql += " " + d.Where.SQL() - return sql + return "DELETE FROM " + + d.TableName.SQL() + " " + + sqlOpt("", d.As, " ") + + d.Where.SQL() + + sqlOpt(" ", d.ThenReturn, "") } func (u *Update) SQL() string { - sql := "UPDATE " + u.TableName.SQL() - if u.As != nil { - sql += " " + u.As.SQL() - } - sql += " SET " + u.Updates[0].SQL() - for _, item := range u.Updates[1:] { - sql += ", " + item.SQL() - } - sql += " " + u.Where.SQL() - return sql + return "UPDATE " + u.TableName.SQL() + " " + + sqlOpt("", u.As, " ") + + "SET " + + sqlJoin(u.Updates, ", ") + + " " + u.Where.SQL() + + sqlOpt(" ", u.ThenReturn, "") } func (u *UpdateItem) SQL() string { diff --git a/parser.go b/parser.go index 72f17544..40a3d9a6 100644 --- a/parser.go +++ b/parser.go @@ -4124,6 +4124,39 @@ func (p *Parser) parseDML() ast.DML { panic(p.errorfAtToken(id, "expect pseudo keyword: INSERT, DELETE, UPDATE but: %s", id.AsString)) } +func (p *Parser) tryParseWithAction() *ast.WithAction { + if p.Token.Kind != "WITH" { + return nil + } + + with := p.expect("WITH").Pos + action := p.expectKeywordLike("ACTION").Pos + alias := p.tryParseAsAlias(withRequiredAs) + + return &ast.WithAction{ + With: with, + Action: action, + Alias: alias, + } +} + +func (p *Parser) tryParseThenReturn() *ast.ThenReturn { + if p.Token.Kind != "THEN" { + return nil + } + + then := p.expect("THEN").Pos + p.expectKeywordLike("RETURN") + withAction := p.tryParseWithAction() + items := parseCommaSeparatedList(p, p.parseSelectItem) + + return &ast.ThenReturn{ + Then: then, + WithAction: withAction, + Items: items, + } +} + func (p *Parser) parseInsert(pos token.Pos) *ast.Insert { var insertOrType ast.InsertOrType if p.Token.Kind == "OR" { @@ -4165,12 +4198,15 @@ func (p *Parser) parseInsert(pos token.Pos) *ast.Insert { input = p.parseSubQueryInput() } + thenReturn := p.tryParseThenReturn() + return &ast.Insert{ Insert: pos, InsertOrType: insertOrType, TableName: name, Columns: columns, Input: input, + ThenReturn: thenReturn, } } @@ -4238,12 +4274,14 @@ func (p *Parser) parseDelete(pos token.Pos) *ast.Delete { name := p.parseIdent() as := p.tryParseAsAlias(withOptionalAs) where := p.parseWhere() + thenReturn := p.tryParseThenReturn() return &ast.Delete{ - Delete: pos, - TableName: name, - As: as, - Where: where, + Delete: pos, + TableName: name, + As: as, + Where: where, + ThenReturn: thenReturn, } } @@ -4256,13 +4294,15 @@ func (p *Parser) parseUpdate(pos token.Pos) *ast.Update { items := parseCommaSeparatedList(p, p.parseUpdateItem) where := p.parseWhere() + thenReturn := p.tryParseThenReturn() return &ast.Update{ - Update: pos, - TableName: name, - As: as, - Updates: items, - Where: where, + Update: pos, + TableName: name, + As: as, + Updates: items, + Where: where, + ThenReturn: thenReturn, } } diff --git a/testdata/input/dml/delete_then_return.sql b/testdata/input/dml/delete_then_return.sql new file mode 100644 index 00000000..4bbf277e --- /dev/null +++ b/testdata/input/dml/delete_then_return.sql @@ -0,0 +1 @@ +delete foo where foo = 1 and bar = 2 then return * \ No newline at end of file diff --git a/testdata/input/dml/insert_or_update_then_return_with_action_as.sql b/testdata/input/dml/insert_or_update_then_return_with_action_as.sql new file mode 100644 index 00000000..efb81ae3 --- /dev/null +++ b/testdata/input/dml/insert_or_update_then_return_with_action_as.sql @@ -0,0 +1,3 @@ +INSERT OR UPDATE INTO foo +(foo, bar) VALUES (1, 2) +THEN RETURN WITH ACTION AS act * \ No newline at end of file diff --git a/testdata/input/dml/update_then_return_with_action.sql b/testdata/input/dml/update_then_return_with_action.sql new file mode 100644 index 00000000..696d7db4 --- /dev/null +++ b/testdata/input/dml/update_then_return_with_action.sql @@ -0,0 +1 @@ +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 then return with action * \ No newline at end of file diff --git a/testdata/result/dml/delete _from.sql.txt b/testdata/result/dml/delete _from.sql.txt index c9972ae3..29eddeae 100644 --- a/testdata/result/dml/delete _from.sql.txt +++ b/testdata/result/dml/delete _from.sql.txt @@ -43,6 +43,7 @@ delete from foo where foo = 1 and bar = 2 }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/delete.sql.txt b/testdata/result/dml/delete.sql.txt index b5ec82a5..1bf07ded 100644 --- a/testdata/result/dml/delete.sql.txt +++ b/testdata/result/dml/delete.sql.txt @@ -43,6 +43,7 @@ delete foo where foo = 1 and bar = 2 }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/delete_as.sql.txt b/testdata/result/dml/delete_as.sql.txt index 2eee871a..36c330e3 100644 --- a/testdata/result/dml/delete_as.sql.txt +++ b/testdata/result/dml/delete_as.sql.txt @@ -42,6 +42,7 @@ delete foo as F where F.foo = 1 }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/delete_then_return.sql.txt b/testdata/result/dml/delete_then_return.sql.txt new file mode 100644 index 00000000..94b742a1 --- /dev/null +++ b/testdata/result/dml/delete_then_return.sql.txt @@ -0,0 +1,58 @@ +--- delete_then_return.sql +delete foo where foo = 1 and bar = 2 then return * +--- AST +&ast.Delete{ + Delete: 0, + TableName: &ast.Ident{ + NamePos: 7, + NameEnd: 10, + Name: "foo", + }, + As: (*ast.AsAlias)(nil), + Where: &ast.Where{ + Where: 11, + Expr: &ast.BinaryExpr{ + Op: "AND", + Left: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 17, + NameEnd: 20, + Name: "foo", + }, + Right: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + Right: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 29, + NameEnd: 32, + Name: "bar", + }, + Right: &ast.IntLiteral{ + ValuePos: 35, + ValueEnd: 36, + Base: 10, + Value: "2", + }, + }, + }, + }, + ThenReturn: &ast.ThenReturn{ + Then: 37, + WithAction: (*ast.WithAction)(nil), + Items: []ast.SelectItem{ + &ast.Star{ + Star: 49, + }, + }, + }, +} + +--- SQL +DELETE FROM foo WHERE foo = 1 AND bar = 2 THEN RETURN * diff --git a/testdata/result/dml/insert_into_values.sql.txt b/testdata/result/dml/insert_into_values.sql.txt index 047a68ed..dc897ce3 100644 --- a/testdata/result/dml/insert_into_values.sql.txt +++ b/testdata/result/dml/insert_into_values.sql.txt @@ -105,6 +105,7 @@ values (1, 2, 3), }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/insert_or_ignore.sql.txt b/testdata/result/dml/insert_or_ignore.sql.txt index 28adbf40..9994ff8e 100644 --- a/testdata/result/dml/insert_or_ignore.sql.txt +++ b/testdata/result/dml/insert_or_ignore.sql.txt @@ -53,6 +53,7 @@ INSERT OR IGNORE INTO foo }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/insert_or_update.sql.txt b/testdata/result/dml/insert_or_update.sql.txt index fbda7b80..a87b61f8 100644 --- a/testdata/result/dml/insert_or_update.sql.txt +++ b/testdata/result/dml/insert_or_update.sql.txt @@ -53,6 +53,7 @@ INSERT OR UPDATE INTO foo }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/insert_or_update_then_return_with_action_as.sql.txt b/testdata/result/dml/insert_or_update_then_return_with_action_as.sql.txt new file mode 100644 index 00000000..ab5e4a6d --- /dev/null +++ b/testdata/result/dml/insert_or_update_then_return_with_action_as.sql.txt @@ -0,0 +1,80 @@ +--- insert_or_update_then_return_with_action_as.sql +INSERT OR UPDATE INTO foo +(foo, bar) VALUES (1, 2) +THEN RETURN WITH ACTION AS act * +--- AST +&ast.Insert{ + Insert: 0, + InsertOrType: "UPDATE", + TableName: &ast.Ident{ + NamePos: 22, + NameEnd: 25, + Name: "foo", + }, + Columns: []*ast.Ident{ + &ast.Ident{ + NamePos: 27, + NameEnd: 30, + Name: "foo", + }, + &ast.Ident{ + NamePos: 32, + NameEnd: 35, + Name: "bar", + }, + }, + Input: &ast.ValuesInput{ + Values: 37, + Rows: []*ast.ValuesRow{ + &ast.ValuesRow{ + Lparen: 44, + Rparen: 49, + Exprs: []*ast.DefaultExpr{ + &ast.DefaultExpr{ + DefaultPos: -1, + Default: false, + Expr: &ast.IntLiteral{ + ValuePos: 45, + ValueEnd: 46, + Base: 10, + Value: "1", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Default: false, + Expr: &ast.IntLiteral{ + ValuePos: 48, + ValueEnd: 49, + Base: 10, + Value: "2", + }, + }, + }, + }, + }, + }, + ThenReturn: &ast.ThenReturn{ + Then: 51, + WithAction: &ast.WithAction{ + With: 63, + Action: 68, + Alias: &ast.AsAlias{ + As: 75, + Alias: &ast.Ident{ + NamePos: 78, + NameEnd: 81, + Name: "act", + }, + }, + }, + Items: []ast.SelectItem{ + &ast.Star{ + Star: 82, + }, + }, + }, +} + +--- SQL +INSERT OR UPDATE INTO foo (foo, bar) VALUES (1, 2) THEN RETURN WITH ACTION AS act * diff --git a/testdata/result/dml/insert_select.sql.txt b/testdata/result/dml/insert_select.sql.txt index 6afe8c7c..5b3b5590 100644 --- a/testdata/result/dml/insert_select.sql.txt +++ b/testdata/result/dml/insert_select.sql.txt @@ -94,6 +94,7 @@ select * from unnest([(1, 2), (3, 4)]) Limit: (*ast.Limit)(nil), }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/insert_values.sql.txt b/testdata/result/dml/insert_values.sql.txt index 09897f47..aae84cad 100644 --- a/testdata/result/dml/insert_values.sql.txt +++ b/testdata/result/dml/insert_values.sql.txt @@ -105,6 +105,7 @@ values (1, 2, 3), }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/insert_values_default.sql.txt b/testdata/result/dml/insert_values_default.sql.txt index ae7d07c4..2b3b0083 100644 --- a/testdata/result/dml/insert_values_default.sql.txt +++ b/testdata/result/dml/insert_values_default.sql.txt @@ -48,6 +48,7 @@ values (1, default) }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/insert_with_sequence_function.sql.txt b/testdata/result/dml/insert_with_sequence_function.sql.txt index 7f5a4b8d..c78a7cd4 100644 --- a/testdata/result/dml/insert_with_sequence_function.sql.txt +++ b/testdata/result/dml/insert_with_sequence_function.sql.txt @@ -54,6 +54,7 @@ INSERT INTO foo(bar) VALUES (GET_NEXT_SEQUENCE_VALUE(SEQUENCE my_sequence)) }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/update.sql.txt b/testdata/result/dml/update.sql.txt index 8f218958..fcf32946 100644 --- a/testdata/result/dml/update.sql.txt +++ b/testdata/result/dml/update.sql.txt @@ -78,6 +78,7 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/update_as.sql.txt b/testdata/result/dml/update_as.sql.txt index a647ea85..00622578 100644 --- a/testdata/result/dml/update_as.sql.txt +++ b/testdata/result/dml/update_as.sql.txt @@ -84,6 +84,7 @@ update foo as F set F.foo = F.bar + 1 where foo = F.bar }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/dml/update_then_return_with_action.sql.txt b/testdata/result/dml/update_then_return_with_action.sql.txt new file mode 100644 index 00000000..438a476f --- /dev/null +++ b/testdata/result/dml/update_then_return_with_action.sql.txt @@ -0,0 +1,97 @@ +--- update_then_return_with_action.sql +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 then return with action * +--- AST +&ast.Update{ + Update: 0, + TableName: &ast.Ident{ + NamePos: 7, + NameEnd: 10, + Name: "foo", + }, + As: (*ast.AsAlias)(nil), + Updates: []*ast.UpdateItem{ + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 15, + NameEnd: 18, + Name: "foo", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: -1, + Default: false, + Expr: &ast.Ident{ + NamePos: 21, + NameEnd: 24, + Name: "bar", + }, + }, + }, + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 26, + NameEnd: 29, + Name: "bar", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: -1, + Default: false, + Expr: &ast.Ident{ + NamePos: 32, + NameEnd: 35, + Name: "foo", + }, + }, + }, + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 37, + NameEnd: 40, + Name: "baz", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: 43, + Default: true, + Expr: nil, + }, + }, + }, + Where: &ast.Where{ + Where: 51, + Expr: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 57, + NameEnd: 60, + Name: "foo", + }, + Right: &ast.IntLiteral{ + ValuePos: 63, + ValueEnd: 64, + Base: 10, + Value: "1", + }, + }, + }, + ThenReturn: &ast.ThenReturn{ + Then: 65, + WithAction: &ast.WithAction{ + With: 77, + Action: 82, + Alias: (*ast.AsAlias)(nil), + }, + Items: []ast.SelectItem{ + &ast.Star{ + Star: 89, + }, + }, + }, +} + +--- SQL +UPDATE foo SET foo = bar, bar = foo, baz = DEFAULT WHERE foo = 1 THEN RETURN WITH ACTION * diff --git a/testdata/result/statement/delete _from.sql.txt b/testdata/result/statement/delete _from.sql.txt index c9972ae3..29eddeae 100644 --- a/testdata/result/statement/delete _from.sql.txt +++ b/testdata/result/statement/delete _from.sql.txt @@ -43,6 +43,7 @@ delete from foo where foo = 1 and bar = 2 }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/delete.sql.txt b/testdata/result/statement/delete.sql.txt index b5ec82a5..1bf07ded 100644 --- a/testdata/result/statement/delete.sql.txt +++ b/testdata/result/statement/delete.sql.txt @@ -43,6 +43,7 @@ delete foo where foo = 1 and bar = 2 }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/delete_as.sql.txt b/testdata/result/statement/delete_as.sql.txt index 2eee871a..36c330e3 100644 --- a/testdata/result/statement/delete_as.sql.txt +++ b/testdata/result/statement/delete_as.sql.txt @@ -42,6 +42,7 @@ delete foo as F where F.foo = 1 }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/delete_then_return.sql.txt b/testdata/result/statement/delete_then_return.sql.txt new file mode 100644 index 00000000..94b742a1 --- /dev/null +++ b/testdata/result/statement/delete_then_return.sql.txt @@ -0,0 +1,58 @@ +--- delete_then_return.sql +delete foo where foo = 1 and bar = 2 then return * +--- AST +&ast.Delete{ + Delete: 0, + TableName: &ast.Ident{ + NamePos: 7, + NameEnd: 10, + Name: "foo", + }, + As: (*ast.AsAlias)(nil), + Where: &ast.Where{ + Where: 11, + Expr: &ast.BinaryExpr{ + Op: "AND", + Left: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 17, + NameEnd: 20, + Name: "foo", + }, + Right: &ast.IntLiteral{ + ValuePos: 23, + ValueEnd: 24, + Base: 10, + Value: "1", + }, + }, + Right: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 29, + NameEnd: 32, + Name: "bar", + }, + Right: &ast.IntLiteral{ + ValuePos: 35, + ValueEnd: 36, + Base: 10, + Value: "2", + }, + }, + }, + }, + ThenReturn: &ast.ThenReturn{ + Then: 37, + WithAction: (*ast.WithAction)(nil), + Items: []ast.SelectItem{ + &ast.Star{ + Star: 49, + }, + }, + }, +} + +--- SQL +DELETE FROM foo WHERE foo = 1 AND bar = 2 THEN RETURN * diff --git a/testdata/result/statement/insert_into_values.sql.txt b/testdata/result/statement/insert_into_values.sql.txt index 047a68ed..dc897ce3 100644 --- a/testdata/result/statement/insert_into_values.sql.txt +++ b/testdata/result/statement/insert_into_values.sql.txt @@ -105,6 +105,7 @@ values (1, 2, 3), }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/insert_or_ignore.sql.txt b/testdata/result/statement/insert_or_ignore.sql.txt index 28adbf40..9994ff8e 100644 --- a/testdata/result/statement/insert_or_ignore.sql.txt +++ b/testdata/result/statement/insert_or_ignore.sql.txt @@ -53,6 +53,7 @@ INSERT OR IGNORE INTO foo }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/insert_or_update.sql.txt b/testdata/result/statement/insert_or_update.sql.txt index fbda7b80..a87b61f8 100644 --- a/testdata/result/statement/insert_or_update.sql.txt +++ b/testdata/result/statement/insert_or_update.sql.txt @@ -53,6 +53,7 @@ INSERT OR UPDATE INTO foo }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/insert_or_update_then_return_with_action_as.sql.txt b/testdata/result/statement/insert_or_update_then_return_with_action_as.sql.txt new file mode 100644 index 00000000..ab5e4a6d --- /dev/null +++ b/testdata/result/statement/insert_or_update_then_return_with_action_as.sql.txt @@ -0,0 +1,80 @@ +--- insert_or_update_then_return_with_action_as.sql +INSERT OR UPDATE INTO foo +(foo, bar) VALUES (1, 2) +THEN RETURN WITH ACTION AS act * +--- AST +&ast.Insert{ + Insert: 0, + InsertOrType: "UPDATE", + TableName: &ast.Ident{ + NamePos: 22, + NameEnd: 25, + Name: "foo", + }, + Columns: []*ast.Ident{ + &ast.Ident{ + NamePos: 27, + NameEnd: 30, + Name: "foo", + }, + &ast.Ident{ + NamePos: 32, + NameEnd: 35, + Name: "bar", + }, + }, + Input: &ast.ValuesInput{ + Values: 37, + Rows: []*ast.ValuesRow{ + &ast.ValuesRow{ + Lparen: 44, + Rparen: 49, + Exprs: []*ast.DefaultExpr{ + &ast.DefaultExpr{ + DefaultPos: -1, + Default: false, + Expr: &ast.IntLiteral{ + ValuePos: 45, + ValueEnd: 46, + Base: 10, + Value: "1", + }, + }, + &ast.DefaultExpr{ + DefaultPos: -1, + Default: false, + Expr: &ast.IntLiteral{ + ValuePos: 48, + ValueEnd: 49, + Base: 10, + Value: "2", + }, + }, + }, + }, + }, + }, + ThenReturn: &ast.ThenReturn{ + Then: 51, + WithAction: &ast.WithAction{ + With: 63, + Action: 68, + Alias: &ast.AsAlias{ + As: 75, + Alias: &ast.Ident{ + NamePos: 78, + NameEnd: 81, + Name: "act", + }, + }, + }, + Items: []ast.SelectItem{ + &ast.Star{ + Star: 82, + }, + }, + }, +} + +--- SQL +INSERT OR UPDATE INTO foo (foo, bar) VALUES (1, 2) THEN RETURN WITH ACTION AS act * diff --git a/testdata/result/statement/insert_select.sql.txt b/testdata/result/statement/insert_select.sql.txt index 6afe8c7c..5b3b5590 100644 --- a/testdata/result/statement/insert_select.sql.txt +++ b/testdata/result/statement/insert_select.sql.txt @@ -94,6 +94,7 @@ select * from unnest([(1, 2), (3, 4)]) Limit: (*ast.Limit)(nil), }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/insert_values.sql.txt b/testdata/result/statement/insert_values.sql.txt index 09897f47..aae84cad 100644 --- a/testdata/result/statement/insert_values.sql.txt +++ b/testdata/result/statement/insert_values.sql.txt @@ -105,6 +105,7 @@ values (1, 2, 3), }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/insert_values_default.sql.txt b/testdata/result/statement/insert_values_default.sql.txt index ae7d07c4..2b3b0083 100644 --- a/testdata/result/statement/insert_values_default.sql.txt +++ b/testdata/result/statement/insert_values_default.sql.txt @@ -48,6 +48,7 @@ values (1, default) }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/insert_with_sequence_function.sql.txt b/testdata/result/statement/insert_with_sequence_function.sql.txt index 7f5a4b8d..c78a7cd4 100644 --- a/testdata/result/statement/insert_with_sequence_function.sql.txt +++ b/testdata/result/statement/insert_with_sequence_function.sql.txt @@ -54,6 +54,7 @@ INSERT INTO foo(bar) VALUES (GET_NEXT_SEQUENCE_VALUE(SEQUENCE my_sequence)) }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/update.sql.txt b/testdata/result/statement/update.sql.txt index 8f218958..fcf32946 100644 --- a/testdata/result/statement/update.sql.txt +++ b/testdata/result/statement/update.sql.txt @@ -78,6 +78,7 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/update_as.sql.txt b/testdata/result/statement/update_as.sql.txt index a647ea85..00622578 100644 --- a/testdata/result/statement/update_as.sql.txt +++ b/testdata/result/statement/update_as.sql.txt @@ -84,6 +84,7 @@ update foo as F set F.foo = F.bar + 1 where foo = F.bar }, }, }, + ThenReturn: (*ast.ThenReturn)(nil), } --- SQL diff --git a/testdata/result/statement/update_then_return_with_action.sql.txt b/testdata/result/statement/update_then_return_with_action.sql.txt new file mode 100644 index 00000000..438a476f --- /dev/null +++ b/testdata/result/statement/update_then_return_with_action.sql.txt @@ -0,0 +1,97 @@ +--- update_then_return_with_action.sql +update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1 then return with action * +--- AST +&ast.Update{ + Update: 0, + TableName: &ast.Ident{ + NamePos: 7, + NameEnd: 10, + Name: "foo", + }, + As: (*ast.AsAlias)(nil), + Updates: []*ast.UpdateItem{ + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 15, + NameEnd: 18, + Name: "foo", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: -1, + Default: false, + Expr: &ast.Ident{ + NamePos: 21, + NameEnd: 24, + Name: "bar", + }, + }, + }, + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 26, + NameEnd: 29, + Name: "bar", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: -1, + Default: false, + Expr: &ast.Ident{ + NamePos: 32, + NameEnd: 35, + Name: "foo", + }, + }, + }, + &ast.UpdateItem{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 37, + NameEnd: 40, + Name: "baz", + }, + }, + DefaultExpr: &ast.DefaultExpr{ + DefaultPos: 43, + Default: true, + Expr: nil, + }, + }, + }, + Where: &ast.Where{ + Where: 51, + Expr: &ast.BinaryExpr{ + Op: "=", + Left: &ast.Ident{ + NamePos: 57, + NameEnd: 60, + Name: "foo", + }, + Right: &ast.IntLiteral{ + ValuePos: 63, + ValueEnd: 64, + Base: 10, + Value: "1", + }, + }, + }, + ThenReturn: &ast.ThenReturn{ + Then: 65, + WithAction: &ast.WithAction{ + With: 77, + Action: 82, + Alias: (*ast.AsAlias)(nil), + }, + Items: []ast.SelectItem{ + &ast.Star{ + Star: 89, + }, + }, + }, +} + +--- SQL +UPDATE foo SET foo = bar, bar = foo, baz = DEFAULT WHERE foo = 1 THEN RETURN WITH ACTION *