Found by fuzzing (see #415).
Repro
stmt, err := memefish.ParseQuery("", "SELECT WITH(0)")
// err == nil
sql := stmt.SQL()
// sql == "SELECT WITH(, 0)"
_, err = memefish.ParseQuery("", sql)
// err != nil: syntax error: :1:13: unexpected token: ,
Cause
parseWithExpr (parser.go:2651) accepts zero name AS expr variables — the lookahead loop can run zero times — but WithExpr.SQL() (ast/sql.go:583) renders "WITH(" + sqlJoin(w.Vars, ", ") + ", " + w.Expr.SQL() + ")", producing a leading comma when Vars is empty.
Per the GoogleSQL grammar (googlesql/parser/googlesql.tm), a WITH expression requires at least one variable:
with_expression {ASTExpression*}:
KW_WITH_STARTING_WITH_EXPRESSION "(" with_expression_variable_prefix "," expression ")"
(with_expression_variable_prefix is one-or-more identifier AS expression.)
So WITH(0) is not valid GoogleSQL, and the right fix is in the parser: parseWithExpr should require at least one variable. The renderer is then always correct as written.
Found by fuzzing (see #415).
Repro
Cause
parseWithExpr(parser.go:2651) accepts zeroname AS exprvariables — the lookahead loop can run zero times — butWithExpr.SQL()(ast/sql.go:583) renders"WITH(" + sqlJoin(w.Vars, ", ") + ", " + w.Expr.SQL() + ")", producing a leading comma whenVarsis empty.Per the GoogleSQL grammar (
googlesql/parser/googlesql.tm), a WITH expression requires at least one variable:(
with_expression_variable_prefixis one-or-moreidentifier AS expression.)So
WITH(0)is not valid GoogleSQL, and the right fix is in the parser:parseWithExprshould require at least one variable. The renderer is then always correct as written.