Skip to content

Commit b218c3d

Browse files
kyleconroyclaude
andcommitted
Support CREATE INDEX expression without parentheses
ClickHouse allows CREATE INDEX without parentheses around the expression: CREATE INDEX idx ON tbl date(ts) TYPE MinMax This commit: - Parses unparenthesized expressions in CREATE INDEX - Tracks whether columns were parenthesized for correct EXPLAIN output: - Single column in parens: Identifier - Multiple columns in parens: empty Function tuple - Unparenthesized expression: output directly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5121d5c commit b218c3d

4 files changed

Lines changed: 38 additions & 19 deletions

File tree

ast/ast.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,12 +1283,13 @@ func (d *DropWorkloadQuery) statementNode() {}
12831283

12841284
// CreateIndexQuery represents a CREATE INDEX statement.
12851285
type CreateIndexQuery struct {
1286-
Position token.Position `json:"-"`
1287-
IndexName string `json:"index_name"`
1288-
Table string `json:"table"`
1289-
Columns []Expression `json:"columns,omitempty"`
1290-
Type string `json:"type,omitempty"` // Index type (minmax, bloom_filter, etc.)
1291-
Granularity int `json:"granularity,omitempty"` // GRANULARITY value
1286+
Position token.Position `json:"-"`
1287+
IndexName string `json:"index_name"`
1288+
Table string `json:"table"`
1289+
Columns []Expression `json:"columns,omitempty"`
1290+
ColumnsParenthesized bool `json:"columns_parenthesized,omitempty"` // True if columns in (...)
1291+
Type string `json:"type,omitempty"` // Index type (minmax, bloom_filter, etc.)
1292+
Granularity int `json:"granularity,omitempty"` // GRANULARITY value
12921293
}
12931294

12941295
func (c *CreateIndexQuery) Pos() token.Position { return c.Position }

internal/explain/statements.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2461,18 +2461,33 @@ func explainCreateIndexQuery(sb *strings.Builder, n *ast.CreateIndexQuery, inden
24612461
}
24622462
fmt.Fprintf(sb, "%s Index (children %d)\n", indent, indexChildren)
24632463

2464-
// For single column, output as Identifier
2465-
// For multiple columns or if there are any special cases, output as Function tuple
2466-
if len(n.Columns) == 1 {
2467-
if ident, ok := n.Columns[0].(*ast.Identifier); ok {
2468-
fmt.Fprintf(sb, "%s Identifier %s\n", indent, ident.Name())
2464+
// Output columns based on whether they were parenthesized
2465+
if n.ColumnsParenthesized {
2466+
if len(n.Columns) == 1 {
2467+
// Single column in parentheses: output as identifier (if it's an identifier)
2468+
if ident, ok := n.Columns[0].(*ast.Identifier); ok {
2469+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, ident.Name())
2470+
} else {
2471+
// Non-identifier single expression - output directly
2472+
Node(sb, n.Columns[0], depth+2)
2473+
}
24692474
} else {
2470-
// Non-identifier expression - wrap in tuple
2475+
// Multiple columns in parentheses: output as empty Function tuple
24712476
fmt.Fprintf(sb, "%s Function tuple (children 1)\n", indent)
24722477
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
24732478
}
2479+
} else if len(n.Columns) == 1 {
2480+
// Single unparenthesized expression: output directly
2481+
Node(sb, n.Columns[0], depth+2)
2482+
} else if len(n.Columns) > 0 {
2483+
// Multiple columns - wrap in Function tuple with ExpressionList
2484+
fmt.Fprintf(sb, "%s Function tuple (children 1)\n", indent)
2485+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.Columns))
2486+
for _, col := range n.Columns {
2487+
Node(sb, col, depth+3)
2488+
}
24742489
} else {
2475-
// Multiple columns or empty - always Function tuple with ExpressionList
2490+
// No columns - empty Function tuple
24762491
fmt.Fprintf(sb, "%s Function tuple (children 1)\n", indent)
24772492
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
24782493
}

parser/parser.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2352,8 +2352,9 @@ func (p *Parser) parseCreateIndex(pos token.Position) *ast.CreateIndexQuery {
23522352
query.Table = p.parseIdentifierName()
23532353
}
23542354

2355-
// Parse column list in parentheses
2355+
// Parse column expression - can be in parentheses or directly after table name
23562356
if p.currentIs(token.LPAREN) {
2357+
query.ColumnsParenthesized = true
23572358
p.nextToken() // skip (
23582359

23592360
for !p.currentIs(token.RPAREN) && !p.currentIs(token.EOF) {
@@ -2375,6 +2376,12 @@ func (p *Parser) parseCreateIndex(pos token.Position) *ast.CreateIndexQuery {
23752376
if p.currentIs(token.RPAREN) {
23762377
p.nextToken() // skip )
23772378
}
2379+
} else if !p.currentIs(token.SEMICOLON) && !p.currentIs(token.EOF) &&
2380+
!(p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "TYPE") {
2381+
// Expression directly after table name without parentheses
2382+
// e.g., CREATE INDEX idx ON tbl date(ts) TYPE MinMax
2383+
col := p.parseExpression(0)
2384+
query.Columns = append(query.Columns, col)
23782385
}
23792386

23802387
// Parse TYPE clause
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt3": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)