Skip to content

Commit fd34163

Browse files
kyleconroyclaude
andcommitted
Add expression parsing improvements for T-SQL features
- Add bitwise operators (&, |, ^) with correct T-SQL precedence - Add ODBC function call parsing ({ FN convert(...) }, { FN extract(...) }, etc.) - Add ODBC date/time literal parsing ({ T '...' }, { D '...' }, { TS '...' }) - Add parenthesized subquery with UNION support (parseRestOfBinaryQueryExpression) - Add IDENTITY clause parsing after DEFAULT in column definitions - Add large integer detection (>INT range) to use NumericLiteral - Add NULLIF and COALESCE as distinct expression types - Add ExtractFromExpression for ODBC extract function Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3fce598 commit fd34163

4 files changed

Lines changed: 470 additions & 6 deletions

File tree

ast/nullif_coalesce.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package ast
2+
3+
// NullIfExpression represents a NULLIF(expr1, expr2) expression.
4+
type NullIfExpression struct {
5+
FirstExpression ScalarExpression
6+
SecondExpression ScalarExpression
7+
}
8+
9+
func (*NullIfExpression) node() {}
10+
func (*NullIfExpression) scalarExpression() {}
11+
12+
// CoalesceExpression represents a COALESCE(expr1, expr2, ...) expression.
13+
type CoalesceExpression struct {
14+
Expressions []ScalarExpression
15+
}
16+
17+
func (*CoalesceExpression) node() {}
18+
func (*CoalesceExpression) scalarExpression() {}

ast/odbc_literal.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,30 @@ type OdbcLiteral struct {
1010

1111
func (*OdbcLiteral) node() {}
1212
func (*OdbcLiteral) scalarExpression() {}
13+
14+
// OdbcFunctionCall represents an ODBC scalar function call like {fn convert(...)}.
15+
type OdbcFunctionCall struct {
16+
Name *Identifier
17+
ParametersUsed bool
18+
Parameters []ScalarExpression
19+
}
20+
21+
func (*OdbcFunctionCall) node() {}
22+
func (*OdbcFunctionCall) scalarExpression() {}
23+
24+
// OdbcConvertSpecification represents the target type in an ODBC convert function.
25+
type OdbcConvertSpecification struct {
26+
Identifier *Identifier
27+
}
28+
29+
func (*OdbcConvertSpecification) node() {}
30+
func (*OdbcConvertSpecification) scalarExpression() {}
31+
32+
// ExtractFromExpression represents an EXTRACT(element FROM expression) construct.
33+
type ExtractFromExpression struct {
34+
ExtractedElement *Identifier
35+
Expression ScalarExpression
36+
}
37+
38+
func (*ExtractFromExpression) node() {}
39+
func (*ExtractFromExpression) scalarExpression() {}

parser/marshal.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,6 +2275,29 @@ func scalarExpressionToJSON(expr ast.ScalarExpression) jsonNode {
22752275
node["Collation"] = identifierToJSON(e.Collation)
22762276
}
22772277
return node
2278+
case *ast.NullIfExpression:
2279+
node := jsonNode{
2280+
"$type": "NullIfExpression",
2281+
}
2282+
if e.FirstExpression != nil {
2283+
node["FirstExpression"] = scalarExpressionToJSON(e.FirstExpression)
2284+
}
2285+
if e.SecondExpression != nil {
2286+
node["SecondExpression"] = scalarExpressionToJSON(e.SecondExpression)
2287+
}
2288+
return node
2289+
case *ast.CoalesceExpression:
2290+
node := jsonNode{
2291+
"$type": "CoalesceExpression",
2292+
}
2293+
if len(e.Expressions) > 0 {
2294+
exprs := make([]jsonNode, len(e.Expressions))
2295+
for i, expr := range e.Expressions {
2296+
exprs[i] = scalarExpressionToJSON(expr)
2297+
}
2298+
node["Expressions"] = exprs
2299+
}
2300+
return node
22782301
case *ast.IdentityFunctionCall:
22792302
node := jsonNode{
22802303
"$type": "IdentityFunctionCall",
@@ -2444,6 +2467,41 @@ func scalarExpressionToJSON(expr ast.ScalarExpression) jsonNode {
24442467
node["Value"] = e.Value
24452468
}
24462469
return node
2470+
case *ast.OdbcFunctionCall:
2471+
node := jsonNode{
2472+
"$type": "OdbcFunctionCall",
2473+
}
2474+
if e.Name != nil {
2475+
node["Name"] = identifierToJSON(e.Name)
2476+
}
2477+
node["ParametersUsed"] = e.ParametersUsed
2478+
if len(e.Parameters) > 0 {
2479+
params := make([]jsonNode, len(e.Parameters))
2480+
for i, param := range e.Parameters {
2481+
params[i] = scalarExpressionToJSON(param)
2482+
}
2483+
node["Parameters"] = params
2484+
}
2485+
return node
2486+
case *ast.OdbcConvertSpecification:
2487+
node := jsonNode{
2488+
"$type": "OdbcConvertSpecification",
2489+
}
2490+
if e.Identifier != nil {
2491+
node["Identifier"] = identifierToJSON(e.Identifier)
2492+
}
2493+
return node
2494+
case *ast.ExtractFromExpression:
2495+
node := jsonNode{
2496+
"$type": "ExtractFromExpression",
2497+
}
2498+
if e.Expression != nil {
2499+
node["Expression"] = scalarExpressionToJSON(e.Expression)
2500+
}
2501+
if e.ExtractedElement != nil {
2502+
node["ExtractedElement"] = identifierToJSON(e.ExtractedElement)
2503+
}
2504+
return node
24472505
case *ast.NullLiteral:
24482506
node := jsonNode{
24492507
"$type": "NullLiteral",
@@ -7538,6 +7596,51 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) {
75387596
}
75397597
}
75407598
}
7599+
} else if upperLit == "IDENTITY" && col.IdentityOptions == nil {
7600+
// IDENTITY can appear after DEFAULT or other constraints
7601+
p.nextToken() // consume IDENTITY
7602+
identityOpts := &ast.IdentityOptions{}
7603+
7604+
// Check for optional (seed, increment)
7605+
if p.curTok.Type == TokenLParen {
7606+
p.nextToken() // consume (
7607+
7608+
// Parse seed
7609+
seed, err := p.parseScalarExpression()
7610+
if err == nil {
7611+
identityOpts.IdentitySeed = seed
7612+
}
7613+
7614+
// Expect comma
7615+
if p.curTok.Type == TokenComma {
7616+
p.nextToken() // consume ,
7617+
7618+
// Parse increment
7619+
increment, err := p.parseScalarExpression()
7620+
if err == nil {
7621+
identityOpts.IdentityIncrement = increment
7622+
}
7623+
}
7624+
7625+
// Expect closing paren
7626+
if p.curTok.Type == TokenRParen {
7627+
p.nextToken() // consume )
7628+
}
7629+
}
7630+
7631+
// Check for NOT FOR REPLICATION
7632+
if p.curTok.Type == TokenNot {
7633+
p.nextToken() // consume NOT
7634+
if strings.ToUpper(p.curTok.Literal) == "FOR" {
7635+
p.nextToken() // consume FOR
7636+
if strings.ToUpper(p.curTok.Literal) == "REPLICATION" {
7637+
p.nextToken() // consume REPLICATION
7638+
identityOpts.NotForReplication = true
7639+
}
7640+
}
7641+
}
7642+
7643+
col.IdentityOptions = identityOpts
75417644
} else {
75427645
break
75437646
}

0 commit comments

Comments
 (0)