Repository: cloudspannerecosystem/memefish (go)

Issue:
Support current spec of aggregate function calls
Aggregate function calls have several optional clauses.
https://cloud.google.com/spanner/docs/reference/standard-sql/aggregate-function-calls
```
function_name(
  [ DISTINCT ]
  function_arguments
  [ { IGNORE | RESPECT } NULLS ]
  [ HAVING { MAX | MIN } having_expression ]
)

```
I think 
* `{IGNORE | RESPECT } NULLS`
* `HAVING {MAX | MIN}`
are missing in memefish.

```
$ go run ./tools/parse --mode statement 'SELECT ARRAY_AGG(x IGNORE NULLS)'        
2024/08/21 18:08:23 syntax error::1:20: expected token: ), but: IGNORE

  1:  SELECT ARRAY_AGG(x IGNORE NULLS)
                         ^~~~~~
exit status 1
```

```
$ go run ./tools/parse --mode statement 'SELECT ANY_VALUE(year HAVING MAX inches)'
2024/08/21 18:07:17 syntax error::1:23: expected token: ), but: HAVING

  1:  SELECT ANY_VALUE(year HAVING MAX inches)
                            ^~~~~~
exit status 1
```


Pull request:
Implement HAVING MIN/MAX and RESPECT/IGNORE NULLS
This PR implements the current aggregate function call syntax.

https://cloud.google.com/spanner/docs/reference/standard-sql/aggregate-function-calls#aggregate_function_call_syntax

```
function_name(
  [ DISTINCT ]
  function_arguments
  [ { IGNORE | RESPECT } NULLS ]
  [ HAVING { MAX | MIN } having_expression ]
)
```

### Note
* The names `NullHandlingModifier` and `HavingModifier` are borrowed from [ZetaSQL](https://github.com/google/zetasql/blob/194cd32b5d766d60e3ca442651d792c7fe54ea74/zetasql/parser/gen_parse_tree.py#L2562)
* [`COUNT(*)`](https://cloud.google.com/spanner/docs/reference/standard-sql/aggregate_functions#count
) doesn't support these modifiers, so `CountStarExpr` is not updated.



fix #88 

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index e9c023b..c123d13 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -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
@@ -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.
@@ -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(*)
diff --git a/ast/pos.go b/ast/pos.go
index 3a17aef..f8d86cd 100644
--- a/ast/pos.go
+++ b/ast/pos.go
@@ -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 }
 
diff --git a/ast/sql.go b/ast/sql.go
index 2b93cfa..2bdcb60 100644
--- a/ast/sql.go
+++ b/ast/sql.go
@@ -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()
 }
diff --git a/parser.go b/parser.go
index 41f947d..fc59264 100644
--- a/parser.go
+++ b/parser.go
@@ -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() {
@@ -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


Test patch:
diff --git a/testdata/input/query/aggregate_function_calls.sql b/testdata/input/query/aggregate_function_calls.sql
new file mode 100644
index 0000000..b921739
--- /dev/null
+++ b/testdata/input/query/aggregate_function_calls.sql
@@ -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),
diff --git a/testdata/result/ddl/alter_table_add_column_with_if_expression.sql.txt b/testdata/result/ddl/alter_table_add_column_with_if_expression.sql.txt
index 6c00d73..be16d69 100644
--- a/testdata/result/ddl/alter_table_add_column_with_if_expression.sql.txt
+++ b/testdata/result/ddl/alter_table_add_column_with_if_expression.sql.txt
@@ -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{
@@ -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),
diff --git a/testdata/result/ddl/create_table.sql.txt b/testdata/result/ddl/create_table.sql.txt
index 523952f..6ae97cc 100644
--- a/testdata/result/ddl/create_table.sql.txt
+++ b/testdata/result/ddl/create_table.sql.txt
@@ -144,7 +144,9 @@ create table foo (
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       Options: (*ast.Options)(nil),
@@ -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),
diff --git a/testdata/result/ddl/create_table_with_sequence_function.sql.txt b/testdata/result/ddl/create_table_with_sequence_function.sql.txt
index 5025a25..f2b827c 100644
--- a/testdata/result/ddl/create_table_with_sequence_function.sql.txt
+++ b/testdata/result/ddl/create_table_with_sequence_function.sql.txt
@@ -50,7 +50,9 @@ CREATE TABLE foo
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
diff --git a/testdata/result/dml/insert_with_sequence_function.sql.txt b/testdata/result/dml/insert_with_sequence_function.sql.txt
index 2424b7a..7f5a4b8 100644
--- a/testdata/result/dml/insert_with_sequence_function.sql.txt
+++ b/testdata/result/dml/insert_with_sequence_function.sql.txt
@@ -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,
             },
           },
         },
