Skip to content

Commit 9024f5e

Browse files
committed
Add IN, LIKE, BETWEEN boolean expression support
- Add BooleanInExpression for IN (values) and IN (subquery) - Add BooleanLikeExpression for LIKE with optional ESCAPE - Add BooleanBetweenExpression for BETWEEN x AND y - Add NOT IN, NOT LIKE, NOT BETWEEN support - Add IN, LIKE, BETWEEN, ESCAPE tokens to lexer
1 parent c413b26 commit 9024f5e

File tree

5 files changed

+208
-0
lines changed

5 files changed

+208
-0
lines changed

ast/boolean_between_expression.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ast
2+
3+
// BooleanBetweenExpression represents a BETWEEN expression.
4+
type BooleanBetweenExpression struct {
5+
FirstExpression ScalarExpression
6+
SecondExpression ScalarExpression
7+
ThirdExpression ScalarExpression
8+
NotDefined bool
9+
}
10+
11+
func (b *BooleanBetweenExpression) node() {}
12+
func (b *BooleanBetweenExpression) booleanExpression() {}

ast/boolean_in_expression.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ast
2+
3+
// BooleanInExpression represents an IN expression.
4+
type BooleanInExpression struct {
5+
Expression ScalarExpression
6+
NotDefined bool
7+
Values []ScalarExpression
8+
Subquery QueryExpression
9+
}
10+
11+
func (b *BooleanInExpression) node() {}
12+
func (b *BooleanInExpression) booleanExpression() {}

ast/boolean_like_expression.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ast
2+
3+
// BooleanLikeExpression represents a LIKE expression.
4+
type BooleanLikeExpression struct {
5+
FirstExpression ScalarExpression
6+
SecondExpression ScalarExpression
7+
EscapeExpression ScalarExpression
8+
NotDefined bool
9+
}
10+
11+
func (b *BooleanLikeExpression) node() {}
12+
func (b *BooleanLikeExpression) booleanExpression() {}

parser/lexer.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ const (
9292
TokenDefault
9393
TokenNull
9494
TokenIs
95+
TokenIn
96+
TokenLike
97+
TokenBetween
98+
TokenEscape
9599
TokenExec
96100
TokenExecute
97101
TokenOver
@@ -525,6 +529,10 @@ var keywords = map[string]TokenType{
525529
"DEFAULT": TokenDefault,
526530
"NULL": TokenNull,
527531
"IS": TokenIs,
532+
"IN": TokenIn,
533+
"LIKE": TokenLike,
534+
"BETWEEN": TokenBetween,
535+
"ESCAPE": TokenEscape,
528536
"EXEC": TokenExec,
529537
"EXECUTE": TokenExecute,
530538
"OVER": TokenOver,

parser/parser.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,13 @@ func (p *Parser) parseBooleanPrimaryExpression() (ast.BooleanExpression, error)
15821582
return nil, err
15831583
}
15841584

1585+
// Check for NOT before IN/LIKE/BETWEEN
1586+
notDefined := false
1587+
if p.curTok.Type == TokenNot {
1588+
notDefined = true
1589+
p.nextToken() // consume NOT
1590+
}
1591+
15851592
// Check for IS NULL / IS NOT NULL
15861593
if p.curTok.Type == TokenIs {
15871594
p.nextToken() // consume IS
@@ -1603,6 +1610,114 @@ func (p *Parser) parseBooleanPrimaryExpression() (ast.BooleanExpression, error)
16031610
}, nil
16041611
}
16051612

