From 9b8dc3d7e64942c059ca109fd92da8529d979f7b Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Thu, 12 Aug 2021 15:43:35 -0600 Subject: [PATCH] Implement basic SQL COUNT(*) query --- go.mod | 1 + go.sum | 2 + planner.go | 302 ++++ planner_test.go | 77 + server.go | 5 + sql2/ast.go | 3507 ++++++++++++++++++++++++++++++++++++++++++ sql2/ast_test.go | 1161 ++++++++++++++ sql2/parser.go | 2960 +++++++++++++++++++++++++++++++++++ sql2/parser_test.go | 3311 +++++++++++++++++++++++++++++++++++++++ sql2/scanner.go | 350 +++++ sql2/scanner_test.go | 177 +++ sql2/token.go | 556 +++++++ sql2/token_test.go | 27 + sql2/walk.go | 724 +++++++++ 14 files changed, 13160 insertions(+) create mode 100644 planner.go create mode 100644 planner_test.go create mode 100644 sql2/ast.go create mode 100644 sql2/ast_test.go create mode 100644 sql2/parser.go create mode 100644 sql2/parser_test.go create mode 100644 sql2/scanner.go create mode 100644 sql2/scanner_test.go create mode 100644 sql2/token.go create mode 100644 sql2/token_test.go create mode 100644 sql2/walk.go diff --git a/go.mod b/go.mod index 953ef0af3..02133467c 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 // indirect github.com/glycerine/idem v0.0.0-20190127113923-7a8083893311 + github.com/go-test/deep v1.0.7 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.3.3 github.com/google/go-cmp v0.5.5 diff --git a/go.sum b/go.sum index 4755dfe03..f89f91301 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= +github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= diff --git a/planner.go b/planner.go new file mode 100644 index 000000000..daf4d8339 --- /dev/null +++ b/planner.go @@ -0,0 +1,302 @@ +// Copyright 2021 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pilosa + +import ( + "context" + "database/sql" + "fmt" + "io" + "strings" + + "github.com/molecula/featurebase/v2/pql" + "github.com/molecula/featurebase/v2/sql2" +) + +type Planner struct { + executor *executor +} + +func NewPlanner(executor *executor) *Planner { + return &Planner{executor: executor} +} + +func (p *Planner) PlanStatement(ctx context.Context, stmt sql2.Statement) (*Stmt, error) { + node, err := p.planStatement(ctx, stmt) + if err != nil { + return nil, err + } + return &Stmt{node: node}, nil +} + +func (p *Planner) planStatement(ctx context.Context, stmt sql2.Statement) (StmtNode, error) { + switch stmt := stmt.(type) { + case *sql2.SelectStatement: + return p.planSelectStatement(ctx, stmt) + default: + return nil, fmt.Errorf("cannot plan statement: %T", stmt) + } +} + +func (p *Planner) planSelectStatement(ctx context.Context, stmt *sql2.SelectStatement) (_ StmtNode, err error) { + if stmt.IsAggregate() { + return p.planAggregateSelectStatement(ctx, stmt) + } + return p.planNonAggregateSelectStatement(ctx, stmt) +} + +func (p *Planner) planAggregateSelectStatement(ctx context.Context, stmt *sql2.SelectStatement) (_ StmtNode, err error) { + // Extract table name from source. + var source *sql2.QualifiedTableName + switch src := stmt.Source.(type) { + case *sql2.JoinClause: + return nil, fmt.Errorf("cannot use JOIN in aggregate query") + case *sql2.ParenSource: + return nil, fmt.Errorf("cannot use parenthesized source in aggregate query") + case *sql2.QualifiedTableName: + source = src + case *sql2.SelectStatement: + return nil, fmt.Errorf("cannot use sub-select in aggregate query") + default: + return nil, fmt.Errorf("unexpected source type in aggregate query: %T", source) + } + + // TODO: Support multiple aggregate calls. + if len(stmt.Columns) > 1 { + return nil, fmt.Errorf("only one call allowed in aggregate query") + } + + // Extract aggregate call. + col := stmt.Columns[0] + var call *sql2.Call + switch expr := col.Expr.(type) { + case *sql2.Call: + call = expr + default: + return nil, fmt.Errorf("unsupported expression in aggregate query: %T", expr) + } + + callName := strings.ToUpper(sql2.IdentName(call.Name)) + switch callName { + case "COUNT": + return NewCountNode(p.executor, sql2.IdentName(source.Name)), nil + default: + return nil, fmt.Errorf("unsupported call in aggregate query: %T", callName) + } + + // TODO: Support HAVING +} + +func (p *Planner) planNonAggregateSelectStatement(ctx context.Context, stmt *sql2.SelectStatement) (_ StmtNode, err error) { + panic("TODO: Implement non-aggregate SELECT") +} + +type Stmt struct { + node StmtNode +} + +func (stmt *Stmt) Close() error { return nil } + +func (stmt *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *StmtRow { + rows, err := stmt.QueryContext(ctx, args...) + if err != nil { + return &StmtRow{err: err} + } + return &StmtRow{rows: rows} +} + +func (stmt *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*StmtRows, error) { + // TODO: Handle bind arguments. + + rows := &StmtRows{ + ctx: ctx, + node: stmt.node, + } + + // Initialize the node. + if err := rows.node.First(ctx); err != nil { + return nil, fmt.Errorf("Query: initialize statement: %w", err) + } + + return rows, nil +} + +type StmtRows struct { + ctx context.Context + node StmtNode + err error +} + +func (rs *StmtRows) Close() error { + return nil +} + +func (rs *StmtRows) Err() error { + if rs.err != nil && rs.err != sql.ErrNoRows { + return rs.err + } + return nil +} + +func (rs *StmtRows) Next() bool { + if rs.err != nil { + return false + } + + if rs.err = rs.node.Next(rs.ctx); rs.err != nil { + return false + } + return true +} + +func (rs *StmtRows) Scan(dst ...interface{}) error { + if rs.err != nil { + return rs.err + } + + // Check len(dest) against node row length. + row := rs.node.Row() + if len(dst) != len(row) { + return fmt.Errorf("Scan(): expected %d values, received %d values", len(dst), len(row)) + } + + // Copy values from row to destination pointers. + for i := range dst { + // Handle null values. + // TODO: Handle double pointers. + if row[i] == nil { + switch p := dst[i].(type) { + case *int: + *p = 0 + case *int64: + *p = 0 + case *uint: + *p = 0 + case *uint64: + *p = 0 + default: + return fmt.Errorf("cannot scan NULL value into %T destination at index %d", p, i) + } + continue + } + + // Copy row value to scan destination. + switch v := row[i].(type) { + case int64: + switch p := dst[i].(type) { + case *int: + *p = int(v) + case *int64: + *p = v + case *uint: + *p = uint(v) + case *uint64: + *p = uint64(v) + default: + return fmt.Errorf("cannot scan %T value into %T destination at index %d", v, p, i) + } + default: + return fmt.Errorf("unexpected %T value at index %d", v, i) + } + } + + return nil +} + +type StmtRow struct { + err error + rows *StmtRows +} + +func (r *StmtRow) Scan(dest ...interface{}) error { + if r.err != nil { + return r.err + } + defer r.rows.Close() + + if !r.rows.Next() { + if err := r.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + + if err := r.rows.Scan(dest...); err != nil { + return err + } + return r.rows.Close() +} + +func (r *StmtRow) Err() error { + return r.err +} + +type StmtNode interface { + // Initializes the node to its start. + First(ctx context.Context) error + + // Moves the node to the next available row. Returns sql.ErrNoRows if done. + Next(ctx context.Context) error + + // Returns the current row in the node. + Row() []interface{} + + // Returns column definitions for the node. + // Columns() []*Column + + // Returns a reference to the value register for a named column. + // Lookup(table, column string) (interface{}, error) +} + +var _ StmtNode = (*CountNode)(nil) + +// CountNode executes a COUNT(*) against a FeatureBase index and returns a single row. +type CountNode struct { + executor *executor + indexName string + + row []interface{} +} + +func NewCountNode(executor *executor, indexName string) *CountNode { + return &CountNode{ + executor: executor, + indexName: indexName, + } +} + +func (n *CountNode) First(ctx context.Context) error { + n.row = nil + return nil +} + +func (n *CountNode) Next(ctx context.Context) error { + if n.row != nil { + return io.EOF + } + result, err := n.executor.Execute(ctx, n.indexName, &pql.Query{ + Calls: []*pql.Call{ + {Name: "Count", Children: []*pql.Call{{Name: "All"}}}, + }, + }, nil, nil) + if err != nil { + return err + } + + n.row = []interface{}{int64(result.Results[0].(uint64))} + return nil +} + +func (n *CountNode) Row() []interface{} { return n.row } diff --git a/planner_test.go b/planner_test.go new file mode 100644 index 000000000..b1f57c131 --- /dev/null +++ b/planner_test.go @@ -0,0 +1,77 @@ +// Copyright 2021 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pilosa_test + +import ( + "context" + "strings" + "testing" + + "github.com/molecula/featurebase/v2" + "github.com/molecula/featurebase/v2/sql2" + "github.com/molecula/featurebase/v2/test" +) + +func TestPlanner_Count(t *testing.T) { + c := test.MustRunCluster(t, 1) + defer c.Close() + + index, err := c.GetHolder(0).CreateIndex("i", pilosa.IndexOptions{TrackExistence: true}) + if err != nil { + t.Fatal(err) + } + defer index.Close() + + if _, err := index.CreateField("f"); err != nil { + t.Fatal(err) + } + + // Populate with data. + if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{ + Index: "i", + Query: ` + Set(1, f=10) + Set(2, f=10) + Set(3, f=11) + `}); err != nil { + t.Fatal(err) + } + + // Parse SQL into AST. + q := `SELECT COUNT(*) AS "count" FROM i` + st, err := sql2.NewParser(strings.NewReader(q)).ParseStatement() + if err != nil { + t.Fatal(err) + } + + // Generate a prepared statement with the execution plan. + stmt, err := pilosa.NewPlanner(c.GetNode(0).Server.Executor()).PlanStatement(context.Background(), st) + if err != nil { + t.Fatal(err) + } + defer stmt.Close() + + // Scan first row from result set. + var n int + if err := stmt.QueryRowContext(context.Background()).Scan(&n); err != nil { + t.Fatal(err) + } else if got, want := n, 3; got != want { + t.Fatalf("Scan()=%d, want %d", got, want) + } + + if err := stmt.Close(); err != nil { + t.Fatal(err) + } +} diff --git a/server.go b/server.go index b3b0b5128..1d98ffaef 100644 --- a/server.go +++ b/server.go @@ -1346,6 +1346,11 @@ func (srv *Server) GetTransaction(ctx context.Context, id string, remote bool) ( return trns, nil } +// Executor returns the executor attached to the server. For testing only. +func (s *Server) Executor() *executor { + return s.executor +} + // countOpenFiles on operating systems that support lsof. func countOpenFiles() (int, error) { switch runtime.GOOS { diff --git a/sql2/ast.go b/sql2/ast.go new file mode 100644 index 000000000..f94c4a1c6 --- /dev/null +++ b/sql2/ast.go @@ -0,0 +1,3507 @@ +// Copyright 2021 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sql2 + +import ( + "bytes" + "fmt" + "strings" +) + +type Node interface { + node() + fmt.Stringer +} + +func (*AlterTableStatement) node() {} +func (*AnalyzeStatement) node() {} +func (*Assignment) node() {} +func (*BeginStatement) node() {} +func (*BinaryExpr) node() {} +func (*BindExpr) node() {} +func (*BlobLit) node() {} +func (*BoolLit) node() {} +func (*Call) node() {} +func (*CaseBlock) node() {} +func (*CaseExpr) node() {} +func (*CastExpr) node() {} +func (*CheckConstraint) node() {} +func (*ColumnDefinition) node() {} +func (*CommitStatement) node() {} +func (*CreateIndexStatement) node() {} +func (*CreateTableStatement) node() {} +func (*CreateTriggerStatement) node() {} +func (*CreateViewStatement) node() {} +func (*DefaultConstraint) node() {} +func (*DeleteStatement) node() {} +func (*DropIndexStatement) node() {} +func (*DropTableStatement) node() {} +func (*DropTriggerStatement) node() {} +func (*DropViewStatement) node() {} +func (*Exists) node() {} +func (*ExplainStatement) node() {} +func (*ExprList) node() {} +func (*FilterClause) node() {} +func (*ForeignKeyArg) node() {} +func (*ForeignKeyConstraint) node() {} +func (*FrameSpec) node() {} +func (*Ident) node() {} +func (*IndexedColumn) node() {} +func (*InsertStatement) node() {} +func (*JoinClause) node() {} +func (*JoinOperator) node() {} +func (*NotNullConstraint) node() {} +func (*NullLit) node() {} +func (*NumberLit) node() {} +func (*OnConstraint) node() {} +func (*OrderingTerm) node() {} +func (*OverClause) node() {} +func (*ParenExpr) node() {} +func (*ParenSource) node() {} +func (*PrimaryKeyConstraint) node() {} +func (*QualifiedRef) node() {} +func (*QualifiedTableName) node() {} +func (*Raise) node() {} +func (*Range) node() {} +func (*ReleaseStatement) node() {} +func (*ResultColumn) node() {} +func (*RollbackStatement) node() {} +func (*SavepointStatement) node() {} +func (*SelectStatement) node() {} +func (*StringLit) node() {} +func (*Type) node() {} +func (*UnaryExpr) node() {} +func (*UniqueConstraint) node() {} +func (*UpdateStatement) node() {} +func (*UpsertClause) node() {} +func (*UsingConstraint) node() {} +func (*Window) node() {} +func (*WindowDefinition) node() {} +func (*WithClause) node() {} + +type Statement interface { + Node + stmt() +} + +func (*AlterTableStatement) stmt() {} +func (*AnalyzeStatement) stmt() {} +func (*BeginStatement) stmt() {} +func (*CommitStatement) stmt() {} +func (*CreateIndexStatement) stmt() {} +func (*CreateTableStatement) stmt() {} +func (*CreateTriggerStatement) stmt() {} +func (*CreateViewStatement) stmt() {} +func (*DeleteStatement) stmt() {} +func (*DropIndexStatement) stmt() {} +func (*DropTableStatement) stmt() {} +func (*DropTriggerStatement) stmt() {} +func (*DropViewStatement) stmt() {} +func (*ExplainStatement) stmt() {} +func (*InsertStatement) stmt() {} +func (*ReleaseStatement) stmt() {} +func (*RollbackStatement) stmt() {} +func (*SavepointStatement) stmt() {} +func (*SelectStatement) stmt() {} +func (*UpdateStatement) stmt() {} + +// CloneStatement returns a deep copy stmt. +func CloneStatement(stmt Statement) Statement { + if stmt == nil { + return nil + } + + switch stmt := stmt.(type) { + case *AlterTableStatement: + return stmt.Clone() + case *AnalyzeStatement: + return stmt.Clone() + case *BeginStatement: + return stmt.Clone() + case *CommitStatement: + return stmt.Clone() + case *CreateIndexStatement: + return stmt.Clone() + case *CreateTableStatement: + return stmt.Clone() + case *CreateTriggerStatement: + return stmt.Clone() + case *CreateViewStatement: + return stmt.Clone() + case *DeleteStatement: + return stmt.Clone() + case *DropIndexStatement: + return stmt.Clone() + case *DropTableStatement: + return stmt.Clone() + case *DropTriggerStatement: + return stmt.Clone() + case *DropViewStatement: + return stmt.Clone() + case *ExplainStatement: + return stmt.Clone() + case *InsertStatement: + return stmt.Clone() + case *ReleaseStatement: + return stmt.Clone() + case *RollbackStatement: + return stmt.Clone() + case *SavepointStatement: + return stmt.Clone() + case *SelectStatement: + return stmt.Clone() + case *UpdateStatement: + return stmt.Clone() + default: + panic(fmt.Sprintf("invalid statement type: %T", stmt)) + } +} + +func cloneStatements(a []Statement) []Statement { + if a == nil { + return nil + } + other := make([]Statement, len(a)) + for i := range a { + other[i] = CloneStatement(a[i]) + } + return other +} + +// StatementSource returns the root statement for a statement. +func StatementSource(stmt Statement) Source { + switch stmt := stmt.(type) { + case *SelectStatement: + return stmt.Source + case *UpdateStatement: + return stmt.Table + case *DeleteStatement: + return stmt.Table + default: + return nil + } +} + +type Expr interface { + Node + expr() + + IsAggregate() bool +} + +func (*BinaryExpr) expr() {} +func (*BindExpr) expr() {} +func (*BlobLit) expr() {} +func (*BoolLit) expr() {} +func (*Call) expr() {} +func (*CaseExpr) expr() {} +func (*CastExpr) expr() {} +func (*Exists) expr() {} +func (*ExprList) expr() {} +func (*Ident) expr() {} +func (*NullLit) expr() {} +func (*NumberLit) expr() {} +func (*ParenExpr) expr() {} +func (*QualifiedRef) expr() {} +func (*Raise) expr() {} +func (*Range) expr() {} +func (*StringLit) expr() {} +func (*UnaryExpr) expr() {} + +// CloneExpr returns a deep copy expr. +func CloneExpr(expr Expr) Expr { + if expr == nil { + return nil + } + + switch expr := expr.(type) { + case *BinaryExpr: + return expr.Clone() + case *BindExpr: + return expr.Clone() + case *BlobLit: + return expr.Clone() + case *BoolLit: + return expr.Clone() + case *Call: + return expr.Clone() + case *CaseExpr: + return expr.Clone() + case *CastExpr: + return expr.Clone() + case *Exists: + return expr.Clone() + case *ExprList: + return expr.Clone() + case *Ident: + return expr.Clone() + case *NullLit: + return expr.Clone() + case *NumberLit: + return expr.Clone() + case *ParenExpr: + return expr.Clone() + case *QualifiedRef: + return expr.Clone() + case *Raise: + return expr.Clone() + case *Range: + return expr.Clone() + case *StringLit: + return expr.Clone() + case *UnaryExpr: + return expr.Clone() + default: + panic(fmt.Sprintf("invalid expr type: %T", expr)) + } +} + +func cloneExprs(a []Expr) []Expr { + if a == nil { + return nil + } + other := make([]Expr, len(a)) + for i := range a { + other[i] = CloneExpr(a[i]) + } + return other +} + +// ExprString returns the string representation of expr. +// Returns a blank string if expr is nil. +func ExprString(expr Expr) string { + if expr == nil { + return "" + } + return expr.String() +} + +// SplitExprTree splits apart expr so it is a list of all AND joined expressions. +// For example, the expression "A AND B AND (C OR (D AND E))" would be split into +// a list of "A", "B", "C OR (D AND E)". +func SplitExprTree(expr Expr) []Expr { + if expr == nil { + return nil + } + + var a []Expr + splitExprTree(expr, &a) + return a +} + +func splitExprTree(expr Expr, a *[]Expr) { + switch expr := expr.(type) { + case *BinaryExpr: + if expr.Op != AND { + *a = append(*a, expr) + return + } + splitExprTree(expr.X, a) + splitExprTree(expr.Y, a) + case *ParenExpr: + splitExprTree(expr.X, a) + default: + *a = append(*a, expr) + } +} + +// Scope represents a context for name resolution. +// Names can be resolved at the current source or in parent scopes. +type Scope struct { + Parent *Scope + Source Source +} + +// Source represents a table or subquery. +type Source interface { + Node + source() +} + +func (*JoinClause) source() {} +func (*ParenSource) source() {} +func (*QualifiedTableName) source() {} +func (*SelectStatement) source() {} + +// CloneSource returns a deep copy src. +func CloneSource(src Source) Source { + if src == nil { + return nil + } + + switch src := src.(type) { + case *JoinClause: + return src.Clone() + case *ParenSource: + return src.Clone() + case *QualifiedTableName: + return src.Clone() + case *SelectStatement: + return src.Clone() + default: + panic(fmt.Sprintf("invalid source type: %T", src)) + } +} + +// SourceName returns the name of the source. +// Only returns for QualifiedTableName & ParenSource. +func SourceName(src Source) string { + switch src := src.(type) { + case *JoinClause, *SelectStatement: + return "" + case *ParenSource: + return IdentName(src.Alias) + case *QualifiedTableName: + return src.TableName() + default: + return "" + } +} + +// SourceList returns a list of scopes in the current scope. +func SourceList(src Source) []Source { + var a []Source + ForEachSource(src, func(s Source) bool { + a = append(a, s) + return true + }) + return a +} + +// ForEachSource calls fn for every source within the current scope. +// Stops iteration if fn returns false. +func ForEachSource(src Source, fn func(Source) bool) { + forEachSource(src, fn) +} + +func forEachSource(src Source, fn func(Source) bool) bool { + if !fn(src) { + return false + } + + switch src := src.(type) { + case *JoinClause: + if !forEachSource(src.X, fn) { + return false + } else if !forEachSource(src.Y, fn) { + return false + } + case *SelectStatement: + if !forEachSource(src.Source, fn) { + return false + } + } + return true +} + +// ResolveSource returns a source with the given name. +// This can either be the table name or the alias for a source. +func ResolveSource(root Source, name string) Source { + var ret Source + ForEachSource(root, func(src Source) bool { + switch src := src.(type) { + case *ParenSource: + if IdentName(src.Alias) == name { + ret = src + } + case *QualifiedTableName: + if src.TableName() == name { + ret = src + } + } + return ret == nil // continue until we find the matching source + }) + return ret +} + +// JoinConstraint represents either an ON or USING join constraint. +type JoinConstraint interface { + Node + joinConstraint() +} + +func (*OnConstraint) joinConstraint() {} +func (*UsingConstraint) joinConstraint() {} + +// CloneJoinConstraint returns a deep copy cons. +func CloneJoinConstraint(cons JoinConstraint) JoinConstraint { + if cons == nil { + return nil + } + + switch cons := cons.(type) { + case *OnConstraint: + return cons.Clone() + case *UsingConstraint: + return cons.Clone() + default: + panic(fmt.Sprintf("invalid join constraint type: %T", cons)) + } +} + +type ExplainStatement struct { + Explain Pos // position of EXPLAIN + Query Pos // position of QUERY (optional) + QueryPlan Pos // position of PLAN after QUERY (optional) + Stmt Statement // target statement +} + +// Clone returns a deep copy of s. +func (s *ExplainStatement) Clone() *ExplainStatement { + if s == nil { + return nil + } + other := *s + other.Stmt = CloneStatement(s.Stmt) + return &other +} + +// String returns the string representation of the statement. +func (s *ExplainStatement) String() string { + var buf bytes.Buffer + buf.WriteString("EXPLAIN") + if s.QueryPlan.IsValid() { + buf.WriteString(" QUERY PLAN") + } + fmt.Fprintf(&buf, " %s", s.Stmt.String()) + return buf.String() +} + +type BeginStatement struct { + Begin Pos // position of BEGIN + Deferred Pos // position of DEFERRED keyword + Immediate Pos // position of IMMEDIATE keyword + Exclusive Pos // position of EXCLUSIVE keyword + Transaction Pos // position of TRANSACTION keyword (optional) +} + +// Clone returns a deep copy of s. +func (s *BeginStatement) Clone() *BeginStatement { + if s == nil { + return nil + } + other := *s + return &other +} + +// String returns the string representation of the statement. +func (s *BeginStatement) String() string { + var buf bytes.Buffer + buf.WriteString("BEGIN") + if s.Deferred.IsValid() { + buf.WriteString(" DEFERRED") + } else if s.Immediate.IsValid() { + buf.WriteString(" IMMEDIATE") + } else if s.Exclusive.IsValid() { + buf.WriteString(" EXCLUSIVE") + } + if s.Transaction.IsValid() { + buf.WriteString(" TRANSACTION") + } + return buf.String() +} + +type CommitStatement struct { + Commit Pos // position of COMMIT keyword + End Pos // position of END keyword + Transaction Pos // position of TRANSACTION keyword (optional) +} + +// Clone returns a deep copy of s. +func (s *CommitStatement) Clone() *CommitStatement { + if s == nil { + return nil + } + other := *s + return &other +} + +// String returns the string representation of the statement. +func (s *CommitStatement) String() string { + var buf bytes.Buffer + if s.End.IsValid() { + buf.WriteString("END") + } else { + buf.WriteString("COMMIT") + } + + if s.Transaction.IsValid() { + buf.WriteString(" TRANSACTION") + } + return buf.String() +} + +type RollbackStatement struct { + Rollback Pos // position of ROLLBACK keyword + Transaction Pos // position of TRANSACTION keyword (optional) + To Pos // position of TO keyword (optional) + Savepoint Pos // position of SAVEPOINT keyword (optional) + SavepointName *Ident // name of savepoint +} + +// Clone returns a deep copy of s. +func (s *RollbackStatement) Clone() *RollbackStatement { + if s == nil { + return s + } + other := *s + other.SavepointName = s.SavepointName.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *RollbackStatement) String() string { + var buf bytes.Buffer + buf.WriteString("ROLLBACK") + if s.Transaction.IsValid() { + buf.WriteString(" TRANSACTION") + } + + if s.SavepointName != nil { + buf.WriteString(" TO") + if s.Savepoint.IsValid() { + buf.WriteString(" SAVEPOINT") + } + fmt.Fprintf(&buf, " %s", s.SavepointName.String()) + } + return buf.String() +} + +type SavepointStatement struct { + Savepoint Pos // position of SAVEPOINT keyword + Name *Ident // name of savepoint +} + +// Clone returns a deep copy of s. +func (s *SavepointStatement) Clone() *SavepointStatement { + if s == nil { + return s + } + other := *s + other.Name = s.Name.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *SavepointStatement) String() string { + return fmt.Sprintf("SAVEPOINT %s", s.Name.String()) +} + +type ReleaseStatement struct { + Release Pos // position of RELEASE keyword + Savepoint Pos // position of SAVEPOINT keyword (optional) + Name *Ident // name of savepoint +} + +// Clone returns a deep copy of s. +func (s *ReleaseStatement) Clone() *ReleaseStatement { + if s == nil { + return s + } + other := *s + other.Name = s.Name.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *ReleaseStatement) String() string { + var buf bytes.Buffer + buf.WriteString("RELEASE") + if s.Savepoint.IsValid() { + buf.WriteString(" SAVEPOINT") + } + fmt.Fprintf(&buf, " %s", s.Name.String()) + return buf.String() +} + +type CreateTableStatement struct { + Create Pos // position of CREATE keyword + Table Pos // position of CREATE keyword + If Pos // position of IF keyword (optional) + IfNot Pos // position of NOT keyword (optional) + IfNotExists Pos // position of EXISTS keyword (optional) + Name *Ident // table name + + Lparen Pos // position of left paren of column list + Columns []*ColumnDefinition // column definitions + Constraints []Constraint // table constraints + Rparen Pos // position of right paren of column list + + As Pos // position of AS keyword (optional) + Select *SelectStatement // select stmt to build from +} + +// Clone returns a deep copy of s. +func (s *CreateTableStatement) Clone() *CreateTableStatement { + if s == nil { + return s + } + other := *s + other.Name = s.Name.Clone() + other.Columns = cloneColumnDefinitions(s.Columns) + other.Constraints = cloneConstraints(s.Constraints) + other.Select = s.Select.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *CreateTableStatement) String() string { + var buf bytes.Buffer + buf.WriteString("CREATE TABLE") + if s.IfNotExists.IsValid() { + buf.WriteString(" IF NOT EXISTS") + } + buf.WriteString(" ") + buf.WriteString(s.Name.String()) + + if s.Select != nil { + buf.WriteString(" AS ") + buf.WriteString(s.Select.String()) + } else { + buf.WriteString(" (") + for i := range s.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(s.Columns[i].String()) + } + for i := range s.Constraints { + buf.WriteString(", ") + buf.WriteString(s.Constraints[i].String()) + } + buf.WriteString(")") + } + + return buf.String() +} + +type ColumnDefinition struct { + Name *Ident // column name + Type *Type // data type + Constraints []Constraint // column constraints +} + +// Clone returns a deep copy of d. +func (d *ColumnDefinition) Clone() *ColumnDefinition { + if d == nil { + return d + } + other := *d + other.Name = d.Name.Clone() + other.Type = d.Type.Clone() + other.Constraints = cloneConstraints(d.Constraints) + return &other +} + +func cloneColumnDefinitions(a []*ColumnDefinition) []*ColumnDefinition { + if a == nil { + return nil + } + other := make([]*ColumnDefinition, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the statement. +func (c *ColumnDefinition) String() string { + var buf bytes.Buffer + buf.WriteString(c.Name.String()) + buf.WriteString(" ") + buf.WriteString(c.Type.String()) + for i := range c.Constraints { + buf.WriteString(" ") + buf.WriteString(c.Constraints[i].String()) + } + return buf.String() +} + +type Constraint interface { + Node + constraint() +} + +func (*PrimaryKeyConstraint) constraint() {} +func (*NotNullConstraint) constraint() {} +func (*UniqueConstraint) constraint() {} +func (*CheckConstraint) constraint() {} +func (*DefaultConstraint) constraint() {} +func (*ForeignKeyConstraint) constraint() {} + +// CloneConstraint returns a deep copy cons. +func CloneConstraint(cons Constraint) Constraint { + if cons == nil { + return nil + } + + switch cons := cons.(type) { + case *PrimaryKeyConstraint: + return cons.Clone() + case *NotNullConstraint: + return cons.Clone() + case *UniqueConstraint: + return cons.Clone() + case *CheckConstraint: + return cons.Clone() + case *DefaultConstraint: + return cons.Clone() + case *ForeignKeyConstraint: + return cons.Clone() + default: + panic(fmt.Sprintf("invalid constraint type: %T", cons)) + } +} + +func cloneConstraints(a []Constraint) []Constraint { + if a == nil { + return nil + } + other := make([]Constraint, len(a)) + for i := range a { + other[i] = CloneConstraint(a[i]) + } + return other +} + +type PrimaryKeyConstraint struct { + Constraint Pos // position of CONSTRAINT keyword + Name *Ident // constraint name + Primary Pos // position of PRIMARY keyword + Key Pos // position of KEY keyword + + Lparen Pos // position of left paren (table only) + Columns []*Ident // indexed columns (table only) + Rparen Pos // position of right paren (table only) + + Autoincrement Pos // position of AUTOINCREMENT keyword (column only) +} + +// Clone returns a deep copy of c. +func (c *PrimaryKeyConstraint) Clone() *PrimaryKeyConstraint { + if c == nil { + return c + } + other := *c + other.Name = c.Name.Clone() + other.Columns = cloneIdents(c.Columns) + return &other +} + +// String returns the string representation of the constraint. +func (c *PrimaryKeyConstraint) String() string { + var buf bytes.Buffer + if c.Name != nil { + buf.WriteString("CONSTRAINT ") + buf.WriteString(c.Name.String()) + buf.WriteString(" ") + } + + buf.WriteString("PRIMARY KEY") + + if len(c.Columns) > 0 { + buf.WriteString(" (") + for i := range c.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(c.Columns[i].String()) + } + buf.WriteString(")") + } + + if c.Autoincrement.IsValid() { + buf.WriteString(" AUTOINCREMENT") + } + return buf.String() +} + +type NotNullConstraint struct { + Constraint Pos // position of CONSTRAINT keyword + Name *Ident // constraint name + Not Pos // position of NOT keyword + Null Pos // position of NULL keyword +} + +// Clone returns a deep copy of c. +func (c *NotNullConstraint) Clone() *NotNullConstraint { + if c == nil { + return c + } + other := *c + other.Name = c.Name.Clone() + return &other +} + +// String returns the string representation of the constraint. +func (c *NotNullConstraint) String() string { + var buf bytes.Buffer + if c.Name != nil { + buf.WriteString("CONSTRAINT ") + buf.WriteString(c.Name.String()) + buf.WriteString(" ") + } + + buf.WriteString("NOT NULL") + + return buf.String() +} + +type UniqueConstraint struct { + Constraint Pos // position of CONSTRAINT keyword + Name *Ident // constraint name + Unique Pos // position of UNIQUE keyword + + Lparen Pos // position of left paren (table only) + Columns []*Ident // indexed columns (table only) + Rparen Pos // position of right paren (table only) +} + +// Clone returns a deep copy of c. +func (c *UniqueConstraint) Clone() *UniqueConstraint { + if c == nil { + return c + } + other := *c + other.Name = c.Name.Clone() + other.Columns = cloneIdents(c.Columns) + return &other +} + +// String returns the string representation of the constraint. +func (c *UniqueConstraint) String() string { + var buf bytes.Buffer + if c.Name != nil { + buf.WriteString("CONSTRAINT ") + buf.WriteString(c.Name.String()) + buf.WriteString(" ") + } + + buf.WriteString("UNIQUE") + + if len(c.Columns) > 0 { + buf.WriteString(" (") + for i := range c.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(c.Columns[i].String()) + } + buf.WriteString(")") + } + + return buf.String() +} + +type CheckConstraint struct { + Constraint Pos // position of CONSTRAINT keyword + Name *Ident // constraint name + Check Pos // position of UNIQUE keyword + Lparen Pos // position of left paren + Expr Expr // check expression + Rparen Pos // position of right paren +} + +// Clone returns a deep copy of c. +func (c *CheckConstraint) Clone() *CheckConstraint { + if c == nil { + return c + } + other := *c + other.Name = c.Name.Clone() + other.Expr = CloneExpr(c.Expr) + return &other +} + +// String returns the string representation of the constraint. +func (c *CheckConstraint) String() string { + var buf bytes.Buffer + if c.Name != nil { + buf.WriteString("CONSTRAINT ") + buf.WriteString(c.Name.String()) + buf.WriteString(" ") + } + + buf.WriteString("CHECK (") + buf.WriteString(c.Expr.String()) + buf.WriteString(")") + return buf.String() +} + +type DefaultConstraint struct { + Constraint Pos // position of CONSTRAINT keyword + Name *Ident // constraint name + Default Pos // position of DEFAULT keyword + Lparen Pos // position of left paren + Expr Expr // default expression + Rparen Pos // position of right paren +} + +// Clone returns a deep copy of c. +func (c *DefaultConstraint) Clone() *DefaultConstraint { + if c == nil { + return c + } + other := *c + other.Name = c.Name.Clone() + other.Expr = CloneExpr(c.Expr) + return &other +} + +// String returns the string representation of the constraint. +func (c *DefaultConstraint) String() string { + var buf bytes.Buffer + if c.Name != nil { + buf.WriteString("CONSTRAINT ") + buf.WriteString(c.Name.String()) + buf.WriteString(" ") + } + + buf.WriteString("DEFAULT ") + + if c.Lparen.IsValid() { + buf.WriteString("(") + buf.WriteString(c.Expr.String()) + buf.WriteString(")") + } else { + buf.WriteString(c.Expr.String()) + } + return buf.String() +} + +type ForeignKeyConstraint struct { + Constraint Pos // position of CONSTRAINT keyword + Name *Ident // constraint name + + Foreign Pos // position of FOREIGN keyword (table only) + ForeignKey Pos // position of KEY keyword after FOREIGN (table only) + Lparen Pos // position of left paren (table only) + Columns []*Ident // indexed columns (table only) + Rparen Pos // position of right paren (table only) + + References Pos // position of REFERENCES keyword + ForeignTable *Ident // foreign table name + ForeignLparen Pos // position of left paren + ForeignColumns []*Ident // column list + ForeignRparen Pos // position of right paren + Args []*ForeignKeyArg // arguments + Deferrable Pos // position of DEFERRABLE keyword + Not Pos // position of NOT keyword + NotDeferrable Pos // position of DEFERRABLE keyword after NOT + Initially Pos // position of INITIALLY keyword + InitiallyDeferred Pos // position of DEFERRED keyword after INITIALLY + InitiallyImmediate Pos // position of IMMEDIATE keyword after INITIALLY +} + +// Clone returns a deep copy of c. +func (c *ForeignKeyConstraint) Clone() *ForeignKeyConstraint { + if c == nil { + return c + } + other := *c + other.Name = c.Name.Clone() + other.Columns = cloneIdents(c.Columns) + other.ForeignTable = c.ForeignTable.Clone() + other.ForeignColumns = cloneIdents(c.ForeignColumns) + other.Args = cloneForeignKeyArgs(c.Args) + return &other +} + +// String returns the string representation of the constraint. +func (c *ForeignKeyConstraint) String() string { + var buf bytes.Buffer + if c.Name != nil { + buf.WriteString("CONSTRAINT ") + buf.WriteString(c.Name.String()) + buf.WriteString(" ") + } + + if len(c.Columns) > 0 { + buf.WriteString("FOREIGN KEY (") + for i := range c.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(c.Columns[i].String()) + } + buf.WriteString(") ") + } + + buf.WriteString("REFERENCES ") + buf.WriteString(c.ForeignTable.String()) + if len(c.ForeignColumns) > 0 { + buf.WriteString(" (") + for i := range c.ForeignColumns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(c.ForeignColumns[i].String()) + } + buf.WriteString(")") + } + + for i := range c.Args { + buf.WriteString(" ") + buf.WriteString(c.Args[i].String()) + } + + if c.Deferrable.IsValid() || c.NotDeferrable.IsValid() { + if c.Deferrable.IsValid() { + buf.WriteString(" DEFERRABLE") + } else { + buf.WriteString(" NOT DEFERRABLE") + } + + if c.InitiallyDeferred.IsValid() { + buf.WriteString(" INITIALLY DEFERRED") + } else if c.InitiallyImmediate.IsValid() { + buf.WriteString(" INITIALLY IMMEDIATE") + } + } + + return buf.String() +} + +type ForeignKeyArg struct { + On Pos // position of ON keyword + OnUpdate Pos // position of the UPDATE keyword + OnDelete Pos // position of the DELETE keyword + Set Pos // position of the SET keyword + SetNull Pos // position of the NULL keyword after SET + SetDefault Pos // position of the DEFAULT keyword after SET + Cascade Pos // position of the CASCADE keyword + Restrict Pos // position of the RESTRICT keyword + No Pos // position of the NO keyword + NoAction Pos // position of the ACTION keyword after NO +} + +// Clone returns a deep copy of arg. +func (arg *ForeignKeyArg) Clone() *ForeignKeyArg { + if arg == nil { + return nil + } + other := *arg + return &other +} + +func cloneForeignKeyArgs(a []*ForeignKeyArg) []*ForeignKeyArg { + if a == nil { + return nil + } + other := make([]*ForeignKeyArg, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the argument. +func (c *ForeignKeyArg) String() string { + var buf bytes.Buffer + buf.WriteString("ON") + if c.OnUpdate.IsValid() { + buf.WriteString(" UPDATE") + } else { + buf.WriteString(" DELETE") + } + + if c.SetNull.IsValid() { + buf.WriteString(" SET NULL") + } else if c.SetDefault.IsValid() { + buf.WriteString(" SET DEFAULT") + } else if c.Cascade.IsValid() { + buf.WriteString(" CASCADE") + } else if c.Restrict.IsValid() { + buf.WriteString(" RESTRICT") + } else if c.NoAction.IsValid() { + buf.WriteString(" NO ACTION") + } + return buf.String() +} + +type AnalyzeStatement struct { + Analyze Pos // position of ANALYZE keyword + Name *Ident // table name +} + +// Clone returns a deep copy of s. +func (s *AnalyzeStatement) Clone() *AnalyzeStatement { + if s == nil { + return nil + } + other := *s + other.Name = s.Name.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *AnalyzeStatement) String() string { + return fmt.Sprintf("ANALYZE %s", s.Name.String()) +} + +type AlterTableStatement struct { + Alter Pos // position of ALTER keyword + Table Pos // position of TABLE keyword + Name *Ident // table name + + Rename Pos // position of RENAME keyword + RenameTo Pos // position of TO keyword after RENAME + NewName *Ident // new table name + + RenameColumn Pos // position of COLUMN keyword after RENAME + ColumnName *Ident // new column name + To Pos // position of TO keyword + NewColumnName *Ident // new column name + + Add Pos // position of ADD keyword + AddColumn Pos // position of COLUMN keyword after ADD + ColumnDef *ColumnDefinition // new column definition +} + +// Clone returns a deep copy of s. +func (s *AlterTableStatement) Clone() *AlterTableStatement { + if s == nil { + return nil + } + other := *s + other.Name = other.Name.Clone() + other.NewName = s.NewName.Clone() + other.ColumnName = s.ColumnName.Clone() + other.NewColumnName = s.NewColumnName.Clone() + other.ColumnDef = s.ColumnDef.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *AlterTableStatement) String() string { + var buf bytes.Buffer + buf.WriteString("ALTER TABLE ") + buf.WriteString(s.Name.String()) + + if s.NewName != nil { + buf.WriteString(" RENAME TO ") + buf.WriteString(s.NewName.String()) + } else if s.ColumnName != nil { + buf.WriteString(" RENAME COLUMN ") + buf.WriteString(s.ColumnName.String()) + buf.WriteString(" TO ") + buf.WriteString(s.NewColumnName.String()) + } else if s.ColumnDef != nil { + buf.WriteString(" ADD COLUMN ") + buf.WriteString(s.ColumnDef.String()) + } + + return buf.String() +} + +type Ident struct { + NamePos Pos // identifier position + Name string // identifier name + Quoted bool // true if double quoted +} + +// IsAggregate returns false. +func (expr *Ident) IsAggregate() bool { return false } + +// Clone returns a deep copy of i. +func (i *Ident) Clone() *Ident { + if i == nil { + return nil + } + other := *i + return &other +} + +func cloneIdents(a []*Ident) []*Ident { + if a == nil { + return nil + } + other := make([]*Ident, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the expression. +func (i *Ident) String() string { + return `"` + strings.Replace(i.Name, `"`, `""`, -1) + `"` +} + +// IdentName returns the name of ident. Returns a blank string if ident is nil. +func IdentName(ident *Ident) string { + if ident == nil { + return "" + } + return ident.Name +} + +type Type struct { + Name *Ident // type name + Lparen Pos // position of left paren (optional) + Precision *NumberLit // precision (optional) + Scale *NumberLit // scale (optional) + Rparen Pos // position of right paren (optional) +} + +// Clone returns a deep copy of t. +func (t *Type) Clone() *Type { + if t == nil { + return nil + } + other := *t + other.Name = t.Name.Clone() + other.Precision = t.Precision.Clone() + other.Scale = t.Scale.Clone() + return &other +} + +// String returns the string representation of the type. +func (t *Type) String() string { + if t.Precision != nil && t.Scale != nil { + return fmt.Sprintf("%s(%s,%s)", t.Name.Name, t.Precision.String(), t.Scale.String()) + } else if t.Precision != nil { + return fmt.Sprintf("%s(%s)", t.Name.Name, t.Precision.String()) + } + return t.Name.Name +} + +type StringLit struct { + ValuePos Pos // literal position + Value string // literal value (without quotes) +} + +// IsAggregate returns false. +func (expr *StringLit) IsAggregate() bool { return false } + +// Clone returns a deep copy of lit. +func (lit *StringLit) Clone() *StringLit { + if lit == nil { + return nil + } + other := *lit + return &other +} + +// String returns the string representation of the expression. +func (lit *StringLit) String() string { + return `'` + strings.Replace(lit.Value, `'`, `''`, -1) + `'` +} + +type BlobLit struct { + ValuePos Pos // literal position + Value string // literal value +} + +// IsAggregate returns false. +func (expr *BlobLit) IsAggregate() bool { return false } + +// Clone returns a deep copy of lit. +func (lit *BlobLit) Clone() *BlobLit { + if lit == nil { + return nil + } + other := *lit + return &other +} + +// String returns the string representation of the expression. +func (lit *BlobLit) String() string { + return `x'` + lit.Value + `'` +} + +type NumberLit struct { + ValuePos Pos // literal position + Value string // literal value +} + +// IsAggregate returns false. +func (expr *NumberLit) IsAggregate() bool { return false } + +// Clone returns a deep copy of lit. +func (lit *NumberLit) Clone() *NumberLit { + if lit == nil { + return nil + } + other := *lit + return &other +} + +// String returns the string representation of the expression. +func (lit *NumberLit) String() string { + return lit.Value +} + +type NullLit struct { + Pos Pos +} + +// IsAggregate returns false. +func (expr *NullLit) IsAggregate() bool { return false } + +// Clone returns a deep copy of lit. +func (lit *NullLit) Clone() *NullLit { + if lit == nil { + return nil + } + other := *lit + return &other +} + +// String returns the string representation of the expression. +func (lit *NullLit) String() string { + return "NULL" +} + +type BoolLit struct { + ValuePos Pos // literal position + Value bool // literal value +} + +// IsAggregate returns false. +func (expr *BoolLit) IsAggregate() bool { return false } + +// Clone returns a deep copy of lit. +func (lit *BoolLit) Clone() *BoolLit { + if lit == nil { + return nil + } + other := *lit + return &other +} + +// String returns the string representation of the expression. +func (lit *BoolLit) String() string { + if lit.Value { + return "TRUE" + } + return "FALSE" +} + +type BindExpr struct { + NamePos Pos // name position + Name string // binding name +} + +// IsAggregate returns false. +func (expr *BindExpr) IsAggregate() bool { return false } + +// Clone returns a deep copy of expr. +func (expr *BindExpr) Clone() *BindExpr { + if expr == nil { + return nil + } + other := *expr + return &other +} + +// String returns the string representation of the expression. +func (expr *BindExpr) String() string { + // TODO(BBJ): Support all bind characters. + return "$" + expr.Name +} + +type UnaryExpr struct { + OpPos Pos // operation position + Op Token // operation + X Expr // target expression +} + +// IsAggregate returns true if it contains an aggregate call. +func (expr *UnaryExpr) IsAggregate() bool { + return expr.X.IsAggregate() +} + +// Clone returns a deep copy of expr. +func (expr *UnaryExpr) Clone() *UnaryExpr { + if expr == nil { + return nil + } + other := *expr + other.X = CloneExpr(expr.X) + return &other +} + +// String returns the string representation of the expression. +func (expr *UnaryExpr) String() string { + switch expr.Op { + case PLUS: + return "+" + expr.X.String() + case MINUS: + return "-" + expr.X.String() + default: + panic(fmt.Sprintf("sql.UnaryExpr.String(): invalid op %s", expr.Op)) + } +} + +type BinaryExpr struct { + X Expr // lhs + OpPos Pos // position of Op + Op Token // operator + Y Expr // rhs +} + +// IsAggregate returns true if it contains an aggregate call. +func (expr *BinaryExpr) IsAggregate() bool { + if expr.X.IsAggregate() { + return true + } + return expr.Y.IsAggregate() +} + +// Clone returns a deep copy of expr. +func (expr *BinaryExpr) Clone() *BinaryExpr { + if expr == nil { + return nil + } + other := *expr + other.X = CloneExpr(expr.X) + other.Y = CloneExpr(expr.Y) + return &other +} + +// String returns the string representation of the expression. +func (expr *BinaryExpr) String() string { + switch expr.Op { + case PLUS: + return expr.X.String() + " + " + expr.Y.String() + case MINUS: + return expr.X.String() + " - " + expr.Y.String() + case STAR: + return expr.X.String() + " * " + expr.Y.String() + case SLASH: + return expr.X.String() + " / " + expr.Y.String() + case REM: + return expr.X.String() + " % " + expr.Y.String() + case CONCAT: + return expr.X.String() + " || " + expr.Y.String() + case BETWEEN: + return expr.X.String() + " BETWEEN " + expr.Y.String() + case NOTBETWEEN: + return expr.X.String() + " NOT BETWEEN " + expr.Y.String() + case LSHIFT: + return expr.X.String() + " << " + expr.Y.String() + case RSHIFT: + return expr.X.String() + " >> " + expr.Y.String() + case BITAND: + return expr.X.String() + " & " + expr.Y.String() + case BITOR: + return expr.X.String() + " | " + expr.Y.String() + case LT: + return expr.X.String() + " < " + expr.Y.String() + case LE: + return expr.X.String() + " <= " + expr.Y.String() + case GT: + return expr.X.String() + " > " + expr.Y.String() + case GE: + return expr.X.String() + " >= " + expr.Y.String() + case EQ: + return expr.X.String() + " = " + expr.Y.String() + case NE: + return expr.X.String() + " != " + expr.Y.String() + case IS: + return expr.X.String() + " IS " + expr.Y.String() + case ISNOT: + return expr.X.String() + " IS NOT " + expr.Y.String() + case IN: + return expr.X.String() + " IN " + expr.Y.String() + case NOTIN: + return expr.X.String() + " NOT IN " + expr.Y.String() + case LIKE: + return expr.X.String() + " LIKE " + expr.Y.String() + case NOTLIKE: + return expr.X.String() + " NOT LIKE " + expr.Y.String() + case GLOB: + return expr.X.String() + " GLOB " + expr.Y.String() + case NOTGLOB: + return expr.X.String() + " NOT GLOB " + expr.Y.String() + case MATCH: + return expr.X.String() + " MATCH " + expr.Y.String() + case NOTMATCH: + return expr.X.String() + " NOT MATCH " + expr.Y.String() + case REGEXP: + return expr.X.String() + " REGEXP " + expr.Y.String() + case NOTREGEXP: + return expr.X.String() + " NOT REGEXP " + expr.Y.String() + case AND: + return expr.X.String() + " AND " + expr.Y.String() + case OR: + return expr.X.String() + " OR " + expr.Y.String() + default: + panic(fmt.Sprintf("sql.BinaryExpr.String(): invalid op %s", expr.Op)) + } +} + +type CastExpr struct { + Cast Pos // position of CAST keyword + Lparen Pos // position of left paren + X Expr // target expression + As Pos // position of AS keyword + Type *Type // cast type + Rparen Pos // position of right paren +} + +// IsAggregate returns true if it contains an aggregate call. +func (expr *CastExpr) IsAggregate() bool { + return expr.X.IsAggregate() +} + +// Clone returns a deep copy of expr. +func (expr *CastExpr) Clone() *CastExpr { + if expr == nil { + return nil + } + other := *expr + other.X = CloneExpr(expr.X) + other.Type = expr.Type.Clone() + return &other +} + +// String returns the string representation of the expression. +func (expr *CastExpr) String() string { + return fmt.Sprintf("CAST(%s AS %s)", expr.X.String(), expr.Type.String()) +} + +type CaseExpr struct { + Case Pos // position of CASE keyword + Operand Expr // optional condition after the CASE keyword + Blocks []*CaseBlock // list of WHEN/THEN pairs + Else Pos // position of ELSE keyword + ElseExpr Expr // expression used by default case + End Pos // position of END keyword +} + +// IsAggregate returns false +func (expr *CaseExpr) IsAggregate() bool { return false } + +// Clone returns a deep copy of expr. +func (expr *CaseExpr) Clone() *CaseExpr { + if expr == nil { + return nil + } + other := *expr + other.Operand = CloneExpr(expr.Operand) + other.Blocks = cloneCaseBlocks(expr.Blocks) + other.ElseExpr = CloneExpr(expr.ElseExpr) + return &other +} + +// String returns the string representation of the expression. +func (expr *CaseExpr) String() string { + var buf bytes.Buffer + buf.WriteString("CASE") + if expr.Operand != nil { + buf.WriteString(" ") + buf.WriteString(expr.Operand.String()) + } + for _, blk := range expr.Blocks { + buf.WriteString(" ") + buf.WriteString(blk.String()) + } + if expr.ElseExpr != nil { + buf.WriteString(" ELSE ") + buf.WriteString(expr.ElseExpr.String()) + } + buf.WriteString(" END") + return buf.String() +} + +type CaseBlock struct { + When Pos // position of WHEN keyword + Condition Expr // block condition + Then Pos // position of THEN keyword + Body Expr // result expression +} + +// Clone returns a deep copy of blk. +func (blk *CaseBlock) Clone() *CaseBlock { + if blk == nil { + return nil + } + other := *blk + other.Condition = CloneExpr(blk.Condition) + other.Body = CloneExpr(blk.Body) + return &other +} + +func cloneCaseBlocks(a []*CaseBlock) []*CaseBlock { + if a == nil { + return nil + } + other := make([]*CaseBlock, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the block. +func (b *CaseBlock) String() string { + return fmt.Sprintf("WHEN %s THEN %s", b.Condition.String(), b.Body.String()) +} + +type Raise struct { + Raise Pos // position of RAISE keyword + Lparen Pos // position of left paren + Ignore Pos // position of IGNORE keyword + Rollback Pos // position of ROLLBACK keyword + Abort Pos // position of ABORT keyword + Fail Pos // position of FAIL keyword + Comma Pos // position of comma + Error *StringLit // error message + Rparen Pos // position of right paren +} + +// IsAggregate returns false. +func (expr *Raise) IsAggregate() bool { return false } + +// Clone returns a deep copy of r. +func (r *Raise) Clone() *Raise { + if r == nil { + return nil + } + other := *r + other.Error = r.Error.Clone() + return &other +} + +// String returns the string representation of the raise function. +func (r *Raise) String() string { + var buf bytes.Buffer + buf.WriteString("RAISE(") + if r.Rollback.IsValid() { + fmt.Fprintf(&buf, "ROLLBACK, %s", r.Error.String()) + } else if r.Abort.IsValid() { + fmt.Fprintf(&buf, "ABORT, %s", r.Error.String()) + } else if r.Fail.IsValid() { + fmt.Fprintf(&buf, "FAIL, %s", r.Error.String()) + } else { + buf.WriteString("IGNORE") + } + buf.WriteString(")") + return buf.String() +} + +type Exists struct { + Not Pos // position of optional NOT keyword + Exists Pos // position of EXISTS keyword + Lparen Pos // position of left paren + Select *SelectStatement // select statement + Rparen Pos // position of right paren +} + +// IsAggregate returns false. +func (expr *Exists) IsAggregate() bool { return false } + +// Clone returns a deep copy of expr. +func (expr *Exists) Clone() *Exists { + if expr == nil { + return nil + } + other := *expr + other.Select = expr.Select.Clone() + return &other +} + +// String returns the string representation of the expression. +func (expr *Exists) String() string { + if expr.Not.IsValid() { + return fmt.Sprintf("NOT EXISTS (%s)", expr.Select.String()) + } + return fmt.Sprintf("EXISTS (%s)", expr.Select.String()) +} + +type ExprList struct { + Lparen Pos // position of left paren + Exprs []Expr // list of expressions + Rparen Pos // position of right paren +} + +// IsAggregate returns true if any child expression is an aggregate. +func (expr *ExprList) IsAggregate() bool { + for _, e := range expr.Exprs { + if e.IsAggregate() { + return true + } + } + return false +} + +// Clone returns a deep copy of l. +func (l *ExprList) Clone() *ExprList { + if l == nil { + return nil + } + other := *l + other.Exprs = cloneExprs(l.Exprs) + return &other +} + +func cloneExprLists(a []*ExprList) []*ExprList { + if a == nil { + return nil + } + other := make([]*ExprList, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the expression. +func (l *ExprList) String() string { + var buf bytes.Buffer + buf.WriteString("(") + for i, expr := range l.Exprs { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(expr.String()) + } + buf.WriteString(")") + return buf.String() +} + +type Range struct { + X Expr // lhs expression + And Pos // position of AND keyword + Y Expr // rhs expression +} + +// IsAggregate returns false. +func (expr *Range) IsAggregate() bool { return false } + +// Clone returns a deep copy of r. +func (r *Range) Clone() *Range { + if r == nil { + return nil + } + other := *r + other.X = CloneExpr(r.X) + other.Y = CloneExpr(r.Y) + return &other +} + +// String returns the string representation of the expression. +func (r *Range) String() string { + return fmt.Sprintf("%s AND %s", r.X.String(), r.Y.String()) +} + +type QualifiedRef struct { + Table *Ident // table name + Dot Pos // position of dot + Star Pos // position of * (result column only) + Column *Ident // column name +} + +// IsAggregate returns false. +func (expr *QualifiedRef) IsAggregate() bool { return false } + +// Clone returns a deep copy of r. +func (r *QualifiedRef) Clone() *QualifiedRef { + if r == nil { + return nil + } + other := *r + other.Table = r.Table.Clone() + other.Column = r.Column.Clone() + return &other +} + +// String returns the string representation of the expression. +func (r *QualifiedRef) String() string { + if r.Star.IsValid() { + return fmt.Sprintf("%s.*", r.Table.String()) + } + return fmt.Sprintf("%s.%s", r.Table.String(), r.Column.String()) +} + +type Call struct { + Name *Ident // function name + Lparen Pos // position of left paren + Star Pos // position of * + Distinct Pos // position of DISTINCT keyword + Args []Expr // argument list + Rparen Pos // position of right paren + Filter *FilterClause // filter clause + Over *OverClause // over clause +} + +// IsAggregate returns true if call is an aggregate function or it contains one. +func (expr *Call) IsAggregate() bool { + // Check if this is an aggregate call. + switch strings.ToUpper(IdentName(expr.Name)) { + case "COUNT", "MIN", "MAX", "SUM": + return true + } + + // Check if any arguments to the call are aggregate. + for _, arg := range expr.Args { + if arg.IsAggregate() { + return true + } + } + return false +} + +// Clone returns a deep copy of c. +func (c *Call) Clone() *Call { + if c == nil { + return nil + } + other := *c + other.Name = c.Name.Clone() + other.Args = cloneExprs(c.Args) + other.Filter = c.Filter.Clone() + other.Over = c.Over.Clone() + return &other +} + +// String returns the string representation of the expression. +func (c *Call) String() string { + var buf bytes.Buffer + buf.WriteString(c.Name.Name) + buf.WriteString("(") + if c.Star.IsValid() { + buf.WriteString("*") + } else { + if c.Distinct.IsValid() { + buf.WriteString("DISTINCT") + if len(c.Args) != 0 { + buf.WriteString(" ") + } + } + for i, arg := range c.Args { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(arg.String()) + } + } + buf.WriteString(")") + + if c.Filter != nil { + buf.WriteString(" ") + buf.WriteString(c.Filter.String()) + } + + if c.Over != nil { + buf.WriteString(" ") + buf.WriteString(c.Over.String()) + } + + return buf.String() +} + +type FilterClause struct { + Filter Pos // position of FILTER keyword + Lparen Pos // position of left paren + Where Pos // position of WHERE keyword + X Expr // filter expression + Rparen Pos // position of right paren +} + +// Clone returns a deep copy of c. +func (c *FilterClause) Clone() *FilterClause { + if c == nil { + return nil + } + other := *c + other.X = CloneExpr(c.X) + return &other +} + +// String returns the string representation of the clause. +func (c *FilterClause) String() string { + return fmt.Sprintf("FILTER (WHERE %s)", c.X.String()) +} + +type OverClause struct { + Over Pos // position of OVER keyword + Name *Ident // window name + Definition *WindowDefinition // window definition +} + +// Clone returns a deep copy of c. +func (c *OverClause) Clone() *OverClause { + if c == nil { + return nil + } + other := *c + other.Name = c.Name.Clone() + other.Definition = c.Definition.Clone() + return &other +} + +// String returns the string representation of the clause. +func (c *OverClause) String() string { + if c.Name != nil { + return fmt.Sprintf("OVER %s", c.Name.String()) + } + return fmt.Sprintf("OVER %s", c.Definition.String()) +} + +type OrderingTerm struct { + X Expr // ordering expression + + Asc Pos // position of ASC keyword + Desc Pos // position of DESC keyword + + Nulls Pos // position of NULLS keyword + NullsFirst Pos // position of FIRST keyword + NullsLast Pos // position of LAST keyword +} + +// Clone returns a deep copy of t. +func (t *OrderingTerm) Clone() *OrderingTerm { + if t == nil { + return nil + } + other := *t + other.X = CloneExpr(t.X) + return &other +} + +func cloneOrderingTerms(a []*OrderingTerm) []*OrderingTerm { + if a == nil { + return nil + } + other := make([]*OrderingTerm, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the term. +func (t *OrderingTerm) String() string { + var buf bytes.Buffer + buf.WriteString(t.X.String()) + + if t.Asc.IsValid() { + buf.WriteString(" ASC") + } else if t.Desc.IsValid() { + buf.WriteString(" DESC") + } + + if t.NullsFirst.IsValid() { + buf.WriteString(" NULLS FIRST") + } else if t.NullsLast.IsValid() { + buf.WriteString(" NULLS LAST") + } + + return buf.String() +} + +type FrameSpec struct { + Range Pos // position of RANGE keyword + Rows Pos // position of ROWS keyword + Groups Pos // position of GROUPS keyword + + Between Pos // position of BETWEEN keyword + + X Expr // lhs expression + UnboundedX Pos // position of lhs UNBOUNDED keyword + PrecedingX Pos // position of lhs PRECEDING keyword + CurrentX Pos // position of lhs CURRENT keyword + CurrentRowX Pos // position of lhs ROW keyword + FollowingX Pos // position of lhs FOLLOWING keyword + + And Pos // position of AND keyword + + Y Expr // lhs expression + UnboundedY Pos // position of rhs UNBOUNDED keyword + FollowingY Pos // position of rhs FOLLOWING keyword + CurrentY Pos // position of rhs CURRENT keyword + CurrentRowY Pos // position of rhs ROW keyword + PrecedingY Pos // position of rhs PRECEDING keyword + + Exclude Pos // position of EXCLUDE keyword + ExcludeNo Pos // position of NO keyword after EXCLUDE + ExcludeNoOthers Pos // position of OTHERS keyword after EXCLUDE NO + ExcludeCurrent Pos // position of CURRENT keyword after EXCLUDE + ExcludeCurrentRow Pos // position of ROW keyword after EXCLUDE CURRENT + ExcludeGroup Pos // position of GROUP keyword after EXCLUDE + ExcludeTies Pos // position of TIES keyword after EXCLUDE +} + +// Clone returns a deep copy of s. +func (s *FrameSpec) Clone() *FrameSpec { + if s == nil { + return nil + } + other := *s + other.X = CloneExpr(s.X) + other.X = CloneExpr(s.Y) + return &other +} + +// String returns the string representation of the frame spec. +func (s *FrameSpec) String() string { + var buf bytes.Buffer + if s.Range.IsValid() { + buf.WriteString("RANGE") + } else if s.Rows.IsValid() { + buf.WriteString("ROWS") + } else if s.Groups.IsValid() { + buf.WriteString("GROUPS") + } + + if s.Between.IsValid() { + buf.WriteString(" BETWEEN") + if s.UnboundedX.IsValid() && s.PrecedingX.IsValid() { + buf.WriteString(" UNBOUNDED PRECEDING") + } else if s.X != nil && s.PrecedingX.IsValid() { + fmt.Fprintf(&buf, " %s PRECEDING", s.X.String()) + } else if s.CurrentRowX.IsValid() { + buf.WriteString(" CURRENT ROW") + } else if s.X != nil && s.FollowingX.IsValid() { + fmt.Fprintf(&buf, " %s FOLLOWING", s.X.String()) + } + + buf.WriteString(" AND") + + if s.Y != nil && s.PrecedingY.IsValid() { + fmt.Fprintf(&buf, " %s PRECEDING", s.Y.String()) + } else if s.CurrentRowY.IsValid() { + buf.WriteString(" CURRENT ROW") + } else if s.Y != nil && s.FollowingY.IsValid() { + fmt.Fprintf(&buf, " %s FOLLOWING", s.Y.String()) + } else if s.UnboundedY.IsValid() && s.FollowingY.IsValid() { + buf.WriteString(" UNBOUNDED FOLLOWING") + } + } else { + if s.UnboundedX.IsValid() && s.PrecedingX.IsValid() { + buf.WriteString(" UNBOUNDED PRECEDING") + } else if s.X != nil && s.PrecedingX.IsValid() { + fmt.Fprintf(&buf, " %s PRECEDING", s.X.String()) + } else if s.CurrentRowX.IsValid() { + buf.WriteString(" CURRENT ROW") + } + } + + if s.ExcludeNoOthers.IsValid() { + buf.WriteString(" EXCLUDE NO OTHERS") + } else if s.ExcludeCurrentRow.IsValid() { + buf.WriteString(" EXCLUDE CURRENT ROW") + } else if s.ExcludeGroup.IsValid() { + buf.WriteString(" EXCLUDE GROUP") + } else if s.ExcludeTies.IsValid() { + buf.WriteString(" EXCLUDE TIES") + } + + return buf.String() +} + +type ColumnArg interface { + Node + columnArg() +} + +type DropTableStatement struct { + Drop Pos // position of DROP keyword + Table Pos // position of TABLE keyword + If Pos // position of IF keyword + IfExists Pos // position of EXISTS keyword after IF + Name *Ident // view name +} + +// Clone returns a deep copy of s. +func (s *DropTableStatement) Clone() *DropTableStatement { + if s == nil { + return nil + } + other := *s + other.Name = s.Name.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *DropTableStatement) String() string { + var buf bytes.Buffer + buf.WriteString("DROP TABLE") + if s.IfExists.IsValid() { + buf.WriteString(" IF EXISTS") + } + fmt.Fprintf(&buf, " %s", s.Name.String()) + return buf.String() +} + +type CreateViewStatement struct { + Create Pos // position of CREATE keyword + View Pos // position of VIEW keyword + If Pos // position of IF keyword + IfNot Pos // position of NOT keyword after IF + IfNotExists Pos // position of EXISTS keyword after IF NOT + Name *Ident // view name + Lparen Pos // position of column list left paren + Columns []*Ident // column list + Rparen Pos // position of column list right paren + As Pos // position of AS keyword + Select *SelectStatement // source statement +} + +// Clone returns a deep copy of s. +func (s *CreateViewStatement) Clone() *CreateViewStatement { + if s == nil { + return nil + } + other := *s + other.Name = s.Name.Clone() + other.Columns = cloneIdents(s.Columns) + other.Select = s.Select.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *CreateViewStatement) String() string { + var buf bytes.Buffer + buf.WriteString("CREATE VIEW") + if s.IfNotExists.IsValid() { + buf.WriteString(" IF NOT EXISTS") + } + fmt.Fprintf(&buf, " %s", s.Name.String()) + + if len(s.Columns) > 0 { + buf.WriteString(" (") + for i, col := range s.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + buf.WriteString(")") + } + + fmt.Fprintf(&buf, " AS %s", s.Select.String()) + + return buf.String() +} + +type DropViewStatement struct { + Drop Pos // position of DROP keyword + View Pos // position of VIEW keyword + If Pos // position of IF keyword + IfExists Pos // position of EXISTS keyword after IF + Name *Ident // view name +} + +// Clone returns a deep copy of s. +func (s *DropViewStatement) Clone() *DropViewStatement { + if s == nil { + return nil + } + other := *s + other.Name = s.Name.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *DropViewStatement) String() string { + var buf bytes.Buffer + buf.WriteString("DROP VIEW") + if s.IfExists.IsValid() { + buf.WriteString(" IF EXISTS") + } + fmt.Fprintf(&buf, " %s", s.Name.String()) + return buf.String() +} + +type CreateIndexStatement struct { + Create Pos // position of CREATE keyword + Unique Pos // position of optional UNIQUE keyword + Index Pos // position of INDEX keyword + If Pos // position of IF keyword + IfNot Pos // position of NOT keyword after IF + IfNotExists Pos // position of EXISTS keyword after IF NOT + Name *Ident // index name + On Pos // position of ON keyword + Table *Ident // index name + Lparen Pos // position of column list left paren + Columns []*IndexedColumn // column list + Rparen Pos // position of column list right paren + Where Pos // position of WHERE keyword + WhereExpr Expr // conditional expression +} + +// Clone returns a deep copy of s. +func (s *CreateIndexStatement) Clone() *CreateIndexStatement { + if s == nil { + return nil + } + other := *s + other.Name = s.Name.Clone() + other.Table = s.Table.Clone() + other.Columns = cloneIndexedColumns(s.Columns) + other.WhereExpr = CloneExpr(s.WhereExpr) + return &other +} + +// String returns the string representation of the statement. +func (s *CreateIndexStatement) String() string { + var buf bytes.Buffer + buf.WriteString("CREATE") + if s.Unique.IsValid() { + buf.WriteString(" UNIQUE") + } + buf.WriteString(" INDEX") + if s.IfNotExists.IsValid() { + buf.WriteString(" IF NOT EXISTS") + } + fmt.Fprintf(&buf, " %s ON %s ", s.Name.String(), s.Table.String()) + + buf.WriteString("(") + for i, col := range s.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + buf.WriteString(")") + + if s.WhereExpr != nil { + fmt.Fprintf(&buf, " WHERE %s", s.WhereExpr.String()) + } + + return buf.String() +} + +type DropIndexStatement struct { + Drop Pos // position of DROP keyword + Index Pos // position of INDEX keyword + If Pos // position of IF keyword + IfExists Pos // position of EXISTS keyword after IF + Name *Ident // index name +} + +// Clone returns a deep copy of s. +func (s *DropIndexStatement) Clone() *DropIndexStatement { + if s == nil { + return nil + } + other := *s + other.Name = s.Name.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *DropIndexStatement) String() string { + var buf bytes.Buffer + buf.WriteString("DROP INDEX") + if s.IfExists.IsValid() { + buf.WriteString(" IF EXISTS") + } + fmt.Fprintf(&buf, " %s", s.Name.String()) + return buf.String() +} + +type CreateTriggerStatement struct { + Create Pos // position of CREATE keyword + Trigger Pos // position of TRIGGER keyword + If Pos // position of IF keyword + IfNot Pos // position of NOT keyword after IF + IfNotExists Pos // position of EXISTS keyword after IF NOT + Name *Ident // index name + + Before Pos // position of BEFORE keyword + After Pos // position of AFTER keyword + Instead Pos // position of INSTEAD keyword + InsteadOf Pos // position of OF keyword after INSTEAD + + Delete Pos // position of DELETE keyword + Insert Pos // position of INSERT keyword + Update Pos // position of UPDATE keyword + UpdateOf Pos // position of OF keyword after UPDATE + UpdateOfColumns []*Ident // columns list for UPDATE OF + On Pos // position of ON keyword + Table *Ident // table name + + For Pos // position of FOR keyword + ForEach Pos // position of EACH keyword after FOR + ForEachRow Pos // position of ROW keyword after FOR EACH + + When Pos // position of WHEN keyword + WhenExpr Expr // conditional expression + + Begin Pos // position of BEGIN keyword + Body []Statement // trigger body + End Pos // position of END keyword +} + +// Clone returns a deep copy of s. +func (s *CreateTriggerStatement) Clone() *CreateTriggerStatement { + if s == nil { + return nil + } + other := *s + other.Name = s.Name.Clone() + other.UpdateOfColumns = cloneIdents(s.UpdateOfColumns) + other.Table = s.Table.Clone() + other.WhenExpr = CloneExpr(s.WhenExpr) + other.Body = cloneStatements(s.Body) + return &other +} + +// String returns the string representation of the statement. +func (s *CreateTriggerStatement) String() string { + var buf bytes.Buffer + buf.WriteString("CREATE TRIGGER") + if s.IfNotExists.IsValid() { + buf.WriteString(" IF NOT EXISTS") + } + fmt.Fprintf(&buf, " %s", s.Name.String()) + + if s.Before.IsValid() { + buf.WriteString(" BEFORE") + } else if s.After.IsValid() { + buf.WriteString(" AFTER") + } else if s.InsteadOf.IsValid() { + buf.WriteString(" INSTEAD OF") + } + + if s.Delete.IsValid() { + buf.WriteString(" DELETE") + } else if s.Insert.IsValid() { + buf.WriteString(" INSERT") + } else if s.Update.IsValid() { + buf.WriteString(" UPDATE") + if s.UpdateOf.IsValid() { + buf.WriteString(" OF ") + for i, col := range s.UpdateOfColumns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + } + } + + fmt.Fprintf(&buf, " ON %s", s.Table.String()) + + if s.ForEachRow.IsValid() { + buf.WriteString(" FOR EACH ROW") + } + + if s.WhenExpr != nil { + fmt.Fprintf(&buf, " WHEN %s", s.WhenExpr.String()) + } + + buf.WriteString(" BEGIN") + for i := range s.Body { + fmt.Fprintf(&buf, " %s;", s.Body[i].String()) + } + buf.WriteString(" END") + + return buf.String() +} + +type DropTriggerStatement struct { + Drop Pos // position of DROP keyword + Trigger Pos // position of TRIGGER keyword + If Pos // position of IF keyword + IfExists Pos // position of EXISTS keyword after IF + Name *Ident // trigger name +} + +// Clone returns a deep copy of s. +func (s *DropTriggerStatement) Clone() *DropTriggerStatement { + if s == nil { + return nil + } + other := *s + other.Name = s.Name.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *DropTriggerStatement) String() string { + var buf bytes.Buffer + buf.WriteString("DROP TRIGGER") + if s.IfExists.IsValid() { + buf.WriteString(" IF EXISTS") + } + fmt.Fprintf(&buf, " %s", s.Name.String()) + return buf.String() +} + +type InsertStatement struct { + WithClause *WithClause // clause containing CTEs + + Insert Pos // position of INSERT keyword + Replace Pos // position of REPLACE keyword + InsertOr Pos // position of OR keyword after INSERT + InsertOrReplace Pos // position of REPLACE keyword after INSERT OR + InsertOrRollback Pos // position of ROLLBACK keyword after INSERT OR + InsertOrAbort Pos // position of ABORT keyword after INSERT OR + InsertOrFail Pos // position of FAIL keyword after INSERT OR + InsertOrIgnore Pos // position of IGNORE keyword after INSERT OR + Into Pos // position of INTO keyword + + Table *Ident // table name + As Pos // position of AS keyword + Alias *Ident // optional alias + + ColumnsLparen Pos // position of column list left paren + Columns []*Ident // optional column list + ColumnsRparen Pos // position of column list right paren + + Values Pos // position of VALUES keyword + ValueLists []*ExprList // lists of lists of values + + Select *SelectStatement // SELECT statement + + Default Pos // position of DEFAULT keyword + DefaultValues Pos // position of VALUES keyword after DEFAULT + + UpsertClause *UpsertClause // optional upsert clause +} + +// Clone returns a deep copy of s. +func (s *InsertStatement) Clone() *InsertStatement { + if s == nil { + return nil + } + other := *s + other.WithClause = s.WithClause.Clone() + other.Table = s.Table.Clone() + other.Alias = s.Alias.Clone() + other.Columns = cloneIdents(s.Columns) + other.ValueLists = cloneExprLists(s.ValueLists) + other.Select = s.Select.Clone() + other.UpsertClause = s.UpsertClause.Clone() + return &other +} + +// String returns the string representation of the statement. +func (s *InsertStatement) String() string { + var buf bytes.Buffer + if s.WithClause != nil { + buf.WriteString(s.WithClause.String()) + buf.WriteString(" ") + } + + if s.Replace.IsValid() { + buf.WriteString("REPLACE") + } else { + buf.WriteString("INSERT") + if s.InsertOrReplace.IsValid() { + buf.WriteString(" OR REPLACE") + } else if s.InsertOrRollback.IsValid() { + buf.WriteString(" OR ROLLBACK") + } else if s.InsertOrAbort.IsValid() { + buf.WriteString(" OR ABORT") + } else if s.InsertOrFail.IsValid() { + buf.WriteString(" OR FAIL") + } else if s.InsertOrIgnore.IsValid() { + buf.WriteString(" OR IGNORE") + } + } + + fmt.Fprintf(&buf, " INTO %s", s.Table.String()) + if s.Alias != nil { + fmt.Fprintf(&buf, " AS %s", s.Alias.String()) + } + + if len(s.Columns) != 0 { + buf.WriteString(" (") + for i, col := range s.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + buf.WriteString(")") + } + + if s.DefaultValues.IsValid() { + buf.WriteString(" DEFAULT VALUES") + } else if s.Select != nil { + fmt.Fprintf(&buf, " %s", s.Select.String()) + } else { + buf.WriteString(" VALUES") + for i := range s.ValueLists { + if i != 0 { + buf.WriteString(",") + } + buf.WriteString(" (") + for j, expr := range s.ValueLists[i].Exprs { + if j != 0 { + buf.WriteString(", ") + } + buf.WriteString(expr.String()) + } + buf.WriteString(")") + } + } + + if s.UpsertClause != nil { + fmt.Fprintf(&buf, " %s", s.UpsertClause.String()) + } + + return buf.String() +} + +type UpsertClause struct { + On Pos // position of ON keyword + OnConflict Pos // position of CONFLICT keyword after ON + + Lparen Pos // position of column list left paren + Columns []*IndexedColumn // optional indexed column list + Rparen Pos // position of column list right paren + Where Pos // position of WHERE keyword + WhereExpr Expr // optional conditional expression + + Do Pos // position of DO keyword + DoNothing Pos // position of NOTHING keyword after DO + DoUpdate Pos // position of UPDATE keyword after DO + DoUpdateSet Pos // position of SET keyword after DO UPDATE + Assignments []*Assignment // list of column assignments + UpdateWhere Pos // position of WHERE keyword for DO UPDATE SET + UpdateWhereExpr Expr // optional conditional expression for DO UPDATE SET +} + +// Clone returns a deep copy of c. +func (c *UpsertClause) Clone() *UpsertClause { + if c == nil { + return nil + } + other := *c + other.Columns = cloneIndexedColumns(c.Columns) + other.WhereExpr = CloneExpr(c.WhereExpr) + other.Assignments = cloneAssignments(c.Assignments) + other.UpdateWhereExpr = CloneExpr(c.UpdateWhereExpr) + return &other +} + +// String returns the string representation of the clause. +func (c *UpsertClause) String() string { + var buf bytes.Buffer + buf.WriteString("ON CONFLICT") + + if len(c.Columns) != 0 { + buf.WriteString(" (") + for i, col := range c.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + buf.WriteString(")") + + if c.WhereExpr != nil { + fmt.Fprintf(&buf, " WHERE %s", c.WhereExpr.String()) + } + } + + buf.WriteString(" DO") + if c.DoNothing.IsValid() { + buf.WriteString(" NOTHING") + } else { + buf.WriteString(" UPDATE SET ") + for i := range c.Assignments { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(c.Assignments[i].String()) + } + + if c.UpdateWhereExpr != nil { + fmt.Fprintf(&buf, " WHERE %s", c.UpdateWhereExpr.String()) + } + } + + return buf.String() +} + +type UpdateStatement struct { + WithClause *WithClause // clause containing CTEs + + Update Pos // position of UPDATE keyword + UpdateOr Pos // position of OR keyword after UPDATE + UpdateOrReplace Pos // position of REPLACE keyword after UPDATE OR + UpdateOrRollback Pos // position of ROLLBACK keyword after UPDATE OR + UpdateOrAbort Pos // position of ABORT keyword after UPDATE OR + UpdateOrFail Pos // position of FAIL keyword after UPDATE OR + UpdateOrIgnore Pos // position of IGNORE keyword after UPDATE OR + + Table *QualifiedTableName // table name + + Set Pos // position of SET keyword + Assignments []*Assignment // list of column assignments + Where Pos // position of WHERE keyword + WhereExpr Expr // conditional expression +} + +// Clone returns a deep copy of s. +func (s *UpdateStatement) Clone() *UpdateStatement { + if s == nil { + return nil + } + other := *s + other.WithClause = s.WithClause.Clone() + other.Table = s.Table.Clone() + other.Assignments = cloneAssignments(s.Assignments) + other.WhereExpr = CloneExpr(s.WhereExpr) + return &other +} + +// String returns the string representation of the clause. +func (s *UpdateStatement) String() string { + var buf bytes.Buffer + if s.WithClause != nil { + buf.WriteString(s.WithClause.String()) + buf.WriteString(" ") + } + + buf.WriteString("UPDATE") + if s.UpdateOrRollback.IsValid() { + buf.WriteString(" OR ROLLBACK") + } else if s.UpdateOrAbort.IsValid() { + buf.WriteString(" OR ABORT") + } else if s.UpdateOrReplace.IsValid() { + buf.WriteString(" OR REPLACE") + } else if s.UpdateOrFail.IsValid() { + buf.WriteString(" OR FAIL") + } else if s.UpdateOrIgnore.IsValid() { + buf.WriteString(" OR IGNORE") + } + + fmt.Fprintf(&buf, " %s ", s.Table.String()) + + buf.WriteString("SET ") + for i := range s.Assignments { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(s.Assignments[i].String()) + } + + if s.WhereExpr != nil { + fmt.Fprintf(&buf, " WHERE %s", s.WhereExpr.String()) + } + + return buf.String() +} + +type DeleteStatement struct { + WithClause *WithClause // clause containing CTEs + Delete Pos // position of UPDATE keyword + From Pos // position of FROM keyword + Table *QualifiedTableName // table name + + Where Pos // position of WHERE keyword + WhereExpr Expr // conditional expression + + Order Pos // position of ORDER keyword + OrderBy Pos // position of BY keyword after ORDER + OrderingTerms []*OrderingTerm // terms of ORDER BY clause + + Limit Pos // position of LIMIT keyword + LimitExpr Expr // limit expression + Offset Pos // position of OFFSET keyword + OffsetComma Pos // position of COMMA (instead of OFFSET) + OffsetExpr Expr // offset expression +} + +// Clone returns a deep copy of s. +func (s *DeleteStatement) Clone() *DeleteStatement { + if s == nil { + return nil + } + other := *s + other.WithClause = s.WithClause.Clone() + other.Table = s.Table.Clone() + other.WhereExpr = CloneExpr(s.WhereExpr) + other.OrderingTerms = cloneOrderingTerms(s.OrderingTerms) + other.LimitExpr = CloneExpr(s.LimitExpr) + other.OffsetExpr = CloneExpr(s.OffsetExpr) + return &other +} + +// String returns the string representation of the clause. +func (s *DeleteStatement) String() string { + var buf bytes.Buffer + if s.WithClause != nil { + buf.WriteString(s.WithClause.String()) + buf.WriteString(" ") + } + + fmt.Fprintf(&buf, "DELETE FROM %s", s.Table.String()) + if s.WhereExpr != nil { + fmt.Fprintf(&buf, " WHERE %s", s.WhereExpr.String()) + } + + // Write ORDER BY. + if len(s.OrderingTerms) != 0 { + buf.WriteString(" ORDER BY ") + for i, term := range s.OrderingTerms { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(term.String()) + } + } + + // Write LIMIT/OFFSET. + if s.LimitExpr != nil { + fmt.Fprintf(&buf, " LIMIT %s", s.LimitExpr.String()) + if s.OffsetExpr != nil { + fmt.Fprintf(&buf, " OFFSET %s", s.OffsetExpr.String()) + } + } + + return buf.String() +} + +// Assignment is used within the UPDATE statement & upsert clause. +// It is similiar to an expression except that it must be an equality. +type Assignment struct { + Lparen Pos // position of column list left paren + Columns []*Ident // column list + Rparen Pos // position of column list right paren + Eq Pos // position of = + Expr Expr // assigned expression +} + +// Clone returns a deep copy of a. +func (a *Assignment) Clone() *Assignment { + if a == nil { + return nil + } + other := *a + other.Columns = cloneIdents(a.Columns) + other.Expr = CloneExpr(a.Expr) + return &other +} + +func cloneAssignments(a []*Assignment) []*Assignment { + if a == nil { + return nil + } + other := make([]*Assignment, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the clause. +func (a *Assignment) String() string { + var buf bytes.Buffer + if len(a.Columns) == 1 { + buf.WriteString(a.Columns[0].String()) + } else if len(a.Columns) > 1 { + buf.WriteString("(") + for i, col := range a.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + buf.WriteString(")") + } + + fmt.Fprintf(&buf, " = %s", a.Expr.String()) + return buf.String() +} + +type IndexedColumn struct { + X Expr // column expression + Asc Pos // position of optional ASC keyword + Desc Pos // position of optional DESC keyword +} + +// Clone returns a deep copy of c. +func (c *IndexedColumn) Clone() *IndexedColumn { + if c == nil { + return nil + } + other := *c + other.X = CloneExpr(c.X) + return &other +} + +func cloneIndexedColumns(a []*IndexedColumn) []*IndexedColumn { + if a == nil { + return nil + } + other := make([]*IndexedColumn, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the column. +func (c *IndexedColumn) String() string { + if c.Asc.IsValid() { + return fmt.Sprintf("%s ASC", c.X.String()) + } else if c.Desc.IsValid() { + return fmt.Sprintf("%s DESC", c.X.String()) + } + return c.X.String() +} + +type SelectStatement struct { + WithClause *WithClause // clause containing CTEs + + Values Pos // position of VALUES keyword + ValueLists []*ExprList // lists of lists of values + + Select Pos // position of SELECT keyword + Distinct Pos // position of DISTINCT keyword + All Pos // position of ALL keyword + Columns []*ResultColumn // list of result columns in the SELECT clause + + From Pos // position of FROM keyword + Source Source // chain of tables & subqueries in FROM clause + + Where Pos // position of WHERE keyword + WhereExpr Expr // condition for WHERE clause + + Group Pos // position of GROUP keyword + GroupBy Pos // position of BY keyword after GROUP + GroupByExprs []Expr // group by expression list + Having Pos // position of HAVING keyword + HavingExpr Expr // HAVING expression + + Window Pos // position of WINDOW keyword + Windows []*Window // window list + + Union Pos // position of UNION keyword + UnionAll Pos // position of ALL keyword after UNION + Intersect Pos // position of INTERSECT keyword + Except Pos // position of EXCEPT keyword + Compound *SelectStatement // compounded SELECT statement + + Order Pos // position of ORDER keyword + OrderBy Pos // position of BY keyword after ORDER + OrderingTerms []*OrderingTerm // terms of ORDER BY clause + + Limit Pos // position of LIMIT keyword + LimitExpr Expr // limit expression + Offset Pos // position of OFFSET keyword + OffsetComma Pos // position of COMMA (instead of OFFSET) + OffsetExpr Expr // offset expression +} + +// Clone returns a deep copy of s. +func (s *SelectStatement) Clone() *SelectStatement { + if s == nil { + return nil + } + other := *s + other.WithClause = s.WithClause.Clone() + other.ValueLists = cloneExprLists(s.ValueLists) + other.Columns = cloneResultColumns(s.Columns) + other.Source = CloneSource(s.Source) + other.WhereExpr = CloneExpr(s.WhereExpr) + other.GroupByExprs = cloneExprs(s.GroupByExprs) + other.HavingExpr = CloneExpr(s.HavingExpr) + other.Windows = cloneWindows(s.Windows) + other.Compound = s.Compound.Clone() + other.OrderingTerms = cloneOrderingTerms(s.OrderingTerms) + other.LimitExpr = CloneExpr(s.LimitExpr) + other.OffsetExpr = CloneExpr(s.OffsetExpr) + return &other +} + +// IsAggregate returns true if statement contains aggregate columns. +func (s *SelectStatement) IsAggregate() bool { + for _, col := range s.Columns { + if col.IsAggregate() { + return true + } + } + return false +} + +// String returns the string representation of the statement. +func (s *SelectStatement) String() string { + var buf bytes.Buffer + if s.WithClause != nil { + buf.WriteString(s.WithClause.String()) + buf.WriteString(" ") + } + + if len(s.ValueLists) > 0 { + buf.WriteString("VALUES ") + for i, exprs := range s.ValueLists { + if i != 0 { + buf.WriteString(", ") + } + + buf.WriteString("(") + for j, expr := range exprs.Exprs { + if j != 0 { + buf.WriteString(", ") + } + buf.WriteString(expr.String()) + } + buf.WriteString(")") + } + } else { + buf.WriteString("SELECT ") + if s.Distinct.IsValid() { + buf.WriteString("DISTINCT ") + } else if s.All.IsValid() { + buf.WriteString("ALL ") + } + + for i, col := range s.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + + if s.Source != nil { + fmt.Fprintf(&buf, " FROM %s", s.Source.String()) + } + + if s.WhereExpr != nil { + fmt.Fprintf(&buf, " WHERE %s", s.WhereExpr.String()) + } + + if len(s.GroupByExprs) != 0 { + buf.WriteString(" GROUP BY ") + for i, expr := range s.GroupByExprs { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(expr.String()) + } + + if s.HavingExpr != nil { + fmt.Fprintf(&buf, " HAVING %s", s.HavingExpr.String()) + } + } + + if len(s.Windows) != 0 { + buf.WriteString(" WINDOW ") + for i, window := range s.Windows { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(window.String()) + } + } + } + + // Write compound operator. + if s.Compound != nil { + switch { + case s.Union.IsValid(): + buf.WriteString(" UNION") + if s.UnionAll.IsValid() { + buf.WriteString(" ALL") + } + case s.Intersect.IsValid(): + buf.WriteString(" INTERSECT") + case s.Except.IsValid(): + buf.WriteString(" EXCEPT") + } + + fmt.Fprintf(&buf, " %s", s.Compound.String()) + } + + // Write ORDER BY. + if len(s.OrderingTerms) != 0 { + buf.WriteString(" ORDER BY ") + for i, term := range s.OrderingTerms { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(term.String()) + } + } + + // Write LIMIT/OFFSET. + if s.LimitExpr != nil { + fmt.Fprintf(&buf, " LIMIT %s", s.LimitExpr.String()) + if s.OffsetExpr != nil { + fmt.Fprintf(&buf, " OFFSET %s", s.OffsetExpr.String()) + } + } + + return buf.String() +} + +type ResultColumn struct { + Star Pos // position of * + Expr Expr // column expression (may be "tbl.*") + As Pos // position of AS keyword + Alias *Ident // alias name +} + +// IsAggregate returns true if column contains an aggregate function expression. +func (c *ResultColumn) IsAggregate() bool { + if c.Star.IsValid() { + return false + } + return c.Expr.IsAggregate() +} + +// Clone returns a deep copy of c. +func (c *ResultColumn) Clone() *ResultColumn { + if c == nil { + return nil + } + other := *c + other.Expr = CloneExpr(c.Expr) + other.Alias = c.Alias.Clone() + return &other +} + +func cloneResultColumns(a []*ResultColumn) []*ResultColumn { + if a == nil { + return nil + } + other := make([]*ResultColumn, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the column. +func (c *ResultColumn) String() string { + if c.Star.IsValid() { + return "*" + } else if c.Alias != nil { + return fmt.Sprintf("%s AS %s", c.Expr.String(), c.Alias.String()) + } + return c.Expr.String() +} + +type QualifiedTableName struct { + Name *Ident // table name + As Pos // position of AS keyword + Alias *Ident // optional table alias + Indexed Pos // position of INDEXED keyword + IndexedBy Pos // position of BY keyword after INDEXED + Not Pos // position of NOT keyword before INDEXED + NotIndexed Pos // position of NOT keyword before INDEXED + Index *Ident // name of index +} + +// TableName returns the name used to identify n. +// Returns the alias, if one is specified. Otherwise returns the name. +func (n *QualifiedTableName) TableName() string { + if s := IdentName(n.Alias); s != "" { + return s + } + return IdentName(n.Name) +} + +// Clone returns a deep copy of n. +func (n *QualifiedTableName) Clone() *QualifiedTableName { + if n == nil { + return nil + } + other := *n + other.Name = n.Name.Clone() + other.Alias = n.Alias.Clone() + other.Index = n.Index.Clone() + return &other +} + +// String returns the string representation of the table name. +func (n *QualifiedTableName) String() string { + var buf bytes.Buffer + buf.WriteString(n.Name.String()) + if n.Alias != nil { + fmt.Fprintf(&buf, " AS %s", n.Alias.String()) + } + + if n.Index != nil { + fmt.Fprintf(&buf, " INDEXED BY %s", n.Index.String()) + } else if n.NotIndexed.IsValid() { + buf.WriteString(" NOT INDEXED") + } + return buf.String() +} + +type ParenSource struct { + Lparen Pos // position of left paren + X Source // nested source + Rparen Pos // position of right paren + As Pos // position of AS keyword (select source only) + Alias *Ident // optional table alias (select source only) +} + +// Clone returns a deep copy of s. +func (s *ParenSource) Clone() *ParenSource { + if s == nil { + return nil + } + other := *s + other.X = CloneSource(s.X) + other.Alias = s.Alias.Clone() + return &other +} + +// String returns the string representation of the source. +func (s *ParenSource) String() string { + if s.Alias != nil { + return fmt.Sprintf("(%s) AS %s", s.X.String(), s.Alias.String()) + } + return fmt.Sprintf("(%s)", s.X.String()) +} + +type JoinClause struct { + X Source // lhs source + Operator *JoinOperator // join operator + Y Source // rhs source + Constraint JoinConstraint // join constraint +} + +// Clone returns a deep copy of c. +func (c *JoinClause) Clone() *JoinClause { + if c == nil { + return nil + } + other := *c + other.X = CloneSource(c.X) + other.Y = CloneSource(c.Y) + other.Constraint = CloneJoinConstraint(c.Constraint) + return &other +} + +// String returns the string representation of the clause. +func (c *JoinClause) String() string { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s%s%s", c.X.String(), c.Operator.String(), c.Y.String()) + if c.Constraint != nil { + fmt.Fprintf(&buf, " %s", c.Constraint.String()) + } + return buf.String() +} + +type JoinOperator struct { + Comma Pos // position of comma + Natural Pos // position of NATURAL keyword + Left Pos // position of LEFT keyword + Outer Pos // position of OUTER keyword + Inner Pos // position of INNER keyword + Cross Pos // position of CROSS keyword + Join Pos // position of JOIN keyword +} + +// Clone returns a deep copy of op. +func (op *JoinOperator) Clone() *JoinOperator { + if op == nil { + return nil + } + other := *op + return &other +} + +// String returns the string representation of the operator. +func (op *JoinOperator) String() string { + if op.Comma.IsValid() { + return ", " + } + + var buf bytes.Buffer + if op.Natural.IsValid() { + buf.WriteString(" NATURAL") + } + if op.Left.IsValid() { + buf.WriteString(" LEFT") + if op.Outer.IsValid() { + buf.WriteString(" OUTER") + } + } else if op.Inner.IsValid() { + buf.WriteString(" INNER") + } else if op.Cross.IsValid() { + buf.WriteString(" CROSS") + } + buf.WriteString(" JOIN ") + + return buf.String() +} + +type OnConstraint struct { + On Pos // position of ON keyword + X Expr // constraint expression +} + +// Clone returns a deep copy of c. +func (c *OnConstraint) Clone() *OnConstraint { + if c == nil { + return nil + } + other := *c + other.X = CloneExpr(c.X) + return &other +} + +// String returns the string representation of the constraint. +func (c *OnConstraint) String() string { + return "ON " + c.X.String() +} + +type UsingConstraint struct { + Using Pos // position of USING keyword + Lparen Pos // position of left paren + Columns []*Ident // column list + Rparen Pos // position of right paren +} + +// Clone returns a deep copy of c. +func (c *UsingConstraint) Clone() *UsingConstraint { + if c == nil { + return nil + } + other := *c + other.Columns = cloneIdents(c.Columns) + return &other +} + +// String returns the string representation of the constraint. +func (c *UsingConstraint) String() string { + var buf bytes.Buffer + buf.WriteString("USING (") + for i, col := range c.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + buf.WriteString(")") + return buf.String() +} + +type WithClause struct { + With Pos // position of WITH keyword + Recursive Pos // position of RECURSIVE keyword + CTEs []*CTE // common table expressions +} + +// Clone returns a deep copy of c. +func (c *WithClause) Clone() *WithClause { + if c == nil { + return nil + } + other := *c + other.CTEs = cloneCTEs(c.CTEs) + return &other +} + +// String returns the string representation of the clause. +func (c *WithClause) String() string { + var buf bytes.Buffer + buf.WriteString("WITH ") + if c.Recursive.IsValid() { + buf.WriteString("RECURSIVE ") + } + + for i, cte := range c.CTEs { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(cte.String()) + } + + return buf.String() +} + +// CTE represents an AST node for a common table expression. +type CTE struct { + TableName *Ident // table name + ColumnsLparen Pos // position of column list left paren + Columns []*Ident // optional column list + ColumnsRparen Pos // position of column list right paren + As Pos // position of AS keyword + SelectLparen Pos // position of select left paren + Select *SelectStatement // select statement + SelectRparen Pos // position of select right paren +} + +// Clone returns a deep copy of cte. +func (cte *CTE) Clone() *CTE { + if cte == nil { + return nil + } + other := *cte + other.TableName = cte.TableName.Clone() + other.Columns = cloneIdents(cte.Columns) + other.Select = cte.Select.Clone() + return &other +} + +func cloneCTEs(a []*CTE) []*CTE { + if a == nil { + return nil + } + other := make([]*CTE, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the CTE. +func (cte *CTE) String() string { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s", cte.TableName.String()) + + if len(cte.Columns) != 0 { + buf.WriteString(" (") + for i, col := range cte.Columns { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(col.String()) + } + buf.WriteString(")") + } + + fmt.Fprintf(&buf, " AS (%s)", cte.Select.String()) + + return buf.String() +} + +type ParenExpr struct { + Lparen Pos // position of left paren + X Expr // parenthesized expression + Rparen Pos // position of right paren +} + +// IsAggregate returns true if inner expression has an aggregate function. +func (expr *ParenExpr) IsAggregate() bool { return false } + +// Clone returns a deep copy of expr. +func (expr *ParenExpr) Clone() *ParenExpr { + if expr == nil { + return nil + } + other := *expr + other.X = CloneExpr(expr.X) + return &other +} + +// String returns the string representation of the expression. +func (expr *ParenExpr) String() string { + return fmt.Sprintf("(%s)", expr.X.String()) +} + +type Window struct { + Name *Ident // name of window + As Pos // position of AS keyword + Definition *WindowDefinition // window definition +} + +// Clone returns a deep copy of w. +func (w *Window) Clone() *Window { + if w == nil { + return nil + } + other := *w + other.Name = w.Name.Clone() + other.Definition = w.Definition.Clone() + return &other +} + +func cloneWindows(a []*Window) []*Window { + if a == nil { + return nil + } + other := make([]*Window, len(a)) + for i := range a { + other[i] = a[i].Clone() + } + return other +} + +// String returns the string representation of the window. +func (w *Window) String() string { + return fmt.Sprintf("%s AS %s", w.Name.String(), w.Definition.String()) +} + +type WindowDefinition struct { + Lparen Pos // position of left paren + Base *Ident // base window name + Partition Pos // position of PARTITION keyword + PartitionBy Pos // position of BY keyword (after PARTITION) + Partitions []Expr // partition expressions + Order Pos // position of ORDER keyword + OrderBy Pos // position of BY keyword (after ORDER) + OrderingTerms []*OrderingTerm // ordering terms + Frame *FrameSpec // frame + Rparen Pos // position of right paren +} + +// Clone returns a deep copy of d. +func (d *WindowDefinition) Clone() *WindowDefinition { + if d == nil { + return nil + } + other := *d + other.Base = d.Base.Clone() + other.Partitions = cloneExprs(d.Partitions) + other.OrderingTerms = cloneOrderingTerms(d.OrderingTerms) + other.Frame = d.Frame.Clone() + return &other +} + +// String returns the string representation of the window definition. +func (d *WindowDefinition) String() string { + var buf bytes.Buffer + buf.WriteString("(") + if d.Base != nil { + buf.WriteString(d.Base.String()) + } + + if len(d.Partitions) != 0 { + if buf.Len() > 1 { + buf.WriteString(" ") + } + buf.WriteString("PARTITION BY ") + + for i, p := range d.Partitions { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(p.String()) + } + } + + if len(d.OrderingTerms) != 0 { + if buf.Len() > 1 { + buf.WriteString(" ") + } + buf.WriteString("ORDER BY ") + + for i, term := range d.OrderingTerms { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(term.String()) + } + } + + if d.Frame != nil { + if buf.Len() > 1 { + buf.WriteString(" ") + } + buf.WriteString(d.Frame.String()) + } + + buf.WriteString(")") + + return buf.String() +} diff --git a/sql2/ast_test.go b/sql2/ast_test.go new file mode 100644 index 000000000..8d3c1fed5 --- /dev/null +++ b/sql2/ast_test.go @@ -0,0 +1,1161 @@ +// Copyright 2021 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sql2_test + +import ( + "reflect" + "strings" + "testing" + + "github.com/go-test/deep" + sql "github.com/molecula/featurebase/v2/sql2" +) + +func TestExprString(t *testing.T) { + if got, want := sql.ExprString(&sql.NullLit{}), "NULL"; got != want { + t.Fatalf("ExprString()=%q, want %q", got, want) + } else if got, want := sql.ExprString(nil), ""; got != want { + t.Fatalf("ExprString()=%q, want %q", got, want) + } +} + +func TestSplitExprTree(t *testing.T) { + t.Run("AND-only", func(t *testing.T) { + AssertSplitExprTree(t, `x = 1 AND y = 2 AND z = 3`, []sql.Expr{ + &sql.BinaryExpr{X: &sql.Ident{Name: "x"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "1"}}, + &sql.BinaryExpr{X: &sql.Ident{Name: "y"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "2"}}, + &sql.BinaryExpr{X: &sql.Ident{Name: "z"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "3"}}, + }) + }) + + t.Run("OR", func(t *testing.T) { + AssertSplitExprTree(t, `x = 1 AND (y = 2 OR y = 3) AND z = 4`, []sql.Expr{ + &sql.BinaryExpr{X: &sql.Ident{Name: "x"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "1"}}, + &sql.BinaryExpr{ + X: &sql.BinaryExpr{X: &sql.Ident{Name: "y"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "2"}}, + Op: sql.OR, + Y: &sql.BinaryExpr{X: &sql.Ident{Name: "y"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "3"}}, + }, + &sql.BinaryExpr{X: &sql.Ident{Name: "z"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "4"}}, + }) + }) + + t.Run("ParenExpr", func(t *testing.T) { + AssertSplitExprTree(t, `x = 1 AND (y = 2 AND z = 3)`, []sql.Expr{ + &sql.BinaryExpr{X: &sql.Ident{Name: "x"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "1"}}, + &sql.BinaryExpr{X: &sql.Ident{Name: "y"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "2"}}, + &sql.BinaryExpr{X: &sql.Ident{Name: "z"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "3"}}, + }) + }) +} + +func AssertSplitExprTree(tb testing.TB, s string, want []sql.Expr) { + tb.Helper() + if diff := deep.Equal(sql.SplitExprTree(StripExprPos(sql.MustParseExprString(s))), want); diff != nil { + tb.Fatal("mismatch: \n" + strings.Join(diff, "\n")) + } +} + +func TestAlterTableStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.AlterTableStatement{ + Name: &sql.Ident{Name: "foo"}, + NewName: &sql.Ident{Name: "bar"}, + }, `ALTER TABLE "foo" RENAME TO "bar"`) + + AssertStatementStringer(t, &sql.AlterTableStatement{ + Name: &sql.Ident{Name: "foo"}, + ColumnName: &sql.Ident{Name: "col1"}, + NewColumnName: &sql.Ident{Name: "col2"}, + }, `ALTER TABLE "foo" RENAME COLUMN "col1" TO "col2"`) + + AssertStatementStringer(t, &sql.AlterTableStatement{ + Name: &sql.Ident{Name: "foo"}, + ColumnDef: &sql.ColumnDefinition{ + Name: &sql.Ident{Name: "bar"}, + Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, + }, + }, `ALTER TABLE "foo" ADD COLUMN "bar" INTEGER`) +} + +func TestAnalyzeStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.AnalyzeStatement{Name: &sql.Ident{Name: "foo"}}, `ANALYZE "foo"`) +} + +func TestBeginStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.BeginStatement{}, `BEGIN`) + AssertStatementStringer(t, &sql.BeginStatement{Deferred: pos(0)}, `BEGIN DEFERRED`) + AssertStatementStringer(t, &sql.BeginStatement{Immediate: pos(0)}, `BEGIN IMMEDIATE`) + AssertStatementStringer(t, &sql.BeginStatement{Exclusive: pos(0)}, `BEGIN EXCLUSIVE`) + AssertStatementStringer(t, &sql.BeginStatement{Immediate: pos(0), Transaction: pos(0)}, `BEGIN IMMEDIATE TRANSACTION`) +} + +func TestCommitStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.CommitStatement{}, `COMMIT`) + AssertStatementStringer(t, &sql.CommitStatement{End: pos(0)}, `END`) + AssertStatementStringer(t, &sql.CommitStatement{End: pos(0), Transaction: pos(0)}, `END TRANSACTION`) +} + +func TestCreateIndexStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.CreateIndexStatement{ + Name: &sql.Ident{Name: "foo"}, + Table: &sql.Ident{Name: "bar"}, + Columns: []*sql.IndexedColumn{{X: &sql.Ident{Name: "baz"}}}, + }, `CREATE INDEX "foo" ON "bar" ("baz")`) + + AssertStatementStringer(t, &sql.CreateIndexStatement{ + Unique: pos(0), + IfNotExists: pos(0), + Name: &sql.Ident{Name: "foo"}, + Table: &sql.Ident{Name: "bar"}, + Columns: []*sql.IndexedColumn{ + {X: &sql.Ident{Name: "baz"}}, + {X: &sql.Ident{Name: "bat"}}, + }, + WhereExpr: &sql.BoolLit{Value: true}, + }, `CREATE UNIQUE INDEX IF NOT EXISTS "foo" ON "bar" ("baz", "bat") WHERE TRUE`) +} + +func TestCreateTableStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.CreateTableStatement{ + Name: &sql.Ident{Name: "foo"}, + IfNotExists: pos(0), + Columns: []*sql.ColumnDefinition{ + { + Name: &sql.Ident{Name: "bar"}, + Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, + }, + { + Name: &sql.Ident{Name: "baz"}, + Type: &sql.Type{Name: &sql.Ident{Name: "TEXT"}}, + }, + }, + }, `CREATE TABLE IF NOT EXISTS "foo" ("bar" INTEGER, "baz" TEXT)`) + + AssertStatementStringer(t, &sql.CreateTableStatement{ + Name: &sql.Ident{Name: "foo"}, + Columns: []*sql.ColumnDefinition{{ + Name: &sql.Ident{Name: "bar"}, + Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, + Constraints: []sql.Constraint{ + &sql.PrimaryKeyConstraint{Autoincrement: pos(0)}, + &sql.NotNullConstraint{Name: &sql.Ident{Name: "nn"}}, + &sql.DefaultConstraint{Name: &sql.Ident{Name: "def"}, Expr: &sql.NumberLit{Value: "123"}}, + &sql.DefaultConstraint{Expr: &sql.NumberLit{Value: "456"}, Lparen: pos(0)}, + &sql.UniqueConstraint{}, + }, + }}, + }, `CREATE TABLE "foo" ("bar" INTEGER PRIMARY KEY AUTOINCREMENT CONSTRAINT "nn" NOT NULL CONSTRAINT "def" DEFAULT 123 DEFAULT (456) UNIQUE)`) + + AssertStatementStringer(t, &sql.CreateTableStatement{ + Name: &sql.Ident{Name: "foo"}, + Columns: []*sql.ColumnDefinition{{ + Name: &sql.Ident{Name: "bar"}, + Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, + Constraints: []sql.Constraint{ + &sql.ForeignKeyConstraint{ + ForeignTable: &sql.Ident{Name: "x"}, + ForeignColumns: []*sql.Ident{{Name: "y"}}, + Args: []*sql.ForeignKeyArg{ + {OnDelete: pos(0), SetNull: pos(0)}, + {OnUpdate: pos(0), SetDefault: pos(0)}, + {OnUpdate: pos(0), Cascade: pos(0)}, + {OnUpdate: pos(0), Restrict: pos(0)}, + {OnUpdate: pos(0), NoAction: pos(0)}, + }, + }, + }, + }}, + }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") ON DELETE SET NULL ON UPDATE SET DEFAULT ON UPDATE CASCADE ON UPDATE RESTRICT ON UPDATE NO ACTION)`) + + AssertStatementStringer(t, &sql.CreateTableStatement{ + Name: &sql.Ident{Name: "foo"}, + Columns: []*sql.ColumnDefinition{{ + Name: &sql.Ident{Name: "bar"}, + Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, + Constraints: []sql.Constraint{ + &sql.ForeignKeyConstraint{ + ForeignTable: &sql.Ident{Name: "x"}, + ForeignColumns: []*sql.Ident{{Name: "y"}}, + Deferrable: pos(0), + InitiallyDeferred: pos(0), + }, + }, + }}, + }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") DEFERRABLE INITIALLY DEFERRED)`) + + AssertStatementStringer(t, &sql.CreateTableStatement{ + Name: &sql.Ident{Name: "foo"}, + Columns: []*sql.ColumnDefinition{{ + Name: &sql.Ident{Name: "bar"}, + Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, + Constraints: []sql.Constraint{ + &sql.ForeignKeyConstraint{ + ForeignTable: &sql.Ident{Name: "x"}, + ForeignColumns: []*sql.Ident{{Name: "y"}}, + NotDeferrable: pos(0), + InitiallyImmediate: pos(0), + }, + }, + }}, + }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") NOT DEFERRABLE INITIALLY IMMEDIATE)`) + + AssertStatementStringer(t, &sql.CreateTableStatement{ + Name: &sql.Ident{Name: "foo"}, + Columns: []*sql.ColumnDefinition{{ + Name: &sql.Ident{Name: "bar"}, + Type: &sql.Type{Name: &sql.Ident{Name: "DECIMAL"}, Precision: &sql.NumberLit{Value: "100"}}, + }}, + Constraints: []sql.Constraint{ + &sql.PrimaryKeyConstraint{ + Name: &sql.Ident{Name: "pk"}, + Columns: []*sql.Ident{ + {Name: "x"}, + {Name: "y"}, + }, + }, + &sql.UniqueConstraint{ + Name: &sql.Ident{Name: "uniq"}, + Columns: []*sql.Ident{ + {Name: "x"}, + {Name: "y"}, + }, + }, + &sql.CheckConstraint{ + Name: &sql.Ident{Name: "chk"}, + Expr: &sql.BoolLit{Value: true}, + }, + }, + }, `CREATE TABLE "foo" ("bar" DECIMAL(100), CONSTRAINT "pk" PRIMARY KEY ("x", "y"), CONSTRAINT "uniq" UNIQUE ("x", "y"), CONSTRAINT "chk" CHECK (TRUE))`) + + AssertStatementStringer(t, &sql.CreateTableStatement{ + Name: &sql.Ident{Name: "foo"}, + Columns: []*sql.ColumnDefinition{{ + Name: &sql.Ident{Name: "bar"}, + Type: &sql.Type{Name: &sql.Ident{Name: "DECIMAL"}, Precision: &sql.NumberLit{Value: "100"}, Scale: &sql.NumberLit{Value: "200"}}, + }}, + Constraints: []sql.Constraint{ + &sql.ForeignKeyConstraint{ + Name: &sql.Ident{Name: "fk"}, + Columns: []*sql.Ident{{Name: "a"}, {Name: "b"}}, + ForeignTable: &sql.Ident{Name: "x"}, + ForeignColumns: []*sql.Ident{{Name: "y"}, {Name: "z"}}, + }, + }, + }, `CREATE TABLE "foo" ("bar" DECIMAL(100,200), CONSTRAINT "fk" FOREIGN KEY ("a", "b") REFERENCES "x" ("y", "z"))`) + + AssertStatementStringer(t, &sql.CreateTableStatement{ + Name: &sql.Ident{Name: "foo"}, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, `CREATE TABLE "foo" AS SELECT *`) +} + +func TestCreateTriggerStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.CreateTriggerStatement{ + Name: &sql.Ident{Name: "trig"}, + Insert: pos(0), + Table: &sql.Ident{Name: "tbl"}, + Body: []sql.Statement{ + &sql.DeleteStatement{Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl2"}}}, + }, + }, `CREATE TRIGGER "trig" INSERT ON "tbl" BEGIN DELETE FROM "tbl2"; END`) + + AssertStatementStringer(t, &sql.CreateTriggerStatement{ + Name: &sql.Ident{Name: "trig"}, + Before: pos(0), + Delete: pos(0), + ForEachRow: pos(0), + Table: &sql.Ident{Name: "tbl"}, + Body: []sql.Statement{ + &sql.DeleteStatement{Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}}, + }, + }, `CREATE TRIGGER "trig" BEFORE DELETE ON "tbl" FOR EACH ROW BEGIN DELETE FROM "x"; END`) + + AssertStatementStringer(t, &sql.CreateTriggerStatement{ + IfNotExists: pos(0), + Name: &sql.Ident{Name: "trig"}, + After: pos(0), + Update: pos(0), + Table: &sql.Ident{Name: "tbl"}, + WhenExpr: &sql.BoolLit{Value: true}, + Body: []sql.Statement{ + &sql.DeleteStatement{Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}}, + }, + }, `CREATE TRIGGER IF NOT EXISTS "trig" AFTER UPDATE ON "tbl" WHEN TRUE BEGIN DELETE FROM "x"; END`) + + AssertStatementStringer(t, &sql.CreateTriggerStatement{ + Name: &sql.Ident{Name: "trig"}, + InsteadOf: pos(0), + Update: pos(0), + UpdateOf: pos(0), + UpdateOfColumns: []*sql.Ident{{Name: "x"}, {Name: "y"}}, + Table: &sql.Ident{Name: "tbl"}, + Body: []sql.Statement{ + &sql.DeleteStatement{Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}}, + }, + }, `CREATE TRIGGER "trig" INSTEAD OF UPDATE OF "x", "y" ON "tbl" BEGIN DELETE FROM "x"; END`) +} + +func TestCreateViewStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.CreateViewStatement{ + Name: &sql.Ident{Name: "vw"}, + Columns: []*sql.Ident{ + {Name: "x"}, + {Name: "y"}, + }, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, `CREATE VIEW "vw" ("x", "y") AS SELECT *`) + + AssertStatementStringer(t, &sql.CreateViewStatement{ + IfNotExists: pos(0), + Name: &sql.Ident{Name: "vw"}, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, `CREATE VIEW IF NOT EXISTS "vw" AS SELECT *`) +} + +func TestDeleteStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.DeleteStatement{ + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}, Alias: &sql.Ident{Name: "tbl2"}}, + }, `DELETE FROM "tbl" AS "tbl2"`) + + AssertStatementStringer(t, &sql.DeleteStatement{ + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}, Index: &sql.Ident{Name: "idx"}}, + }, `DELETE FROM "tbl" INDEXED BY "idx"`) + + AssertStatementStringer(t, &sql.DeleteStatement{ + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}, NotIndexed: pos(0)}, + }, `DELETE FROM "tbl" NOT INDEXED`) + + AssertStatementStringer(t, &sql.DeleteStatement{ + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + WhereExpr: &sql.BoolLit{Value: true}, + OrderingTerms: []*sql.OrderingTerm{ + {X: &sql.Ident{Name: "x"}}, + {X: &sql.Ident{Name: "y"}}, + }, + LimitExpr: &sql.NumberLit{Value: "10"}, + OffsetExpr: &sql.NumberLit{Value: "5"}, + }, `DELETE FROM "tbl" WHERE TRUE ORDER BY "x", "y" LIMIT 10 OFFSET 5`) + + AssertStatementStringer(t, &sql.DeleteStatement{ + WithClause: &sql.WithClause{ + Recursive: pos(0), + CTEs: []*sql.CTE{{ + TableName: &sql.Ident{Name: "cte"}, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }}, + }, + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + }, `WITH RECURSIVE "cte" AS (SELECT *) DELETE FROM "tbl"`) +} + +func TestDropIndexStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.DropIndexStatement{ + Name: &sql.Ident{Name: "idx"}, + }, `DROP INDEX "idx"`) + + AssertStatementStringer(t, &sql.DropIndexStatement{ + IfExists: pos(0), + Name: &sql.Ident{Name: "idx"}, + }, `DROP INDEX IF EXISTS "idx"`) +} + +func TestDropTableStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.DropTableStatement{ + Name: &sql.Ident{Name: "tbl"}, + }, `DROP TABLE "tbl"`) + + AssertStatementStringer(t, &sql.DropTableStatement{ + IfExists: pos(0), + Name: &sql.Ident{Name: "tbl"}, + }, `DROP TABLE IF EXISTS "tbl"`) +} + +func TestDropTriggerStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.DropTriggerStatement{ + Name: &sql.Ident{Name: "trig"}, + }, `DROP TRIGGER "trig"`) + + AssertStatementStringer(t, &sql.DropTriggerStatement{ + IfExists: pos(0), + Name: &sql.Ident{Name: "trig"}, + }, `DROP TRIGGER IF EXISTS "trig"`) +} + +func TestDropViewStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.DropViewStatement{ + Name: &sql.Ident{Name: "vw"}, + }, `DROP VIEW "vw"`) + + AssertStatementStringer(t, &sql.DropViewStatement{ + IfExists: pos(0), + Name: &sql.Ident{Name: "vw"}, + }, `DROP VIEW IF EXISTS "vw"`) +} + +func TestExplainStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.ExplainStatement{ + Stmt: &sql.DropViewStatement{ + Name: &sql.Ident{Name: "vw"}, + }, + }, `EXPLAIN DROP VIEW "vw"`) + + AssertStatementStringer(t, &sql.ExplainStatement{ + QueryPlan: pos(0), + Stmt: &sql.DropViewStatement{ + Name: &sql.Ident{Name: "vw"}, + }, + }, `EXPLAIN QUERY PLAN DROP VIEW "vw"`) +} + +func TestInsertStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.InsertStatement{ + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + }, `INSERT INTO "tbl" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + Table: &sql.Ident{Name: "tbl"}, + Alias: &sql.Ident{Name: "x"}, + DefaultValues: pos(0), + }, `INSERT INTO "tbl" AS "x" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + InsertOrReplace: pos(0), + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + }, `INSERT OR REPLACE INTO "tbl" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + InsertOrRollback: pos(0), + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + }, `INSERT OR ROLLBACK INTO "tbl" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + InsertOrAbort: pos(0), + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + }, `INSERT OR ABORT INTO "tbl" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + InsertOrFail: pos(0), + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + }, `INSERT OR FAIL INTO "tbl" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + InsertOrIgnore: pos(0), + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + }, `INSERT OR IGNORE INTO "tbl" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + Replace: pos(0), + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + }, `REPLACE INTO "tbl" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + Table: &sql.Ident{Name: "tbl"}, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, `INSERT INTO "tbl" SELECT *`) + + AssertStatementStringer(t, &sql.InsertStatement{ + Table: &sql.Ident{Name: "tbl"}, + Columns: []*sql.Ident{ + {Name: "x"}, + {Name: "y"}, + }, + ValueLists: []*sql.ExprList{ + {Exprs: []sql.Expr{&sql.NullLit{}, &sql.NullLit{}}}, + {Exprs: []sql.Expr{&sql.NullLit{}, &sql.NullLit{}}}, + }, + }, `INSERT INTO "tbl" ("x", "y") VALUES (NULL, NULL), (NULL, NULL)`) + + AssertStatementStringer(t, &sql.InsertStatement{ + WithClause: &sql.WithClause{ + CTEs: []*sql.CTE{ + { + TableName: &sql.Ident{Name: "cte"}, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, + { + TableName: &sql.Ident{Name: "cte2"}, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, + }, + }, + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + }, `WITH "cte" AS (SELECT *), "cte2" AS (SELECT *) INSERT INTO "tbl" DEFAULT VALUES`) + + AssertStatementStringer(t, &sql.InsertStatement{ + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + UpsertClause: &sql.UpsertClause{ + DoNothing: pos(0), + }, + }, `INSERT INTO "tbl" DEFAULT VALUES ON CONFLICT DO NOTHING`) + + AssertStatementStringer(t, &sql.InsertStatement{ + Table: &sql.Ident{Name: "tbl"}, + DefaultValues: pos(0), + UpsertClause: &sql.UpsertClause{ + Columns: []*sql.IndexedColumn{ + {X: &sql.Ident{Name: "x"}, Asc: pos(0)}, + {X: &sql.Ident{Name: "y"}, Desc: pos(0)}, + }, + WhereExpr: &sql.BoolLit{Value: true}, + Assignments: []*sql.Assignment{ + {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, + {Columns: []*sql.Ident{{Name: "y"}, {Name: "z"}}, Expr: &sql.NumberLit{Value: "200"}}, + }, + UpdateWhereExpr: &sql.BoolLit{Value: false}, + }, + }, `INSERT INTO "tbl" DEFAULT VALUES ON CONFLICT ("x" ASC, "y" DESC) WHERE TRUE DO UPDATE SET "x" = 100, ("y", "z") = 200 WHERE FALSE`) +} + +func TestReleaseStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.ReleaseStatement{Name: &sql.Ident{Name: "x"}}, `RELEASE "x"`) + AssertStatementStringer(t, &sql.ReleaseStatement{Savepoint: pos(0), Name: &sql.Ident{Name: "x"}}, `RELEASE SAVEPOINT "x"`) +} + +func TestRollbackStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.RollbackStatement{}, `ROLLBACK`) + AssertStatementStringer(t, &sql.RollbackStatement{Transaction: pos(0)}, `ROLLBACK TRANSACTION`) + AssertStatementStringer(t, &sql.RollbackStatement{SavepointName: &sql.Ident{Name: "x"}}, `ROLLBACK TO "x"`) + AssertStatementStringer(t, &sql.RollbackStatement{Savepoint: pos(0), SavepointName: &sql.Ident{Name: "x"}}, `ROLLBACK TO SAVEPOINT "x"`) +} + +func TestSavepointStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.SavepointStatement{Name: &sql.Ident{Name: "x"}}, `SAVEPOINT "x"`) +} + +func TestSelectStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{ + {Expr: &sql.Ident{Name: "x"}, Alias: &sql.Ident{Name: "y"}}, + {Expr: &sql.Ident{Name: "z"}}, + }, + }, `SELECT "x" AS "y", "z"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Distinct: pos(0), + Columns: []*sql.ResultColumn{ + {Expr: &sql.Ident{Name: "x"}}, + }, + }, `SELECT DISTINCT "x"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + All: pos(0), + Columns: []*sql.ResultColumn{ + {Expr: &sql.Ident{Name: "x"}}, + }, + }, `SELECT ALL "x"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + WhereExpr: &sql.BoolLit{Value: true}, + GroupByExprs: []sql.Expr{&sql.Ident{Name: "x"}, &sql.Ident{Name: "y"}}, + HavingExpr: &sql.Ident{Name: "z"}, + }, `SELECT * FROM "tbl" WHERE TRUE GROUP BY "x", "y" HAVING "z"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.ParenSource{ + X: &sql.SelectStatement{Columns: []*sql.ResultColumn{{Star: pos(0)}}}, + Alias: &sql.Ident{Name: "tbl"}, + }, + }, `SELECT * FROM (SELECT *) AS "tbl"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.ParenSource{ + X: &sql.SelectStatement{Columns: []*sql.ResultColumn{{Star: pos(0)}}}, + }, + }, `SELECT * FROM (SELECT *)`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + Windows: []*sql.Window{ + { + Name: &sql.Ident{Name: "win1"}, + Definition: &sql.WindowDefinition{ + Base: &sql.Ident{Name: "base"}, + Partitions: []sql.Expr{&sql.Ident{Name: "x"}, &sql.Ident{Name: "y"}}, + OrderingTerms: []*sql.OrderingTerm{ + {X: &sql.Ident{Name: "x"}, Asc: pos(0), NullsFirst: pos(0)}, + {X: &sql.Ident{Name: "y"}, Desc: pos(0), NullsLast: pos(0)}, + }, + Frame: &sql.FrameSpec{ + Range: pos(0), + UnboundedX: pos(0), + PrecedingX: pos(0), + }, + }, + }, + { + Name: &sql.Ident{Name: "win2"}, + Definition: &sql.WindowDefinition{ + Base: &sql.Ident{Name: "base2"}, + }, + }, + }, + }, `SELECT * FROM "tbl" WINDOW "win1" AS ("base" PARTITION BY "x", "y" ORDER BY "x" ASC NULLS FIRST, "y" DESC NULLS LAST RANGE UNBOUNDED PRECEDING), "win2" AS ("base2")`) + + AssertStatementStringer(t, &sql.SelectStatement{ + WithClause: &sql.WithClause{ + CTEs: []*sql.CTE{{ + TableName: &sql.Ident{Name: "cte"}, + Columns: []*sql.Ident{ + {Name: "x"}, + {Name: "y"}, + }, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }}, + }, + ValueLists: []*sql.ExprList{ + {Exprs: []sql.Expr{&sql.NumberLit{Value: "1"}, &sql.NumberLit{Value: "2"}}}, + {Exprs: []sql.Expr{&sql.NumberLit{Value: "3"}, &sql.NumberLit{Value: "4"}}}, + }, + }, `WITH "cte" ("x", "y") AS (SELECT *) VALUES (1, 2), (3, 4)`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Union: pos(0), + Compound: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, `SELECT * UNION SELECT *`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Union: pos(0), + UnionAll: pos(0), + Compound: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, `SELECT * UNION ALL SELECT *`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Intersect: pos(0), + Compound: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, `SELECT * INTERSECT SELECT *`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Except: pos(0), + Compound: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }, `SELECT * EXCEPT SELECT *`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + OrderingTerms: []*sql.OrderingTerm{ + {X: &sql.Ident{Name: "x"}}, + {X: &sql.Ident{Name: "y"}}, + }, + }, `SELECT * ORDER BY "x", "y"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + LimitExpr: &sql.NumberLit{Value: "1"}, + OffsetExpr: &sql.NumberLit{Value: "2"}, + }, `SELECT * LIMIT 1 OFFSET 2`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.JoinClause{ + X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, + Operator: &sql.JoinOperator{Comma: pos(0)}, + Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, + }, + }, `SELECT * FROM "x", "y"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.JoinClause{ + X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, + Operator: &sql.JoinOperator{}, + Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, + Constraint: &sql.OnConstraint{X: &sql.BoolLit{Value: true}}, + }, + }, `SELECT * FROM "x" JOIN "y" ON TRUE`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.JoinClause{ + X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, + Operator: &sql.JoinOperator{Natural: pos(0), Inner: pos(0)}, + Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, + Constraint: &sql.UsingConstraint{ + Columns: []*sql.Ident{{Name: "a"}, {Name: "b"}}, + }, + }, + }, `SELECT * FROM "x" NATURAL INNER JOIN "y" USING ("a", "b")`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.JoinClause{ + X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, + Operator: &sql.JoinOperator{Left: pos(0)}, + Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, + }, + }, `SELECT * FROM "x" LEFT JOIN "y"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.JoinClause{ + X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, + Operator: &sql.JoinOperator{Left: pos(0), Outer: pos(0)}, + Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, + }, + }, `SELECT * FROM "x" LEFT OUTER JOIN "y"`) + + AssertStatementStringer(t, &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + Source: &sql.JoinClause{ + X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, + Operator: &sql.JoinOperator{Cross: pos(0)}, + Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, + }, + }, `SELECT * FROM "x" CROSS JOIN "y"`) +} + +func TestUpdateStatement_String(t *testing.T) { + AssertStatementStringer(t, &sql.UpdateStatement{ + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + Assignments: []*sql.Assignment{ + {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, + {Columns: []*sql.Ident{{Name: "y"}}, Expr: &sql.NumberLit{Value: "200"}}, + }, + WhereExpr: &sql.BoolLit{Value: true}, + }, `UPDATE "tbl" SET "x" = 100, "y" = 200 WHERE TRUE`) + + AssertStatementStringer(t, &sql.UpdateStatement{ + UpdateOrRollback: pos(0), + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + Assignments: []*sql.Assignment{ + {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, + }, + }, `UPDATE OR ROLLBACK "tbl" SET "x" = 100`) + + AssertStatementStringer(t, &sql.UpdateStatement{ + UpdateOrAbort: pos(0), + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + Assignments: []*sql.Assignment{ + {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, + }, + }, `UPDATE OR ABORT "tbl" SET "x" = 100`) + + AssertStatementStringer(t, &sql.UpdateStatement{ + UpdateOrReplace: pos(0), + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + Assignments: []*sql.Assignment{ + {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, + }, + }, `UPDATE OR REPLACE "tbl" SET "x" = 100`) + + AssertStatementStringer(t, &sql.UpdateStatement{ + UpdateOrFail: pos(0), + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + Assignments: []*sql.Assignment{ + {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, + }, + }, `UPDATE OR FAIL "tbl" SET "x" = 100`) + + AssertStatementStringer(t, &sql.UpdateStatement{ + UpdateOrIgnore: pos(0), + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + Assignments: []*sql.Assignment{ + {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, + }, + }, `UPDATE OR IGNORE "tbl" SET "x" = 100`) + + AssertStatementStringer(t, &sql.UpdateStatement{ + WithClause: &sql.WithClause{ + CTEs: []*sql.CTE{{ + TableName: &sql.Ident{Name: "cte"}, + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{{Star: pos(0)}}, + }, + }}, + }, + Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, + Assignments: []*sql.Assignment{ + {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, + }, + }, `WITH "cte" AS (SELECT *) UPDATE "tbl" SET "x" = 100`) +} + +func TestIdent_String(t *testing.T) { + AssertExprStringer(t, &sql.Ident{Name: "foo"}, `"foo"`) + AssertExprStringer(t, &sql.Ident{Name: "foo \" bar"}, `"foo "" bar"`) +} + +func TestStringLit_String(t *testing.T) { + AssertExprStringer(t, &sql.StringLit{Value: "foo"}, `'foo'`) + AssertExprStringer(t, &sql.StringLit{Value: "foo ' bar"}, `'foo '' bar'`) +} + +func TestNumberLit_String(t *testing.T) { + AssertExprStringer(t, &sql.NumberLit{Value: "123.45"}, `123.45`) +} + +func TestBlobLit_String(t *testing.T) { + AssertExprStringer(t, &sql.BlobLit{Value: "0123abcd"}, `x'0123abcd'`) +} + +func TestBoolLit_String(t *testing.T) { + AssertExprStringer(t, &sql.BoolLit{Value: true}, `TRUE`) + AssertExprStringer(t, &sql.BoolLit{Value: false}, `FALSE`) +} + +func TestNullLit_String(t *testing.T) { + AssertExprStringer(t, &sql.NullLit{}, `NULL`) +} + +func TestBindExpr_String(t *testing.T) { + AssertExprStringer(t, &sql.BindExpr{Name: "foo"}, `$foo`) +} + +func TestParenExpr_String(t *testing.T) { + AssertExprStringer(t, &sql.ParenExpr{X: &sql.NullLit{}}, `(NULL)`) +} + +func TestUnaryExpr_String(t *testing.T) { + AssertExprStringer(t, &sql.UnaryExpr{Op: sql.PLUS, X: &sql.NumberLit{Value: "100"}}, `+100`) + AssertExprStringer(t, &sql.UnaryExpr{Op: sql.MINUS, X: &sql.NumberLit{Value: "100"}}, `-100`) + AssertNodeStringerPanic(t, &sql.UnaryExpr{X: &sql.NumberLit{Value: "100"}}, `sql.UnaryExpr.String(): invalid op ILLEGAL`) +} + +func TestBinaryExpr_String(t *testing.T) { + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.PLUS, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 + 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.MINUS, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 - 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.STAR, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 * 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.SLASH, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 / 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.REM, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 % 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.CONCAT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 || 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.BETWEEN, X: &sql.NumberLit{Value: "1"}, Y: &sql.Range{X: &sql.NumberLit{Value: "2"}, Y: &sql.NumberLit{Value: "3"}}}, `1 BETWEEN 2 AND 3`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTBETWEEN, X: &sql.NumberLit{Value: "1"}, Y: &sql.BinaryExpr{Op: sql.AND, X: &sql.NumberLit{Value: "2"}, Y: &sql.NumberLit{Value: "3"}}}, `1 NOT BETWEEN 2 AND 3`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.LSHIFT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 << 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.RSHIFT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 >> 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.BITAND, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 & 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.BITOR, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 | 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.LT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 < 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.LE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 <= 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.GT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 > 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.GE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 >= 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.EQ, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 = 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 != 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.IS, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 IS 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.ISNOT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 IS NOT 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.IN, X: &sql.NumberLit{Value: "1"}, Y: &sql.ExprList{Exprs: []sql.Expr{&sql.NumberLit{Value: "2"}}}}, `1 IN (2)`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTIN, X: &sql.NumberLit{Value: "1"}, Y: &sql.ExprList{Exprs: []sql.Expr{&sql.NumberLit{Value: "2"}}}}, `1 NOT IN (2)`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.LIKE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 LIKE 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTLIKE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 NOT LIKE 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.GLOB, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 GLOB 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTGLOB, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 NOT GLOB 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.MATCH, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 MATCH 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTMATCH, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 NOT MATCH 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.REGEXP, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 REGEXP 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTREGEXP, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 NOT REGEXP 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.AND, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 AND 2`) + AssertExprStringer(t, &sql.BinaryExpr{Op: sql.OR, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 OR 2`) + AssertNodeStringerPanic(t, &sql.BinaryExpr{}, `sql.BinaryExpr.String(): invalid op ILLEGAL`) +} + +func TestCastExpr_String(t *testing.T) { + AssertExprStringer(t, &sql.CastExpr{X: &sql.NumberLit{Value: "1"}, Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}}, `CAST(1 AS INTEGER)`) +} + +func TestCaseExpr_String(t *testing.T) { + AssertExprStringer(t, &sql.CaseExpr{ + Operand: &sql.Ident{Name: "foo"}, + Blocks: []*sql.CaseBlock{ + {Condition: &sql.NumberLit{Value: "1"}, Body: &sql.BoolLit{Value: true}}, + {Condition: &sql.NumberLit{Value: "2"}, Body: &sql.BoolLit{Value: false}}, + }, + ElseExpr: &sql.NullLit{}, + }, `CASE "foo" WHEN 1 THEN TRUE WHEN 2 THEN FALSE ELSE NULL END`) + + AssertExprStringer(t, &sql.CaseExpr{ + Blocks: []*sql.CaseBlock{ + {Condition: &sql.NumberLit{Value: "1"}, Body: &sql.BoolLit{Value: true}}, + }, + }, `CASE WHEN 1 THEN TRUE END`) +} + +func TestExprList_String(t *testing.T) { + AssertExprStringer(t, &sql.ExprList{Exprs: []sql.Expr{&sql.NullLit{}}}, `(NULL)`) + AssertExprStringer(t, &sql.ExprList{Exprs: []sql.Expr{&sql.NullLit{}, &sql.NullLit{}}}, `(NULL, NULL)`) +} + +func TestQualifiedRef_String(t *testing.T) { + AssertExprStringer(t, &sql.QualifiedRef{Table: &sql.Ident{Name: "tbl"}, Column: &sql.Ident{Name: "col"}}, `"tbl"."col"`) + AssertExprStringer(t, &sql.QualifiedRef{Table: &sql.Ident{Name: "tbl"}, Star: pos(0)}, `"tbl".*`) +} + +func TestCall_String(t *testing.T) { + AssertExprStringer(t, &sql.Call{Name: &sql.Ident{Name: "foo"}}, `foo()`) + AssertExprStringer(t, &sql.Call{Name: &sql.Ident{Name: "foo"}, Star: pos(0)}, `foo(*)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Distinct: pos(0), + Args: []sql.Expr{ + &sql.NullLit{}, + &sql.NullLit{}, + }, + }, `foo(DISTINCT NULL, NULL)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Filter: &sql.FilterClause{ + X: &sql.BoolLit{Value: true}, + }, + }, `foo() FILTER (WHERE TRUE)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Name: &sql.Ident{Name: "win"}, + }, + }, `foo() OVER "win"`) + + t.Run("FrameSpec", func(t *testing.T) { + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Definition: &sql.WindowDefinition{ + Frame: &sql.FrameSpec{ + Rows: pos(0), + X: &sql.NullLit{}, + PrecedingX: pos(0), + ExcludeNoOthers: pos(0), + }, + }, + }, + }, `foo() OVER (ROWS NULL PRECEDING EXCLUDE NO OTHERS)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Definition: &sql.WindowDefinition{ + Frame: &sql.FrameSpec{ + Groups: pos(0), + CurrentRowX: pos(0), + ExcludeCurrentRow: pos(0), + }, + }, + }, + }, `foo() OVER (GROUPS CURRENT ROW EXCLUDE CURRENT ROW)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Definition: &sql.WindowDefinition{ + Frame: &sql.FrameSpec{ + Rows: pos(0), + UnboundedX: pos(0), + PrecedingX: pos(0), + Between: pos(0), + CurrentRowY: pos(0), + }, + }, + }, + }, `foo() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Definition: &sql.WindowDefinition{ + Frame: &sql.FrameSpec{ + Rows: pos(0), + X: &sql.NullLit{}, + PrecedingX: pos(0), + Between: pos(0), + CurrentRowY: pos(0), + }, + }, + }, + }, `foo() OVER (ROWS BETWEEN NULL PRECEDING AND CURRENT ROW)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Definition: &sql.WindowDefinition{ + Frame: &sql.FrameSpec{ + Range: pos(0), + X: &sql.NullLit{}, + FollowingX: pos(0), + Between: pos(0), + Y: &sql.BoolLit{Value: true}, + PrecedingY: pos(0), + ExcludeGroup: pos(0), + }, + }, + }, + }, `foo() OVER (RANGE BETWEEN NULL FOLLOWING AND TRUE PRECEDING EXCLUDE GROUP)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Definition: &sql.WindowDefinition{ + Frame: &sql.FrameSpec{ + Range: pos(0), + CurrentRowX: pos(0), + Between: pos(0), + Y: &sql.BoolLit{Value: true}, + FollowingY: pos(0), + ExcludeTies: pos(0), + }, + }, + }, + }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND TRUE FOLLOWING EXCLUDE TIES)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Definition: &sql.WindowDefinition{ + Frame: &sql.FrameSpec{ + Range: pos(0), + CurrentRowX: pos(0), + Between: pos(0), + CurrentRowY: pos(0), + }, + }, + }, + }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND CURRENT ROW)`) + + AssertExprStringer(t, &sql.Call{ + Name: &sql.Ident{Name: "foo"}, + Over: &sql.OverClause{ + Definition: &sql.WindowDefinition{ + Frame: &sql.FrameSpec{ + Range: pos(0), + CurrentRowX: pos(0), + Between: pos(0), + UnboundedY: pos(0), + FollowingY: pos(0), + }, + }, + }, + }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)`) + }) +} + +func TestRaise_String(t *testing.T) { + AssertExprStringer(t, &sql.Raise{Rollback: pos(0), Error: &sql.StringLit{Value: "err"}}, `RAISE(ROLLBACK, 'err')`) + AssertExprStringer(t, &sql.Raise{Abort: pos(0), Error: &sql.StringLit{Value: "err"}}, `RAISE(ABORT, 'err')`) + AssertExprStringer(t, &sql.Raise{Fail: pos(0), Error: &sql.StringLit{Value: "err"}}, `RAISE(FAIL, 'err')`) + AssertExprStringer(t, &sql.Raise{Ignore: pos(0)}, `RAISE(IGNORE)`) +} + +func TestExists_String(t *testing.T) { + AssertExprStringer(t, &sql.Exists{ + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{ + {Star: pos(0)}, + }, + }, + }, `EXISTS (SELECT *)`) + + AssertExprStringer(t, &sql.Exists{ + Not: pos(0), + Exists: pos(0), + Select: &sql.SelectStatement{ + Columns: []*sql.ResultColumn{ + {Star: pos(0)}, + }, + }, + }, `NOT EXISTS (SELECT *)`) +} + +func AssertExprStringer(tb testing.TB, expr sql.Expr, s string) { + tb.Helper() + if str := expr.String(); str != s { + tb.Fatalf("String()=%s, expected %s", str, s) + } else if _, err := sql.NewParser(strings.NewReader(str)).ParseExpr(); err != nil { + tb.Fatalf("cannot parse string: %s; err=%s", str, err) + } +} + +func AssertStatementStringer(tb testing.TB, stmt sql.Statement, s string) { + tb.Helper() + if str := stmt.String(); str != s { + tb.Fatalf("String()=%s, expected %s", str, s) + } else if _, err := sql.NewParser(strings.NewReader(str)).ParseStatement(); err != nil { + tb.Fatalf("cannot parse string: %s; err=%s", str, err) + } +} + +func AssertNodeStringerPanic(tb testing.TB, node sql.Node, msg string) { + tb.Helper() + var r interface{} + func() { + defer func() { r = recover() }() + _ = node.String() + }() + if r == nil { + tb.Fatal("expected node stringer to panic") + } else if r != msg { + tb.Fatalf("recover()=%s, want %s", r, msg) + } +} + +// StripPos removes the position data from a node and its children. +// This function returns the root argument passed in. +func StripPos(root sql.Node) sql.Node { + zero := reflect.ValueOf(sql.Pos{}) + + _ = sql.Walk(sql.VisitFunc(func(node sql.Node) error { + value := reflect.Indirect(reflect.ValueOf(node)) + for i := 0; i < value.NumField(); i++ { + if field := value.Field(i); field.Type() == zero.Type() { + field.Set(zero) + } + } + return nil + }), root) + return root +} + +func StripExprPos(root sql.Expr) sql.Expr { + StripPos(root) + return root +} diff --git a/sql2/parser.go b/sql2/parser.go new file mode 100644 index 000000000..c0b2f4ac9 --- /dev/null +++ b/sql2/parser.go @@ -0,0 +1,2960 @@ +// Copyright 2021 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sql2 + +import ( + "io" + "strings" +) + +// Parser represents a SQL parser. +type Parser struct { + s *Scanner + + pos Pos // current position + tok Token // current token + lit string // current literal value + full bool // buffer full +} + +// NewParser returns a new instance of Parser that reads from r. +func NewParser(r io.Reader) *Parser { + return &Parser{ + s: NewScanner(r), + } +} + +// ParseExprString parses s into an expression. Returns nil if s is blank. +func ParseExprString(s string) (Expr, error) { + if s == "" { + return nil, nil + } + return NewParser(strings.NewReader(s)).ParseExpr() +} + +// MustParseExprString parses s into an expression. Panic on error. +func MustParseExprString(s string) Expr { + expr, err := ParseExprString(s) + if err != nil { + panic(err) + } + return expr +} + +func (p *Parser) ParseStatement() (stmt Statement, err error) { + switch tok := p.peek(); tok { + case EOF: + return nil, io.EOF + case EXPLAIN: + if stmt, err = p.parseExplainStatement(); err != nil { + return stmt, err + } + default: + if stmt, err = p.parseNonExplainStatement(); err != nil { + return stmt, err + } + } + + // Read trailing semicolon or end of file. + if tok := p.peek(); tok != EOF && tok != SEMI { + return stmt, p.errorExpected(p.pos, p.tok, "semicolon or EOF") + } + p.scan() + + return stmt, nil +} + +// parseExplain parses EXPLAIN [QUERY PLAN] STMT. +func (p *Parser) parseExplainStatement() (_ *ExplainStatement, err error) { + var tok Token + + // Parse initial "EXPLAIN" token. + var stmt ExplainStatement + stmt.Explain, tok, _ = p.scan() + assert(tok == EXPLAIN) + + // Parse optional "QUERY PLAN" tokens. + if p.peek() == QUERY { + stmt.Query, _, _ = p.scan() + + if p.peek() != PLAN { + return &stmt, p.errorExpected(p.pos, p.tok, "PLAN") + } + stmt.QueryPlan, _, _ = p.scan() + } + + // Parse statement to be explained. + if stmt.Stmt, err = p.parseNonExplainStatement(); err != nil { + return &stmt, err + } + return &stmt, nil +} + +// parseStmt parses all statement types. +func (p *Parser) parseNonExplainStatement() (Statement, error) { + switch p.peek() { + case ANALYZE: + return p.parseAnalyzeStatement() + case ALTER: + return p.parseAlterTableStatement() + case BEGIN: + return p.parseBeginStatement() + case COMMIT, END: + return p.parseCommitStatement() + case ROLLBACK: + return p.parseRollbackStatement() + case SAVEPOINT: + return p.parseSavepointStatement() + case RELEASE: + return p.parseReleaseStatement() + case CREATE: + return p.parseCreateStatement() + case DROP: + return p.parseDropStatement() + case SELECT, VALUES: + return p.parseSelectStatement(false, nil) + case INSERT, REPLACE: + return p.parseInsertStatement(nil) + case UPDATE: + return p.parseUpdateStatement(nil) + case DELETE: + return p.parseDeleteStatement(nil) + case WITH: + return p.parseWithStatement() + default: + return nil, p.errorExpected(p.pos, p.tok, "statement") + } +} + +// parseWithStatement is called only from parseNonExplainStatement as we don't +// know what kind of statement we'll have after the CTEs (e.g. SELECT, INSERT, etc). +func (p *Parser) parseWithStatement() (Statement, error) { + withClause, err := p.parseWithClause() + if err != nil { + return nil, err + } + + switch p.peek() { + case SELECT, VALUES: + return p.parseSelectStatement(false, withClause) + case INSERT, REPLACE: + return p.parseInsertStatement(withClause) + case UPDATE: + return p.parseUpdateStatement(withClause) + case DELETE: + return p.parseDeleteStatement(withClause) + default: + return nil, p.errorExpected(p.pos, p.tok, "SELECT, VALUES, INSERT, REPLACE, UPDATE, or DELETE") + } +} + +func (p *Parser) parseBeginStatement() (*BeginStatement, error) { + assert(p.peek() == BEGIN) + + var stmt BeginStatement + stmt.Begin, _, _ = p.scan() + + // Parse transaction type. + switch p.peek() { + case DEFERRED: + stmt.Deferred, _, _ = p.scan() + case IMMEDIATE: + stmt.Immediate, _, _ = p.scan() + case EXCLUSIVE: + stmt.Exclusive, _, _ = p.scan() + } + + // Parse optional TRANSCTION keyword. + if p.peek() == TRANSACTION { + stmt.Transaction, _, _ = p.scan() + } + return &stmt, nil +} + +func (p *Parser) parseCommitStatement() (*CommitStatement, error) { + assert(p.peek() == COMMIT || p.peek() == END) + + var stmt CommitStatement + if p.peek() == COMMIT { + stmt.Commit, _, _ = p.scan() + } else { + stmt.End, _, _ = p.scan() + } + + if p.peek() == TRANSACTION { + stmt.Transaction, _, _ = p.scan() + } + return &stmt, nil +} + +func (p *Parser) parseRollbackStatement() (_ *RollbackStatement, err error) { + assert(p.peek() == ROLLBACK) + + var stmt RollbackStatement + stmt.Rollback, _, _ = p.scan() + + // Parse optional "TRANSACTION". + if p.peek() == TRANSACTION { + stmt.Transaction, _, _ = p.scan() + } + + // Parse optional "TO SAVEPOINT savepoint-name" + if p.peek() == TO { + stmt.To, _, _ = p.scan() + if p.peek() == SAVEPOINT { + stmt.Savepoint, _, _ = p.scan() + } + if stmt.SavepointName, err = p.parseIdent("savepoint name"); err != nil { + return &stmt, err + } + } + return &stmt, nil +} + +func (p *Parser) parseSavepointStatement() (_ *SavepointStatement, err error) { + assert(p.peek() == SAVEPOINT) + + var stmt SavepointStatement + stmt.Savepoint, _, _ = p.scan() + if stmt.Name, err = p.parseIdent("savepoint name"); err != nil { + return &stmt, err + } + return &stmt, nil +} + +func (p *Parser) parseReleaseStatement() (_ *ReleaseStatement, err error) { + assert(p.peek() == RELEASE) + + var stmt ReleaseStatement + stmt.Release, _, _ = p.scan() + + if p.peek() == SAVEPOINT { + stmt.Savepoint, _, _ = p.scan() + } + + if stmt.Name, err = p.parseIdent("savepoint name"); err != nil { + return &stmt, err + } + return &stmt, nil +} + +func (p *Parser) parseCreateStatement() (Statement, error) { + assert(p.peek() == CREATE) + pos, tok, _ := p.scan() + + switch p.peek() { + case TABLE: + return p.parseCreateTableStatement(pos) + case VIEW: + return p.parseCreateViewStatement(pos) + case INDEX, UNIQUE: + return p.parseCreateIndexStatement(pos) + case TRIGGER: + return p.parseCreateTriggerStatement(pos) + default: + return nil, p.errorExpected(pos, tok, "TABLE, VIEW, INDEX, TRIGGER") + } +} + +func (p *Parser) parseDropStatement() (Statement, error) { + assert(p.peek() == DROP) + pos, tok, _ := p.scan() + + switch p.peek() { + case TABLE: + return p.parseDropTableStatement(pos) + case VIEW: + return p.parseDropViewStatement(pos) + case INDEX: + return p.parseDropIndexStatement(pos) + case TRIGGER: + return p.parseDropTriggerStatement(pos) + default: + return nil, p.errorExpected(pos, tok, "TABLE, VIEW, INDEX, or TRIGGER") + } +} + +func (p *Parser) parseCreateTableStatement(createPos Pos) (_ *CreateTableStatement, err error) { + assert(p.peek() == TABLE) + + var stmt CreateTableStatement + stmt.Create = createPos + stmt.Table, _, _ = p.scan() + + // Parse optional "IF NOT EXISTS". + if p.peek() == IF { + stmt.If, _, _ = p.scan() + + pos, tok, _ := p.scan() + if tok != NOT { + return &stmt, p.errorExpected(pos, tok, "NOT") + } + stmt.IfNot = pos + + pos, tok, _ = p.scan() + if tok != EXISTS { + return &stmt, p.errorExpected(pos, tok, "EXISTS") + } + stmt.IfNotExists = pos + } + + if stmt.Name, err = p.parseIdent("table name"); err != nil { + return &stmt, err + } + + // Parse either a column/constraint list or build table from "AS