diff --git a/ast/ast.go b/ast/ast.go index ba0da57e..1602bd4c 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -133,38 +133,41 @@ type Expr interface { isExpr() } -func (BinaryExpr) isExpr() {} -func (UnaryExpr) isExpr() {} -func (InExpr) isExpr() {} -func (IsNullExpr) isExpr() {} -func (IsBoolExpr) isExpr() {} -func (BetweenExpr) isExpr() {} -func (SelectorExpr) isExpr() {} -func (IndexExpr) isExpr() {} -func (CallExpr) isExpr() {} -func (CountStarExpr) isExpr() {} -func (CastExpr) isExpr() {} -func (ExtractExpr) isExpr() {} -func (CaseExpr) isExpr() {} -func (ParenExpr) isExpr() {} -func (ScalarSubQuery) isExpr() {} -func (ArraySubQuery) isExpr() {} -func (ExistsSubQuery) isExpr() {} -func (Param) isExpr() {} -func (Ident) isExpr() {} -func (Path) isExpr() {} -func (ArrayLiteral) isExpr() {} -func (StructLiteral) isExpr() {} -func (NullLiteral) isExpr() {} -func (BoolLiteral) isExpr() {} -func (IntLiteral) isExpr() {} -func (FloatLiteral) isExpr() {} -func (StringLiteral) isExpr() {} -func (BytesLiteral) isExpr() {} -func (DateLiteral) isExpr() {} -func (TimestampLiteral) isExpr() {} -func (NumericLiteral) isExpr() {} -func (JSONLiteral) isExpr() {} +func (BinaryExpr) isExpr() {} +func (UnaryExpr) isExpr() {} +func (InExpr) isExpr() {} +func (IsNullExpr) isExpr() {} +func (IsBoolExpr) isExpr() {} +func (BetweenExpr) isExpr() {} +func (SelectorExpr) isExpr() {} +func (IndexExpr) isExpr() {} +func (CallExpr) isExpr() {} +func (CountStarExpr) isExpr() {} +func (CastExpr) isExpr() {} +func (ExtractExpr) isExpr() {} +func (CaseExpr) isExpr() {} +func (ParenExpr) isExpr() {} +func (ScalarSubQuery) isExpr() {} +func (ArraySubQuery) isExpr() {} +func (ExistsSubQuery) isExpr() {} +func (Param) isExpr() {} +func (Ident) isExpr() {} +func (Path) isExpr() {} +func (ArrayLiteral) isExpr() {} +func (StructLiteral) isExpr() {} +func (NullLiteral) isExpr() {} +func (BoolLiteral) isExpr() {} +func (IntLiteral) isExpr() {} +func (FloatLiteral) isExpr() {} +func (StringLiteral) isExpr() {} +func (BytesLiteral) isExpr() {} +func (DateLiteral) isExpr() {} +func (TimestampLiteral) isExpr() {} +func (NumericLiteral) isExpr() {} +func (JSONLiteral) isExpr() {} +func (NewConstructor) isExpr() {} +func (BracedNewConstructor) isExpr() {} +func (BracedConstructor) isExpr() {} // Arg represents argument of function call. type Arg interface { @@ -1381,6 +1384,94 @@ type JSONLiteral struct { Value *StringLiteral } +// ================================================================================ +// +// NEW constructors +// +// ================================================================================ + +// BracedConstructorFieldValue represents value part of fields in BracedNewConstructor. +type BracedConstructorFieldValue interface { + Node + isBracedConstructorFieldValue() +} + +func (*BracedConstructor) isBracedConstructorFieldValue() {} +func (*BracedConstructorFieldValueExpr) isBracedConstructorFieldValue() {} + +// NewConstructorArg represents a single argument in NEW constructor. +// +// {{Expr | sql}} {{Alias | sqlOpt}} +type NewConstructorArg struct { + // pos = Expr.pos + // end = (Alias ?? Expr).end + + Expr Expr + Alias *AsAlias // optional +} + +// NewConstructor represents NEW operator which creates a protocol buffer using a parenthesized list of arguments. +// +// NEW {{.Type | sql}} ({{.Args | sqlJoin ", "}}) +type NewConstructor struct { + // pos = New + // end = Rparen + 1 + + New token.Pos + Type *NamedType + + Args []*NewConstructorArg + + Rparen token.Pos +} + +// BracedNewConstructor represents NEW operator which creates a protocol buffer using a map constructor. +// +// NEW {{.Type | sql}} {{"{"}}{{"}"}} +type BracedNewConstructor struct { + // pos = New + // end = Rbrace + 1 + + New token.Pos + Type *NamedType + Body *BracedConstructor +} + +// BracedConstructor represents a single map constructor which is used in BracedNewConstructor. +// Actually, it is a top level Expr in syntax, but it is not permitted semantically in other place. +// +// {{"{"}}{{.Fields | sqlJoin ", "}}{{"}"}} +type BracedConstructor struct { + // pos = Lbrace + // end = Rbrace + 1 + + Lbrace, Rbrace token.Pos + + Fields []*BracedConstructorField +} + +// BracedConstructorField represents a single field in BracedConstructor. +// +// {{.Name | sql}} {{.Value | sql}} +type BracedConstructorField struct { + // pos = Name.pos + // end = Value.end + + Name *Ident + Value BracedConstructorFieldValue +} + +// BracedConstructorFieldValueExpr represents a field value node. +// +// : {{.Expr | sql}} +type BracedConstructorFieldValueExpr struct { + // pos = Colon + // end = Expr.end + + Colon token.Pos + Expr Expr +} + // ================================================================================ // // Type diff --git a/ast/pos.go b/ast/pos.go index c0d892e0..680ee651 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -476,6 +476,32 @@ func (t *NumericLiteral) End() token.Pos { return t.Value.End() } func (t *JSONLiteral) Pos() token.Pos { return t.JSON } func (t *JSONLiteral) End() token.Pos { return t.Value.End() } +// ================================================================================ +// +// NEW constructors +// +// ================================================================================ + +func (n *NewConstructorArg) Pos() token.Pos { return n.Expr.Pos() } +func (n *NewConstructorArg) End() token.Pos { + return firstValidEnd(n.Alias, n.Expr) +} + +func (n *NewConstructor) Pos() token.Pos { return n.New } +func (n *NewConstructor) End() token.Pos { return n.Rparen + 1 } + +func (b *BracedNewConstructor) Pos() token.Pos { return b.New } +func (b *BracedNewConstructor) End() token.Pos { return b.Body.End() } + +func (b *BracedConstructor) Pos() token.Pos { return b.Lbrace } +func (b *BracedConstructor) End() token.Pos { return b.Rbrace + 1 } + +func (b *BracedConstructorField) Pos() token.Pos { return b.Name.Pos() } +func (b *BracedConstructorField) End() token.Pos { return b.Value.End() } + +func (b *BracedConstructorFieldValueExpr) Pos() token.Pos { return b.Colon } +func (b *BracedConstructorFieldValueExpr) End() token.Pos { return b.Expr.End() } + // ================================================================================ // // Type diff --git a/ast/sql.go b/ast/sql.go index bf913bb1..dd3a4ed8 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -692,6 +692,39 @@ func (t *JSONLiteral) SQL() string { return "JSON " + t.Value.SQL() } +// ================================================================================ +// +// NEW constructors +// +// ================================================================================ + +func (n *NewConstructorArg) SQL() string { + return n.Expr.SQL() + sqlOpt(" ", n.Alias, "") +} + +func (n *NewConstructor) SQL() string { + return "NEW " + n.Type.SQL() + "(" + sqlJoin(n.Args, ", ") + ")" +} + +func (b *BracedNewConstructor) SQL() string { + return "NEW " + b.Type.SQL() + " " + b.Body.SQL() +} + +func (b *BracedConstructor) SQL() string { + return "{" + sqlJoin(b.Fields, ", ") + "}" +} + +func (b *BracedConstructorField) SQL() string { + if _, ok := b.Value.(*BracedConstructor); ok { + // Name {...} + return b.Name.SQL() + " " + b.Value.SQL() + } + // Name: value + return b.Name.SQL() + b.Value.SQL() +} + +func (b *BracedConstructorFieldValueExpr) SQL() string { return ": " + b.Expr.SQL() } + // ================================================================================ // // Type diff --git a/lexer.go b/lexer.go index 26d679b0..17e3d46d 100644 --- a/lexer.go +++ b/lexer.go @@ -109,7 +109,7 @@ func (l *Lexer) consumeToken() { } switch l.peek(0) { - case '(', ')', '{', '}', ';', ',', '[', ']', '~', '*', '/', '&', '^', '+', '-': + case '(', ')', '{', '}', ';', ',', '[', ']', '~', '*', '/', '&', '^', '+', '-', ':': l.Token.Kind = token.TokenKind([]byte{l.skip()}) return case '.': diff --git a/parser.go b/parser.go index 24ad76a8..d560dc92 100644 --- a/parser.go +++ b/parser.go @@ -1413,6 +1413,11 @@ func (p *Parser) parseLit() ast.Expr { return p.parseSimpleArrayLiteral() case "(": return p.parseParenExpr() + case "NEW": + return p.parseNewConstructors() + // In parser level, it is a valid ast.Expr, but it is semantically valid only in ast.BracedConstructorFieldExpr. + case "{": + return p.parseBracedConstructor() case token.TokenIdent: id := p.Token p.nextToken() @@ -2009,6 +2014,106 @@ func (p *Parser) parseStructTypeFields() (fields []*ast.StructField, gt token.Po return } +func (p *Parser) parseNewConstructorArg() *ast.NewConstructorArg { + expr := p.parseExpr() + + // Whole "AS alias" is optional, but "AS" keyword can't be omitted. + var alias *ast.AsAlias + if p.Token.Kind == "AS" { + alias = p.tryParseAsAlias() + } + + return &ast.NewConstructorArg{ + Expr: expr, + Alias: alias, + } +} +func (p *Parser) parseNewConstructor(newPos token.Pos, namedType *ast.NamedType) *ast.NewConstructor { + p.expect("(") + + // Args can be empty like `NEW pkg.TypeName ()`. + var args []*ast.NewConstructorArg + if p.Token.Kind != ")" { + args = parseCommaSeparatedList(p, p.parseNewConstructorArg) + } + + rparen := p.expect(")").Pos + return &ast.NewConstructor{ + New: newPos, + Type: namedType, + Args: args, + Rparen: rparen, + } +} + +func (p *Parser) parseBracedNewConstructorField() *ast.BracedConstructorField { + name := p.parseIdent() + var fieldValue ast.BracedConstructorFieldValue + switch p.Token.Kind { + case ":": + colon := p.expect(":").Pos + expr := p.parseExpr() + fieldValue = &ast.BracedConstructorFieldValueExpr{Colon: colon, Expr: expr} + case "{": + fieldValue = p.parseBracedConstructor() + } + return &ast.BracedConstructorField{Name: name, Value: fieldValue} +} + +func (p *Parser) parseBracedConstructor() *ast.BracedConstructor { + lbrace := p.expect("{").Pos + + // Braced constructor permits empty. + var fields []*ast.BracedConstructorField + for { + if p.Token.Kind == "}" { + break + } + + if p.Token.Kind != token.TokenIdent { + p.panicfAtToken(&p.Token, "expect , but %v", p.Token.Kind) + } + fields = append(fields, p.parseBracedNewConstructorField()) + + // It is an optional comma. + if p.Token.Kind == "," { + p.nextToken() + } + } + + rbrace := p.expect("}").Pos + + return &ast.BracedConstructor{ + Lbrace: lbrace, + Rbrace: rbrace, + Fields: fields, + } +} + +func (p *Parser) parseBracedNewConstructor(newPos token.Pos, namedType *ast.NamedType) *ast.BracedNewConstructor { + body := p.parseBracedConstructor() + return &ast.BracedNewConstructor{ + New: newPos, + Type: namedType, + Body: body, + } +} + +func (p *Parser) parseNewConstructors() ast.Expr { + newPos := p.expect("NEW").Pos + namedType := p.parseNamedType() + + switch p.Token.Kind { + case "(": + return p.parseNewConstructor(newPos, namedType) + case "{": + return p.parseBracedNewConstructor(newPos, namedType) + default: + p.panicfAtToken(&p.Token, `expect '{' or '(', but %v`, p.Token.Kind) + } + return nil +} + func (p *Parser) parseFieldType() *ast.StructField { lexer := p.Lexer.Clone() // Try to parse as "x INT64" case. diff --git a/testdata/input/expr/braced_new_constructor.sql b/testdata/input/expr/braced_new_constructor.sql new file mode 100644 index 00000000..28cf679d --- /dev/null +++ b/testdata/input/expr/braced_new_constructor.sql @@ -0,0 +1,17 @@ +-- Example from https://cloud.google.com/spanner/docs/reference/standard-sql/operators#new_operator +NEW Universe { + name: "Sol" + closest_planets: ["Mercury", "Venus", "Earth" ] + star { + radius_miles: 432690 + age: 4603000000 + } + constellations: [{ + name: "Libra" + index: 0 + }, { + name: "Scorpio" + index: 1 + }] + all_planets: (SELECT planets FROM SolTable) +} \ No newline at end of file diff --git a/testdata/input/expr/braced_new_constructor_empty.sql b/testdata/input/expr/braced_new_constructor_empty.sql new file mode 100644 index 00000000..d3d99104 --- /dev/null +++ b/testdata/input/expr/braced_new_constructor_empty.sql @@ -0,0 +1 @@ +NEW Universe {} \ No newline at end of file diff --git a/testdata/input/expr/braced_new_constructor_oneliner.sql b/testdata/input/expr/braced_new_constructor_oneliner.sql new file mode 100644 index 00000000..a3902848 --- /dev/null +++ b/testdata/input/expr/braced_new_constructor_oneliner.sql @@ -0,0 +1 @@ +NEW Universe {name: "Sol", closest_planets: ["Mercury", "Venus", "Earth" ]} \ No newline at end of file diff --git a/testdata/input/expr/new_constructor.sql b/testdata/input/expr/new_constructor.sql new file mode 100644 index 00000000..c1c8f9e9 --- /dev/null +++ b/testdata/input/expr/new_constructor.sql @@ -0,0 +1 @@ +NEW googlesql.examples.music.Chart(key AS rank, name AS chart_name) \ No newline at end of file diff --git a/testdata/input/expr/new_constructor_empty.sql b/testdata/input/expr/new_constructor_empty.sql new file mode 100644 index 00000000..87bd24b3 --- /dev/null +++ b/testdata/input/expr/new_constructor_empty.sql @@ -0,0 +1 @@ +NEW googlesql.examples.music.Chart() diff --git a/testdata/result/expr/braced_new_constructor.sql.txt b/testdata/result/expr/braced_new_constructor.sql.txt new file mode 100644 index 00000000..18d206e3 --- /dev/null +++ b/testdata/result/expr/braced_new_constructor.sql.txt @@ -0,0 +1,271 @@ +--- braced_new_constructor.sql +-- Example from https://cloud.google.com/spanner/docs/reference/standard-sql/operators#new_operator +NEW Universe { + name: "Sol" + closest_planets: ["Mercury", "Venus", "Earth" ] + star { + radius_miles: 432690 + age: 4603000000 + } + constellations: [{ + name: "Libra" + index: 0 + }, { + name: "Scorpio" + index: 1 + }] + all_planets: (SELECT planets FROM SolTable) +} +--- AST +&ast.BracedNewConstructor{ + New: 100, + Type: &ast.NamedType{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 104, + NameEnd: 112, + Name: "Universe", + }, + }, + }, + Body: &ast.BracedConstructor{ + Lbrace: 113, + Rbrace: 380, + Fields: []*ast.BracedConstructorField{ + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 117, + NameEnd: 121, + Name: "name", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 121, + Expr: &ast.StringLiteral{ + ValuePos: 123, + ValueEnd: 128, + Value: "Sol", + }, + }, + }, + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 131, + NameEnd: 146, + Name: "closest_planets", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 146, + Expr: &ast.ArrayLiteral{ + Array: -1, + Lbrack: 148, + Rbrack: 177, + Type: nil, + Values: []ast.Expr{ + &ast.StringLiteral{ + ValuePos: 149, + ValueEnd: 158, + Value: "Mercury", + }, + &ast.StringLiteral{ + ValuePos: 160, + ValueEnd: 167, + Value: "Venus", + }, + &ast.StringLiteral{ + ValuePos: 169, + ValueEnd: 176, + Value: "Earth", + }, + }, + }, + }, + }, + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 181, + NameEnd: 185, + Name: "star", + }, + Value: &ast.BracedConstructor{ + Lbrace: 186, + Rbrace: 235, + Fields: []*ast.BracedConstructorField{ + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 192, + NameEnd: 204, + Name: "radius_miles", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 204, + Expr: &ast.IntLiteral{ + ValuePos: 206, + ValueEnd: 212, + Base: 10, + Value: "432690", + }, + }, + }, + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 217, + NameEnd: 220, + Name: "age", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 220, + Expr: &ast.IntLiteral{ + ValuePos: 222, + ValueEnd: 232, + Base: 10, + Value: "4603000000", + }, + }, + }, + }, + }, + }, + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 239, + NameEnd: 253, + Name: "constellations", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 253, + Expr: &ast.ArrayLiteral{ + Array: -1, + Lbrack: 255, + Rbrack: 332, + Type: nil, + Values: []ast.Expr{ + &ast.BracedConstructor{ + Lbrace: 256, + Rbrace: 291, + Fields: []*ast.BracedConstructorField{ + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 262, + NameEnd: 266, + Name: "name", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 266, + Expr: &ast.StringLiteral{ + ValuePos: 268, + ValueEnd: 275, + Value: "Libra", + }, + }, + }, + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 280, + NameEnd: 285, + Name: "index", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 285, + Expr: &ast.IntLiteral{ + ValuePos: 287, + ValueEnd: 288, + Base: 10, + Value: "0", + }, + }, + }, + }, + }, + &ast.BracedConstructor{ + Lbrace: 294, + Rbrace: 331, + Fields: []*ast.BracedConstructorField{ + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 300, + NameEnd: 304, + Name: "name", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 304, + Expr: &ast.StringLiteral{ + ValuePos: 306, + ValueEnd: 315, + Value: "Scorpio", + }, + }, + }, + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 320, + NameEnd: 325, + Name: "index", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 325, + Expr: &ast.IntLiteral{ + ValuePos: 327, + ValueEnd: 328, + Base: 10, + Value: "1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 336, + NameEnd: 347, + Name: "all_planets", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 347, + Expr: &ast.ScalarSubQuery{ + Lparen: 349, + Rparen: 378, + Query: &ast.Select{ + Select: 350, + Distinct: false, + As: nil, + Results: []ast.SelectItem{ + &ast.ExprSelectItem{ + Expr: &ast.Ident{ + NamePos: 357, + NameEnd: 364, + Name: "planets", + }, + }, + }, + From: &ast.From{ + From: 365, + Source: &ast.TableName{ + Table: &ast.Ident{ + NamePos: 370, + NameEnd: 378, + Name: "SolTable", + }, + Hint: (*ast.Hint)(nil), + As: (*ast.AsAlias)(nil), + Sample: (*ast.TableSample)(nil), + }, + }, + Where: (*ast.Where)(nil), + GroupBy: (*ast.GroupBy)(nil), + Having: (*ast.Having)(nil), + OrderBy: (*ast.OrderBy)(nil), + Limit: (*ast.Limit)(nil), + }, + }, + }, + }, + }, + }, +} + +--- SQL +NEW Universe {name: "Sol", closest_planets: ["Mercury", "Venus", "Earth"], star {radius_miles: 432690, age: 4603000000}, constellations: [{name: "Libra", index: 0}, {name: "Scorpio", index: 1}], all_planets: (SELECT planets FROM SolTable)} diff --git a/testdata/result/expr/braced_new_constructor_empty.sql.txt b/testdata/result/expr/braced_new_constructor_empty.sql.txt new file mode 100644 index 00000000..60878f90 --- /dev/null +++ b/testdata/result/expr/braced_new_constructor_empty.sql.txt @@ -0,0 +1,23 @@ +--- braced_new_constructor_empty.sql +NEW Universe {} +--- AST +&ast.BracedNewConstructor{ + New: 0, + Type: &ast.NamedType{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 4, + NameEnd: 12, + Name: "Universe", + }, + }, + }, + Body: &ast.BracedConstructor{ + Lbrace: 13, + Rbrace: 14, + Fields: []*ast.BracedConstructorField(nil), + }, +} + +--- SQL +NEW Universe {} diff --git a/testdata/result/expr/braced_new_constructor_oneliner.sql.txt b/testdata/result/expr/braced_new_constructor_oneliner.sql.txt new file mode 100644 index 00000000..cc34fbeb --- /dev/null +++ b/testdata/result/expr/braced_new_constructor_oneliner.sql.txt @@ -0,0 +1,72 @@ +--- braced_new_constructor_oneliner.sql +NEW Universe {name: "Sol", closest_planets: ["Mercury", "Venus", "Earth" ]} +--- AST +&ast.BracedNewConstructor{ + New: 0, + Type: &ast.NamedType{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 4, + NameEnd: 12, + Name: "Universe", + }, + }, + }, + Body: &ast.BracedConstructor{ + Lbrace: 13, + Rbrace: 74, + Fields: []*ast.BracedConstructorField{ + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 14, + NameEnd: 18, + Name: "name", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 18, + Expr: &ast.StringLiteral{ + ValuePos: 20, + ValueEnd: 25, + Value: "Sol", + }, + }, + }, + &ast.BracedConstructorField{ + Name: &ast.Ident{ + NamePos: 27, + NameEnd: 42, + Name: "closest_planets", + }, + Value: &ast.BracedConstructorFieldValueExpr{ + Colon: 42, + Expr: &ast.ArrayLiteral{ + Array: -1, + Lbrack: 44, + Rbrack: 73, + Type: nil, + Values: []ast.Expr{ + &ast.StringLiteral{ + ValuePos: 45, + ValueEnd: 54, + Value: "Mercury", + }, + &ast.StringLiteral{ + ValuePos: 56, + ValueEnd: 63, + Value: "Venus", + }, + &ast.StringLiteral{ + ValuePos: 65, + ValueEnd: 72, + Value: "Earth", + }, + }, + }, + }, + }, + }, + }, +} + +--- SQL +NEW Universe {name: "Sol", closest_planets: ["Mercury", "Venus", "Earth"]} diff --git a/testdata/result/expr/new_constructor.sql.txt b/testdata/result/expr/new_constructor.sql.txt new file mode 100644 index 00000000..de222c2d --- /dev/null +++ b/testdata/result/expr/new_constructor.sql.txt @@ -0,0 +1,66 @@ +--- new_constructor.sql +NEW googlesql.examples.music.Chart(key AS rank, name AS chart_name) +--- AST +&ast.NewConstructor{ + New: 0, + Type: &ast.NamedType{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 4, + NameEnd: 13, + Name: "googlesql", + }, + &ast.Ident{ + NamePos: 14, + NameEnd: 22, + Name: "examples", + }, + &ast.Ident{ + NamePos: 23, + NameEnd: 28, + Name: "music", + }, + &ast.Ident{ + NamePos: 29, + NameEnd: 34, + Name: "Chart", + }, + }, + }, + Args: []*ast.NewConstructorArg{ + &ast.NewConstructorArg{ + Expr: &ast.Ident{ + NamePos: 35, + NameEnd: 38, + Name: "key", + }, + Alias: &ast.AsAlias{ + As: -1, + Alias: &ast.Ident{ + NamePos: 42, + NameEnd: 46, + Name: "rank", + }, + }, + }, + &ast.NewConstructorArg{ + Expr: &ast.Ident{ + NamePos: 48, + NameEnd: 52, + Name: "name", + }, + Alias: &ast.AsAlias{ + As: -1, + Alias: &ast.Ident{ + NamePos: 56, + NameEnd: 66, + Name: "chart_name", + }, + }, + }, + }, + Rparen: 66, +} + +--- SQL +NEW googlesql.examples.music.Chart(key AS rank, name AS chart_name) diff --git a/testdata/result/expr/new_constructor_empty.sql.txt b/testdata/result/expr/new_constructor_empty.sql.txt new file mode 100644 index 00000000..11db9262 --- /dev/null +++ b/testdata/result/expr/new_constructor_empty.sql.txt @@ -0,0 +1,36 @@ +--- new_constructor_empty.sql +NEW googlesql.examples.music.Chart() + +--- AST +&ast.NewConstructor{ + New: 0, + Type: &ast.NamedType{ + Path: []*ast.Ident{ + &ast.Ident{ + NamePos: 4, + NameEnd: 13, + Name: "googlesql", + }, + &ast.Ident{ + NamePos: 14, + NameEnd: 22, + Name: "examples", + }, + &ast.Ident{ + NamePos: 23, + NameEnd: 28, + Name: "music", + }, + &ast.Ident{ + NamePos: 29, + NameEnd: 34, + Name: "Chart", + }, + }, + }, + Args: []*ast.NewConstructorArg(nil), + Rparen: 35, +} + +--- SQL +NEW googlesql.examples.music.Chart()