Skip to content

Commit 18ec952

Browse files
kyleconroyclaude
andcommitted
Add LEFT and RIGHT string function support
- Add LeftFunctionCall AST type for LEFT(string, count) function - Add RightFunctionCall AST type for RIGHT(string, count) function - Add parsing support in parsePrimaryExpression for TokenLeft/TokenRight - Add parseLeftFunctionCall and parseRightFunctionCall functions - Add JSON marshaling for both function call types Note: ExpressionTests has additional parsing issues with nested UNION subqueries that need separate implementation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 53e4097 commit 18ec952

4 files changed

Lines changed: 124 additions & 0 deletions

File tree

ast/left_function_call.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ast
2+
3+
// LeftFunctionCall represents the LEFT(string, count) function
4+
type LeftFunctionCall struct {
5+
Parameters []ScalarExpression
6+
}
7+
8+
func (*LeftFunctionCall) node() {}
9+
func (*LeftFunctionCall) expression() {}
10+
func (*LeftFunctionCall) scalarExpression() {}

ast/right_function_call.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ast
2+
3+
// RightFunctionCall represents the RIGHT(string, count) function
4+
type RightFunctionCall struct {
5+
Parameters []ScalarExpression
6+
}
7+
8+
func (*RightFunctionCall) node() {}
9+
func (*RightFunctionCall) expression() {}
10+
func (*RightFunctionCall) scalarExpression() {}

parser/marshal.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,6 +2287,30 @@ func scalarExpressionToJSON(expr ast.ScalarExpression) jsonNode {
22872287
node["Increment"] = scalarExpressionToJSON(e.Increment)
22882288
}
22892289
return node
2290+
case *ast.LeftFunctionCall:
2291+
node := jsonNode{
2292+
"$type": "LeftFunctionCall",
2293+
}
2294+
if len(e.Parameters) > 0 {
2295+
params := make([]jsonNode, len(e.Parameters))
2296+
for i, p := range e.Parameters {
2297+
params[i] = scalarExpressionToJSON(p)
2298+
}
2299+
node["Parameters"] = params
2300+
}
2301+
return node
2302+
case *ast.RightFunctionCall:
2303+
node := jsonNode{
2304+
"$type": "RightFunctionCall",
2305+
}
2306+
if len(e.Parameters) > 0 {
2307+
params := make([]jsonNode, len(e.Parameters))
2308+
for i, p := range e.Parameters {
2309+
params[i] = scalarExpressionToJSON(p)
2310+
}
2311+
node["Parameters"] = params
2312+
}
2313+
return node
22902314
case *ast.AtTimeZoneCall:
22912315
node := jsonNode{
22922316
"$type": "AtTimeZoneCall",

parser/parse_select.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,20 @@ func (p *Parser) parsePrimaryExpression() (ast.ScalarExpression, error) {
10721072
return &ast.UnaryExpression{UnaryExpressionType: "BitwiseNot", Expression: expr}, nil
10731073
}
10741074
return nil, fmt.Errorf("unexpected token in expression: %s", p.curTok.Literal)
1075+
case TokenLeft:
1076+
// LEFT can be a function name (string function)
1077+
if p.peekTok.Type == TokenLParen {
1078+
p.nextToken() // consume LEFT
1079+
return p.parseLeftFunctionCall()
1080+
}
1081+
return nil, fmt.Errorf("unexpected token in expression: %s", p.curTok.Literal)
1082+
case TokenRight:
1083+
// RIGHT can be a function name (string function)
1084+
if p.peekTok.Type == TokenLParen {
1085+
p.nextToken() // consume RIGHT
1086+
return p.parseRightFunctionCall()
1087+
}
1088+
return nil, fmt.Errorf("unexpected token in expression: %s", p.curTok.Literal)
10751089
case TokenIdent:
10761090
// Check if it's a global variable reference (starts with @@)
10771091
if strings.HasPrefix(p.curTok.Literal, "@@") {
@@ -5917,6 +5931,72 @@ func (p *Parser) parseIdentityFunctionCall() (ast.ScalarExpression, error) {
59175931
return identity, nil
59185932
}
59195933

5934+
// parseLeftFunctionCall parses LEFT(string, count)
5935+
func (p *Parser) parseLeftFunctionCall() (ast.ScalarExpression, error) {
5936+
// Already consumed LEFT, now on (
5937+
if p.curTok.Type != TokenLParen {
5938+
return nil, fmt.Errorf("expected ( after LEFT, got %s", p.curTok.Literal)
5939+
}
5940+
p.nextToken() // consume (
5941+
5942+
var params []ast.ScalarExpression
5943+
5944+
// Parse parameters
5945+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
5946+
param, err := p.parseScalarExpression()
5947+
if err != nil {
5948+
return nil, err
5949+
}
5950+
params = append(params, param)
5951+
5952+
if p.curTok.Type == TokenComma {
5953+
p.nextToken() // consume ,
5954+
} else {
5955+
break
5956+
}
5957+
}
5958+
5959+
if p.curTok.Type != TokenRParen {
5960+
return nil, fmt.Errorf("expected ) in LEFT function, got %s", p.curTok.Literal)
5961+
}
5962+
p.nextToken() // consume )
5963+
5964+
return &ast.LeftFunctionCall{Parameters: params}, nil
5965+
}
5966+
5967+
// parseRightFunctionCall parses RIGHT(string, count)
5968+
func (p *Parser) parseRightFunctionCall() (ast.ScalarExpression, error) {
5969+
// Already consumed RIGHT, now on (
5970+
if p.curTok.Type != TokenLParen {
5971+
return nil, fmt.Errorf("expected ( after RIGHT, got %s", p.curTok.Literal)
5972+
}
5973+
p.nextToken() // consume (
5974+
5975+
var params []ast.ScalarExpression
5976+
5977+
// Parse parameters
5978+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
5979+
param, err := p.parseScalarExpression()
5980+
if err != nil {
5981+
return nil, err
5982+
}
5983+
params = append(params, param)
5984+
5985+
if p.curTok.Type == TokenComma {
5986+
p.nextToken() // consume ,
5987+
} else {
5988+
break
5989+
}
5990+
}
5991+
5992+
if p.curTok.Type != TokenRParen {
5993+
return nil, fmt.Errorf("expected ) in RIGHT function, got %s", p.curTok.Literal)
5994+
}
5995+
p.nextToken() // consume )
5996+
5997+
return &ast.RightFunctionCall{Parameters: params}, nil
5998+
}
5999+
59206000
// parsePredictTableReference parses PREDICT(...) in FROM clause
59216001
// PREDICT(MODEL = expression, DATA = table AS alias, RUNTIME=ident) WITH (columns) AS alias
59226002
func (p *Parser) parsePredictTableReference() (*ast.PredictTableReference, error) {

0 commit comments

Comments
 (0)