@@ -115,13 +115,35 @@ func explainFunctionCallWithAlias(sb *strings.Builder, n *ast.FunctionCall, alia
115115 if n .Distinct {
116116 fnName = fnName + "Distinct"
117117 }
118+ // Append "If" if the function has a FILTER clause
119+ if n .Filter != nil {
120+ fnName = fnName + "If"
121+ }
118122 if alias != "" {
119123 fmt .Fprintf (sb , "%sFunction %s (alias %s) (children %d)\n " , indent , fnName , alias , children )
120124 } else {
121125 fmt .Fprintf (sb , "%sFunction %s (children %d)\n " , indent , fnName , children )
122126 }
123127 // Arguments (Settings are included as part of argument count)
124- argCount := len (n .Arguments )
128+ // FILTER condition is appended to arguments for -If suffix functions
129+ // count(name) FILTER (WHERE cond) -> countIf(name, cond) - 2 args
130+ // count(*) FILTER (WHERE cond) -> countIf(cond) - 1 arg (asterisk dropped)
131+ var argCount int
132+ filterArgs := n .Arguments
133+ if n .Filter != nil {
134+ // Filter condition is appended as an extra argument
135+ // But first, remove any Asterisk arguments (count(*) case)
136+ var nonAsteriskArgs []ast.Expression
137+ for _ , arg := range n .Arguments {
138+ if _ , isAsterisk := arg .(* ast.Asterisk ); ! isAsterisk {
139+ nonAsteriskArgs = append (nonAsteriskArgs , arg )
140+ }
141+ }
142+ filterArgs = nonAsteriskArgs
143+ argCount = len (filterArgs ) + 1 // +1 for filter condition
144+ } else {
145+ argCount = len (n .Arguments )
146+ }
125147 if len (n .Settings ) > 0 {
126148 argCount ++ // Set is counted as one argument
127149 }
@@ -130,7 +152,12 @@ func explainFunctionCallWithAlias(sb *strings.Builder, n *ast.FunctionCall, alia
130152 fmt .Fprintf (sb , " (children %d)" , argCount )
131153 }
132154 fmt .Fprintln (sb )
133- for _ , arg := range n .Arguments {
155+ // Output arguments (filterArgs excludes Asterisk when FILTER is present)
156+ argsToOutput := filterArgs
157+ if n .Filter == nil {
158+ argsToOutput = n .Arguments
159+ }
160+ for _ , arg := range argsToOutput {
134161 // For view() table function, unwrap Subquery wrapper
135162 // Also reset the subquery context since view() SELECT is not in a Subquery node
136163 if strings .ToLower (n .Name ) == "view" {
@@ -144,6 +171,10 @@ func explainFunctionCallWithAlias(sb *strings.Builder, n *ast.FunctionCall, alia
144171 }
145172 Node (sb , arg , depth + 2 )
146173 }
174+ // Append filter condition at the end
175+ if n .Filter != nil {
176+ Node (sb , n .Filter , depth + 2 )
177+ }
147178 // Settings appear as Set node inside ExpressionList
148179 if len (n .Settings ) > 0 {
149180 fmt .Fprintf (sb , "%s Set\n " , indent )
@@ -567,8 +598,8 @@ func explainCastExprWithAlias(sb *strings.Builder, n *ast.CastExpr, alias string
567598 if lit .Type == ast .LiteralArray || lit .Type == ast .LiteralTuple {
568599 if useArrayFormat {
569600 fmt .Fprintf (sb , "%s Literal %s\n " , indent , FormatLiteral (lit ))
570- } else if containsCastExpressions (lit ) {
571- // Array contains CastExpr elements - output as Function array with children
601+ } else if containsCastExpressions (lit ) || ! containsOnlyLiterals ( lit ) {
602+ // Array contains CastExpr or non-literal elements - output as Function array with children
572603 Node (sb , n .Expr , depth + 2 )
573604 } else {
574605 // Simple literals (including negative numbers) - format as string
@@ -738,6 +769,7 @@ func containsCastExpressions(lit *ast.Literal) bool {
738769}
739770
740771// containsOnlyLiterals checks if a literal array/tuple contains only literal values (no expressions)
772+ // This includes negated literals (UnaryExpr with Op="-" and Literal operand)
741773func containsOnlyLiterals (lit * ast.Literal ) bool {
742774 var exprs []ast.Expression
743775 switch lit .Type {
@@ -752,16 +784,24 @@ func containsOnlyLiterals(lit *ast.Literal) bool {
752784 }
753785
754786 for _ , e := range exprs {
755- innerLit , ok := e .(* ast.Literal )
756- if ! ok {
757- return false
787+ // Check if it's a direct literal
788+ if innerLit , ok := e .(* ast.Literal ); ok {
789+ // Nested arrays/tuples need recursive check
790+ if innerLit .Type == ast .LiteralArray || innerLit .Type == ast .LiteralTuple {
791+ if ! containsOnlyLiterals (innerLit ) {
792+ return false
793+ }
794+ }
795+ continue
758796 }
759- // Nested arrays/tuples need recursive check
760- if innerLit . Type == ast .LiteralArray || innerLit . Type == ast . LiteralTuple {
761- if ! containsOnlyLiterals ( innerLit ) {
762- return false
797+ // Check if it's a negated literal (e.g., -1)
798+ if unary , ok := e .( * ast.UnaryExpr ); ok && unary . Op == "-" {
799+ if _ , isLit := unary . Operand .( * ast. Literal ); isLit {
800+ continue
763801 }
764802 }
803+ // Not a literal or negated literal
804+ return false
765805 }
766806 return true
767807}
@@ -986,10 +1026,11 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int)
9861026 // Check if this tuple contains only primitive literals (including unary negation)
9871027 if ! containsOnlyPrimitiveLiteralsWithUnary (lit ) {
9881028 allTuplesArePrimitive = false
1029+ allPrimitiveLiterals = false // Non-primitive tuple breaks the mixed literal check too
9891030 }
9901031 }
991- // Check if it's a primitive literal type (not a tuple or complex type)
992- if lit .Type == ast .LiteralTuple || lit . Type == ast . LiteralArray {
1032+ // Arrays break the primitive literals check
1033+ if lit .Type == ast .LiteralArray {
9931034 allPrimitiveLiterals = false
9941035 }
9951036 } else if isNumericExpr (item ) {
@@ -1133,7 +1174,8 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
11331174 allBooleansOrNull := true
11341175 allTuples := true
11351176 allTuplesArePrimitive := true
1136- hasNonNull := false // Need at least one non-null value
1177+ allPrimitiveLiterals := true // Any mix of primitive literals (numbers, strings, booleans, null, primitive tuples)
1178+ hasNonNull := false // Need at least one non-null value
11371179 for _ , item := range n .List {
11381180 if lit , ok := item .(* ast.Literal ); ok {
11391181 if lit .Type == ast .LiteralNull {
@@ -1155,6 +1197,7 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
11551197 } else {
11561198 if ! containsOnlyPrimitiveLiterals (lit ) {
11571199 allTuplesArePrimitive = false
1200+ allPrimitiveLiterals = false
11581201 }
11591202 }
11601203 } else if isNumericExpr (item ) {
@@ -1167,10 +1210,11 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
11671210 allStringsOrNull = false
11681211 allBooleansOrNull = false
11691212 allTuples = false
1213+ allPrimitiveLiterals = false
11701214 break
11711215 }
11721216 }
1173- canBeTupleLiteral = hasNonNull && (allNumericOrNull || (allStringsOrNull && len (n .List ) <= maxStringTupleSizeWithAlias ) || allBooleansOrNull || (allTuples && allTuplesArePrimitive ))
1217+ canBeTupleLiteral = hasNonNull && (allNumericOrNull || (allStringsOrNull && len (n .List ) <= maxStringTupleSizeWithAlias ) || allBooleansOrNull || (allTuples && allTuplesArePrimitive ) || allPrimitiveLiterals )
11741218 }
11751219
11761220 // Count arguments
0 commit comments