diff --git a/testdata/result/query/aggregate_function_calls.sql.txt b/testdata/result/query/aggregate_function_calls.sql.txt
new file mode 100644
index 0000000..9e2ad6c
--- /dev/null
+++ b/testdata/result/query/aggregate_function_calls.sql.txt
@@ -0,0 +1,174 @@
+--- aggregate_function_calls.sql
+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),
+
+--- AST
+&ast.QueryStatement{
+  Hint:  (*ast.Hint)(nil),
+  With:  (*ast.With)(nil),
+  Query: &ast.Select{
+    Select:   0,
+    Distinct: false,
+    As:       nil,
+    Results:  []ast.SelectItem{
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 43,
+          Func:   &ast.Ident{
+            NamePos: 11,
+            NameEnd: 20,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 21,
+                NameEnd: 27,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       &ast.HavingMax{
+            Having: 28,
+            Expr:   &ast.Ident{
+              NamePos: 39,
+              NameEnd: 43,
+              Name:    "year",
+            },
+          },
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 82,
+          Func:   &ast.Ident{
+            NamePos: 50,
+            NameEnd: 59,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 60,
+                NameEnd: 66,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       &ast.HavingMin{
+            Having: 67,
+            Expr:   &ast.Ident{
+              NamePos: 78,
+              NameEnd: 82,
+              Name:    "year",
+            },
+          },
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 118,
+          Func:   &ast.Ident{
+            NamePos: 89,
+            NameEnd: 98,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 99,
+                NameEnd: 105,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: &ast.IgnoreNulls{
+            Ignore: 106,
+            Nulls:  113,
+          },
+          Having: nil,
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 155,
+          Func:   &ast.Ident{
+            NamePos: 125,
+            NameEnd: 134,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 135,
+                NameEnd: 141,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: &ast.RespectNulls{
+            Respect: 142,
+            Nulls:   150,
+          },
+          Having: nil,
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 208,
+          Func:   &ast.Ident{
+            NamePos: 162,
+            NameEnd: 171,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 172,
+                NameEnd: 178,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: &ast.RespectNulls{
+            Respect: 179,
+            Nulls:   187,
+          },
+          Having: &ast.HavingMax{
+            Having: 193,
+            Expr:   &ast.Ident{
+              NamePos: 204,
+              NameEnd: 208,
+              Name:    "year",
+            },
+          },
+        },
+      },
+    },
+    From:    (*ast.From)(nil),
+    Where:   (*ast.Where)(nil),
+    GroupBy: (*ast.GroupBy)(nil),
+    Having:  (*ast.Having)(nil),
+    OrderBy: (*ast.OrderBy)(nil),
+    Limit:   (*ast.Limit)(nil),
+  },
+}
+
+--- SQL
+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)
diff --git a/testdata/result/query/select_call_with_named_expr.sql.txt b/testdata/result/query/select_call_with_named_expr.sql.txt
index 347e513..78d1d15 100644
--- a/testdata/result/query/select_call_with_named_expr.sql.txt
+++ b/testdata/result/query/select_call_with_named_expr.sql.txt
@@ -137,6 +137,8 @@ WHERE a.SingerId = 1 AND SEARCH(a.DescriptionTokens, 'classic albums', enhance_q
               },
             },
           },
+          NullHandling: nil,
+          Having:       nil,
         },
       },
     },
diff --git a/testdata/result/query/select_count_distinct.sql.txt b/testdata/result/query/select_count_distinct.sql.txt
index 4cb7d98..76755e4 100644
--- a/testdata/result/query/select_count_distinct.sql.txt
+++ b/testdata/result/query/select_count_distinct.sql.txt
@@ -27,7 +27,9 @@ select count(distinct x) from foo
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
     },
diff --git a/testdata/result/query/select_expr.sql.txt b/testdata/result/query/select_expr.sql.txt
index eeb46ce..e6c89c3 100644
--- a/testdata/result/query/select_expr.sql.txt
+++ b/testdata/result/query/select_expr.sql.txt
@@ -617,7 +617,9 @@ select 1 + 2, 1 - 2,
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -655,7 +657,9 @@ select 1 + 2, 1 - 2,
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -899,7 +903,9 @@ select 1 + 2, 1 - 2,
                 },
               },
             },
