@@ -490,7 +490,7 @@ func (p *Parser) selectListTerminator() bool {
490490}
491491
492492// parseSelectTarget parses one item in the SELECT list:
493- // - * [EXCLUDE (col, ...)] [REPLACE (expr AS col, ...)] [RENAME (col AS alias, ...)]
493+ // - * [ILIKE 'pattern'] [ EXCLUDE (col, ...)] [REPLACE (expr AS col, ...)] [RENAME (col AS alias, ...)]
494494// - expr [AS alias]
495495//
496496// The expression parser already handles * (StarExpr) and qualifier.*
@@ -508,13 +508,37 @@ func (p *Parser) parseSelectTarget() (*ast.SelectTarget, error) {
508508 Loc : ast.Loc {Start : startLoc .Start },
509509 }
510510
511+ // Star ILIKE transform: ILIKE is an infix operator, so for
512+ // `* ILIKE '<pattern>'` (and `tbl.* ILIKE ...`) the expression parser has
513+ // already bound the star into LikeExpr(StarExpr, pattern) before this
514+ // function can see it. A star is not a scalar operand, so in a SELECT
515+ // list that shape is unambiguously Snowflake's star ILIKE
516+ // column-transform, not a boolean expression — unwrap it here, at the
517+ // select-target boundary, so other expression contexts are untouched.
518+ // The unwrap keys on the exact documented shape (plain ILIKE with a
519+ // string-literal pattern); NOT/ANY/ESCAPE variants and non-literal
520+ // patterns are not transforms and stay expressions.
521+ if like , ok := expr .(* ast.LikeExpr ); ok &&
522+ like .Op == ast .LikeOpILike && ! like .Not && ! like .Any && like .Escape == nil {
523+ if _ , isStar := like .Expr .(* ast.StarExpr ); isStar {
524+ if pat , isLit := like .Pattern .(* ast.Literal ); isLit && pat .Kind == ast .LitString {
525+ expr = like .Expr
526+ target .Ilike = pat
527+ }
528+ }
529+ }
530+
511531 // Check if the expression is a star (* or qualifier.*)
512532 if _ , ok := expr .(* ast.StarExpr ); ok {
513533 target .Star = true
514534 target .Expr = expr
515535
516- // Star column-transforms, in Snowflake's documented order: EXCLUDE,
517- // then REPLACE, then RENAME (any subset may be present).
536+ // Star column-transforms, in Snowflake's documented order: ILIKE
537+ // (unwrapped above), then EXCLUDE, then REPLACE, then RENAME (any
538+ // subset may be present; the docs forbid combining ILIKE with
539+ // EXCLUDE, which the parser over-accepts — the combination is
540+ // positionally in order and parses soundly).
541+ // ILIKE '<pattern>'
518542 // EXCLUDE <col> | EXCLUDE (<col>, ...)
519543 // REPLACE (<expr> AS <col>, ...)
520544 // RENAME <col> AS <alias> | RENAME (<col> AS <alias>, ...)
@@ -537,15 +561,17 @@ func (p *Parser) parseSelectTarget() (*ast.SelectTarget, error) {
537561 }
538562 }
539563 // A transform keyword still pending here is out of documented order
540- // (e.g. `* RENAME (...) REPLACE (...)`). parseSingle ignores tokens
541- // after a completed statement, so without this check the rest of the
542- // statement — including FROM — would be dropped silently. Fail loudly
543- // instead.
564+ // (e.g. `* RENAME (...) REPLACE (...)`, or ILIKE after any other
565+ // transform — ILIKE must come first, and in first position it was
566+ // already consumed by the expression parse and unwrapped above).
567+ // parseSingle ignores tokens after a completed statement, so without
568+ // this check the rest of the statement — including FROM — would be
569+ // dropped silently. Fail loudly instead.
544570 switch p .cur .Type {
545- case kwEXCLUDE , kwREPLACE , kwRENAME :
571+ case kwILIKE , kwEXCLUDE , kwREPLACE , kwRENAME :
546572 return nil , & ParseError {
547573 Loc : p .cur .Loc ,
548- Msg : "star column-transforms must appear in EXCLUDE, REPLACE, RENAME order" ,
574+ Msg : "star column-transforms must appear in ILIKE/ EXCLUDE, REPLACE, RENAME order" ,
549575 }
550576 }
551577 } else {
0 commit comments