Skip to content

Commit

Permalink
Support named arguments (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
apstndb authored Sep 23, 2024
1 parent fe5ac17 commit 45b3add
Show file tree
Hide file tree
Showing 26 changed files with 431 additions and 14 deletions.
20 changes: 16 additions & 4 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -944,16 +944,17 @@ type IndexExpr struct {

// CallExpr is function call expression node.
//
// {{.Func | sql}}({{if .Distinct}}DISTINCT{{end}} {{.Args | sql}})
// {{.Func | sql}}({{if .Distinct}}DISTINCT{{end}} {{.Args | sqlJoin ", "}}{{if len(.Args) > 0 && len(.NamedArgs) > 0}}, {{end}}{{.NamedArgs || sqlJoin ", "}})
type CallExpr struct {
// pos = Func.pos
// end = Rparen + 1

Rparen token.Pos // position of ")"

Func *Ident
Distinct bool
Args []Arg
Func *Ident
Distinct bool
Args []Arg
NamedArgs []*NamedArg
}

// ExprArg is argument of the generic function call.
Expand Down Expand Up @@ -991,6 +992,17 @@ type SequenceArg struct {
Expr Expr
}

// NamedArg represents a name and value pair in named arguments
//
// {{.Name | sql}} => {{.Value | sql}}
type NamedArg struct {
// pos = Name.pos
// end = Value.end

Name *Ident
Value Expr
}

// CountStarExpr is node just for COUNT(*).
//
// COUNT(*)
Expand Down
3 changes: 3 additions & 0 deletions ast/pos.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ func (i *IntervalArg) End() token.Pos {
func (s *SequenceArg) Pos() token.Pos { return s.Sequence }
func (s *SequenceArg) End() token.Pos { return s.Expr.End() }

func (n *NamedArg) Pos() token.Pos { return n.Name.Pos() }
func (n *NamedArg) End() token.Pos { return n.Value.End() }

func (c *CountStarExpr) Pos() token.Pos { return c.Count }
func (c *CountStarExpr) End() token.Pos { return c.Rparen + 1 }

Expand Down
11 changes: 11 additions & 0 deletions ast/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,21 @@ func (c *CallExpr) SQL() string {
}
sql += a.SQL()
}
if len(c.Args) > 0 && len(c.NamedArgs) > 0 {
sql += ", "
}
for i, v := range c.NamedArgs {
if i != 0 {
sql += ", "
}
sql += v.SQL()
}
sql += ")"
return sql
}

func (n *NamedArg) SQL() string { return n.Name.SQL() + " => " + n.Value.SQL() }

func (o *SequenceOption) SQL() string {
return o.Name.SQL() + " = " + o.Value.SQL()
}
Expand Down
12 changes: 11 additions & 1 deletion lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (l *Lexer) consumeToken() {
}

switch l.peek(0) {
case '(', ')', '{', '}', ';', ',', '[', ']', '~', '*', '/', '&', '^', '=', '+', '-':
case '(', ')', '{', '}', ';', ',', '[', ']', '~', '*', '/', '&', '^', '+', '-':
l.Token.Kind = token.TokenKind([]byte{l.skip()})
return
case '.':
Expand Down Expand Up @@ -151,6 +151,16 @@ func (l *Lexer) consumeToken() {
l.Token.Kind = ">"
}
return
case '=':
switch {
case l.peekIs(1, '>'):
l.skipN(2)
l.Token.Kind = "=>"
default:
l.skip()
l.Token.Kind = "="
}
return
case '|':
switch {
case l.peekIs(1, '|'):
Expand Down
52 changes: 47 additions & 5 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1460,20 +1460,62 @@ func (p *Parser) parseCall(id token.Token) ast.Expr {

var args []ast.Arg
if p.Token.Kind != ")" {
for p.Token.Kind != token.TokenEOF {
for p.Token.Kind != token.TokenEOF && !p.lookaheadNamedArg() {
args = append(args, p.parseArg())
if p.Token.Kind != "," {
break
}
p.nextToken()
}
}

// https://github.com/google/zetasql/blob/master/docs/functions-reference.md#named-arguments
// You cannot specify positional arguments after named arguments.
var namedArgs []*ast.NamedArg
for {
namedArg := p.tryParseNamedArg()
if namedArg == nil {
break
}
namedArgs = append(namedArgs, namedArg)
if p.Token.Kind != "," {
break
}
p.nextToken()
}

rparen := p.expect(")").Pos
return &ast.CallExpr{
Rparen: rparen,
Func: fn,
Distinct: distinct,
Args: args,
Rparen: rparen,
Func: fn,
Distinct: distinct,
Args: args,
NamedArgs: namedArgs,
}
}
func (p *Parser) lookaheadNamedArg() bool {
lexer := p.Lexer.Clone()
defer func() {
p.Lexer = lexer
}()

if p.Token.Kind != token.TokenIdent {
return false
}
p.parseIdent()
return p.Token.Kind == "=>"
}

func (p *Parser) tryParseNamedArg() *ast.NamedArg {
if !p.lookaheadNamedArg() {
return nil
}
name := p.parseIdent()
p.expect("=>")
value := p.parseExpr()
return &ast.NamedArg{
Name: name,
Value: value,
}
}

Expand Down
3 changes: 3 additions & 0 deletions testdata/input/query/select_call_with_named_expr.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SELECT a.AlbumId, a.Description
FROM Albums a
WHERE a.SingerId = 1 AND SEARCH(a.DescriptionTokens, 'classic albums', enhance_query => TRUE)
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ ALTER TABLE foo ADD COLUMN expired_at TIMESTAMP AS (IF (status != "OPEN" AND sta
},
},
},
NamedArgs: []*ast.NamedArg(nil),
},
},
&ast.ExprArg{
Expand All @@ -107,6 +108,7 @@ ALTER TABLE foo ADD COLUMN expired_at TIMESTAMP AS (IF (status != "OPEN" AND sta
},
},
},
NamedArgs: []*ast.NamedArg(nil),
},
},
Options: (*ast.ColumnDefOptions)(nil),
Expand Down
6 changes: 4 additions & 2 deletions testdata/result/ddl/create_table.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ create table foo (
},
},
},
NamedArgs: []*ast.NamedArg(nil),
},
},
Options: (*ast.ColumnDefOptions)(nil),
Expand Down Expand Up @@ -175,8 +176,9 @@ create table foo (
NameEnd: 546,
Name: "current_timestamp",
},
Distinct: false,
Args: []ast.Arg(nil),
Distinct: false,
Args: []ast.Arg(nil),
NamedArgs: []*ast.NamedArg(nil),
},
},
GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ CREATE TABLE foo
},
},
},
NamedArgs: []*ast.NamedArg(nil),
},
},
GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
Expand Down
1 change: 1 addition & 0 deletions testdata/result/dml/insert_with_sequence_function.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ INSERT INTO foo(bar) VALUES (GET_NEXT_SEQUENCE_VALUE(SEQUENCE my_sequence))
},
},
},
NamedArgs: []*ast.NamedArg(nil),
},
},
},
Expand Down
151 changes: 151 additions & 0 deletions testdata/result/query/select_call_with_named_expr.sql.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
--- select_call_with_named_expr.sql
SELECT a.AlbumId, a.Description
FROM Albums a
WHERE a.SingerId = 1 AND SEARCH(a.DescriptionTokens, 'classic albums', enhance_query => TRUE)
--- AST
&ast.QueryStatement{
Hint: (*ast.Hint)(nil),
With: (*ast.With)(nil),
Query: &ast.Select{
Select: 0,
Distinct: false,
AsStruct: false,
Results: []ast.SelectItem{
&ast.ExprSelectItem{
Expr: &ast.Path{
Idents: []*ast.Ident{
&ast.Ident{
NamePos: 7,
NameEnd: 8,
Name: "a",
},
&ast.Ident{
NamePos: 9,
NameEnd: 16,
Name: "AlbumId",
},
},
},
},
&ast.ExprSelectItem{
Expr: &ast.Path{
Idents: []*ast.Ident{
&ast.Ident{
NamePos: 18,
NameEnd: 19,
Name: "a",
},
&ast.Ident{
NamePos: 20,
NameEnd: 31,
Name: "Description",
},
},
},
},
},
From: &ast.From{
From: 32,
Source: &ast.TableName{
Table: &ast.Ident{
NamePos: 37,
NameEnd: 43,
Name: "Albums",
},
Hint: (*ast.Hint)(nil),
As: &ast.AsAlias{
As: 44,
Alias: &ast.Ident{
NamePos: 44,
NameEnd: 45,
Name: "a",
},
},
Sample: (*ast.TableSample)(nil),
},
},
Where: &ast.Where{
Where: 46,
Expr: &ast.BinaryExpr{
Op: "AND",
Left: &ast.BinaryExpr{
Op: "=",
Left: &ast.Path{
Idents: []*ast.Ident{
&ast.Ident{
NamePos: 52,
NameEnd: 53,
Name: "a",
},
&ast.Ident{
NamePos: 54,
NameEnd: 62,
Name: "SingerId",
},
},
},
Right: &ast.IntLiteral{
ValuePos: 65,
ValueEnd: 66,
Base: 10,
Value: "1",
},
},
Right: &ast.CallExpr{
Rparen: 138,
Func: &ast.Ident{
NamePos: 71,
NameEnd: 77,
Name: "SEARCH",
},
Distinct: false,
Args: []ast.Arg{
&ast.ExprArg{
Expr: &ast.Path{
Idents: []*ast.Ident{
&ast.Ident{
NamePos: 78,
NameEnd: 79,
Name: "a",
},
&ast.Ident{
NamePos: 80,
NameEnd: 97,
Name: "DescriptionTokens",
},
},
},
},
&ast.ExprArg{
Expr: &ast.StringLiteral{
ValuePos: 99,
ValueEnd: 115,
Value: "classic albums",
},
},
},
NamedArgs: []*ast.NamedArg{
&ast.NamedArg{
Name: &ast.Ident{
NamePos: 117,
NameEnd: 130,
Name: "enhance_query",
},
Value: &ast.BoolLiteral{
ValuePos: 134,
Value: true,
},
},
},
},
},
},
GroupBy: (*ast.GroupBy)(nil),
Having: (*ast.Having)(nil),
OrderBy: (*ast.OrderBy)(nil),
Limit: (*ast.Limit)(nil),
},
}

--- SQL
SELECT a.AlbumId, a.Description FROM Albums AS a WHERE a.SingerId = 1 AND SEARCH(a.DescriptionTokens, "classic albums", enhance_query => TRUE)
1 change: 1 addition & 0 deletions testdata/result/query/select_count_distinct.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ select count(distinct x) from foo
},
},
},
NamedArgs: []*ast.NamedArg(nil),
},
},
},
Expand Down
Loading

0 comments on commit 45b3add

Please sign in to comment.