Skip to content

Commit

Permalink
Refactor parseExpr to allow to use the priority precedence
Browse files Browse the repository at this point in the history
  • Loading branch information
git-hulk committed Sep 25, 2024
1 parent c1f72b5 commit fec4808
Show file tree
Hide file tree
Showing 45 changed files with 866 additions and 356 deletions.
42 changes: 25 additions & 17 deletions parser/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -1475,7 +1475,8 @@ type CreateTable struct {
OnCluster *ClusterClause
TableSchema *TableSchemaClause
Engine *EngineExpr
SubQuery *SubQueryClause
Alias *AliasExpr
SubQuery *SubQuery
HasTemporary bool
}

Expand Down Expand Up @@ -1518,6 +1519,7 @@ func (c *CreateTable) String() string {
builder.WriteString(c.Engine.String())
}
if c.SubQuery != nil {
builder.WriteString(" AS ")
builder.WriteString(c.SubQuery.String())
}
return builder.String()
Expand Down Expand Up @@ -1562,7 +1564,7 @@ type CreateMaterializedView struct {
OnCluster *ClusterClause
Engine *EngineExpr
Destination *DestinationClause
SubQuery *SubQueryClause
SubQuery *SubQuery
Populate bool
}

Expand Down Expand Up @@ -1604,6 +1606,7 @@ func (c *CreateMaterializedView) String() string {
builder.WriteString(" POPULATE ")
}
if c.SubQuery != nil {
builder.WriteString(" AS ")
builder.WriteString(c.SubQuery.String())
}
return builder.String()
Expand Down Expand Up @@ -1651,7 +1654,7 @@ type CreateView struct {
UUID *UUID
OnCluster *ClusterClause
TableSchema *TableSchemaClause
SubQuery *SubQueryClause
SubQuery *SubQuery
}