-            NamedArgs: []*ast.NamedArg(nil),
+            NamedArgs:    []*ast.NamedArg(nil),
+            NullHandling: nil,
+            Having:       nil,
           },
           Right: &ast.IntLiteral{
             ValuePos: 790,
diff --git a/testdata/result/query/select_literals_all.sql.txt b/testdata/result/query/select_literals_all.sql.txt
index b8556bb..3e35cc3 100644
--- a/testdata/result/query/select_literals_all.sql.txt
+++ b/testdata/result/query/select_literals_all.sql.txt
@@ -504,7 +504,9 @@ lines''',
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -555,7 +557,9 @@ lines''',
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
diff --git a/testdata/result/query/select_literals_date.sql.txt b/testdata/result/query/select_literals_date.sql.txt
index 1ccd8fb..fd6b359 100644
--- a/testdata/result/query/select_literals_date.sql.txt
+++ b/testdata/result/query/select_literals_date.sql.txt
@@ -48,7 +48,9 @@ SELECT
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -109,7 +111,9 @@ SELECT
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -140,7 +144,9 @@ SELECT
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
         As: &ast.AsAlias{
           As:    -1,
@@ -169,7 +175,9 @@ SELECT
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
         As: &ast.AsAlias{
           As:    -1,
diff --git a/testdata/result/query/select_with_sequence_function.sql.txt b/testdata/result/query/select_with_sequence_function.sql.txt
index 1abdadb..491d47f 100644
--- a/testdata/result/query/select_with_sequence_function.sql.txt
+++ b/testdata/result/query/select_with_sequence_function.sql.txt
@@ -29,7 +29,9 @@ SELECT GET_NEXT_SEQUENCE_VALUE(SEQUENCE my_sequence) as next_id
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
         As: &ast.AsAlias{
           As:    -1,
diff --git a/testdata/result/statement/aggregate_function_calls.sql.txt b/testdata/result/statement/aggregate_function_calls.sql.txt
new file mode 100644
index 0000000..9e2ad6c
--- /dev/null
+++ b/testdata/result/statement/aggregate_function_calls.sql.txt
@@ -0,0 +1,174 @@
+--- aggregate_function_calls.sql
+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),
+
+--- AST
+&ast.QueryStatement{
+  Hint:  (*ast.Hint)(nil),
+  With:  (*ast.With)(nil),
+  Query: &ast.Select{
+    Select:   0,
+    Distinct: false,
+    As:       nil,
+    Results:  []ast.SelectItem{
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 43,
+          Func:   &ast.Ident{
+            NamePos: 11,
+            NameEnd: 20,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 21,
+                NameEnd: 27,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       &ast.HavingMax{
+            Having: 28,
+            Expr:   &ast.Ident{
+              NamePos: 39,
+              NameEnd: 43,
+              Name:    "year",
+            },
+          },
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 82,
+          Func:   &ast.Ident{
+            NamePos: 50,
+            NameEnd: 59,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 60,
+                NameEnd: 66,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       &ast.HavingMin{
+            Having: 67,
+            Expr:   &ast.Ident{
+              NamePos: 78,
+              NameEnd: 82,
+              Name:    "year",
+            },
+          },
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 118,
+          Func:   &ast.Ident{
+            NamePos: 89,
+            NameEnd: 98,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 99,
+                NameEnd: 105,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: &ast.IgnoreNulls{
+            Ignore: 106,
+            Nulls:  113,
+          },
+          Having: nil,
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 155,
+          Func:   &ast.Ident{
+            NamePos: 125,
+            NameEnd: 134,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 135,
+                NameEnd: 141,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: &ast.RespectNulls{
+            Respect: 142,
+            Nulls:   150,
+          },
+          Having: nil,
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.CallExpr{
+          Rparen: 208,
+          Func:   &ast.Ident{
+            NamePos: 162,
+            NameEnd: 171,
+            Name:    "ARRAY_AGG",
+          },
+          Distinct: false,
+          Args:     []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.Ident{
+                NamePos: 172,
+                NameEnd: 178,
+                Name:    "inches",
+              },
+            },
+          },
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: &ast.RespectNulls{
+            Respect: 179,
+            Nulls:   187,
+          },
+          Having: &ast.HavingMax{
+            Having: 193,
+            Expr:   &ast.Ident{
+              NamePos: 204,
+              NameEnd: 208,
+              Name:    "year",
+            },
+          },
+        },
+      },
+    },
+    From:    (*ast.From)(nil),
+    Where:   (*ast.Where)(nil),
+    GroupBy: (*ast.GroupBy)(nil),
+    Having:  (*ast.Having)(nil),
+    OrderBy: (*ast.OrderBy)(nil),
+    Limit:   (*ast.Limit)(nil),
+  },
+}
+
+--- SQL
+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)
diff --git a/testdata/result/statement/alter_table_add_column_with_if_expression.sql.txt b/testdata/result/statement/alter_table_add_column_with_if_expression.sql.txt
index 6c00d73..be16d69 100644
--- a/testdata/result/statement/alter_table_add_column_with_if_expression.sql.txt
+++ b/testdata/result/statement/alter_table_add_column_with_if_expression.sql.txt
@@ -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{
@@ -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),
diff --git a/testdata/result/statement/create_table.sql.txt b/testdata/result/statement/create_table.sql.txt
index 523952f..6ae97cc 100644
--- a/testdata/result/statement/create_table.sql.txt
+++ b/testdata/result/statement/create_table.sql.txt
@@ -144,7 +144,9 @@ create table foo (
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       Options: (*ast.Options)(nil),
@@ -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),
diff --git a/testdata/result/statement/create_table_with_sequence_function.sql.txt b/testdata/result/statement/create_table_with_sequence_function.sql.txt
index 5025a25..f2b827c 100644
--- a/testdata/result/statement/create_table_with_sequence_function.sql.txt
+++ b/testdata/result/statement/create_table_with_sequence_function.sql.txt
@@ -50,7 +50,9 @@ CREATE TABLE foo
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
diff --git a/testdata/result/statement/insert_with_sequence_function.sql.txt b/testdata/result/statement/insert_with_sequence_function.sql.txt
index 2424b7a..7f5a4b8 100644
--- a/testdata/result/statement/insert_with_sequence_function.sql.txt
+++ b/testdata/result/statement/insert_with_sequence_function.sql.txt
@@ -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,
             },
           },
         },
