Skip to content

Commit

Permalink
Implement pipe syntax, only FROM, SELECT, WHERE
Browse files Browse the repository at this point in the history
  • Loading branch information
apstndb committed Nov 13, 2024
1 parent 2b2b4b2 commit be067cd
Show file tree
Hide file tree
Showing 181 changed files with 1,073 additions and 959 deletions.
75 changes: 71 additions & 4 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ type QueryExpr interface {
}

func (Select) isQueryExpr() {}
func (Query) isQueryExpr() {}
func (FromQuery) isQueryExpr() {}
func (SubQuery) isQueryExpr() {}
func (CompoundQuery) isQueryExpr() {}

Expand Down Expand Up @@ -467,16 +469,69 @@ func (ChangeStreamSetOptions) isChangeStreamAlteration() {}

// QueryStatement is query statement node.
//
// {{.Query | sql}}
type QueryStatement struct {
// pos = Query.pos
// end = Query.end

Query QueryExpr
}

// Query is query node with hints and CTE and pipe operators.
//
// {{.Hint | sqlOpt}} {{.With | sqlOpt}} {{.Query | sql}}
//
// https://cloud.google.com/spanner/docs/query-syntax
type QueryStatement struct {
type Query struct {
// pos = (Hint ?? With ?? Query).pos
// end = Query.end

Hint *Hint // optional
With *With // optional
Query QueryExpr
Hint *Hint // optional
With *With // optional
Query QueryExpr
PipeOperators []PipeOperator
}

type PipeOperator interface {
Node
isPipeOperator()
}

func (PipeSelect) isPipeOperator() {}
func (PipeWhere) isPipeOperator() {}

// PipeSelect is SELECT pipe operator node.
//
// |> SELECT {{if .Distinct}}DISTINCT{{end}} {{.As | sqlOpt}} {{.Results | sqlJoin ", "}}
type PipeSelect struct {
// pos = Pipe
// end = Results[$].end

Pipe token.Pos // position of "|>"

Distinct bool
As SelectAs // optional
Results []SelectItem // len(Results) > 0
}

func (p *PipeSelect) SQL() string {
return "|> SELECT " + strOpt(p.Distinct, "DISTINCT ") + sqlOpt("", p.As, " ") + sqlJoin(p.Results, ", ")
}

// PipeWhere is WHERE pipe operator node.
//
// |> WHERE {{.Expr | sql}}
type PipeWhere struct {
// pos = Pipe
// end = Expr.end

Pipe token.Pos // position of "|>"

Expr Expr
}

func (p *PipeWhere) SQL() string {
return "|> WHERE " + p.Expr.SQL()
}

// Hint is hint node.
Expand Down Expand Up @@ -590,6 +645,18 @@ type AsTypeName struct {
TypeName *NamedType
}

// FromQuery is FROM relational operator node.
type FromQuery struct {
// pos = From.pos
// end = From.end

From *From
}

func (f *FromQuery) SQL() string {
return f.From.SQL()
}

// CompoundQuery is query statement node compounded by set operators.
//
// {{.Queries | sqlJoin (printf "%s %s" .Op or(and(.Distinct, "DISTINCT"), "ALL"))}}
Expand Down
34 changes: 33 additions & 1 deletion ast/pos.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions ast/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ func paren(p prec, e Expr) string {
// ================================================================================

func (q *QueryStatement) SQL() string {
var sql string
if q.Hint != nil {
sql += q.Hint.SQL() + " "
}
if q.With != nil {
sql += q.With.SQL() + " "
}
sql += q.Query.SQL()
return sql
return q.Query.SQL()
}

func (q *Query) SQL() string {
return sqlOpt("", q.Hint, " ") +
sqlOpt("", q.With, " ") +
q.Query.SQL() +
strOpt(len(q.PipeOperators) > 0, " ") +
sqlJoin(q.PipeOperators, " ")
}

func (h *Hint) SQL() string {
Expand Down
87 changes: 76 additions & 11 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func (p *Parser) ParseStatements() (stmts []ast.Statement, err error) {
return
}

// ParseQuery parses a SELECT query statement.
func (p *Parser) ParseQuery() (stmt *ast.QueryStatement, err error) {
// ParseQueryStatement parses a SELECT query statement.
func (p *Parser) ParseQueryStatement() (stmt *ast.QueryStatement, err error) {
defer func() {
if r := recover(); r != nil {
stmt = nil
Expand Down Expand Up @@ -210,7 +210,7 @@ func (p *Parser) ParseDMLs() (dmls []ast.DML, err error) {

func (p *Parser) parseStatement() ast.Statement {
switch {
case p.Token.Kind == "SELECT" || p.Token.Kind == "@" || p.Token.Kind == "WITH" || p.Token.Kind == "(":
case p.Token.Kind == "SELECT" || p.Token.Kind == "@" || p.Token.Kind == "WITH" || p.Token.Kind == "(" || p.Token.Kind == "FROM":
return p.parseQueryStatement()
case p.Token.Kind == "CREATE" || p.Token.IsKeywordLike("ALTER") || p.Token.IsKeywordLike("DROP") ||
p.Token.IsKeywordLike("RENAME") || p.Token.IsKeywordLike("GRANT") || p.Token.IsKeywordLike("REVOKE") ||
Expand Down Expand Up @@ -245,14 +245,55 @@ func (p *Parser) parseStatements(doParse func()) {
// ================================================================================

func (p *Parser) parseQueryStatement() *ast.QueryStatement {
query := p.parseQueryExpr()

return &ast.QueryStatement{Query: query}
}

func (p *Parser) parsePipeOperators() []ast.PipeOperator {
var pipeOps []ast.PipeOperator
for {
if p.Token.Kind != "|>" {
break
}

pos := p.expect("|>").Pos
var op ast.PipeOperator
switch {
case p.Token.Kind == "SELECT":
p.nextToken()
as := p.tryParseSelectAs()
results := p.parseSelectResults()
op = &ast.PipeSelect{
Pipe: pos,
Distinct: false, // TODO
As: as,
Results: results,
}
case p.Token.Kind == "WHERE":
p.nextToken()
expr := p.parseExpr()
op = &ast.PipeWhere{
Pipe: pos,
Expr: expr,
}
}
pipeOps = append(pipeOps, op)
}
return pipeOps
}

func (p *Parser) parseQuery() *ast.Query {
hint := p.tryParseHint()
with := p.tryParseWith()
query := p.parseQueryExpr()
pipeOps := p.parsePipeOperators()

return &ast.QueryStatement{
Hint: hint,
With: with,
Query: query,
return &ast.Query{
Hint: hint,
With: with,
Query: query,
PipeOperators: pipeOps,
}
}

Expand Down Expand Up @@ -367,19 +408,43 @@ func (p *Parser) parseQueryExpr() ast.QueryExpr {
return p.parseQueryExprSuffix(query)
}

func (p *Parser) parseFromQuery() *ast.FromQuery {
from := p.tryParseFrom()
if from == nil {
panic(p.errorfAtToken(&p.Token, "expected 'FROM', got %s", p.Token.Kind))
}

return &ast.FromQuery{From: from}
}

func (p *Parser) parseSimpleQueryExpr() ast.QueryExpr {
if p.Token.Kind == "(" {
var queryExpr ast.QueryExpr
switch p.Token.Kind {
case "FROM":
queryExpr = p.parseFromQuery()
case "(":
lparen := p.expect("(").Pos
query := p.parseQueryExpr()
rparen := p.expect(")").Pos
return &ast.SubQuery{
queryExpr = &ast.SubQuery{
Lparen: lparen,
Rparen: rparen,
Query: query,
}
case "SELECT":
queryExpr = p.parseSelect()
default:
queryExpr = p.parseQuery()
}
pipeOps := p.parsePipeOperators()
if pipeOps != nil {
return &ast.Query{
Query: queryExpr,
PipeOperators: pipeOps,
}
} else {
return queryExpr
}

return p.parseSelect()
}

func (p *Parser) tryParseSelectAs() ast.SelectAs {
Expand Down
1 change: 1 addition & 0 deletions testdata/input/query/from_query.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM Singers
2 changes: 2 additions & 0 deletions testdata/input/query/pipe_from_select.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM Singers
|> SELECT *
3 changes: 3 additions & 0 deletions testdata/input/query/pipe_from_where_select.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM Singers
|> WHERE FirstName = "John"
|> SELECT *
2 changes: 0 additions & 2 deletions testdata/result/query/aggregate_function_calls.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ SELECT

--- AST
&ast.QueryStatement{
Hint: (*ast.Hint)(nil),
With: (*ast.With)(nil),
Query: &ast.Select{
Select: 0,
Distinct: false,
Expand Down
23 changes: 23 additions & 0 deletions testdata/result/query/from_query.sql.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--- from_query.sql
FROM Singers
--- AST
&ast.QueryStatement{
Query: &ast.FromQuery{
From: &ast.From{
From: 0,
Source: &ast.TableName{
Table: &ast.Ident{
NamePos: 5,
NameEnd: 12,
Name: "Singers",
},
Hint: (*ast.Hint)(nil),
As: (*ast.AsAlias)(nil),
Sample: (*ast.TableSample)(nil),
},
},
},
}

--- SQL
FROM Singers
40 changes: 40 additions & 0 deletions testdata/result/query/pipe_from_select.sql.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--- pipe_from_select.sql
FROM Singers
|> SELECT *
--- AST
&ast.QueryStatement{
Query: &ast.Query{
Hint: (*ast.Hint)(nil),
With: (*ast.With)(nil),
Query: &ast.FromQuery{
From: &ast.From{
From: 0,
Source: &ast.TableName{
Table: &ast.Ident{
NamePos: 5,
NameEnd: 12,
Name: "Singers",
},
Hint: (*ast.Hint)(nil),
As: (*ast.AsAlias)(nil),
Sample: (*ast.TableSample)(nil),
},
},
},
PipeOperators: []ast.PipeOperator{
&ast.PipeSelect{
Pipe: 13,
Distinct: false,
As: nil,
Results: []ast.SelectItem{
&ast.Star{
Star: 23,
},
},
},
},
},
}

--- SQL
FROM Singers |> SELECT *
Loading

0 comments on commit be067cd

Please sign in to comment.