Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ type SelectQuery struct {
Qualify Expression `json:"qualify,omitempty"`
Window []*WindowDefinition `json:"window,omitempty"`
OrderBy []*OrderByElement `json:"order_by,omitempty"`
Limit Expression `json:"limit,omitempty"`
Offset Expression `json:"offset,omitempty"`
Limit Expression `json:"limit,omitempty"`
LimitBy []Expression `json:"limit_by,omitempty"`
LimitByHasLimit bool `json:"limit_by_has_limit,omitempty"` // true if LIMIT BY was followed by another LIMIT
Offset Expression `json:"offset,omitempty"`
Settings []*SettingExpr `json:"settings,omitempty"`
IntoOutfile *IntoOutfileClause `json:"into_outfile,omitempty"`
Format *Identifier `json:"format,omitempty"`
Expand Down Expand Up @@ -1067,11 +1069,12 @@ func (i *IsNullExpr) expressionNode() {}

// LikeExpr represents a LIKE or ILIKE expression.
type LikeExpr struct {
Position token.Position `json:"-"`
Expr Expression `json:"expr"`
Not bool `json:"not,omitempty"`
CaseInsensitive bool `json:"case_insensitive,omitempty"` // true for ILIKE
Pattern Expression `json:"pattern"`
Position token.Position `json:"-"`
Expr Expression `json:"expr"`
Not bool `json:"not,omitempty"`
CaseInsensitive bool `json:"case_insensitive,omitempty"` // true for ILIKE
Pattern Expression `json:"pattern"`
Alias string `json:"alias,omitempty"`
}

func (l *LikeExpr) Pos() token.Position { return l.Position }
Expand Down
14 changes: 9 additions & 5 deletions internal/explain/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ func FormatFloat(val float64) string {
if math.IsNaN(val) {
return "nan"
}
// Use scientific notation for extremely small numbers (< 1e-10)
// This matches ClickHouse's behavior where numbers like 0.000001 stay decimal
// but extremely small numbers like 1e-38 use scientific notation
// Use scientific notation for very small numbers (< 1e-6)
// This matches ClickHouse's behavior where numbers like 0.0000001 (-1e-7)
// are displayed in scientific notation
absVal := math.Abs(val)
if absVal > 0 && absVal < 1e-10 {
return strconv.FormatFloat(val, 'e', -1, 64)
if absVal > 0 && absVal < 1e-6 {
s := strconv.FormatFloat(val, 'e', -1, 64)
// Remove leading zeros from exponent (e-07 -> e-7)
s = strings.Replace(s, "e-0", "e-", 1)
s = strings.Replace(s, "e+0", "e+", 1)
return s
}
// Use decimal notation for normal-sized numbers
return strconv.FormatFloat(val, 'f', -1, 64)
Expand Down
6 changes: 5 additions & 1 deletion internal/explain/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,11 @@ func explainLikeExpr(sb *strings.Builder, n *ast.LikeExpr, indent string, depth
if n.Not {
fnName = "not" + strings.Title(fnName)
}
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
if n.Alias != "" {
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
} else {
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
}
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
Node(sb, n.Expr, depth+2)
Node(sb, n.Pattern, depth+2)
Expand Down
20 changes: 20 additions & 0 deletions internal/explain/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ func explainSelectQuery(sb *strings.Builder, n *ast.SelectQuery, indent string,
if n.Limit != nil {
Node(sb, n.Limit, depth+1)
}
// LIMIT BY - only output when there's no ORDER BY and no second LIMIT (matches ClickHouse behavior)
if len(n.LimitBy) > 0 && len(n.OrderBy) == 0 && !n.LimitByHasLimit {
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.LimitBy))
for _, expr := range n.LimitBy {
Node(sb, expr, depth+2)
}
}
// SETTINGS - output here if there's no FORMAT, otherwise it's at SelectWithUnionQuery level
if len(n.Settings) > 0 && n.Format == nil {
fmt.Fprintf(sb, "%s Set\n", indent)
Expand Down Expand Up @@ -195,6 +202,16 @@ func isComplexExpr(expr ast.Expression) bool {
}
}

// hasOnlyLiterals checks if all expressions in a slice are literals
func hasOnlyLiterals(exprs []ast.Expression) bool {
for _, expr := range exprs {
if _, ok := expr.(*ast.Literal); !ok {
return false
}
}
return true
}

func countSelectUnionChildren(n *ast.SelectWithUnionQuery) int {
count := 1 // ExpressionList of selects
// Check if any SelectQuery has IntoOutfile set
Expand Down Expand Up @@ -259,6 +276,9 @@ func countSelectQueryChildren(n *ast.SelectQuery) int {
if n.Limit != nil {
count++
}
if len(n.LimitBy) > 0 && len(n.OrderBy) == 0 && !n.LimitByHasLimit {
count++
}
if n.Offset != nil {
count++
}
Expand Down
30 changes: 30 additions & 0 deletions parser/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,33 @@ func (p *Parser) parseIdentifierOrFunction() ast.Expression {
name := p.current.Value
p.nextToken()

// Check for MySQL-style @@variable syntax (system variables)
// Convert to globalVariable('varname') function call with alias @@varname
if strings.HasPrefix(name, "@@") {
varName := name[2:] // Strip @@
// Handle @@session.var or @@global.var
if p.currentIs(token.DOT) {
p.nextToken()
if p.currentIs(token.IDENT) || p.current.Token.IsKeyword() {
varName = varName + "." + p.current.Value
name = name + "." + p.current.Value
p.nextToken()
}
}
return &ast.FunctionCall{
Position: pos,
Name: "globalVariable",
Alias: name,
Arguments: []ast.Expression{
&ast.Literal{
Position: pos,
Type: "String",
Value: varName,
},
},
}
}

// Check for function call
if p.currentIs(token.LPAREN) {
return p.parseFunctionCall(name, pos)
Expand Down Expand Up @@ -1591,6 +1618,9 @@ func (p *Parser) parseAlias(left ast.Expression) ast.Expression {
case *ast.ExtractExpr:
e.Alias = alias
return e
case *ast.LikeExpr:
e.Alias = alias
return e
default:
return &ast.AliasedExpr{
Position: left.Pos(),
Expand Down
6 changes: 4 additions & 2 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,9 +444,10 @@ func (p *Parser) parseSelect() *ast.SelectQuery {
// LIMIT BY clause (ClickHouse specific: LIMIT n BY expr1, expr2, ...)
if p.currentIs(token.BY) {
p.nextToken()
// Parse LIMIT BY expressions - skip them for now
// Parse LIMIT BY expressions
for !p.isEndOfExpression() {
p.parseExpression(LOWEST)
expr := p.parseExpression(LOWEST)
sel.LimitBy = append(sel.LimitBy, expr)
if p.currentIs(token.COMMA) {
p.nextToken()
} else {
Expand All @@ -457,6 +458,7 @@ func (p *Parser) parseSelect() *ast.SelectQuery {
if p.currentIs(token.LIMIT) {
p.nextToken()
sel.Limit = p.parseExpression(LOWEST)
sel.LimitByHasLimit = true
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,15 @@
"limit": {
"type": "Integer",
"value": 10
}
},
"limit_by": [
{
"parts": [
"Title"
]
}
],
"limit_by_has_limit": true
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion parser/testdata/00583_limit_by_expressions/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"todo": true}
{}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"todo": true}
{}
2 changes: 1 addition & 1 deletion parser/testdata/01337_mysql_global_variables/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"todo": true}
{}
2 changes: 1 addition & 1 deletion parser/testdata/02045_like_function/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"todo": true}
{}
9 changes: 8 additions & 1 deletion parser/testdata/02281_limit_by_distributed/ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,14 @@
"limit": {
"type": "Integer",
"value": 1
}
},
"limit_by": [
{
"parts": [
"k"
]
}
]
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion parser/testdata/03213_rand_dos/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"todo": true}
{}
9 changes: 8 additions & 1 deletion parser/testdata/03366_with_fill_dag/ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@
"limit": {
"type": "Integer",
"value": 1
}
},
"limit_by": [
{
"parts": [
"number"
]
}
]
}
]
}