1613+
// Check for IN expression
1614+
if p.curTok.Type == TokenIn {
1615+
p.nextToken() // consume IN
1616+
1617+
if p.curTok.Type != TokenLParen {
1618+
return nil, fmt.Errorf("expected ( after IN, got %s", p.curTok.Literal)
1619+
}
1620+
p.nextToken() // consume (
1621+
1622+
// Check if it's a subquery or value list
1623+
if p.curTok.Type == TokenSelect {
1624+
subquery, err := p.parseQueryExpression()
1625+
if err != nil {
1626+
return nil, err
1627+
}
1628+
if p.curTok.Type != TokenRParen {
1629+
return nil, fmt.Errorf("expected ), got %s", p.curTok.Literal)
1630+
}
1631+
p.nextToken() // consume )
1632+
return &ast.BooleanInExpression{
1633+
Expression: left,
1634+
NotDefined: notDefined,
1635+
Subquery: subquery,
1636+
}, nil
1637+
}
1638+
1639+
// Parse value list
1640+
var values []ast.ScalarExpression
1641+
for {
1642+
val, err := p.parseScalarExpression()
1643+
if err != nil {
1644+
return nil, err
1645+
}
1646+
values = append(values, val)
1647+
if p.curTok.Type != TokenComma {
1648+
break
1649+
}
1650+
p.nextToken() // consume ,
1651+
}
1652+
if p.curTok.Type != TokenRParen {
1653+
return nil, fmt.Errorf("expected ), got %s", p.curTok.Literal)
1654+
}
1655+
p.nextToken() // consume )
1656+
return &ast.BooleanInExpression{
1657+
Expression: left,
1658+
NotDefined: notDefined,
1659+
Values: values,
1660+
}, nil
1661+
}
1662+
1663+
// Check for LIKE expression
1664+
if p.curTok.Type == TokenLike {
1665+
p.nextToken() // consume LIKE
1666+
1667+
pattern, err := p.parseScalarExpression()
1668+
if err != nil {
1669+
return nil, err
1670+
}
1671+
1672+
var escapeExpr ast.ScalarExpression
1673+
if p.curTok.Type == TokenEscape {
1674+
p.nextToken() // consume ESCAPE
1675+
escapeExpr, err = p.parseScalarExpression()
1676+
if err != nil {
1677+
return nil, err
1678+
}
1679+
}
1680+
1681+
return &ast.BooleanLikeExpression{
1682+
FirstExpression: left,
1683+
SecondExpression: pattern,
1684+
EscapeExpression: escapeExpr,
1685+
NotDefined: notDefined,
1686+
}, nil
1687+
}
1688+
1689+
// Check for BETWEEN expression
1690+
if p.curTok.Type == TokenBetween {
1691+
p.nextToken() // consume BETWEEN
1692+
1693+
low, err := p.parseScalarExpression()
1694+
if err != nil {
1695+
return nil, err
1696+
}
1697+
1698+
if p.curTok.Type != TokenAnd {
1699+
return nil, fmt.Errorf("expected AND in BETWEEN, got %s", p.curTok.Literal)
1700+
}
1701+
p.nextToken() // consume AND
1702+
1703+
high, err := p.parseScalarExpression()
1704+
if err != nil {
1705+
return nil, err
1706+
}
1707+
1708+
return &ast.BooleanBetweenExpression{
1709+
FirstExpression: left,
1710+
SecondExpression: low,
1711+
ThirdExpression: high,
1712+
NotDefined: notDefined,
1713+
}, nil
1714+
}
1715+
1716+
// If we saw NOT but didn't get IN/LIKE/BETWEEN, error
1717+
if notDefined {
1718+
return nil, fmt.Errorf("expected IN, LIKE, or BETWEEN after NOT, got %s", p.curTok.Literal)
1719+
}
1720+
16061721
// Check for comparison operator
16071722
var compType string
16081723
switch p.curTok.Type {
@@ -4647,6 +4762,55 @@ func booleanExpressionToJSON(expr ast.BooleanExpression) jsonNode {
46474762
node["Expression"] = scalarExpressionToJSON(e.Expression)
46484763
}
46494764
return node
4765+
case *ast.BooleanInExpression:
4766+
node := jsonNode{
4767+
"$type": "BooleanInExpression",
4768+
}
4769+
if e.Expression != nil {
4770+
node["Expression"] = scalarExpressionToJSON(e.Expression)
4771+
}
4772+
node["NotDefined"] = e.NotDefined
4773+
if len(e.Values) > 0 {
4774+
values := make([]jsonNode, len(e.Values))
4775+
for i, v := range e.Values {
4776+
values[i] = scalarExpressionToJSON(v)
4777+
}
4778+
node["Values"] = values
4779+
}
4780+
if e.Subquery != nil {
4781+
node["Subquery"] = queryExpressionToJSON(e.Subquery)
4782+
}
4783+
return node
4784+
case *ast.BooleanLikeExpression:
4785+
node := jsonNode{
4786+
"$type": "BooleanLikeExpression",
4787+
}
4788+
if e.FirstExpression != nil {
4789+
node["FirstExpression"] = scalarExpressionToJSON(e.FirstExpression)
4790+
}
4791+
if e.SecondExpression != nil {
4792+
node["SecondExpression"] = scalarExpressionToJSON(e.SecondExpression)
4793+
}
4794+
if e.EscapeExpression != nil {
4795+
node["EscapeExpression"] = scalarExpressionToJSON(e.EscapeExpression)
4796+
}
4797+
node["NotDefined"] = e.NotDefined
4798+
return node
4799+
case *ast.BooleanBetweenExpression:
4800+
node := jsonNode{
4801+
"$type": "BooleanBetweenExpression",
4802+
}
4803+
if e.FirstExpression != nil {
4804+
node["FirstExpression"] = scalarExpressionToJSON(e.FirstExpression)
4805+
}
4806+
if e.SecondExpression != nil {
4807+
node["SecondExpression"] = scalarExpressionToJSON(e.SecondExpression)
4808+
}
4809+
if e.ThirdExpression != nil {
4810+
node["ThirdExpression"] = scalarExpressionToJSON(e.ThirdExpression)
4811+
}
4812+
node["NotDefined"] = e.NotDefined
4813+
return node
46504814
default:
46514815
return jsonNode{"$type": "UnknownBooleanExpression"}
46524816
}

0 commit comments

Comments
 (0)