func (c *CreateView) Pos() Pos {
Expand Down Expand Up @@ -1689,6 +1692,7 @@ func (c *CreateView) String() string {
}

if c.SubQuery != nil {
builder.WriteString(" AS ")
builder.WriteString(c.SubQuery.String())
}
return builder.String()
Expand Down Expand Up @@ -4113,7 +4117,7 @@ type CreateLiveView struct {
Destination *DestinationClause
TableSchema *TableSchemaClause
WithTimeout *WithTimeoutClause
SubQuery *SubQueryClause
SubQuery *SubQuery
}

func (c *CreateLiveView) Type() string {
Expand Down Expand Up @@ -4157,6 +4161,7 @@ func (c *CreateLiveView) String() string {
}

if c.SubQuery != nil {
builder.WriteString(" AS ")
builder.WriteString(c.SubQuery.String())
}

Expand Down Expand Up @@ -5298,28 +5303,31 @@ func (s *SelectQuery) Accept(visitor ASTVisitor) error {
return visitor.VisitSelectQuery(s)
}

type SubQueryClause struct {
AsPos Pos
Select *SelectQuery
type SubQuery struct {
HasParen bool
Select *SelectQuery
}

func (s *SubQueryClause) Pos() Pos {
return s.AsPos
func (s *SubQuery) Pos() Pos {
return s.Select.Pos()
}

func (s *SubQueryClause) End() Pos {
func (s *SubQuery) End() Pos {
return s.Select.End()
}

func (s *SubQueryClause) String() string {
var builder strings.Builder
builder.WriteString(" AS (")
builder.WriteString(s.Select.String())
builder.WriteString(")")
return builder.String()
func (s *SubQuery) String() string {
if s.HasParen {
var builder strings.Builder
builder.WriteString("(")
builder.WriteString(s.Select.String())
builder.WriteString(")")
return builder.String()
}
return s.Select.String()
}

func (s *SubQueryClause) Accept(visitor ASTVisitor) error {
func (s *SubQuery) Accept(visitor ASTVisitor) error {
visitor.enter(s)
defer visitor.leave(s)
if s.Select != nil {
Expand Down
4 changes: 2 additions & 2 deletions parser/ast_visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ type ASTVisitor interface {
VisitWindowFrameNumber(expr *WindowFrameNumber) error
VisitArrayJoinExpr(expr *ArrayJoinClause) error
VisitSelectQuery(expr *SelectQuery) error
VisitSubQueryExpr(expr *SubQueryClause) error
VisitSubQueryExpr(expr *SubQuery) error
VisitNotExpr(expr *NotExpr) error
VisitNegateExpr(expr *NegateExpr) error
VisitGlobalInExpr(expr *GlobalInOperation) error
Expand Down Expand Up @@ -1006,7 +1006,7 @@ func (v *DefaultASTVisitor) VisitSelectQuery(expr *SelectQuery) error {
return nil
}

func (v *DefaultASTVisitor) VisitSubQueryExpr(expr *SubQueryClause) error {
func (v *DefaultASTVisitor) VisitSubQueryExpr(expr *SubQuery) error {
if v.Visit != nil {
return v.Visit(expr)
}
Expand Down
7 changes: 7 additions & 0 deletions parser/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ type Token struct {
QuoteType int
}

func (t *Token) ToString() string {
if t.Kind == TokenKeyword {
return strings.ToUpper(t.String)
}
return t.String
}

type Lexer struct {
input string
current int
Expand Down
181 changes: 173 additions & 8 deletions parser/parser_column.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,197 @@ import (
"strings"
)

const (
PrecedenceUnknown = iota
PrecedenceOr
PrecedenceAnd
PrecedenceQuery
PrecedenceNot
PrecedenceGlobal
PrecedenceIs
PrecedenceCompare
PrecedenceBetweenLike
precedenceIn
PrecedenceAddSub
PrecedenceMulDivMod
PrecedenceBracket
PrecedenceArrow
PrecedenceDoubleColon
)

func (p *Parser) tryParseColumnComment(pos Pos) (*StringLiteral, error) {
if p.tryConsumeKeyword(KeywordComment) == nil {
return nil, nil // nolint
}
return p.parseString(pos)
}

func (p *Parser) getNextPrecedence(pos Pos) int {
switch {
case p.matchKeyword(KeywordOr):
return PrecedenceOr
case p.matchKeyword(KeywordAnd):
return PrecedenceAnd
case p.matchKeyword(KeywordIs):
return PrecedenceIs
case p.matchKeyword(KeywordNot):
return PrecedenceNot
case p.matchTokenKind(opTypeCast):
return PrecedenceDoubleColon
case p.matchTokenKind(opTypeEQ), p.matchTokenKind(opTypeLT), p.matchTokenKind(opTypeLE),
p.matchTokenKind(opTypeGE), p.matchTokenKind(opTypeGT), p.matchTokenKind(opTypeDoubleEQ),
p.matchTokenKind(opTypeNE), p.matchTokenKind("<>"):
return PrecedenceCompare
case p.matchTokenKind(opTypePlus), p.matchTokenKind(opTypeMinus):
return PrecedenceAddSub
case p.matchTokenKind(opTypeMul), p.matchTokenKind(opTypeDiv), p.matchTokenKind(opTypeMod):
return PrecedenceMulDivMod
case p.matchTokenKind(opTypeArrow):
return PrecedenceArrow
case p.matchTokenKind("("), p.matchTokenKind("["):
return PrecedenceBracket
case p.matchTokenKind(opTypeCast):
return PrecedenceDoubleColon
case p.matchKeyword(KeywordBetween), p.matchKeyword(KeywordLike), p.matchKeyword(KeywordIlike):
return PrecedenceBetweenLike
case p.matchKeyword(KeywordIn):
return precedenceIn
case p.matchKeyword(KeywordGlobal):
return PrecedenceGlobal
case p.matchTokenKind(opTypeQuery):
return PrecedenceQuery
default:
return PrecedenceUnknown
}
}

func (p *Parser) parseInfix(expr Expr, precedence int) (Expr, error) {
switch {
case p.matchTokenKind(opTypeEQ), p.matchTokenKind(opTypeLT), p.matchTokenKind(opTypeLE),
p.matchTokenKind(opTypeGE), p.matchTokenKind(opTypeGT),
p.matchTokenKind(opTypeNE), p.matchTokenKind("<>"),
p.matchTokenKind(opTypeMinus), p.matchTokenKind(opTypePlus), p.matchTokenKind(opTypeMul),
p.matchTokenKind(opTypeDiv), p.matchTokenKind(opTypeMod),
p.matchKeyword(KeywordIn), p.matchKeyword(KeywordLike),
p.matchKeyword(KeywordIlike), p.matchKeyword(KeywordAnd), p.matchKeyword(KeywordOr),
p.matchTokenKind(opTypeCast), p.matchTokenKind(opTypeArrow), p.matchTokenKind(opTypeDoubleEQ):
op := p.last().ToString()
_ = p.lexer.consumeToken()
rightExpr, err := p.parseSubExpr(p.Pos(), precedence)
if err != nil {
return nil, err
}
return &BinaryOperation{
LeftExpr: expr,
Operation: TokenKind(op),
RightExpr: rightExpr,
}, nil
case p.matchKeyword(KeywordGlobal):
_ = p.lexer.consumeToken()
if p.consumeKeyword(KeywordIn) != nil {
return nil, fmt.Errorf("expected IN after GLOBAL, got %s", p.lastTokenKind())
}
rightExpr, err := p.parseSubExpr(p.Pos(), precedence)
if err != nil {
return nil, err
}
return &BinaryOperation{
LeftExpr: expr,
Operation: "GLOBAL IN",
RightExpr: rightExpr,
}, nil

case p.matchKeyword(KeywordNot):
_ = p.lexer.consumeToken()
switch {
case p.matchKeyword(KeywordIn):
case p.matchKeyword(KeywordLike):
case p.matchKeyword(KeywordIlike):
case p.matchKeyword(KeywordBetween):
default:
return nil, fmt.Errorf("expected IN, LIKE, ILIKE or BETWEEN after NOT, got %s", p.lastTokenKind())
}
op := p.last().ToString()
_ = p.lexer.consumeToken()
rightExpr, err := p.parseSubExpr(p.Pos(), precedence)
if err != nil {
return nil, err
}
return &BinaryOperation{
LeftExpr: expr,
Operation: TokenKind("NOT " + op),
RightExpr: rightExpr,
}, nil
case p.matchTokenKind("["):
params, err := p.parseArrayParams(p.Pos())
if err != nil {
return nil, err
}
return &ObjectParams{
Object: expr,
Params: params,
}, nil
case p.matchTokenKind(opTypeQuery):
return p.parseTernaryExpr(expr)
case p.matchKeyword(KeywordIs):
_ = p.lexer.consumeToken()
isNotNull := p.tryConsumeKeyword(KeywordNot) != nil
if err := p.consumeKeyword(KeywordNull); err != nil {
return nil, err
}
if isNotNull {
return &IsNotNullExpr{
IsPos: p.Pos(),
Expr: expr,
}, nil
}
return &IsNullExpr{
IsPos: p.Pos(),
Expr: expr,
}, nil
default:
return nil, fmt.Errorf("unexpected token kind: %s", p.lastTokenKind())
}
}

func (p *Parser) parseExpr(pos Pos) (Expr, error) {
orExpr, err := p.parseOrExpr(pos)
expr, err := p.parseSubExpr(pos, PrecedenceUnknown)
if err != nil {
return orExpr, err
return nil, err
}
switch {
case p.matchKeyword(KeywordAs): // syntax: columnExpr (alias | AS identifier)
if p.matchKeyword(KeywordAs) {
pos := p.Pos()
_ = p.lexer.consumeToken()
alias, err := p.parseIdent()
if err != nil {
return nil, err
}
return &AliasExpr{
AliasPos: alias.Pos(),
Expr: orExpr,
AliasPos: pos,
Expr: expr,
Alias: alias,
}, nil
}
return orExpr, nil
return expr, nil
}

func (p *Parser) parseSubExpr(pos Pos, precedence int) (Expr, error) {
expr, err := p.parseUnaryExpr(pos)
if err != nil {
return nil, err
}
for !p.lexer.isEOF() {
nextPrecedence := p.getNextPrecedence(p.Pos())
if nextPrecedence <= precedence {
return expr, nil
}
// parse binary operation
expr, err = p.parseInfix(expr, nextPrecedence)
if err != nil {
return nil, err
}
}
return expr, nil
}

func (p *Parser) parseOrExpr(pos Pos) (Expr, error) {
Expand Down Expand Up @@ -365,7 +530,7 @@ func (p *Parser) parseColumnExpr(pos Pos) (Expr, error) { //nolint:funlen
case p.matchTokenKind("("):
if peek, _ := p.lexer.peekToken(); peek != nil {
if peek.Kind == TokenKeyword && strings.EqualFold(peek.String, KeywordSelect) {
return p.parseSelectQuery(pos)
return p.parseSubQuery(pos)
}
}
return p.parseFunctionParams(p.Pos())
Expand Down
20 changes: 12 additions & 8 deletions parser/parser_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ func (p *Parser) parseTableExpr(pos Pos) (*TableExpr, error) {
}
}
case p.matchTokenKind("("):
expr, err = p.parseSelectQuery(p.Pos())
expr, err = p.parseSubQuery(p.Pos())
default:
return nil, errors.New("expect table name or subquery")
}
Expand Down Expand Up @@ -729,19 +729,23 @@ func (p *Parser) parseHavingClause(pos Pos) (*HavingClause, error) {
}, nil
}

func (p *Parser) parseSubQueryClause(pos Pos) (*SubQueryClause, error) {
if err := p.consumeKeyword(KeywordAs); err != nil {
return nil, err
}
func (p *Parser) parseSubQuery(pos Pos) (*SubQuery, error) {

Check failure on line 732 in parser/parser_query.go

View workflow job for this annotation

GitHub Actions / Lint/Build/Test (1.18.x, ubuntu-latest)

`(*Parser).parseSubQuery` - `pos` is unused (unparam)

hasParen := p.tryConsumeTokenKind("(") != nil

selectQuery, err := p.parseSelectQuery(p.Pos())
if err != nil {
return nil, err
}
if hasParen {
if _, err := p.consumeTokenKind(")"); err != nil {
return nil, err
}
}

return &SubQueryClause{
AsPos: pos,
Select: selectQuery,
return &SubQuery{
HasParen: hasParen,
Select: selectQuery,
}, nil
}

Expand Down
Loading

0 comments on commit fec4808

Please sign in to comment.