Skip to content

Commit 16db47c

Browse files
authored
Fix 23 todo tests: BETWEEN transformation and float literal parsing (#25)
1 parent 40df19b commit 16db47c

File tree

149 files changed

+4747
-170
lines changed

Some content is hidden

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

149 files changed

+4747
-170
lines changed

ast/ast.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,7 @@ type CastExpr struct {
911911
TypeExpr Expression `json:"type_expr,omitempty"` // For dynamic type like CAST(x, if(cond, 'Type1', 'Type2'))
912912
Alias string `json:"alias,omitempty"`
913913
OperatorSyntax bool `json:"operator_syntax,omitempty"` // true if using :: syntax
914+
UsedASSyntax bool `json:"-"` // true if CAST(x AS Type) syntax used (not CAST(x, 'Type'))
914915
}
915916

916917
func (c *CastExpr) Pos() token.Position { return c.Position }

internal/explain/expressions.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,11 @@ func explainUnaryExpr(sb *strings.Builder, n *ast.UnaryExpr, indent string, dept
198198

199199
func explainSubquery(sb *strings.Builder, n *ast.Subquery, indent string, depth int) {
200200
children := 1
201-
fmt.Fprintf(sb, "%sSubquery (children %d)\n", indent, children)
201+
if n.Alias != "" {
202+
fmt.Fprintf(sb, "%sSubquery (alias %s) (children %d)\n", indent, n.Alias, children)
203+
} else {
204+
fmt.Fprintf(sb, "%sSubquery (children %d)\n", indent, children)
205+
}
202206
Node(sb, n.Query, depth+1)
203207
}
204208

@@ -270,8 +274,12 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
270274
Node(sb, e.Then, depth+2)
271275
Node(sb, e.Else, depth+2)
272276
case *ast.CastExpr:
273-
// CAST expressions - ClickHouse doesn't show aliases on CAST in EXPLAIN AST
274-
explainCastExpr(sb, e, indent, depth)
277+
// CAST expressions - show alias only for CAST(x AS Type) syntax, not CAST(x, 'Type')
278+
if e.UsedASSyntax {
279+
explainCastExprWithAlias(sb, e, n.Alias, indent, depth)
280+
} else {
281+
explainCastExpr(sb, e, indent, depth)
282+
}
275283
case *ast.ArrayAccess:
276284
// Array access - ClickHouse doesn't show aliases on arrayElement in EXPLAIN AST
277285
explainArrayAccess(sb, e, indent, depth)

internal/explain/format.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package explain
22

33
import (
44
"fmt"
5+
"math"
56
"strconv"
67
"strings"
78

@@ -10,6 +11,16 @@ import (
1011

1112
// FormatFloat formats a float value for EXPLAIN AST output
1213
func FormatFloat(val float64) string {
14+
// Handle special float values - ClickHouse uses lowercase
15+
if math.IsInf(val, 1) {
16+
return "inf"
17+
}
18+
if math.IsInf(val, -1) {
19+
return "-inf"
20+
}
21+
if math.IsNaN(val) {
22+
return "nan"
23+
}
1324
// Use 'f' format to avoid scientific notation, -1 precision for smallest representation
1425
return strconv.FormatFloat(val, 'f', -1, 64)
1526
}
@@ -142,6 +153,21 @@ func formatTupleLiteral(val interface{}) string {
142153
return fmt.Sprintf("Tuple_(%s)", strings.Join(parts, ", "))
143154
}
144155

156+
// formatInListAsTuple formats an IN expression's value list as a tuple literal
157+
func formatInListAsTuple(list []ast.Expression) string {
158+
var parts []string
159+
for _, e := range list {
160+
if lit, ok := e.(*ast.Literal); ok {
161+
parts = append(parts, FormatLiteral(lit))
162+
} else if ident, ok := e.(*ast.Identifier); ok {
163+
parts = append(parts, ident.Name())
164+
} else {
165+
parts = append(parts, formatExprAsString(e))
166+
}
167+
}
168+
return fmt.Sprintf("Tuple_(%s)", strings.Join(parts, ", "))
169+
}
170+
145171
// FormatDataType formats a DataType for EXPLAIN AST output
146172
func FormatDataType(dt *ast.DataType) string {
147173
if dt == nil {
@@ -161,6 +187,9 @@ func FormatDataType(dt *ast.DataType) string {
161187
}
162188
} else if nested, ok := p.(*ast.DataType); ok {
163189
params = append(params, FormatDataType(nested))
190+
} else if ntp, ok := p.(*ast.NameTypePair); ok {
191+
// Named tuple field: "name Type"
192+
params = append(params, ntp.Name+" "+FormatDataType(ntp.Type))
164193
} else {
165194
params = append(params, fmt.Sprintf("%v", p))
166195
}

internal/explain/functions.go

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,19 @@ func explainCastExpr(sb *strings.Builder, n *ast.CastExpr, indent string, depth
8383
}
8484

8585
func explainCastExprWithAlias(sb *strings.Builder, n *ast.CastExpr, alias string, indent string, depth int) {
86+
// For :: operator syntax, ClickHouse hides alias only when expression is
87+
// an array/tuple with complex content that gets formatted as string
88+
hideAlias := false
89+
if n.OperatorSyntax {
90+
if lit, ok := n.Expr.(*ast.Literal); ok {
91+
if lit.Type == ast.LiteralArray || lit.Type == ast.LiteralTuple {
92+
hideAlias = !containsOnlyPrimitives(lit)
93+
}
94+
}
95+
}
96+
8697
// CAST is represented as Function CAST with expr and type as arguments
87-
if alias != "" {
98+
if alias != "" && !hideAlias {
8899
fmt.Fprintf(sb, "%sFunction CAST (alias %s) (children %d)\n", indent, alias, 1)
89100
} else {
90101
fmt.Fprintf(sb, "%sFunction CAST (children %d)\n", indent, 1)
@@ -205,6 +216,18 @@ func explainArrayAccess(sb *strings.Builder, n *ast.ArrayAccess, indent string,
205216
Node(sb, n.Index, depth+2)
206217
}
207218

219+
func explainArrayAccessWithAlias(sb *strings.Builder, n *ast.ArrayAccess, alias string, indent string, depth int) {
220+
// Array access is represented as Function arrayElement
221+
if alias != "" {
222+
fmt.Fprintf(sb, "%sFunction arrayElement (alias %s) (children %d)\n", indent, alias, 1)
223+
} else {
224+
fmt.Fprintf(sb, "%sFunction arrayElement (children %d)\n", indent, 1)
225+
}
226+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
227+
Node(sb, n.Array, depth+2)
228+
Node(sb, n.Index, depth+2)
229+
}
230+
208231
func explainTupleAccess(sb *strings.Builder, n *ast.TupleAccess, indent string, depth int) {
209232
// Tuple access is represented as Function tupleElement
210233
fmt.Fprintf(sb, "%sFunction tupleElement (children %d)\n", indent, 1)
@@ -213,6 +236,18 @@ func explainTupleAccess(sb *strings.Builder, n *ast.TupleAccess, indent string,
213236
Node(sb, n.Index, depth+2)
214237
}
215238

239+
func explainTupleAccessWithAlias(sb *strings.Builder, n *ast.TupleAccess, alias string, indent string, depth int) {
240+
// Tuple access is represented as Function tupleElement
241+
if alias != "" {
242+
fmt.Fprintf(sb, "%sFunction tupleElement (alias %s) (children %d)\n", indent, alias, 1)
243+
} else {
244+
fmt.Fprintf(sb, "%sFunction tupleElement (children %d)\n", indent, 1)
245+
}
246+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
247+
Node(sb, n.Tuple, depth+2)
248+
Node(sb, n.Index, depth+2)
249+
}
250+
216251
func explainLikeExpr(sb *strings.Builder, n *ast.LikeExpr, indent string, depth int) {
217252
// LIKE is represented as Function like
218253
fnName := "like"
@@ -229,17 +264,37 @@ func explainLikeExpr(sb *strings.Builder, n *ast.LikeExpr, indent string, depth
229264
}
230265

231266
func explainBetweenExpr(sb *strings.Builder, n *ast.BetweenExpr, indent string, depth int) {
232-
// BETWEEN is represented as Function and with two comparisons
233-
// But for explain, we can use a simpler form
234-
fnName := "between"
235267
if n.Not {
236-
fnName = "notBetween"
268+
// NOT BETWEEN is transformed to: expr < low OR expr > high
269+
// Represented as: Function or with two comparisons: less and greater
270+
fmt.Fprintf(sb, "%sFunction or (children %d)\n", indent, 1)
271+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
272+
// less(expr, low)
273+
fmt.Fprintf(sb, "%s Function less (children %d)\n", indent, 1)
274+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
275+
Node(sb, n.Expr, depth+4)
276+
Node(sb, n.Low, depth+4)
277+
// greater(expr, high)
278+
fmt.Fprintf(sb, "%s Function greater (children %d)\n", indent, 1)
279+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
280+
Node(sb, n.Expr, depth+4)
281+
Node(sb, n.High, depth+4)
282+
} else {
283+
// BETWEEN is represented as Function and with two comparisons
284+
// expr >= low AND expr <= high
285+
fmt.Fprintf(sb, "%sFunction and (children %d)\n", indent, 1)
286+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
287+
// greaterOrEquals(expr, low)
288+
fmt.Fprintf(sb, "%s Function greaterOrEquals (children %d)\n", indent, 1)
289+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
290+
Node(sb, n.Expr, depth+4)
291+
Node(sb, n.Low, depth+4)
292+
// lessOrEquals(expr, high)
293+
fmt.Fprintf(sb, "%s Function lessOrEquals (children %d)\n", indent, 1)
294+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
295+
Node(sb, n.Expr, depth+4)
296+
Node(sb, n.High, depth+4)
237297
}
238-
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
239-
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 3)
240-
Node(sb, n.Expr, depth+2)
241-
Node(sb, n.Low, depth+2)
242-
Node(sb, n.High, depth+2)
243298
}
244299

245300
func explainIsNullExpr(sb *strings.Builder, n *ast.IsNullExpr, indent string, depth int) {
@@ -325,6 +380,7 @@ func explainExtractExpr(sb *strings.Builder, n *ast.ExtractExpr, indent string,
325380
func explainWindowSpec(sb *strings.Builder, n *ast.WindowSpec, indent string, depth int) {
326381
// Window spec is represented as WindowDefinition
327382
// For simple cases like OVER (), just output WindowDefinition without children
383+
// Note: ClickHouse's EXPLAIN AST does not output frame info (ROWS BETWEEN etc)
328384
children := 0
329385
if n.Name != "" {
330386
children++
@@ -335,9 +391,6 @@ func explainWindowSpec(sb *strings.Builder, n *ast.WindowSpec, indent string, de
335391
if len(n.OrderBy) > 0 {
336392
children++
337393
}
338-
if n.Frame != nil {
339-
children++
340-
}
341394
if children > 0 {
342395
fmt.Fprintf(sb, "%sWindowDefinition (children %d)\n", indent, children)
343396
if n.Name != "" {
@@ -352,7 +405,7 @@ func explainWindowSpec(sb *strings.Builder, n *ast.WindowSpec, indent string, de
352405
if len(n.OrderBy) > 0 {
353406
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.OrderBy))
354407
for _, o := range n.OrderBy {
355-
Node(sb, o.Expression, depth+2)
408+
explainOrderByElement(sb, o, strings.Repeat(" ", depth+2), depth+2)
356409
}
357410
}
358411
// Frame handling would go here if needed

internal/explain/statements.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,12 @@ func explainExplainQuery(sb *strings.Builder, n *ast.ExplainQuery, indent string
316316
}
317317

318318
func explainShowQuery(sb *strings.Builder, n *ast.ShowQuery, indent string) {
319-
// Capitalize ShowType correctly for display
319+
// ClickHouse maps certain SHOW types to ShowTables in EXPLAIN AST
320320
showType := strings.Title(strings.ToLower(string(n.ShowType)))
321+
// SHOW SETTINGS is displayed as ShowTables in ClickHouse
322+
if showType == "Settings" {
323+
showType = "Tables"
324+
}
321325
fmt.Fprintf(sb, "%sShow%s\n", indent, showType)
322326
}
323327

@@ -327,13 +331,14 @@ func explainUseQuery(sb *strings.Builder, n *ast.UseQuery, indent string) {
327331

328332
func explainDescribeQuery(sb *strings.Builder, n *ast.DescribeQuery, indent string) {
329333
if n.TableFunction != nil {
330-
// DESCRIBE on a table function
334+
// DESCRIBE on a table function - wrap in TableExpression
331335
children := 1
332336
if len(n.Settings) > 0 {
333337
children++
334338
}
335339
fmt.Fprintf(sb, "%sDescribeQuery (children %d)\n", indent, children)
336-
explainFunctionCall(sb, n.TableFunction, indent+" ", 1)
340+
fmt.Fprintf(sb, "%s TableExpression (children 1)\n", indent)
341+
explainFunctionCall(sb, n.TableFunction, indent+" ", 2)
337342
if len(n.Settings) > 0 {
338343
fmt.Fprintf(sb, "%s Set\n", indent)
339344
}

lexer/lexer.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -594,16 +594,21 @@ func (l *Lexer) readNumber() Item {
594594
}
595595
}
596596

597-
// Check for decimal point
598-
if l.ch == '.' && unicode.IsDigit(l.peekChar()) {
599-
sb.WriteRune(l.ch)
600-
l.readChar()
601-
for unicode.IsDigit(l.ch) {
597+
// Check for decimal point (either followed by digit or end of number like 1.)
598+
if l.ch == '.' {
599+
nextCh := l.peekChar()
600+
// Allow 1. (trailing dot with no digits) and 1.5 (dot with digits)
601+
// But not 1.something (identifier-like)
602+
if unicode.IsDigit(nextCh) || (!isIdentStart(nextCh) && nextCh != '.') {
602603
sb.WriteRune(l.ch)
603604
l.readChar()
604-
// Handle underscore separators
605-
for l.ch == '_' && unicode.IsDigit(l.peekChar()) {
605+
for unicode.IsDigit(l.ch) {
606+
sb.WriteRune(l.ch)
606607
l.readChar()
608+
// Handle underscore separators
609+
for l.ch == '_' && unicode.IsDigit(l.peekChar()) {
610+
l.readChar()
611+
}
607612
}
608613
}
609614
}
@@ -675,15 +680,20 @@ func (l *Lexer) readNumberOrIdent() Item {
675680
}
676681
}
677682

678-
// Check for decimal point
679-
if l.ch == '.' && unicode.IsDigit(l.peekChar()) {
680-
sb.WriteRune(l.ch)
681-
l.readChar()
682-
for unicode.IsDigit(l.ch) {
683+
// Check for decimal point (either followed by digit or end of number like 1.)
684+
if l.ch == '.' {
685+
nextCh := l.peekChar()
686+
// Allow 1. (trailing dot with no digits) and 1.5 (dot with digits)
687+
// But not 1.something (identifier-like)
688+
if unicode.IsDigit(nextCh) || (!isIdentStart(nextCh) && nextCh != '.') {
683689
sb.WriteRune(l.ch)
684690
l.readChar()
685-
for l.ch == '_' && unicode.IsDigit(l.peekChar()) {
691+
for unicode.IsDigit(l.ch) {
692+
sb.WriteRune(l.ch)
686693
l.readChar()
694+
for l.ch == '_' && unicode.IsDigit(l.peekChar()) {
695+
l.readChar()
696+
}
687697
}
688698
}
689699
}

parser/expression.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,7 @@ func (p *Parser) parseCast() ast.Expression {
958958
if p.currentIs(token.AS) {
959959
p.nextToken()
960960
expr.Type = p.parseDataType()
961+
expr.UsedASSyntax = true
961962
} else if p.currentIs(token.COMMA) {
962963
p.nextToken()
963964
// Type can be given as a string literal or an expression (e.g., if(cond, 'Type1', 'Type2'))
@@ -1564,6 +1565,18 @@ func (p *Parser) parseAlias(left ast.Expression) ast.Expression {
15641565
case *ast.Subquery:
15651566
e.Alias = alias
15661567
return e
1568+
case *ast.CastExpr:
1569+
// For :: operator syntax, set alias directly on CastExpr
1570+
// For function-style CAST(), wrap in AliasedExpr
1571+
if e.OperatorSyntax {
1572+
e.Alias = alias
1573+
return e
1574+
}
1575+
return &ast.AliasedExpr{
1576+
Position: left.Pos(),
1577+
Expr: left,
1578+
Alias: alias,
1579+
}
15671580
case *ast.CaseExpr:
15681581
e.Alias = alias
15691582
return e

0 commit comments

Comments
 (0)