Skip to content

Commit

Permalink
Add support of SELECT modifiers REPLACE|EXCEPT|APPLY (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
git-hulk authored Sep 19, 2024
1 parent 04f353e commit c1f72b5
Show file tree
Hide file tree
Showing 40 changed files with 1,027 additions and 529 deletions.
58 changes: 50 additions & 8 deletions parser/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,47 @@ type DDL interface {
Type() string
}

type SelectItem struct {
Expr Expr
// Please refer: https://clickhouse.com/docs/en/sql-reference/statements/select#select-modifiers
Modifiers []*FunctionExpr
}

func (s *SelectItem) Pos() Pos {
return s.Expr.Pos()
}

func (s *SelectItem) End() Pos {
if len(s.Modifiers) > 0 {
return s.Modifiers[len(s.Modifiers)-1].End()
}
return s.Expr.End()
}

func (s *SelectItem) String() string {
var builder strings.Builder
builder.WriteString(s.Expr.String())
for _, modifier := range s.Modifiers {
builder.WriteByte(' ')
builder.WriteString(modifier.String())
}
return builder.String()
}

func (s *SelectItem) Accept(visitor ASTVisitor) error {
visitor.enter(s)
defer visitor.leave(s)
if err := s.Expr.Accept(visitor); err != nil {
return err
}
for _, modifier := range s.Modifiers {
if err := modifier.Accept(visitor); err != nil {
return err
}
}
return visitor.VisitSelectItem(s)
}

type OperationExpr struct {
OperationPos Pos
Kind TokenKind
Expand Down Expand Up @@ -5047,7 +5088,7 @@ type SelectQuery struct {
StatementEnd Pos
With *WithClause
Top *TopClause
SelectColumns *ColumnExprList
SelectItems []*SelectItem
From *FromClause
ArrayJoin *ArrayJoinClause
Window *WindowClause
Expand Down Expand Up @@ -5092,10 +5133,9 @@ func (s *SelectQuery) String() string { // nolint: funlen
builder.WriteString(s.Top.String())
builder.WriteString(" ")
}
columns := s.SelectColumns.Items
for i, column := range columns {
builder.WriteString(column.String())
if i != len(columns)-1 {
for i, selectItem := range s.SelectItems {
builder.WriteString(selectItem.String())
if i != len(s.SelectItems)-1 {
builder.WriteString(", ")
}
}
Expand Down Expand Up @@ -5173,9 +5213,11 @@ func (s *SelectQuery) Accept(visitor ASTVisitor) error {
return err
}
}
if s.SelectColumns != nil {
if err := s.SelectColumns.Accept(visitor); err != nil {
return err
if s.SelectItems != nil {
for _, item := range s.SelectItems {
if err := item.Accept(visitor); err != nil {
return err
}
}
}
if s.From != nil {
Expand Down
8 changes: 8 additions & 0 deletions parser/ast_visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ type ASTVisitor interface {
VisitExplainExpr(expr *ExplainStmt) error
VisitPrivilegeExpr(expr *PrivilegeClause) error
VisitGrantPrivilegeExpr(expr *GrantPrivilegeStmt) error
VisitSelectItem(expr *SelectItem) error

enter(expr Expr)
leave(expr Expr)
Expand Down Expand Up @@ -1236,6 +1237,13 @@ func (v *DefaultASTVisitor) VisitGrantPrivilegeExpr(expr *GrantPrivilegeStmt) er
return nil
}

func (v *DefaultASTVisitor) VisitSelectItem(expr *SelectItem) error {
if v.Visit != nil {
return v.Visit(expr)
}
return nil
}

func (v *DefaultASTVisitor) enter(expr Expr) {}

func (v *DefaultASTVisitor) leave(expr Expr) {}
2 changes: 2 additions & 0 deletions parser/keyword.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
KeywordAnd = "AND"
KeywordAnti = "ANTI"
KeywordAny = "ANY"
KeywordApply = "APPLY"
KeywordArray = "ARRAY"
KeywordAs = "AS"
KeywordAsc = "ASC"
Expand Down Expand Up @@ -233,6 +234,7 @@ var keywords = NewSet(
KeywordAnd,
KeywordAnti,
KeywordAny,
KeywordApply,
KeywordArray,
KeywordAs,
KeywordAsc,
Expand Down
47 changes: 43 additions & 4 deletions parser/parser_column.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,24 @@ func (p *Parser) parseColumnExprListWithTerm(term TokenKind, pos Pos) (*ColumnEx
return columnExprList, nil
}

func (p *Parser) parseSelectItems() ([]*SelectItem, error) {
selectItems := make([]*SelectItem, 0)
for !p.lexer.isEOF() || p.last() != nil {
selectItem, err := p.parseSelectItem()
if err != nil {
return nil, err
}
if selectItem == nil {
break
}
selectItems = append(selectItems, selectItem)
if p.tryConsumeTokenKind(",") == nil {
break
}
}
return selectItems, nil
}

// Syntax: INTERVAL expr interval
func (p *Parser) parseColumnExprInterval(pos Pos) (Expr, error) {
if err := p.consumeKeyword(KeywordInterval); err != nil {
Expand Down Expand Up @@ -504,10 +522,7 @@ func (p *Parser) parseColumnExprInterval(pos Pos) (Expr, error) {
}, nil
}

func (p *Parser) parseFunctionExpr(_ Pos) (Expr, error) {
if _, err := p.consumeTokenKind(TokenIdent); err != nil {
return nil, err
}
func (p *Parser) parseFunctionExpr(_ Pos) (*FunctionExpr, error) {
// parse function name
name, err := p.parseIdent()
if err != nil {
Expand Down Expand Up @@ -674,6 +689,30 @@ func (p *Parser) parseColumnsExpr(pos Pos) (Expr, error) {
return p.parseExpr(pos)
}

func (p *Parser) parseSelectItem() (*SelectItem, error) {
expr, err := p.parseExpr(p.Pos())
if err != nil {
return nil, err
}

modifiers := make([]*FunctionExpr, 0)
for {
if p.matchKeyword(KeywordExcept) || p.matchKeyword(KeywordApply) || p.matchKeyword(KeywordReplace) {
modifier, err := p.parseFunctionExpr(p.Pos())
if err != nil {
return nil, err
}
modifiers = append(modifiers, modifier)
} else {
break
}
}
return &SelectItem{
Expr: expr,
Modifiers: modifiers,
}, nil
}

func (p *Parser) parseColumnCaseExpr(pos Pos) (*CaseExpr, error) {
// CASE expr
caseExpr := &CaseExpr{CasePos: pos}
Expand Down
44 changes: 24 additions & 20 deletions parser/parser_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -803,11 +803,15 @@ func (p *Parser) parseSelectStmt(pos Pos) (*SelectQuery, error) { // nolint: fun
if err != nil {
return nil, err
}
selectColumns, err := p.parseColumnExprListWithRoundBracket(p.Pos())
selectItems, err := p.parseSelectItems()
if err != nil {
return nil, err
}
statementEnd := selectColumns.End()

statementEnd := pos
if len(selectItems) > 0 {
statementEnd = selectItems[len(selectItems)-1].End()
}
from, err := p.tryParseFromClause(p.Pos())
if err != nil {
return nil, err
Expand Down Expand Up @@ -916,24 +920,24 @@ func (p *Parser) parseSelectStmt(pos Pos) (*SelectQuery, error) { // nolint: fun
}

return &SelectQuery{
With: withClause,
SelectPos: pos,
StatementEnd: statementEnd,
Top: top,
SelectColumns: selectColumns,
From: from,
ArrayJoin: arrayJoin,
Window: window,
Prewhere: prewhere,
Where: where,
GroupBy: groupBy,
Having: having,
OrderBy: orderBy,
LimitBy: limitBy,
Limit: limit,
Settings: settings,
Format: format,
WithTotal: withTotal,
With: withClause,
SelectPos: pos,
StatementEnd: statementEnd,
Top: top,
SelectItems: selectItems,
From: from,
ArrayJoin: arrayJoin,
Window: window,
Prewhere: prewhere,
Where: where,
GroupBy: groupBy,
Having: having,
OrderBy: orderBy,
LimitBy: limitBy,
Limit: limit,
Settings: settings,
Format: format,
WithTotal: withTotal,
}, nil
}

Expand Down
66 changes: 47 additions & 19 deletions parser/testdata/ddl/output/bug_001.sql.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,27 @@
"StatementEnd": 635,
"With": null,
"Top": null,
"SelectColumns": {
"ListPos": 118,
"ListEnd": 591,
"HasDistinct": false,
"Items": [
{
"SelectItems": [
{
"Expr": {
"Name": "event_ts",
"QuoteType": 1,
"NamePos": 118,
"NameEnd": 126
},
{
"Modifiers": []
},
{
"Expr": {
"Name": "org_id",
"QuoteType": 1,
"NamePos": 132,
"NameEnd": 138
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractString",
Expand Down Expand Up @@ -108,7 +111,10 @@
"NameEnd": 189
}
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractString",
Expand Down Expand Up @@ -148,7 +154,10 @@
"NameEnd": 240
}
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractString",
Expand Down Expand Up @@ -188,7 +197,10 @@
"NameEnd": 291
}
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractString",
Expand Down Expand Up @@ -228,7 +240,10 @@
"NameEnd": 342
}
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractString",
Expand Down Expand Up @@ -268,7 +283,10 @@
"NameEnd": 393
}
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractString",
Expand Down Expand Up @@ -308,7 +326,10 @@
"NameEnd": 444
}
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractString",
Expand Down Expand Up @@ -348,7 +369,10 @@
"NameEnd": 495
}
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractInt",
Expand Down Expand Up @@ -388,7 +412,10 @@
"NameEnd": 543
}
},
{
"Modifiers": []
},
{
"Expr": {
"Expr": {
"Name": {
"Name": "visitParamExtractInt",
Expand Down Expand Up @@ -427,9 +454,10 @@
"NamePos": 590,
"NameEnd": 591
}
}
]
},
},
"Modifiers": []
}
],
"From": {
"FromPos": 592,
"Expr": {
Expand Down
Loading

0 comments on commit c1f72b5

Please sign in to comment.