Skip to content

Commit 17fe6ac

Browse files
kyleconroyclaude
andcommitted
Handle trailing comma in IN expressions as single-element tuple
When an IN expression has a trailing comma like `IN (2,)`, it should be represented as a Function tuple with one element, not as a plain literal. - Add TrailingComma field to InExpr to track trailing comma presence - Add parseInList helper to detect trailing commas during parsing - Update explainInExpr and explainInExprWithAlias to wrap single elements with trailing comma in Function tuple Fixes stmt45 and stmt46 in 01756_optimize_skip_unused_shards_rewrite_in and stmt5 in 01757_optimize_skip_unused_shards_limit. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0855448 commit 17fe6ac

File tree

5 files changed

+58
-18
lines changed

5 files changed

+58
-18
lines changed

ast/ast.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,12 +1717,13 @@ func (b *BetweenExpr) expressionNode() {}
17171717

17181718
// InExpr represents an IN expression.
17191719
type InExpr struct {
1720-
Position token.Position `json:"-"`
1721-
Expr Expression `json:"expr"`
1722-
Not bool `json:"not,omitempty"`
1723-
Global bool `json:"global,omitempty"`
1724-
List []Expression `json:"list,omitempty"`
1725-
Query Statement `json:"query,omitempty"`
1720+
Position token.Position `json:"-"`
1721+
Expr Expression `json:"expr"`
1722+
Not bool `json:"not,omitempty"`
1723+
Global bool `json:"global,omitempty"`
1724+
List []Expression `json:"list,omitempty"`
1725+
Query Statement `json:"query,omitempty"`
1726+
TrailingComma bool `json:"trailing_comma,omitempty"` // true if list had trailing comma like (2,)
17261727
}
17271728

17281729
func (i *InExpr) Pos() token.Position { return i.Position }

internal/explain/functions.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,9 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int)
10791079
if lit, ok := n.List[0].(*ast.Literal); ok && lit.Type == ast.LiteralTuple {
10801080
// Single tuple literal gets wrapped in Function tuple, so count as 1
10811081
argCount++
1082+
} else if n.TrailingComma {
1083+
// Single element with trailing comma (e.g., (2,)) gets wrapped in Function tuple
1084+
argCount++
10821085
} else {
10831086
argCount += len(n.List)
10841087
}
@@ -1148,6 +1151,11 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int)
11481151
Node(sb, n.List[0], depth+4)
11491152
}
11501153
}
1154+
} else if n.TrailingComma {
1155+
// Single element with trailing comma (e.g., (2,)) - wrap in Function tuple
1156+
fmt.Fprintf(sb, "%s Function tuple (children %d)\n", indent, 1)
1157+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 1)
1158+
Node(sb, n.List[0], depth+4)
11511159
} else {
11521160
// Single non-tuple element - output directly
11531161
Node(sb, n.List[0], depth+2)
@@ -1277,6 +1285,9 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
12771285
if len(n.List) == 1 {
12781286
if lit, ok := n.List[0].(*ast.Literal); ok && lit.Type == ast.LiteralTuple {
12791287
argCount++
1288+
} else if n.TrailingComma {
1289+
// Single element with trailing comma (e.g., (2,)) gets wrapped in Function tuple
1290+
argCount++
12801291
} else {
12811292
argCount += len(n.List)
12821293
}
@@ -1315,6 +1326,11 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in
13151326
fmt.Fprintf(sb, "%s Function tuple (children %d)\n", indent, 1)
13161327
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 1)
13171328
Node(sb, n.List[0], depth+4)
1329+
} else if n.TrailingComma {
1330+
// Single element with trailing comma (e.g., (2,)) - wrap in Function tuple
1331+
fmt.Fprintf(sb, "%s Function tuple (children %d)\n", indent, 1)
1332+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 1)
1333+
Node(sb, n.List[0], depth+4)
13181334
} else {
13191335
Node(sb, n.List[0], depth+2)
13201336
}

parser/expression.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2110,7 +2110,8 @@ func (p *Parser) parseInExpression(left ast.Expression, not bool) ast.Expression
21102110
if p.currentIs(token.SELECT) || p.currentIs(token.WITH) {
21112111
expr.Query = p.parseSelectWithUnion()
21122112
} else {
2113-
expr.List = p.parseExpressionList()
2113+
// Parse IN list manually to detect trailing comma
2114+
expr.List, expr.TrailingComma = p.parseInList()
21142115
}
21152116
p.expect(token.RPAREN)
21162117
} else if p.currentIs(token.LBRACKET) {
@@ -2130,6 +2131,37 @@ func (p *Parser) parseInExpression(left ast.Expression, not bool) ast.Expression
21302131
return expr
21312132
}
21322133

2134+
// parseInList parses an expression list for IN expressions and returns
2135+
// whether the list had a trailing comma (which indicates a single-element tuple).
2136+
func (p *Parser) parseInList() ([]ast.Expression, bool) {
2137+
var exprs []ast.Expression
2138+
trailingComma := false
2139+
2140+
if p.currentIs(token.RPAREN) || p.currentIs(token.EOF) {
2141+
return exprs, false
2142+
}
2143+
2144+
expr := p.parseExpression(LOWEST)
2145+
if expr != nil {
2146+
exprs = append(exprs, expr)
2147+
}
2148+
2149+
for p.currentIs(token.COMMA) {
2150+
p.nextToken() // consume comma
2151+
// Check if this is a trailing comma (followed by RPAREN)
2152+
if p.currentIs(token.RPAREN) {
2153+
trailingComma = true
2154+
break
2155+
}
2156+
expr := p.parseExpression(LOWEST)
2157+
if expr != nil {
2158+
exprs = append(exprs, expr)
2159+
}
2160+
}
2161+
2162+
return exprs, trailingComma
2163+
}
2164+
21332165
func (p *Parser) parseBetweenExpression(left ast.Expression, not bool) ast.Expression {
21342166
expr := &ast.BetweenExpr{
21352167
Position: p.current.Pos,
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt45": true,
4-
"stmt46": true
5-
}
6-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt5": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)