@@ -1034,13 +1034,15 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int)
10341034 allTuples := true
10351035 allTuplesArePrimitive := true
10361036 allPrimitiveLiterals := true // New: check if all are primitive literals (any type)
1037+ allNull := true // Track if all items are NULL
10371038 hasNonNull := false // Need at least one non-null value
10381039 for _ , item := range n .List {
10391040 if lit , ok := item .(* ast.Literal ); ok {
10401041 if lit .Type == ast .LiteralNull {
10411042 // NULL is compatible with all literal type lists
10421043 continue
10431044 }
1045+ allNull = false
10441046 hasNonNull = true
10451047 if lit .Type != ast .LiteralInteger && lit .Type != ast .LiteralFloat {
10461048 allNumericOrNull = false
@@ -1066,12 +1068,14 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int)
10661068 }
10671069 } else if isNumericExpr (item ) {
10681070 // Unary minus of numeric is still numeric
1071+ allNull = false
10691072 hasNonNull = true
10701073 allStringsOrNull = false
10711074 allBooleansOrNull = false
10721075 allTuples = false
10731076 // Numeric expression counts as primitive
10741077 } else {
1078+ allNull = false
10751079 allNumericOrNull = false
10761080 allStringsOrNull = false
10771081 allBooleansOrNull = false
@@ -1082,7 +1086,8 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int)
10821086 }
10831087 // Allow combining mixed primitive literals into a tuple when comparing tuples
10841088 // This handles cases like: (1,'') IN (-1,'') where the right side should be a single tuple literal
1085- canBeTupleLiteral = hasNonNull && (allNumericOrNull || allStringsOrNull || allBooleansOrNull || (allTuples && allTuplesArePrimitive ) || allPrimitiveLiterals )
1089+ // Also allow all-NULL lists to be formatted as tuple literals
1090+ canBeTupleLiteral = allNull || (hasNonNull && (allNumericOrNull || allStringsOrNull || allBooleansOrNull || (allTuples && allTuplesArePrimitive ) || allPrimitiveLiterals ))
10861091 }
10871092
10881093 // Count arguments: expr + list items or subquery
@@ -1252,13 +1257,15 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
12521257 allTuples := true
12531258 allTuplesArePrimitive := true
12541259 allPrimitiveLiterals := true // Any mix of primitive literals (numbers, strings, booleans, null, primitive tuples)
1260+ allNull := true // Track if all items are NULL
12551261 hasNonNull := false // Need at least one non-null value
12561262 for _ , item := range n .List {
12571263 if lit , ok := item .(* ast.Literal ); ok {
12581264 if lit .Type == ast .LiteralNull {
12591265 // NULL is compatible with all literal type lists
12601266 continue
12611267 }
1268+ allNull = false
12621269 hasNonNull = true
12631270 if lit .Type != ast .LiteralInteger && lit .Type != ast .LiteralFloat {
12641271 allNumericOrNull = false
@@ -1278,11 +1285,13 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
12781285 }
12791286 }
12801287 } else if isNumericExpr (item ) {
1288+ allNull = false
12811289 hasNonNull = true
12821290 allStringsOrNull = false
12831291 allBooleansOrNull = false
12841292 allTuples = false
12851293 } else {
1294+ allNull = false
12861295 allNumericOrNull = false
12871296 allStringsOrNull = false
12881297 allBooleansOrNull = false
@@ -1291,7 +1300,7 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
12911300 break
12921301 }
12931302 }
1294- canBeTupleLiteral = hasNonNull && (allNumericOrNull || (allStringsOrNull && len (n .List ) <= maxStringTupleSizeWithAlias ) || allBooleansOrNull || (allTuples && allTuplesArePrimitive ) || allPrimitiveLiterals )
1303+ canBeTupleLiteral = allNull || ( hasNonNull && (allNumericOrNull || (allStringsOrNull && len (n .List ) <= maxStringTupleSizeWithAlias ) || allBooleansOrNull || (allTuples && allTuplesArePrimitive ) || allPrimitiveLiterals ) )
12951304 }
12961305
12971306 // Count arguments
@@ -1342,9 +1351,8 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
13421351 fmt .Fprintf (sb , "%s Literal %s\n " , indent , FormatLiteral (tupleLit ))
13431352 } else if len (n .List ) == 1 {
13441353 if lit , ok := n .List [0 ].(* ast.Literal ); ok && lit .Type == ast .LiteralTuple {
1345- fmt .Fprintf (sb , "%s Function tuple (children %d)\n " , indent , 1 )
1346- fmt .Fprintf (sb , "%s ExpressionList (children %d)\n " , indent , 1 )
1347- Node (sb , n .List [0 ], depth + 4 )
1354+ // Use explainTupleInInList to properly handle primitive-only tuples as Literal Tuple_
1355+ explainTupleInInList (sb , lit , indent + " " , depth + 2 )
13481356 } else if n .TrailingComma {
13491357 // Single element with trailing comma (e.g., (2,)) - wrap in Function tuple
13501358 fmt .Fprintf (sb , "%s Function tuple (children %d)\n " , indent , 1 )
0 commit comments