Skip to content

Commit e2d74d1

Browse files
authored
Fix CASE expression alias handling and enable 2 passing tests (#46)
1 parent ab22e1a commit e2d74d1

File tree

9 files changed

+27
-10
lines changed

9 files changed

+27
-10
lines changed

ast/ast.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,7 @@ type CaseExpr struct {
920920
Whens []*WhenClause `json:"whens"`
921921
Else Expression `json:"else,omitempty"`
922922
Alias string `json:"alias,omitempty"`
923+
QuotedAlias bool `json:"quoted_alias,omitempty"` // true if alias was double-quoted
923924
}
924925

925926
func (c *CaseExpr) Pos() token.Position { return c.Position }

internal/explain/explain.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
6969
explainWithElement(sb, n, indent, depth)
7070
case *ast.Asterisk:
7171
explainAsterisk(sb, n, indent)
72+
case *ast.ColumnsMatcher:
73+
fmt.Fprintf(sb, "%sColumnsRegexpMatcher\n", indent)
7274

7375
// Functions
7476
case *ast.FunctionCall:

internal/explain/format.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func escapeStringLiteral(s string) string {
4747
case '\\':
4848
sb.WriteString("\\\\\\\\") // backslash becomes four backslashes (\\\\)
4949
case '\'':
50-
sb.WriteString("\\\\\\'") // single quote becomes \\\' (escaped backslash + escaped quote)
50+
sb.WriteString("\\\\\\'") // single quote becomes \\\' (three backslashes + quote)
5151
case '\n':
5252
sb.WriteString("\\\\n") // newline becomes \\n
5353
case '\t':

internal/explain/functions.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ func explainCastExprWithAlias(sb *strings.Builder, n *ast.CastExpr, alias string
139139
Node(sb, n.TypeExpr, depth+2)
140140
} else {
141141
typeStr := FormatDataType(n.Type)
142+
// Only escape if the DataType doesn't have parameters - this means the entire
143+
// type was parsed from a string literal and may contain unescaped quotes.
144+
// If it has parameters, FormatDataType already handles escaping.
145+
if n.Type == nil || len(n.Type.Parameters) == 0 {
146+
typeStr = escapeStringLiteral(typeStr)
147+
}
142148
fmt.Fprintf(sb, "%s Literal \\'%s\\'\n", indent, typeStr)
143149
}
144150
}
@@ -711,7 +717,12 @@ func explainIsNullExpr(sb *strings.Builder, n *ast.IsNullExpr, indent string, de
711717
}
712718

713719
func explainCaseExpr(sb *strings.Builder, n *ast.CaseExpr, indent string, depth int) {
714-
explainCaseExprWithAlias(sb, n, "", indent, depth)
720+
// Only output alias if it's unquoted (ClickHouse doesn't show quoted aliases)
721+
alias := ""
722+
if n.Alias != "" && !n.QuotedAlias {
723+
alias = n.Alias
724+
}
725+
explainCaseExprWithAlias(sb, n, alias, indent, depth)
715726
}
716727

717728
func explainCaseExprWithAlias(sb *strings.Builder, n *ast.CaseExpr, alias string, indent string, depth int) {

lexer/lexer.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ type Lexer struct {
2121

2222
// Item represents a lexical token with its value and position.
2323
type Item struct {
24-
Token token.Token
25-
Value string
26-
Pos token.Position
24+
Token token.Token
25+
Value string
26+
Pos token.Position
27+
Quoted bool // true if this identifier was double-quoted
2728
}
2829

2930
// New creates a new Lexer from an io.Reader.
@@ -453,7 +454,7 @@ func (l *Lexer) readQuotedIdentifier() Item {
453454
sb.WriteRune(l.ch)
454455
l.readChar()
455456
}
456-
return Item{Token: token.IDENT, Value: sb.String(), Pos: pos}
457+
return Item{Token: token.IDENT, Value: sb.String(), Pos: pos, Quoted: true}
457458
}
458459

459460
// readUnicodeString reads a string enclosed in Unicode curly quotes (' or ')
@@ -497,7 +498,7 @@ func (l *Lexer) readUnicodeQuotedIdentifier(openQuote rune) Item {
497498
if l.ch == closeQuote {
498499
l.readChar() // skip closing quote
499500
}
500-
return Item{Token: token.IDENT, Value: sb.String(), Pos: pos}
501+
return Item{Token: token.IDENT, Value: sb.String(), Pos: pos, Quoted: true}
501502
}
502503

503504
func (l *Lexer) readBacktickIdentifier() Item {

parser/expression.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,7 @@ func (p *Parser) parseCase() ast.Expression {
969969
p.nextToken()
970970
if p.currentIs(token.IDENT) {
971971
expr.Alias = p.current.Value
972+
expr.QuotedAlias = p.current.Quoted
972973
p.nextToken()
973974
}
974975
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}

parser/testdata/02244_casewithexpression_return_type/ast.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@
8989
"value": 555555
9090
}
9191
},
92-
"alias": "LONG_COL_0"
92+
"alias": "LONG_COL_0",
93+
"quoted_alias": true
9394
}
9495
],
9596
"from": {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}

0 commit comments

Comments
 (0)