Skip to content

Commit

Permalink
Implement HAVING MIN/MAX and RESPECT/IGNORE NULLS (#154)
Browse files Browse the repository at this point in the history
* Implement aggregate functions calls(IGNORE/RESPECT NULLS, HAVING MIN/MAX)

* Add testdata aggregate_function_calls.sql

* Do go test --update

* Fix some placement

* Update ast/ast.go

Co-authored-by: Hiroya Fujinami <[email protected]>

---------

Co-authored-by: Hiroya Fujinami <[email protected]>
  • Loading branch information
apstndb and makenowjust authored Oct 20, 2024
1 parent 60438f4 commit 90f4dcb
Show file tree
Hide file tree
Showing 27 changed files with 625 additions and 48 deletions.
81 changes: 76 additions & 5 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,24 @@ func (ExprArg) isArg() {}
func (IntervalArg) isArg() {}
func (SequenceArg) isArg() {}

// NullHandlingModifier represents IGNORE/RESPECT NULLS of aggregate function calls
type NullHandlingModifier interface {
Node
isNullHandlingModifier()
}

func (IgnoreNulls) isNullHandlingModifier() {}
func (RespectNulls) isNullHandlingModifier() {}

// HavingModifier represents HAVING clause of aggregate function calls.
type HavingModifier interface {
Node
isHavingModifier()
}

func (HavingMax) isHavingModifier() {}
func (HavingMin) isHavingModifier() {}

// InCondition is right-side value of IN operator.
type InCondition interface {
Node
Expand Down Expand Up @@ -989,17 +1007,26 @@ type IndexExpr struct {

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

Rparen token.Pos // position of ")"

Func *Ident
Distinct bool
Args []Arg
NamedArgs []*NamedArg
Func *Ident
Distinct bool
Args []Arg
NamedArgs []*NamedArg
NullHandling NullHandlingModifier // optional
Having HavingModifier // optional
}

// ExprArg is argument of the generic function call.
Expand Down Expand Up @@ -1048,6 +1075,50 @@ type NamedArg struct {
Value Expr
}

// IgnoreNulls represents IGNORE NULLS of aggregate function calls.
//
// IGNORE NULLS
type IgnoreNulls struct {
// pos = Ignore
// end = Nulls + 5

Ignore token.Pos
Nulls token.Pos
}

// RespectNulls represents RESPECT NULLS of aggregate function calls
//
// RESPECT NULLS
type RespectNulls struct {
// pos = Respect
// end = Nulls + 5

Respect token.Pos
Nulls token.Pos
}

// HavingMax represents HAVING MAX of aggregate function calls.
//
// HAVING MAX {{Expr | sql}}
type HavingMax struct {
// pos = Having
// end = Expr.end

Having token.Pos
Expr Expr
}

// HavingMin represents HAVING MIN of aggregate function calls.
//
// HAVING MIN {{Expr | sql}}
type HavingMin struct {
// pos = Having
// end = Expr.end

Having token.Pos
Expr Expr
}

// CountStarExpr is node just for COUNT(*).
//
// COUNT(*)
Expand Down
12 changes: 12 additions & 0 deletions ast/pos.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,18 @@ 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 (i *IgnoreNulls) Pos() token.Pos { return i.Ignore }
func (i *IgnoreNulls) End() token.Pos { return i.Nulls + 5 }

func (r *RespectNulls) Pos() token.Pos { return r.Respect }
func (r *RespectNulls) End() token.Pos { return r.Nulls + 5 }

func (h *HavingMax) Pos() token.Pos { return h.Having }
func (h *HavingMax) End() token.Pos { return h.Expr.End() }

func (h *HavingMin) Pos() token.Pos { return h.Having }
func (h *HavingMin) End() token.Pos { return h.Expr.End() }

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

Expand Down
10 changes: 10 additions & 0 deletions ast/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,11 +506,21 @@ func (c *CallExpr) SQL() string {
sqlJoin(c.Args, ", ") +
strOpt(len(c.Args) > 0 && len(c.NamedArgs) > 0, ", ") +
sqlJoin(c.NamedArgs, ", ") +
sqlOpt(" ", c.NullHandling, "") +
sqlOpt(" ", c.Having, "") +
")"
}

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

func (i *IgnoreNulls) SQL() string { return "IGNORE NULLS" }

func (r *RespectNulls) SQL() string { return "RESPECT NULLS" }

func (h *HavingMax) SQL() string { return "HAVING MAX " + h.Expr.SQL() }

func (h *HavingMin) SQL() string { return "HAVING MIN " + h.Expr.SQL() }

func (s *ExprArg) SQL() string {
return s.Expr.SQL()
}
Expand Down
68 changes: 63 additions & 5 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1492,15 +1492,21 @@ func (p *Parser) parseCall(id token.Token) ast.Expr {
p.nextToken()
}

nullHandling := p.tryParseNullHandlingModifier()
having := p.tryParseHavingModifier()

rparen := p.expect(")").Pos
return &ast.CallExpr{
Rparen: rparen,
Func: fn,
Distinct: distinct,
Args: args,
NamedArgs: namedArgs,
Rparen: rparen,
Func: fn,
Distinct: distinct,
Args: args,
NamedArgs: namedArgs,
NullHandling: nullHandling,
Having: having,
}
}

func (p *Parser) lookaheadNamedArg() bool {
lexer := p.Lexer.Clone()
defer func() {
Expand Down Expand Up @@ -1574,6 +1580,58 @@ func (p *Parser) parseExprArg() *ast.ExprArg {
}
}

func (p *Parser) tryParseHavingModifier() ast.HavingModifier {
if p.Token.Kind != "HAVING" {
return nil
}

having := p.expect("HAVING").Pos

switch {
case p.Token.IsKeywordLike("MIN"):
p.nextToken()
expr := p.parseExpr()

return &ast.HavingMin{
Having: having,
Expr: expr,
}
case p.Token.IsKeywordLike("MAX"):
p.nextToken()
expr := p.parseExpr()

return &ast.HavingMax{
Having: having,
Expr: expr,
}
default:
panic(p.errorfAtToken(&p.Token, `expect MIN or MAX, but: %v`, p.Token.Kind))
}
}

func (p *Parser) tryParseNullHandlingModifier() ast.NullHandlingModifier {
switch p.Token.Kind {
case "IGNORE":
ignore := p.expect("IGNORE").Pos
nulls := p.expect("NULLS").Pos

return &ast.IgnoreNulls{
Ignore: ignore,
Nulls: nulls,
}
case "RESPECT":
respect := p.expect("RESPECT").Pos
nulls := p.expect("NULLS").Pos

return &ast.RespectNulls{
Respect: respect,
Nulls: nulls,
}
default:
return nil
}
}

func (p *Parser) parseCaseExpr() *ast.CaseExpr {
pos := p.expect("CASE").Pos
var expr ast.Expr
Expand Down
6 changes: 6 additions & 0 deletions testdata/input/query/aggregate_function_calls.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SELECT
ARRAY_AGG(inches HAVING MAX year),
ARRAY_AGG(inches HAVING MIN year),
ARRAY_AGG(inches IGNORE NULLS),
ARRAY_AGG(inches RESPECT NULLS),
ARRAY_AGG(inches RESPECT NULLS HAVING MAX year),
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ ALTER TABLE foo ADD COLUMN expired_at TIMESTAMP AS (IF (status != "OPEN" AND sta
},
},
},
NamedArgs: []*ast.NamedArg(nil),
NamedArgs: []*ast.NamedArg(nil),
NullHandling: nil,
Having: nil,
},
},
&ast.ExprArg{
Expand All @@ -108,7 +110,9 @@ ALTER TABLE foo ADD COLUMN expired_at TIMESTAMP AS (IF (status != "OPEN" AND sta
},
},
},
NamedArgs: []*ast.NamedArg(nil),
NamedArgs: []*ast.NamedArg(nil),
NullHandling: nil,
Having: nil,
},
},
Options: (*ast.Options)(nil),
Expand Down
12 changes: 8 additions & 4 deletions testdata/result/ddl/create_table.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ create table foo (
},
},
},
NamedArgs: []*ast.NamedArg(nil),
NamedArgs: []*ast.NamedArg(nil),
NullHandling: nil,
Having: nil,
},
},
Options: (*ast.Options)(nil),
Expand Down Expand Up @@ -187,9 +189,11 @@ create table foo (
NameEnd: 546,
Name: "current_timestamp",
},
Distinct: false,
Args: []ast.Arg(nil),
NamedArgs: []*ast.NamedArg(nil),
Distinct: false,
Args: []ast.Arg(nil),
NamedArgs: []*ast.NamedArg(nil),
NullHandling: nil,
Having: nil,
},
},
GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ CREATE TABLE foo
},
},
},
NamedArgs: []*ast.NamedArg(nil),
NamedArgs: []*ast.NamedArg(nil),
NullHandling: nil,
Having: nil,
},
},
GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
Expand Down
4 changes: 3 additions & 1 deletion testdata/result/dml/insert_with_sequence_function.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ INSERT INTO foo(bar) VALUES (GET_NEXT_SEQUENCE_VALUE(SEQUENCE my_sequence))
},
},
},
NamedArgs: []*ast.NamedArg(nil),
NamedArgs: []*ast.NamedArg(nil),
NullHandling: nil,
Having: nil,
},
},
},
Expand Down
Loading

0 comments on commit 90f4dcb

Please sign in to comment.