Skip to content

Commit 918155d

Browse files
authored
Add alias support in CAST, SUBSTRING, and TRIM functions (#107)
1 parent f1157ad commit 918155d

File tree

472 files changed

+1269
-3010
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

472 files changed

+1269
-3010
lines changed

ast/ast.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type SelectQuery struct {
7373
OrderBy []*OrderByElement `json:"order_by,omitempty"`
7474
Limit Expression `json:"limit,omitempty"`
7575
LimitBy []Expression `json:"limit_by,omitempty"`
76+
LimitByLimit Expression `json:"limit_by_limit,omitempty"` // LIMIT value before BY (e.g., LIMIT 1 BY x LIMIT 3)
7677
LimitByHasLimit bool `json:"limit_by_has_limit,omitempty"` // true if LIMIT BY was followed by another LIMIT
7778
Offset Expression `json:"offset,omitempty"`
7879
Settings []*SettingExpr `json:"settings,omitempty"`
@@ -126,9 +127,10 @@ func (t *TablesInSelectQuery) End() token.Position { return t.Position }
126127

127128
// TablesInSelectQueryElement represents a single table element in a SELECT.
128129
type TablesInSelectQueryElement struct {
129-
Position token.Position `json:"-"`
130-
Table *TableExpression `json:"table"`
131-
Join *TableJoin `json:"join,omitempty"`
130+
Position token.Position `json:"-"`
131+
Table *TableExpression `json:"table,omitempty"`
132+
Join *TableJoin `json:"join,omitempty"`
133+
ArrayJoin *ArrayJoinClause `json:"array_join,omitempty"` // For ARRAY JOIN as table element
132134
}
133135

134136
func (t *TablesInSelectQueryElement) Pos() token.Position { return t.Position }
@@ -225,6 +227,7 @@ type InsertQuery struct {
225227
Table string `json:"table,omitempty"`
226228
Function *FunctionCall `json:"function,omitempty"` // For INSERT INTO FUNCTION syntax
227229
Columns []*Identifier `json:"columns,omitempty"`
230+
AllColumns bool `json:"all_columns,omitempty"` // For (*) syntax meaning all columns
228231
PartitionBy Expression `json:"partition_by,omitempty"` // For PARTITION BY clause
229232
Infile string `json:"infile,omitempty"` // For FROM INFILE clause
230233
Compression string `json:"compression,omitempty"` // For COMPRESSION clause
@@ -271,6 +274,7 @@ type CreateQuery struct {
271274
CreateUser bool `json:"create_user,omitempty"`
272275
AlterUser bool `json:"alter_user,omitempty"`
273276
HasAuthenticationData bool `json:"has_authentication_data,omitempty"`
277+
AuthenticationValues []string `json:"authentication_values,omitempty"` // Password/hash values from IDENTIFIED BY
274278
CreateDictionary bool `json:"create_dictionary,omitempty"`
275279
DictionaryAttrs []*DictionaryAttributeDeclaration `json:"dictionary_attrs,omitempty"`
276280
DictionaryDef *DictionaryDefinition `json:"dictionary_def,omitempty"`
@@ -531,6 +535,7 @@ type AlterCommand struct {
531535
ProjectionName string `json:"projection_name,omitempty"` // For DROP/MATERIALIZE/CLEAR PROJECTION
532536
StatisticsColumns []string `json:"statistics_columns,omitempty"` // For ADD/DROP/CLEAR/MATERIALIZE STATISTICS
533537
StatisticsTypes []*FunctionCall `json:"statistics_types,omitempty"` // For ADD/MODIFY STATISTICS TYPE
538+
Comment string `json:"comment,omitempty"` // For COMMENT COLUMN
534539
}
535540

536541
// Projection represents a projection definition.
@@ -698,11 +703,12 @@ const (
698703

699704
// ExplainQuery represents an EXPLAIN statement.
700705
type ExplainQuery struct {
701-
Position token.Position `json:"-"`
702-
ExplainType ExplainType `json:"explain_type"`
703-
Statement Statement `json:"statement"`
704-
HasSettings bool `json:"has_settings,omitempty"`
705-
ExplicitType bool `json:"explicit_type,omitempty"` // true if type was explicitly specified
706+
Position token.Position `json:"-"`
707+
ExplainType ExplainType `json:"explain_type"`
708+
Statement Statement `json:"statement"`
709+
HasSettings bool `json:"has_settings,omitempty"`
710+
ExplicitType bool `json:"explicit_type,omitempty"` // true if type was explicitly specified
711+
OptionsString string `json:"options_string,omitempty"` // Formatted options like "actions = 1"
706712
}
707713

708714
func (e *ExplainQuery) Pos() token.Position { return e.Position }
@@ -739,6 +745,7 @@ type OptimizeQuery struct {
739745
Table string `json:"table"`
740746
Partition Expression `json:"partition,omitempty"`
741747
Final bool `json:"final,omitempty"`
748+
Cleanup bool `json:"cleanup,omitempty"`
742749
Dedupe bool `json:"dedupe,omitempty"`
743750
OnCluster string `json:"on_cluster,omitempty"`
744751
}
@@ -772,6 +779,17 @@ func (s *SystemQuery) Pos() token.Position { return s.Position }
772779
func (s *SystemQuery) End() token.Position { return s.Position }
773780
func (s *SystemQuery) statementNode() {}
774781

782+
// TransactionControlQuery represents a transaction control statement (BEGIN, COMMIT, ROLLBACK, SET TRANSACTION SNAPSHOT).
783+
type TransactionControlQuery struct {
784+
Position token.Position `json:"-"`
785+
Action string `json:"action"` // "BEGIN", "COMMIT", "ROLLBACK", "SET_SNAPSHOT"
786+
Snapshot int64 `json:"snapshot,omitempty"`
787+
}
788+
789+
func (t *TransactionControlQuery) Pos() token.Position { return t.Position }
790+
func (t *TransactionControlQuery) End() token.Position { return t.Position }
791+
func (t *TransactionControlQuery) statementNode() {}
792+
775793
// RenamePair represents a single rename pair in RENAME TABLE.
776794
type RenamePair struct {
777795
FromDatabase string `json:"from_database,omitempty"`
@@ -1064,6 +1082,7 @@ type Literal struct {
10641082
Position token.Position `json:"-"`
10651083
Type LiteralType `json:"type"`
10661084
Value interface{} `json:"value"`
1085+
Source string `json:"source,omitempty"` // Original source text (for preserving 0.0 vs 0)
10671086
Negative bool `json:"negative,omitempty"` // True if literal was explicitly negative (for -0)
10681087
}
10691088

@@ -1126,6 +1145,7 @@ type Asterisk struct {
11261145
Table string `json:"table,omitempty"` // for table.*
11271146
Except []string `json:"except,omitempty"` // for * EXCEPT (col1, col2)
11281147
Replace []*ReplaceExpr `json:"replace,omitempty"` // for * REPLACE (expr AS col)
1148+
Apply []string `json:"apply,omitempty"` // for * APPLY (func1) APPLY(func2)
11291149
}
11301150

11311151
func (a *Asterisk) Pos() token.Position { return a.Position }

internal/explain/explain.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
121121
explainSetQuery(sb, indent)
122122
case *ast.SystemQuery:
123123
explainSystemQuery(sb, n, indent)
124+
case *ast.TransactionControlQuery:
125+
fmt.Fprintf(sb, "%sASTTransactionControl\n", indent)
124126
case *ast.ExplainQuery:
125127
explainExplainQuery(sb, n, indent, depth)
126128
case *ast.ShowQuery:

internal/explain/expressions.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,8 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
529529
}
530530

531531
func explainAsterisk(sb *strings.Builder, n *ast.Asterisk, indent string, depth int) {
532-
// Check if there are any column transformers (EXCEPT, REPLACE)
533-
hasTransformers := len(n.Except) > 0 || len(n.Replace) > 0
532+
// Check if there are any column transformers (EXCEPT, REPLACE, APPLY)
533+
hasTransformers := len(n.Except) > 0 || len(n.Replace) > 0 || len(n.Apply) > 0
534534

535535
if n.Table != "" {
536536
if hasTransformers {
@@ -559,6 +559,8 @@ func explainColumnsTransformers(sb *strings.Builder, n *ast.Asterisk, indent str
559559
if len(n.Replace) > 0 {
560560
transformerCount++
561561
}
562+
// Each APPLY adds one transformer
563+
transformerCount += len(n.Apply)
562564

563565
fmt.Fprintf(sb, "%sColumnsTransformerList (children %d)\n", indent, transformerCount)
564566

@@ -583,6 +585,11 @@ func explainColumnsTransformers(sb *strings.Builder, n *ast.Asterisk, indent str
583585
}
584586
}
585587
}
588+
589+
// Each APPLY function gets its own ColumnsApplyTransformer
590+
for range n.Apply {
591+
fmt.Fprintf(sb, "%s ColumnsApplyTransformer\n", indent)
592+
}
586593
}
587594

588595
func explainColumnsMatcher(sb *strings.Builder, n *ast.ColumnsMatcher, indent string, depth int) {

internal/explain/format.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ func formatBinaryExprForType(expr *ast.BinaryExpr) string {
277277
func NormalizeFunctionName(name string) string {
278278
// ClickHouse normalizes certain function names in EXPLAIN AST
279279
normalized := map[string]string{
280+
"trim": "trimBoth",
280281
"ltrim": "trimLeft",
281282
"rtrim": "trimRight",
282283
"lcase": "lower",
@@ -374,6 +375,10 @@ func formatExprAsString(expr ast.Expression) string {
374375
}
375376
return fmt.Sprintf("%d", e.Value)
376377
case ast.LiteralFloat:
378+
// Use Source field if available to preserve original representation (e.g., "0.0")
379+
if e.Source != "" {
380+
return e.Source
381+
}
377382
if e.Negative {
378383
switch v := e.Value.(type) {
379384
case float64:

internal/explain/select.go

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,26 @@ import (
99

1010
func explainSelectIntersectExceptQuery(sb *strings.Builder, n *ast.SelectIntersectExceptQuery, indent string, depth int) {
1111
fmt.Fprintf(sb, "%sSelectIntersectExceptQuery (children %d)\n", indent, len(n.Selects))
12-
for _, sel := range n.Selects {
13-
Node(sb, sel, depth+1)
12+
13+
// ClickHouse wraps first operand in SelectWithUnionQuery when EXCEPT is present
14+
hasExcept := false
15+
for _, op := range n.Operators {
16+
if op == "EXCEPT" {
17+
hasExcept = true
18+
break
19+
}
20+
}
21+
22+
childIndent := strings.Repeat(" ", depth+1)
23+
for i, sel := range n.Selects {
24+
if hasExcept && i == 0 {
25+
// Wrap first operand in SelectWithUnionQuery -> ExpressionList format
26+
fmt.Fprintf(sb, "%sSelectWithUnionQuery (children 1)\n", childIndent)
27+
fmt.Fprintf(sb, "%s ExpressionList (children 1)\n", childIndent)
28+
Node(sb, sel, depth+3)
29+
} else {
30+
Node(sb, sel, depth+1)
31+
}
1432
}
1533
}
1634

@@ -111,16 +129,31 @@ func explainSelectQuery(sb *strings.Builder, n *ast.SelectQuery, indent string,
111129
if n.Offset != nil {
112130
Node(sb, n.Offset, depth+1)
113131
}
114-
// LIMIT
115-
if n.Limit != nil {
116-
Node(sb, n.Limit, depth+1)
117-
}
118-
// LIMIT BY - only output when there's no ORDER BY and no second LIMIT (matches ClickHouse behavior)
119-
if len(n.LimitBy) > 0 && len(n.OrderBy) == 0 && !n.LimitByHasLimit {
132+
// LIMIT BY handling
133+
if n.LimitByLimit != nil {
134+
// Case: LIMIT n BY x LIMIT m -> output LimitByLimit, LimitBy, Limit
135+
Node(sb, n.LimitByLimit, depth+1)
136+
if len(n.LimitBy) > 0 {
137+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.LimitBy))
138+
for _, expr := range n.LimitBy {
139+
Node(sb, expr, depth+2)
140+
}
141+
}
142+
if n.Limit != nil {
143+
Node(sb, n.Limit, depth+1)
144+
}
145+
} else if len(n.LimitBy) > 0 {
146+
// Case: LIMIT n BY x (no second LIMIT) -> output Limit, then LimitBy
147+
if n.Limit != nil {
148+
Node(sb, n.Limit, depth+1)
149+
}
120150
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.LimitBy))
121151
for _, expr := range n.LimitBy {
122152
Node(sb, expr, depth+2)
123153
}
154+
} else if n.Limit != nil {
155+
// Case: plain LIMIT n (no BY)
156+
Node(sb, n.Limit, depth+1)
124157
}
125158
// SETTINGS is output at SelectQuery level only when NOT after FORMAT
126159
// When SettingsAfterFormat is true, it's output at SelectWithUnionQuery level instead
@@ -285,10 +318,13 @@ func countSelectQueryChildren(n *ast.SelectQuery) int {
285318
if len(n.OrderBy) > 0 {
286319
count++
287320
}
321+
if n.LimitByLimit != nil {
322+
count++ // LIMIT n in "LIMIT n BY x LIMIT m"
323+
}
288324
if n.Limit != nil {
289325
count++
290326
}
291-
if len(n.LimitBy) > 0 && len(n.OrderBy) == 0 && !n.LimitByHasLimit {
327+
if len(n.LimitBy) > 0 {
292328
count++
293329
}
294330
if n.Offset != nil {

0 commit comments

Comments
 (0)