diff --git a/testdata/result/statement/select_call_with_named_expr.sql.txt b/testdata/result/statement/select_call_with_named_expr.sql.txt
index 347e513..78d1d15 100644
--- a/testdata/result/statement/select_call_with_named_expr.sql.txt
+++ b/testdata/result/statement/select_call_with_named_expr.sql.txt
@@ -137,6 +137,8 @@ WHERE a.SingerId = 1 AND SEARCH(a.DescriptionTokens, 'classic albums', enhance_q
               },
             },
           },
+          NullHandling: nil,
+          Having:       nil,
         },
       },
     },
diff --git a/testdata/result/statement/select_count_distinct.sql.txt b/testdata/result/statement/select_count_distinct.sql.txt
index 4cb7d98..76755e4 100644
--- a/testdata/result/statement/select_count_distinct.sql.txt
+++ b/testdata/result/statement/select_count_distinct.sql.txt
@@ -27,7 +27,9 @@ select count(distinct x) from foo
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
     },
diff --git a/testdata/result/statement/select_expr.sql.txt b/testdata/result/statement/select_expr.sql.txt
index eeb46ce..e6c89c3 100644
--- a/testdata/result/statement/select_expr.sql.txt
+++ b/testdata/result/statement/select_expr.sql.txt
@@ -617,7 +617,9 @@ select 1 + 2, 1 - 2,
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -655,7 +657,9 @@ select 1 + 2, 1 - 2,
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -899,7 +903,9 @@ select 1 + 2, 1 - 2,
                 },
               },
             },
-            NamedArgs: []*ast.NamedArg(nil),
+            NamedArgs:    []*ast.NamedArg(nil),
+            NullHandling: nil,
+            Having:       nil,
           },
           Right: &ast.IntLiteral{
             ValuePos: 790,
diff --git a/testdata/result/statement/select_literals_all.sql.txt b/testdata/result/statement/select_literals_all.sql.txt
index b8556bb..3e35cc3 100644
--- a/testdata/result/statement/select_literals_all.sql.txt
+++ b/testdata/result/statement/select_literals_all.sql.txt
@@ -504,7 +504,9 @@ lines''',
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -555,7 +557,9 @@ lines''',
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
diff --git a/testdata/result/statement/select_literals_date.sql.txt b/testdata/result/statement/select_literals_date.sql.txt
index 1ccd8fb..fd6b359 100644
--- a/testdata/result/statement/select_literals_date.sql.txt
+++ b/testdata/result/statement/select_literals_date.sql.txt
@@ -48,7 +48,9 @@ SELECT
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -109,7 +111,9 @@ SELECT
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
       },
       &ast.ExprSelectItem{
@@ -140,7 +144,9 @@ SELECT
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
         As: &ast.AsAlias{
           As:    -1,
@@ -169,7 +175,9 @@ SELECT
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
         As: &ast.AsAlias{
           As:    -1,
diff --git a/testdata/result/statement/select_with_sequence_function.sql.txt b/testdata/result/statement/select_with_sequence_function.sql.txt
index 1abdadb..491d47f 100644
--- a/testdata/result/statement/select_with_sequence_function.sql.txt
+++ b/testdata/result/statement/select_with_sequence_function.sql.txt
@@ -29,7 +29,9 @@ SELECT GET_NEXT_SEQUENCE_VALUE(SEQUENCE my_sequence) as next_id
               },
             },
           },
-          NamedArgs: []*ast.NamedArg(nil),
+          NamedArgs:    []*ast.NamedArg(nil),
+          NullHandling: nil,
+          Having:       nil,
         },
         As: &ast.AsAlias{
           As:    -1,