featurebase/sql2/parser_test.go
2021-10-29 13:14:27 -05:00

3311 lines
116 KiB
Go

// 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 (
"strings"
"testing"
"github.com/go-test/deep"
sql "github.com/molecula/featurebase/v2/sql2"
)
func TestParser_ParseStatement(t *testing.T) {
t.Run("ErrNoStatement", func(t *testing.T) {
AssertParseStatementError(t, `123`, `1:1: expected statement, found 123`)
})
t.Run("Explain", func(t *testing.T) {
t.Run("", func(t *testing.T) {
AssertParseStatement(t, `EXPLAIN BEGIN`, &sql.ExplainStatement{
Explain: pos(0),
Stmt: &sql.BeginStatement{
Begin: pos(8),
},
})
})
t.Run("QueryPlan", func(t *testing.T) {
AssertParseStatement(t, `EXPLAIN QUERY PLAN BEGIN`, &sql.ExplainStatement{
Explain: pos(0),
Query: pos(8),
QueryPlan: pos(14),
Stmt: &sql.BeginStatement{
Begin: pos(19),
},
})
})
t.Run("ErrNoPlan", func(t *testing.T) {
AssertParseStatementError(t, `EXPLAIN QUERY`, `1:13: expected PLAN, found 'EOF'`)
})
t.Run("ErrStmt", func(t *testing.T) {
AssertParseStatementError(t, `EXPLAIN CREATE`, `1:9: expected TABLE, VIEW, INDEX, TRIGGER`)
})
})
t.Run("Begin", func(t *testing.T) {
t.Run("", func(t *testing.T) {
AssertParseStatement(t, `BEGIN`, &sql.BeginStatement{
Begin: pos(0),
})
})
t.Run("Transaction", func(t *testing.T) {
AssertParseStatement(t, `BEGIN TRANSACTION`, &sql.BeginStatement{
Begin: pos(0),
Transaction: pos(6),
})
})
t.Run("DeferredTransaction", func(t *testing.T) {
AssertParseStatement(t, `BEGIN DEFERRED TRANSACTION`, &sql.BeginStatement{
Begin: pos(0),
Deferred: pos(6),
Transaction: pos(15),
})
})
t.Run("Immediate", func(t *testing.T) {
AssertParseStatement(t, `BEGIN IMMEDIATE;`, &sql.BeginStatement{
Begin: pos(0),
Immediate: pos(6),
})
})
t.Run("Exclusive", func(t *testing.T) {
AssertParseStatement(t, `BEGIN EXCLUSIVE`, &sql.BeginStatement{
Begin: pos(0),
Exclusive: pos(6),
})
})
t.Run("ErrOverrun", func(t *testing.T) {
AssertParseStatementError(t, `BEGIN COMMIT`, `1:7: expected semicolon or EOF, found 'COMMIT'`)
})
})
t.Run("Commit", func(t *testing.T) {
t.Run("", func(t *testing.T) {
AssertParseStatement(t, `COMMIT`, &sql.CommitStatement{
Commit: pos(0),
})
})
t.Run("Transaction", func(t *testing.T) {
AssertParseStatement(t, `COMMIT TRANSACTION`, &sql.CommitStatement{
Commit: pos(0),
Transaction: pos(7),
})
})
})
t.Run("End", func(t *testing.T) {
t.Run("", func(t *testing.T) {
AssertParseStatement(t, `END`, &sql.CommitStatement{
End: pos(0),
})
})
t.Run("Transaction", func(t *testing.T) {
AssertParseStatement(t, `END TRANSACTION`, &sql.CommitStatement{
End: pos(0),
Transaction: pos(4),
})
})
})
t.Run("Rollback", func(t *testing.T) {
t.Run("", func(t *testing.T) {
AssertParseStatement(t, `ROLLBACK`, &sql.RollbackStatement{
Rollback: pos(0),
})
})
t.Run("Transaction", func(t *testing.T) {
AssertParseStatement(t, `ROLLBACK TRANSACTION`, &sql.RollbackStatement{
Rollback: pos(0),
Transaction: pos(9),
})
})
t.Run("To", func(t *testing.T) {
AssertParseStatement(t, `ROLLBACK TO svpt`, &sql.RollbackStatement{
Rollback: pos(0),
To: pos(9),
SavepointName: &sql.Ident{
Name: "svpt",
NamePos: pos(12),
},
})
})
t.Run("TransactionToSavepoint", func(t *testing.T) {
AssertParseStatement(t, `ROLLBACK TRANSACTION TO SAVEPOINT "svpt"`, &sql.RollbackStatement{
Rollback: pos(0),
Transaction: pos(9),
To: pos(21),
Savepoint: pos(24),
SavepointName: &sql.Ident{
Name: "svpt",
NamePos: pos(34),
Quoted: true,
},
})
})
t.Run("ErrSavepointName", func(t *testing.T) {
AssertParseStatementError(t, `ROLLBACK TO SAVEPOINT 123`, `1:23: expected savepoint name, found 123`)
})
})
t.Run("Savepoint", func(t *testing.T) {
t.Run("Ident", func(t *testing.T) {
AssertParseStatement(t, `SAVEPOINT svpt`, &sql.SavepointStatement{
Savepoint: pos(0),
Name: &sql.Ident{
Name: "svpt",
NamePos: pos(10),
},
})
})
t.Run("String", func(t *testing.T) {
AssertParseStatement(t, `SAVEPOINT "svpt"`, &sql.SavepointStatement{
Savepoint: pos(0),
Name: &sql.Ident{
Name: "svpt",
NamePos: pos(10),
Quoted: true,
},
})
})
t.Run("ErrSavepointName", func(t *testing.T) {
AssertParseStatementError(t, `SAVEPOINT 123`, `1:11: expected savepoint name, found 123`)
})
})
t.Run("Release", func(t *testing.T) {
t.Run("Ident", func(t *testing.T) {
AssertParseStatement(t, `RELEASE svpt`, &sql.ReleaseStatement{
Release: pos(0),
Name: &sql.Ident{
Name: "svpt",
NamePos: pos(8),
},
})
})
t.Run("String", func(t *testing.T) {
AssertParseStatement(t, `RELEASE "svpt"`, &sql.ReleaseStatement{
Release: pos(0),
Name: &sql.Ident{
Name: "svpt",
NamePos: pos(8),
Quoted: true,
},
})
})
t.Run("SavepointIdent", func(t *testing.T) {
AssertParseStatement(t, `RELEASE SAVEPOINT svpt`, &sql.ReleaseStatement{
Release: pos(0),
Savepoint: pos(8),
Name: &sql.Ident{
Name: "svpt",
NamePos: pos(18),
},
})
})
t.Run("ErrSavepointName", func(t *testing.T) {
AssertParseStatementError(t, `RELEASE 123`, `1:9: expected savepoint name, found 123`)
})
})
t.Run("CreateTable", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT, col2 DECIMAL(10,5))`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{
Name: "tbl",
NamePos: pos(13),
},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{NamePos: pos(18), Name: "col1"},
Type: &sql.Type{
Name: &sql.Ident{NamePos: pos(23), Name: "TEXT"},
},
},
{
Name: &sql.Ident{NamePos: pos(29), Name: "col2"},
Type: &sql.Type{
Name: &sql.Ident{NamePos: pos(34), Name: "DECIMAL"},
Lparen: pos(41),
Precision: &sql.NumberLit{ValuePos: pos(42), Value: "10"},
Scale: &sql.NumberLit{ValuePos: pos(45), Value: "5"},
Rparen: pos(46),
},
},
},
Rparen: pos(47),
})
AssertParseStatementError(t, `CREATE TABLE`, `1:12: expected table name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl `, `1:17: expected AS or left paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (`, `1:18: expected column name, CONSTRAINT, or right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT`, `1:27: expected column name, CONSTRAINT, or right paren, found 'EOF'`)
AssertParseStatement(t, `CREATE TABLE IF NOT EXISTS tbl (col1 TEXT)`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
If: pos(13),
IfNot: pos(16),
IfNotExists: pos(20),
Name: &sql.Ident{
Name: "tbl",
NamePos: pos(27),
},
Lparen: pos(31),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{
NamePos: pos(32),
Name: "col1",
},
Type: &sql.Type{
Name: &sql.Ident{
NamePos: pos(37),
Name: "TEXT",
},
},
},
},
Rparen: pos(41),
})
AssertParseStatementError(t, `CREATE TABLE IF`, `1:15: expected NOT, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE IF NOT`, `1:19: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1`, `1:22: expected type name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 DECIMAL(`, `1:31: expected precision, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 DECIMAL(-12,`, `1:35: expected scale, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 DECIMAL(1,2`, `1:34: expected right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 DECIMAL(1`, `1:32: expected right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT CONSTRAINT`, `1:38: expected constraint name, found 'EOF'`)
AssertParseStatement(t, `CREATE TABLE tbl AS SELECT foo`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{
Name: "tbl",
NamePos: pos(13),
},
As: pos(17),
Select: &sql.SelectStatement{
Select: pos(20),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(27), Name: "foo"}},
},
},
})
AssertParseStatement(t, `CREATE TABLE tbl AS WITH cte (x) AS (SELECT y) SELECT foo`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{
Name: "tbl",
NamePos: pos(13),
},
As: pos(17),
Select: &sql.SelectStatement{
WithClause: &sql.WithClause{
With: pos(20),
CTEs: []*sql.CTE{
{
TableName: &sql.Ident{NamePos: pos(25), Name: "cte"},
ColumnsLparen: pos(29),
Columns: []*sql.Ident{
{NamePos: pos(30), Name: "x"},
},
ColumnsRparen: pos(31),
As: pos(33),
SelectLparen: pos(36),
Select: &sql.SelectStatement{
Select: pos(37),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(44), Name: "y"}},
},
},
SelectRparen: pos(45),
},
},
},
Select: pos(47),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(54), Name: "foo"}},
},
},
})
AssertParseStatementError(t, `CREATE TABLE tbl AS`, `1:19: expected SELECT or VALUES, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl AS WITH`, `1:24: expected table name, found 'EOF'`)
t.Run("ColumnConstraint", func(t *testing.T) {
t.Run("PrimaryKey", func(t *testing.T) {
t.Run("Simple", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT PRIMARY KEY)`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
Constraints: []sql.Constraint{
&sql.PrimaryKeyConstraint{
Primary: pos(28),
Key: pos(36),
},
},
},
},
Rparen: pos(39),
})
})
t.Run("Full", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT CONSTRAINT cons1 PRIMARY KEY AUTOINCREMENT)`).(*sql.CreateTableStatement)
if diff := deep.Equal(stmt.Columns[0].Constraints[0], &sql.PrimaryKeyConstraint{
Constraint: pos(28),
Name: &sql.Ident{Name: "cons1", NamePos: pos(39)},
Primary: pos(45),
Key: pos(53),
Autoincrement: pos(57),
}); diff != nil {
t.Fatal(diff)
}
})
t.Run("ErrNoKey", func(t *testing.T) {
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT PRIMARY`, `1:35: expected KEY, found 'EOF'`)
})
})
t.Run("NotNull", func(t *testing.T) {
t.Run("ErrNoKey", func(t *testing.T) {
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT NOT`, `1:31: expected NULL, found 'EOF'`)
})
t.Run("Simple", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT CONSTRAINT con1 NOT NULL)`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
Constraints: []sql.Constraint{
&sql.NotNullConstraint{
Constraint: pos(28),
Name: &sql.Ident{Name: "con1", NamePos: pos(39)},
Not: pos(44),
Null: pos(48),
},
},
},
},
Rparen: pos(52),
})
})
})
t.Run("Unique", func(t *testing.T) {
t.Run("Simple", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT CONSTRAINT con1 UNIQUE)`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
Constraints: []sql.Constraint{
&sql.UniqueConstraint{
Constraint: pos(28),
Name: &sql.Ident{Name: "con1", NamePos: pos(39)},
Unique: pos(44),
},
},
},
},
Rparen: pos(50),
})
})
})
t.Run("Check", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT CHECK (col1 > 1))`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
Constraints: []sql.Constraint{
&sql.CheckConstraint{
Check: pos(28),
Lparen: pos(34),
Expr: &sql.BinaryExpr{
X: &sql.Ident{Name: "col1", NamePos: pos(35)},
Op: sql.GT, OpPos: pos(40),
Y: &sql.NumberLit{Value: "1", ValuePos: pos(42)},
},
Rparen: pos(43),
},
},
},
},
Rparen: pos(44),
})
})
t.Run("Default", func(t *testing.T) {
t.Run("Expr", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT DEFAULT (1))`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
Constraints: []sql.Constraint{
&sql.DefaultConstraint{
Default: pos(28),
Lparen: pos(36),
Expr: &sql.NumberLit{Value: "1", ValuePos: pos(37)},
Rparen: pos(38),
},
},
},
},
Rparen: pos(39),
})
})
t.Run("String", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT DEFAULT 'foo')`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.DefaultConstraint{
Default: pos(28),
Expr: &sql.StringLit{Value: "foo", ValuePos: pos(36)},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("Blob", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT DEFAULT x'0F0F')`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.DefaultConstraint{
Default: pos(28),
Expr: &sql.BlobLit{Value: "0F0F", ValuePos: pos(36)},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("Number", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT DEFAULT 1)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.DefaultConstraint{
Default: pos(28),
Expr: &sql.NumberLit{Value: "1", ValuePos: pos(36)},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("Null", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT DEFAULT NULL)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.DefaultConstraint{
Default: pos(28),
Expr: &sql.NullLit{Pos: pos(36)},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("Bool", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT DEFAULT true)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.DefaultConstraint{
Default: pos(28),
Expr: &sql.BoolLit{Value: true, ValuePos: pos(36)},
}); diff != "" {
t.Fatal(diff)
}
})
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT DEFAULT +`, `1:37: expected signed number, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT DEFAULT -`, `1:37: expected signed number, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT DEFAULT `, `1:36: expected literal value or left paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT DEFAULT (TABLE`, `1:38: expected expression, found 'TABLE'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT DEFAULT (true`, `1:41: expected right paren, found 'EOF'`)
})
t.Run("ForeignKey", func(t *testing.T) {
t.Run("Simple", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2))`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
Constraints: []sql.Constraint{
&sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
},
},
},
},
Rparen: pos(49),
})
})
t.Run("OnDeleteSetNull", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) ON DELETE SET NULL)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Args: []*sql.ForeignKeyArg{
{
On: pos(50),
OnDelete: pos(53),
Set: pos(60),
SetNull: pos(64),
},
},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("OnDeleteSetDefault", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) ON DELETE SET DEFAULT)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Args: []*sql.ForeignKeyArg{
{
On: pos(50),
OnDelete: pos(53),
Set: pos(60),
SetDefault: pos(64),
},
},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("OnDeleteSetDefault", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) ON DELETE CASCADE)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Args: []*sql.ForeignKeyArg{
{
On: pos(50),
OnDelete: pos(53),
Cascade: pos(60),
},
},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("OnDeleteSetRestrict", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) ON DELETE RESTRICT)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Args: []*sql.ForeignKeyArg{
{
On: pos(50),
OnDelete: pos(53),
Restrict: pos(60),
},
},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("OnDeleteSetNoAction", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) ON DELETE NO ACTION)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Args: []*sql.ForeignKeyArg{
{
On: pos(50),
OnDelete: pos(53),
No: pos(60),
NoAction: pos(63),
},
},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("Multiple", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) ON DELETE CASCADE ON UPDATE RESTRICT)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Args: []*sql.ForeignKeyArg{
{
On: pos(50),
OnDelete: pos(53),
Cascade: pos(60),
},
{
On: pos(68),
OnUpdate: pos(71),
Restrict: pos(78),
},
},
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("Deferrable", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) DEFERRABLE)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Deferrable: pos(50),
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("NotDeferrable", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) NOT DEFERRABLE)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Not: pos(50),
NotDeferrable: pos(54),
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("InitiallyDeferred", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) DEFERRABLE INITIALLY DEFERRED)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Deferrable: pos(50),
Initially: pos(61),
InitiallyDeferred: pos(71),
}); diff != "" {
t.Fatal(diff)
}
})
t.Run("InitiallyImmediate", func(t *testing.T) {
stmt := ParseStatementOrFail(t, `CREATE TABLE tbl (col1 TEXT REFERENCES foo (col2) DEFERRABLE INITIALLY IMMEDIATE)`).(*sql.CreateTableStatement)
if diff := deepEqual(stmt.Columns[0].Constraints[0], &sql.ForeignKeyConstraint{
References: pos(28),
ForeignTable: &sql.Ident{Name: "foo", NamePos: pos(39)},
ForeignLparen: pos(43),
ForeignColumns: []*sql.Ident{
{Name: "col2", NamePos: pos(44)},
},
ForeignRparen: pos(48),
Deferrable: pos(50),
Initially: pos(61),
InitiallyImmediate: pos(71),
}); diff != "" {
t.Fatal(diff)
}
})
})
})
t.Run("TableConstraint", func(t *testing.T) {
t.Run("PrimaryKey", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT, PRIMARY KEY (col1, col2))`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
},
},
Constraints: []sql.Constraint{
&sql.PrimaryKeyConstraint{
Primary: pos(29),
Key: pos(37),
Lparen: pos(41),
Columns: []*sql.Ident{
{Name: "col1", NamePos: pos(42)},
{Name: "col2", NamePos: pos(48)},
},
Rparen: pos(52),
},
},
Rparen: pos(53),
})
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, PRIMARY`, `1:36: expected KEY, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, PRIMARY KEY`, `1:40: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, PRIMARY KEY (col1)`, `1:47: expected right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, PRIMARY KEY (1`, `1:43: expected column name, found 1`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, PRIMARY KEY (foo x`, `1:47: expected comma or right paren, found x`)
})
t.Run("Unique", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT, CONSTRAINT con1 UNIQUE (col1, col2))`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
},
},
Constraints: []sql.Constraint{
&sql.UniqueConstraint{
Constraint: pos(29),
Name: &sql.Ident{Name: "con1", NamePos: pos(40)},
Unique: pos(45),
Lparen: pos(52),
Columns: []*sql.Ident{
{Name: "col1", NamePos: pos(53)},
{Name: "col2", NamePos: pos(59)},
},
Rparen: pos(63),
},
},
Rparen: pos(64),
})
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, UNIQUE`, `1:35: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, UNIQUE (1`, `1:38: expected column name, found 1`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, UNIQUE (x y`, `1:40: expected comma or right paren, found y`)
})
t.Run("Check", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT, CHECK(foo = bar))`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
},
},
Constraints: []sql.Constraint{
&sql.CheckConstraint{
Check: pos(29),
Lparen: pos(34),
Expr: &sql.BinaryExpr{
X: &sql.Ident{Name: "foo", NamePos: pos(35)},
Op: sql.EQ, OpPos: pos(39),
Y: &sql.Ident{Name: "bar", NamePos: pos(41)},
},
Rparen: pos(44),
},
},
Rparen: pos(45),
})
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, CHECK`, `1:34: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, CHECK (TABLE`, `1:37: expected expression, found 'TABLE'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, CHECK (true`, `1:40: expected right paren, found 'EOF'`)
})
t.Run("ForeignKey", func(t *testing.T) {
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (col1, col2) REFERENCES tbl2 (x, y))`, &sql.CreateTableStatement{
Create: pos(0),
Table: pos(7),
Name: &sql.Ident{Name: "tbl", NamePos: pos(13)},
Lparen: pos(17),
Columns: []*sql.ColumnDefinition{
{
Name: &sql.Ident{Name: "col1", NamePos: pos(18)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(23)},
},
},
},
Constraints: []sql.Constraint{
&sql.ForeignKeyConstraint{
Foreign: pos(29),
ForeignKey: pos(37),
Lparen: pos(41),
Columns: []*sql.Ident{
{Name: "col1", NamePos: pos(42)},
{Name: "col2", NamePos: pos(48)},
},
Rparen: pos(52),
References: pos(54),
ForeignTable: &sql.Ident{Name: "tbl2", NamePos: pos(65)},
ForeignLparen: pos(70),
ForeignColumns: []*sql.Ident{
{Name: "x", NamePos: pos(71)},
{Name: "y", NamePos: pos(74)},
},
ForeignRparen: pos(75),
},
},
Rparen: pos(76),
})
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN`, `1:36: expected KEY, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY`, `1:40: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (`, `1:42: expected column name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x`, `1:43: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x)`, `1:44: expected REFERENCES, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES`, `1:55: expected foreign table name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES tbl`, `1:59: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES tbl (`, `1:61: expected foreign column name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES tbl (x`, `1:62: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES tbl (x) ON`, `1:66: expected UPDATE or DELETE, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES tbl (x) ON UPDATE SET`, `1:77: expected NULL or DEFAULT, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES tbl (x) ON UPDATE NO`, `1:76: expected ACTION, found 'EOF'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES tbl (x) ON UPDATE TABLE`, `1:75: expected SET NULL, SET DEFAULT, CASCADE, RESTRICT, or NO ACTION, found 'TABLE'`)
AssertParseStatementError(t, `CREATE TABLE tbl (col1 TEXT, FOREIGN KEY (x) REFERENCES tbl (x) ON UPDATE CASCADE NOT`, `1:85: expected DEFERRABLE, found 'EOF'`)
})
})
})
t.Run("DropTable", func(t *testing.T) {
AssertParseStatement(t, `DROP TABLE vw`, &sql.DropTableStatement{
Drop: pos(0),
Table: pos(5),
Name: &sql.Ident{NamePos: pos(11), Name: "vw"},
})
AssertParseStatement(t, `DROP TABLE IF EXISTS vw`, &sql.DropTableStatement{
Drop: pos(0),
Table: pos(5),
If: pos(11),
IfExists: pos(14),
Name: &sql.Ident{NamePos: pos(21), Name: "vw"},
})
AssertParseStatementError(t, `DROP TABLE`, `1:10: expected table name, found 'EOF'`)
AssertParseStatementError(t, `DROP TABLE IF`, `1:13: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `DROP TABLE IF EXISTS`, `1:20: expected table name, found 'EOF'`)
})
t.Run("CreateView", func(t *testing.T) {
AssertParseStatement(t, `CREATE VIEW vw (col1, col2) AS SELECT x, y`, &sql.CreateViewStatement{
Create: pos(0),
View: pos(7),
Name: &sql.Ident{NamePos: pos(12), Name: "vw"},
Lparen: pos(15),
Columns: []*sql.Ident{
{NamePos: pos(16), Name: "col1"},
{NamePos: pos(22), Name: "col2"},
},
Rparen: pos(26),
As: pos(28),
Select: &sql.SelectStatement{
Select: pos(31),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(38), Name: "x"}},
{Expr: &sql.Ident{NamePos: pos(41), Name: "y"}},
},
},
})
AssertParseStatement(t, `CREATE VIEW vw AS SELECT x`, &sql.CreateViewStatement{
Create: pos(0),
View: pos(7),
Name: &sql.Ident{NamePos: pos(12), Name: "vw"},
As: pos(15),
Select: &sql.SelectStatement{
Select: pos(18),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(25), Name: "x"}},
},
},
})
AssertParseStatement(t, `CREATE VIEW IF NOT EXISTS vw AS SELECT x`, &sql.CreateViewStatement{
Create: pos(0),
View: pos(7),
If: pos(12),
IfNot: pos(15),
IfNotExists: pos(19),
Name: &sql.Ident{NamePos: pos(26), Name: "vw"},
As: pos(29),
Select: &sql.SelectStatement{
Select: pos(32),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(39), Name: "x"}},
},
},
})
AssertParseStatementError(t, `CREATE VIEW`, `1:11: expected view name, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW IF`, `1:14: expected NOT, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW IF NOT`, `1:18: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw`, `1:14: expected AS, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw (`, `1:16: expected column name, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw (x`, `1:17: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw AS`, `1:17: expected SELECT or VALUES, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw AS SELECT`, `1:24: expected expression, found 'EOF'`)
})
t.Run("DropView", func(t *testing.T) {
AssertParseStatement(t, `DROP VIEW vw`, &sql.DropViewStatement{
Drop: pos(0),
View: pos(5),
Name: &sql.Ident{NamePos: pos(10), Name: "vw"},
})
AssertParseStatement(t, `DROP VIEW IF EXISTS vw`, &sql.DropViewStatement{
Drop: pos(0),
View: pos(5),
If: pos(10),
IfExists: pos(13),
Name: &sql.Ident{NamePos: pos(20), Name: "vw"},
})
AssertParseStatementError(t, `DROP`, `1:1: expected TABLE, VIEW, INDEX, or TRIGGER`)
AssertParseStatementError(t, `DROP VIEW`, `1:9: expected view name, found 'EOF'`)
AssertParseStatementError(t, `DROP VIEW IF`, `1:12: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `DROP VIEW IF EXISTS`, `1:19: expected view name, found 'EOF'`)
})
t.Run("CreateIndex", func(t *testing.T) {
AssertParseStatement(t, `CREATE INDEX idx ON tbl (x ASC, y DESC, z)`, &sql.CreateIndexStatement{
Create: pos(0),
Index: pos(7),
Name: &sql.Ident{NamePos: pos(13), Name: "idx"},
On: pos(17),
Table: &sql.Ident{NamePos: pos(20), Name: "tbl"},
Lparen: pos(24),
Columns: []*sql.IndexedColumn{
{X: &sql.Ident{NamePos: pos(25), Name: "x"}, Asc: pos(27)},
{X: &sql.Ident{NamePos: pos(32), Name: "y"}, Desc: pos(34)},
{X: &sql.Ident{NamePos: pos(40), Name: "z"}},
},
Rparen: pos(41),
})
AssertParseStatement(t, `CREATE UNIQUE INDEX idx ON tbl (x)`, &sql.CreateIndexStatement{
Create: pos(0),
Unique: pos(7),
Index: pos(14),
Name: &sql.Ident{NamePos: pos(20), Name: "idx"},
On: pos(24),
Table: &sql.Ident{NamePos: pos(27), Name: "tbl"},
Lparen: pos(31),
Columns: []*sql.IndexedColumn{
{X: &sql.Ident{NamePos: pos(32), Name: "x"}},
},
Rparen: pos(33),
})
AssertParseStatement(t, `CREATE INDEX idx ON tbl (x) WHERE true`, &sql.CreateIndexStatement{
Create: pos(0),
Index: pos(7),
Name: &sql.Ident{NamePos: pos(13), Name: "idx"},
On: pos(17),
Table: &sql.Ident{NamePos: pos(20), Name: "tbl"},
Lparen: pos(24),
Columns: []*sql.IndexedColumn{
{X: &sql.Ident{NamePos: pos(25), Name: "x"}},
},
Rparen: pos(26),
Where: pos(28),
WhereExpr: &sql.BoolLit{ValuePos: pos(34), Value: true},
})
AssertParseStatement(t, `CREATE INDEX IF NOT EXISTS idx ON tbl (x)`, &sql.CreateIndexStatement{
Create: pos(0),
Index: pos(7),
If: pos(13),
IfNot: pos(16),
IfNotExists: pos(20),
Name: &sql.Ident{NamePos: pos(27), Name: "idx"},
On: pos(31),
Table: &sql.Ident{NamePos: pos(34), Name: "tbl"},
Lparen: pos(38),
Columns: []*sql.IndexedColumn{
{X: &sql.Ident{NamePos: pos(39), Name: "x"}},
},
Rparen: pos(40),
})
AssertParseStatementError(t, `CREATE UNIQUE`, `1:13: expected INDEX, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX`, `1:12: expected index name, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX IF`, `1:15: expected NOT, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX IF NOT`, `1:19: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX idx`, `1:16: expected ON, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX idx ON`, `1:19: expected table name, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX idx ON tbl`, `1:23: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX idx ON tbl (`, `1:25: expected expression, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX idx ON tbl (x`, `1:26: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE INDEX idx ON tbl (x) WHERE`, `1:33: expected expression, found 'EOF'`)
})
t.Run("DropIndex", func(t *testing.T) {
AssertParseStatement(t, `DROP INDEX idx`, &sql.DropIndexStatement{
Drop: pos(0),
Index: pos(5),
Name: &sql.Ident{NamePos: pos(11), Name: "idx"},
})
AssertParseStatement(t, `DROP INDEX IF EXISTS idx`, &sql.DropIndexStatement{
Drop: pos(0),
Index: pos(5),
If: pos(11),
IfExists: pos(14),
Name: &sql.Ident{NamePos: pos(21), Name: "idx"},
})
AssertParseStatementError(t, `DROP INDEX`, `1:10: expected index name, found 'EOF'`)
AssertParseStatementError(t, `DROP INDEX IF`, `1:13: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `DROP INDEX IF EXISTS`, `1:20: expected index name, found 'EOF'`)
})
t.Run("CreateTrigger", func(t *testing.T) {
AssertParseStatement(t, `CREATE TRIGGER trig DELETE ON tbl BEGIN INSERT INTO new DEFAULT VALUES; UPDATE new SET x = 1; END`, &sql.CreateTriggerStatement{
Create: pos(0),
Trigger: pos(7),
Name: &sql.Ident{NamePos: pos(15), Name: "trig"},
Delete: pos(20),
On: pos(27),
Table: &sql.Ident{NamePos: pos(30), Name: "tbl"},
Begin: pos(34),
Body: []sql.Statement{
&sql.InsertStatement{
Insert: pos(40),
Into: pos(47),
Table: &sql.Ident{NamePos: pos(52), Name: "new"},
Default: pos(56),
DefaultValues: pos(64),
},
&sql.UpdateStatement{
Update: pos(72),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(79), Name: "new"},
},
Set: pos(83),
Assignments: []*sql.Assignment{{
Columns: []*sql.Ident{{NamePos: pos(87), Name: "x"}},
Eq: pos(89),
Expr: &sql.NumberLit{ValuePos: pos(91), Value: "1"},
}},
},
},
End: pos(94),
})
AssertParseStatement(t, `CREATE TRIGGER IF NOT EXISTS trig BEFORE INSERT ON tbl BEGIN DELETE FROM new; END`, &sql.CreateTriggerStatement{
Create: pos(0),
Trigger: pos(7),
If: pos(15),
IfNot: pos(18),
IfNotExists: pos(22),
Name: &sql.Ident{NamePos: pos(29), Name: "trig"},
Before: pos(34),
Insert: pos(41),
On: pos(48),
Table: &sql.Ident{NamePos: pos(51), Name: "tbl"},
Begin: pos(55),
Body: []sql.Statement{
&sql.DeleteStatement{
Delete: pos(61),
From: pos(68),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(73), Name: "new"},
},
},
},
End: pos(78),
})
AssertParseStatement(t, `CREATE TRIGGER trig INSTEAD OF UPDATE ON tbl BEGIN SELECT *; END`, &sql.CreateTriggerStatement{
Create: pos(0),
Trigger: pos(7),
Name: &sql.Ident{NamePos: pos(15), Name: "trig"},
Instead: pos(20),
InsteadOf: pos(28),
Update: pos(31),
On: pos(38),
Table: &sql.Ident{NamePos: pos(41), Name: "tbl"},
Begin: pos(45),
Body: []sql.Statement{
&sql.SelectStatement{
Select: pos(51),
Columns: []*sql.ResultColumn{{Star: pos(58)}},
},
},
End: pos(61),
})
AssertParseStatement(t, `CREATE TRIGGER trig INSTEAD OF UPDATE OF x, y ON tbl FOR EACH ROW WHEN true BEGIN SELECT *; END`, &sql.CreateTriggerStatement{
Create: pos(0),
Trigger: pos(7),
Name: &sql.Ident{NamePos: pos(15), Name: "trig"},
Instead: pos(20),
InsteadOf: pos(28),
Update: pos(31),
UpdateOf: pos(38),
UpdateOfColumns: []*sql.Ident{
{NamePos: pos(41), Name: "x"},
{NamePos: pos(44), Name: "y"},
},
On: pos(46),
Table: &sql.Ident{NamePos: pos(49), Name: "tbl"},
For: pos(53),
ForEach: pos(57),
ForEachRow: pos(62),
When: pos(66),
WhenExpr: &sql.BoolLit{ValuePos: pos(71), Value: true},
Begin: pos(76),
Body: []sql.Statement{
&sql.SelectStatement{
Select: pos(82),
Columns: []*sql.ResultColumn{{Star: pos(89)}},
},
},
End: pos(92),
})
AssertParseStatement(t, `CREATE TRIGGER trig AFTER UPDATE ON tbl BEGIN WITH cte (x) AS (SELECT y) SELECT *; END`, &sql.CreateTriggerStatement{
Create: pos(0),
Trigger: pos(7),
Name: &sql.Ident{NamePos: pos(15), Name: "trig"},
After: pos(20),
Update: pos(26),
On: pos(33),
Table: &sql.Ident{NamePos: pos(36), Name: "tbl"},
Begin: pos(40),
Body: []sql.Statement{
&sql.SelectStatement{
WithClause: &sql.WithClause{
With: pos(46),
CTEs: []*sql.CTE{
{
TableName: &sql.Ident{NamePos: pos(51), Name: "cte"},
ColumnsLparen: pos(55),
Columns: []*sql.Ident{
{NamePos: pos(56), Name: "x"},
},
ColumnsRparen: pos(57),
As: pos(59),
SelectLparen: pos(62),
Select: &sql.SelectStatement{
Select: pos(63),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(70), Name: "y"}},
},
},
SelectRparen: pos(71),
},
},
},
Select: pos(73),
Columns: []*sql.ResultColumn{{Star: pos(80)}},
},
},
End: pos(83),
})
AssertParseStatementError(t, `CREATE TRIGGER`, `1:14: expected index name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER IF`, `1:17: expected NOT, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER IF NOT`, `1:21: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig INSTEAD`, `1:27: expected OF, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER`, `1:25: expected DELETE, INSERT, or UPDATE, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig UPDATE OF`, `1:29: expected column name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig UPDATE OF x,`, `1:32: expected column name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT`, `1:32: expected ON, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON `, `1:36: expected table name, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON tbl FOR`, `1:43: expected EACH, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON tbl FOR EACH`, `1:48: expected ROW, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON tbl WHEN`, `1:44: expected expression, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON tbl`, `1:39: expected BEGIN, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON tbl BEGIN`, `1:45: expected statement, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON tbl BEGIN SELECT`, `1:52: expected expression, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON tbl BEGIN SELECT *`, `1:54: expected semicolon, found 'EOF'`)
AssertParseStatementError(t, `CREATE TRIGGER trig AFTER INSERT ON tbl BEGIN SELECT *;`, `1:55: expected statement, found 'EOF'`)
})
t.Run("DropTrigger", func(t *testing.T) {
AssertParseStatement(t, `DROP TRIGGER trig`, &sql.DropTriggerStatement{
Drop: pos(0),
Trigger: pos(5),
Name: &sql.Ident{NamePos: pos(13), Name: "trig"},
})
AssertParseStatement(t, `DROP TRIGGER IF EXISTS trig`, &sql.DropTriggerStatement{
Drop: pos(0),
Trigger: pos(5),
If: pos(13),
IfExists: pos(16),
Name: &sql.Ident{NamePos: pos(23), Name: "trig"},
})
AssertParseStatementError(t, `DROP TRIGGER`, `1:12: expected trigger name, found 'EOF'`)
AssertParseStatementError(t, `DROP TRIGGER IF`, `1:15: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `DROP TRIGGER IF EXISTS`, `1:22: expected trigger name, found 'EOF'`)
})
t.Run("Select", func(t *testing.T) {
AssertParseStatement(t, `SELECT * FROM tbl`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "tbl"},
},
})
AssertParseStatement(t, `SELECT DISTINCT * FROM tbl`, &sql.SelectStatement{
Select: pos(0),
Distinct: pos(7),
Columns: []*sql.ResultColumn{
{Star: pos(16)},
},
From: pos(18),
Source: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(23), Name: "tbl"},
},
})
AssertParseStatement(t, `SELECT ALL * FROM tbl`, &sql.SelectStatement{
Select: pos(0),
All: pos(7),
Columns: []*sql.ResultColumn{
{Star: pos(11)},
},
From: pos(13),
Source: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(18), Name: "tbl"},
},
})
AssertParseStatement(t, `SELECT foo AS FOO, bar baz, tbl.* FROM tbl`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{
Expr: &sql.Ident{NamePos: pos(7), Name: "foo"},
As: pos(11),
Alias: &sql.Ident{NamePos: pos(14), Name: "FOO"},
},
{
Expr: &sql.Ident{NamePos: pos(19), Name: "bar"},
Alias: &sql.Ident{NamePos: pos(23), Name: "baz"},
},
{
Expr: &sql.QualifiedRef{
Table: &sql.Ident{NamePos: pos(28), Name: "tbl"},
Dot: pos(31),
Star: pos(32),
},
},
},
From: pos(34),
Source: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(39), Name: "tbl"},
},
})
AssertParseStatement(t, `SELECT * FROM tbl tbl2`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "tbl"},
Alias: &sql.Ident{NamePos: pos(18), Name: "tbl2"},
},
})
AssertParseStatement(t, `SELECT * FROM tbl AS tbl2`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "tbl"},
As: pos(18),
Alias: &sql.Ident{NamePos: pos(21), Name: "tbl2"},
},
})
AssertParseStatement(t, `SELECT * FROM tbl INDEXED BY idx`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "tbl"},
Indexed: pos(18),
IndexedBy: pos(26),
Index: &sql.Ident{NamePos: pos(29), Name: "idx"},
},
})
AssertParseStatement(t, `SELECT * FROM tbl NOT INDEXED`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "tbl"},
Not: pos(18),
NotIndexed: pos(22),
},
})
AssertParseStatement(t, `SELECT * FROM (SELECT *) AS tbl`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.ParenSource{
Lparen: pos(14),
X: &sql.SelectStatement{
Select: pos(15),
Columns: []*sql.ResultColumn{
{Star: pos(22)},
},
},
Rparen: pos(23),
As: pos(25),
Alias: &sql.Ident{NamePos: pos(28), Name: "tbl"},
},
})
AssertParseStatement(t, `SELECT * FROM foo, bar`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "foo"},
},
Operator: &sql.JoinOperator{Comma: pos(17)},
Y: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(19), Name: "bar"},
},
},
})
AssertParseStatement(t, `SELECT * FROM foo JOIN bar`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "foo"},
},
Operator: &sql.JoinOperator{Join: pos(18)},
Y: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(23), Name: "bar"},
},
},
})
AssertParseStatement(t, `SELECT * FROM foo NATURAL JOIN bar`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "foo"},
},
Operator: &sql.JoinOperator{Natural: pos(18), Join: pos(26)},
Y: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(31), Name: "bar"},
},
},
})
AssertParseStatement(t, `SELECT * FROM foo INNER JOIN bar ON true`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "foo"},
},
Operator: &sql.JoinOperator{Inner: pos(18), Join: pos(24)},
Y: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(29), Name: "bar"},
},
Constraint: &sql.OnConstraint{
On: pos(33),
X: &sql.BoolLit{ValuePos: pos(36), Value: true},
},
},
})
AssertParseStatement(t, `SELECT * FROM foo LEFT JOIN bar USING (x, y)`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "foo"},
},
Operator: &sql.JoinOperator{Left: pos(18), Join: pos(23)},
Y: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(28), Name: "bar"},
},
Constraint: &sql.UsingConstraint{
Using: pos(32),
Lparen: pos(38),
Columns: []*sql.Ident{
{NamePos: pos(39), Name: "x"},
{NamePos: pos(42), Name: "y"},
},
Rparen: pos(43),
},
},
})
AssertParseStatement(t, `SELECT * FROM X INNER JOIN Y ON true INNER JOIN Z ON false`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "X"},
},
Operator: &sql.JoinOperator{Inner: pos(16), Join: pos(22)},
Y: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(27), Name: "Y"},
},
Operator: &sql.JoinOperator{Inner: pos(37), Join: pos(43)},
Y: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(48), Name: "Z"},
},
Constraint: &sql.OnConstraint{
On: pos(50),
X: &sql.BoolLit{ValuePos: pos(53), Value: false},
},
},
Constraint: &sql.OnConstraint{
On: pos(29),
X: &sql.BoolLit{ValuePos: pos(32), Value: true},
},
},
})
AssertParseStatement(t, `SELECT * FROM foo LEFT OUTER JOIN bar`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "foo"},
},
Operator: &sql.JoinOperator{Left: pos(18), Outer: pos(23), Join: pos(29)},
Y: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(34), Name: "bar"},
},
},
})
AssertParseStatement(t, `SELECT * FROM foo CROSS JOIN bar`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
From: pos(9),
Source: &sql.JoinClause{
X: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(14), Name: "foo"},
},
Operator: &sql.JoinOperator{Cross: pos(18), Join: pos(24)},
Y: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(29), Name: "bar"},
},
},
})
AssertParseStatement(t, `WITH cte (foo, bar) AS (SELECT baz), xxx AS (SELECT yyy) SELECT bat`, &sql.SelectStatement{
WithClause: &sql.WithClause{
With: pos(0),
CTEs: []*sql.CTE{
{
TableName: &sql.Ident{NamePos: pos(5), Name: "cte"},
ColumnsLparen: pos(9),
Columns: []*sql.Ident{
{NamePos: pos(10), Name: "foo"},
{NamePos: pos(15), Name: "bar"},
},
ColumnsRparen: pos(18),
As: pos(20),
SelectLparen: pos(23),
Select: &sql.SelectStatement{
Select: pos(24),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(31), Name: "baz"}},
},
},
SelectRparen: pos(34),
},
{
TableName: &sql.Ident{NamePos: pos(37), Name: "xxx"},
As: pos(41),
SelectLparen: pos(44),
Select: &sql.SelectStatement{
Select: pos(45),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(52), Name: "yyy"}},
},
},
SelectRparen: pos(55),
},
},
},
Select: pos(57),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(64), Name: "bat"}},
},
})
AssertParseStatement(t, `WITH RECURSIVE cte AS (SELECT foo) SELECT bar`, &sql.SelectStatement{
WithClause: &sql.WithClause{
With: pos(0),
Recursive: pos(5),
CTEs: []*sql.CTE{
{
TableName: &sql.Ident{NamePos: pos(15), Name: "cte"},
As: pos(19),
SelectLparen: pos(22),
Select: &sql.SelectStatement{
Select: pos(23),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(30), Name: "foo"}},
},
},
SelectRparen: pos(33),
},
},
},
Select: pos(35),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(42), Name: "bar"}},
},
})
AssertParseStatement(t, `SELECT * WHERE true`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{{Star: pos(7)}},
Where: pos(9),
WhereExpr: &sql.BoolLit{ValuePos: pos(15), Value: true},
})
AssertParseStatement(t, `SELECT * GROUP BY foo, bar`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{{Star: pos(7)}},
Group: pos(9),
GroupBy: pos(15),
GroupByExprs: []sql.Expr{
&sql.Ident{NamePos: pos(18), Name: "foo"},
&sql.Ident{NamePos: pos(23), Name: "bar"},
},
})
AssertParseStatement(t, `SELECT * GROUP BY foo HAVING true`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{{Star: pos(7)}},
Group: pos(9),
GroupBy: pos(15),
GroupByExprs: []sql.Expr{
&sql.Ident{NamePos: pos(18), Name: "foo"},
},
Having: pos(22),
HavingExpr: &sql.BoolLit{ValuePos: pos(29), Value: true},
})
AssertParseStatement(t, `SELECT * WINDOW win1 AS (), win2 AS ()`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{{Star: pos(7)}},
Window: pos(9),
Windows: []*sql.Window{
{
Name: &sql.Ident{NamePos: pos(16), Name: "win1"},
As: pos(21),
Definition: &sql.WindowDefinition{
Lparen: pos(24),
Rparen: pos(25),
},
},
{
Name: &sql.Ident{NamePos: pos(28), Name: "win2"},
As: pos(33),
Definition: &sql.WindowDefinition{
Lparen: pos(36),
Rparen: pos(37),
},
},
},
})
AssertParseStatement(t, `SELECT * ORDER BY foo ASC, bar DESC`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
Order: pos(9),
OrderBy: pos(15),
OrderingTerms: []*sql.OrderingTerm{
{X: &sql.Ident{NamePos: pos(18), Name: "foo"}, Asc: pos(22)},
{X: &sql.Ident{NamePos: pos(27), Name: "bar"}, Desc: pos(31)},
},
})
AssertParseStatement(t, `SELECT * LIMIT 1`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
Limit: pos(9),
LimitExpr: &sql.NumberLit{ValuePos: pos(15), Value: "1"},
})
AssertParseStatement(t, `SELECT * LIMIT 1 OFFSET 2`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
Limit: pos(9),
LimitExpr: &sql.NumberLit{ValuePos: pos(15), Value: "1"},
Offset: pos(17),
OffsetExpr: &sql.NumberLit{ValuePos: pos(24), Value: "2"},
})
AssertParseStatement(t, `SELECT * LIMIT 1, 2`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
Limit: pos(9),
LimitExpr: &sql.NumberLit{ValuePos: pos(15), Value: "1"},
OffsetComma: pos(16),
OffsetExpr: &sql.NumberLit{ValuePos: pos(18), Value: "2"},
})
AssertParseStatement(t, `SELECT * UNION SELECT * ORDER BY foo`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
Union: pos(9),
Compound: &sql.SelectStatement{
Select: pos(15),
Columns: []*sql.ResultColumn{
{Star: pos(22)},
},
},
Order: pos(24),
OrderBy: pos(30),
OrderingTerms: []*sql.OrderingTerm{
{X: &sql.Ident{NamePos: pos(33), Name: "foo"}},
},
})
AssertParseStatement(t, `SELECT * UNION ALL SELECT *`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
Union: pos(9),
UnionAll: pos(15),
Compound: &sql.SelectStatement{
Select: pos(19),
Columns: []*sql.ResultColumn{
{Star: pos(26)},
},
},
})
AssertParseStatement(t, `SELECT * INTERSECT SELECT *`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
Intersect: pos(9),
Compound: &sql.SelectStatement{
Select: pos(19),
Columns: []*sql.ResultColumn{
{Star: pos(26)},
},
},
})
AssertParseStatement(t, `SELECT * EXCEPT SELECT *`, &sql.SelectStatement{
Select: pos(0),
Columns: []*sql.ResultColumn{
{Star: pos(7)},
},
Except: pos(9),
Compound: &sql.SelectStatement{
Select: pos(16),
Columns: []*sql.ResultColumn{
{Star: pos(23)},
},
},
})
AssertParseStatement(t, `VALUES (1, 2), (3, 4)`, &sql.SelectStatement{
Values: pos(0),
ValueLists: []*sql.ExprList{
{
Lparen: pos(7),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(8), Value: "1"},
&sql.NumberLit{ValuePos: pos(11), Value: "2"},
},
Rparen: pos(12),
},
{
Lparen: pos(15),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(16), Value: "3"},
&sql.NumberLit{ValuePos: pos(19), Value: "4"},
},
Rparen: pos(20),
},
},
})
AssertParseStatementError(t, `WITH `, `1:5: expected table name, found 'EOF'`)
AssertParseStatementError(t, `WITH cte`, `1:8: expected AS, found 'EOF'`)
AssertParseStatementError(t, `WITH cte (`, `1:10: expected column name, found 'EOF'`)
AssertParseStatementError(t, `WITH cte (foo`, `1:13: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `WITH cte (foo)`, `1:14: expected AS, found 'EOF'`)
AssertParseStatementError(t, `WITH cte AS`, `1:11: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `WITH cte AS (`, `1:13: expected SELECT or VALUES, found 'EOF'`)
AssertParseStatementError(t, `WITH cte AS (SELECT foo`, `1:23: expected right paren, found 'EOF'`)
AssertParseStatementError(t, `WITH cte AS (SELECT foo)`, `1:24: expected SELECT, VALUES, INSERT, REPLACE, UPDATE, or DELETE, found 'EOF'`)
AssertParseStatementError(t, `SELECT `, `1:7: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT 1+`, `1:9: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT foo,`, `1:11: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT foo AS`, `1:13: expected column alias, found 'EOF'`)
AssertParseStatementError(t, `SELECT foo.* AS`, `1:14: expected semicolon or EOF, found 'AS'`)
AssertParseStatementError(t, `SELECT foo FROM`, `1:15: expected table name or left paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT foo FROM foo INDEXED`, `1:27: expected BY, found 'EOF'`)
AssertParseStatementError(t, `SELECT foo FROM foo INDEXED BY`, `1:30: expected index name, found 'EOF'`)
AssertParseStatementError(t, `SELECT foo FROM foo NOT`, `1:23: expected INDEXED, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo INNER`, `1:23: expected JOIN, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo CROSS`, `1:23: expected JOIN, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo NATURAL`, `1:25: expected JOIN, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo LEFT`, `1:22: expected JOIN, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo LEFT OUTER`, `1:28: expected JOIN, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo,`, `1:18: expected table name or left paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo JOIN bar ON`, `1:29: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo JOIN bar USING`, `1:32: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo JOIN bar USING (`, `1:34: expected column name, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo JOIN bar USING (x`, `1:35: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo JOIN bar USING (x,`, `1:36: expected column name, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM (`, `1:15: expected table name or left paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM ((`, `1:16: expected table name or left paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM (SELECT`, `1:21: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM (tbl`, `1:18: expected right paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM (SELECT *) AS`, `1:27: expected table alias, found 'EOF'`)
AssertParseStatementError(t, `SELECT * FROM foo AS`, `1:20: expected table alias, found 'EOF'`)
AssertParseStatementError(t, `SELECT foo WHERE`, `1:16: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * GROUP`, `1:14: expected BY, found 'EOF'`)
AssertParseStatementError(t, `SELECT * GROUP BY`, `1:17: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * GROUP BY foo bar`, `1:23: expected semicolon or EOF, found bar`)
AssertParseStatementError(t, `SELECT * GROUP BY foo HAVING`, `1:28: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * WINDOW`, `1:15: expected window name, found 'EOF'`)
AssertParseStatementError(t, `SELECT * WINDOW win1`, `1:20: expected AS, found 'EOF'`)
AssertParseStatementError(t, `SELECT * WINDOW win1 AS`, `1:23: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT * WINDOW win1 AS (`, `1:25: expected right paren, found 'EOF'`)
AssertParseStatementError(t, `SELECT * WINDOW win1 AS () win2`, `1:28: expected semicolon or EOF, found win2`)
AssertParseStatementError(t, `SELECT * ORDER`, `1:14: expected BY, found 'EOF'`)
AssertParseStatementError(t, `SELECT * ORDER BY`, `1:17: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * ORDER BY 1,`, `1:20: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * LIMIT`, `1:14: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * LIMIT 1,`, `1:17: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * LIMIT 1 OFFSET`, `1:23: expected expression, found 'EOF'`)
AssertParseStatementError(t, `VALUES`, `1:6: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `VALUES (`, `1:8: expected expression, found 'EOF'`)
AssertParseStatementError(t, `VALUES (1`, `1:9: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `VALUES (1,`, `1:10: expected expression, found 'EOF'`)
AssertParseStatementError(t, `SELECT * UNION`, `1:14: expected SELECT or VALUES, found 'EOF'`)
})
t.Run("Insert", func(t *testing.T) {
AssertParseStatement(t, `INSERT INTO tbl (x, y) VALUES (1, 2)`, &sql.InsertStatement{
Insert: pos(0),
Into: pos(7),
Table: &sql.Ident{NamePos: pos(12), Name: "tbl"},
ColumnsLparen: pos(16),
Columns: []*sql.Ident{
{NamePos: pos(17), Name: "x"},
{NamePos: pos(20), Name: "y"},
},
ColumnsRparen: pos(21),
Values: pos(23),
ValueLists: []*sql.ExprList{{
Lparen: pos(30),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(31), Value: "1"},
&sql.NumberLit{ValuePos: pos(34), Value: "2"},
},
Rparen: pos(35),
}},
})
AssertParseStatement(t, `REPLACE INTO tbl (x, y) VALUES (1, 2), (3, 4)`, &sql.InsertStatement{
Replace: pos(0),
Into: pos(8),
Table: &sql.Ident{NamePos: pos(13), Name: "tbl"},
ColumnsLparen: pos(17),
Columns: []*sql.Ident{
{NamePos: pos(18), Name: "x"},
{NamePos: pos(21), Name: "y"},
},
ColumnsRparen: pos(22),
Values: pos(24),
ValueLists: []*sql.ExprList{
{
Lparen: pos(31),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(32), Value: "1"},
&sql.NumberLit{ValuePos: pos(35), Value: "2"},
},
Rparen: pos(36),
},
{
Lparen: pos(39),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(40), Value: "3"},
&sql.NumberLit{ValuePos: pos(43), Value: "4"},
},
Rparen: pos(44),
},
},
})
AssertParseStatement(t, `INSERT OR REPLACE INTO tbl (x) VALUES (1)`, &sql.InsertStatement{
Insert: pos(0),
InsertOr: pos(7),
InsertOrReplace: pos(10),
Into: pos(18),
Table: &sql.Ident{NamePos: pos(23), Name: "tbl"},
ColumnsLparen: pos(27),
Columns: []*sql.Ident{
{NamePos: pos(28), Name: "x"},
},
ColumnsRparen: pos(29),
Values: pos(31),
ValueLists: []*sql.ExprList{{
Lparen: pos(38),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(39), Value: "1"},
},
Rparen: pos(40),
}},
})
AssertParseStatement(t, `INSERT OR ROLLBACK INTO tbl (x) VALUES (1)`, &sql.InsertStatement{
Insert: pos(0),
InsertOr: pos(7),
InsertOrRollback: pos(10),
Into: pos(19),
Table: &sql.Ident{NamePos: pos(24), Name: "tbl"},
ColumnsLparen: pos(28),
Columns: []*sql.Ident{
{NamePos: pos(29), Name: "x"},
},
ColumnsRparen: pos(30),
Values: pos(32),
ValueLists: []*sql.ExprList{{
Lparen: pos(39),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(40), Value: "1"},
},
Rparen: pos(41),
}},
})
AssertParseStatement(t, `INSERT OR ABORT INTO tbl (x) VALUES (1)`, &sql.InsertStatement{
Insert: pos(0),
InsertOr: pos(7),
InsertOrAbort: pos(10),
Into: pos(16),
Table: &sql.Ident{NamePos: pos(21), Name: "tbl"},
ColumnsLparen: pos(25),
Columns: []*sql.Ident{
{NamePos: pos(26), Name: "x"},
},
ColumnsRparen: pos(27),
Values: pos(29),
ValueLists: []*sql.ExprList{{
Lparen: pos(36),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(37), Value: "1"},
},
Rparen: pos(38),
}},
})
AssertParseStatement(t, `INSERT OR FAIL INTO tbl VALUES (1)`, &sql.InsertStatement{
Insert: pos(0),
InsertOr: pos(7),
InsertOrFail: pos(10),
Into: pos(15),
Table: &sql.Ident{NamePos: pos(20), Name: "tbl"},
Values: pos(24),
ValueLists: []*sql.ExprList{{
Lparen: pos(31),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(32), Value: "1"},
},
Rparen: pos(33),
}},
})
AssertParseStatement(t, `INSERT OR IGNORE INTO tbl AS tbl2 VALUES (1)`, &sql.InsertStatement{
Insert: pos(0),
InsertOr: pos(7),
InsertOrIgnore: pos(10),
Into: pos(17),
Table: &sql.Ident{NamePos: pos(22), Name: "tbl"},
As: pos(26),
Alias: &sql.Ident{NamePos: pos(29), Name: "tbl2"},
Values: pos(34),
ValueLists: []*sql.ExprList{{
Lparen: pos(41),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(42), Value: "1"},
},
Rparen: pos(43),
}},
})
AssertParseStatement(t, `WITH cte (foo) AS (SELECT bar) INSERT INTO tbl VALUES (1)`, &sql.InsertStatement{
WithClause: &sql.WithClause{
With: pos(0),
CTEs: []*sql.CTE{{
TableName: &sql.Ident{NamePos: pos(5), Name: "cte"},
ColumnsLparen: pos(9),
Columns: []*sql.Ident{
{NamePos: pos(10), Name: "foo"},
},
ColumnsRparen: pos(13),
As: pos(15),
SelectLparen: pos(18),
Select: &sql.SelectStatement{
Select: pos(19),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(26), Name: "bar"}},
},
},
SelectRparen: pos(29),
}},
},
Insert: pos(31),
Into: pos(38),
Table: &sql.Ident{NamePos: pos(43), Name: "tbl"},
Values: pos(47),
ValueLists: []*sql.ExprList{{
Lparen: pos(54),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(55), Value: "1"},
},
Rparen: pos(56),
}},
})
AssertParseStatement(t, `WITH cte (foo) AS (SELECT bar) INSERT INTO tbl VALUES (1)`, &sql.InsertStatement{
WithClause: &sql.WithClause{
With: pos(0),
CTEs: []*sql.CTE{{
TableName: &sql.Ident{NamePos: pos(5), Name: "cte"},
ColumnsLparen: pos(9),
Columns: []*sql.Ident{
{NamePos: pos(10), Name: "foo"},
},
ColumnsRparen: pos(13),
As: pos(15),
SelectLparen: pos(18),
Select: &sql.SelectStatement{
Select: pos(19),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(26), Name: "bar"}},
},
},
SelectRparen: pos(29),
}},
},
Insert: pos(31),
Into: pos(38),
Table: &sql.Ident{NamePos: pos(43), Name: "tbl"},
Values: pos(47),
ValueLists: []*sql.ExprList{{
Lparen: pos(54),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(55), Value: "1"},
},
Rparen: pos(56),
}},
})
AssertParseStatement(t, `INSERT INTO tbl (x) SELECT y`, &sql.InsertStatement{
Insert: pos(0),
Into: pos(7),
Table: &sql.Ident{NamePos: pos(12), Name: "tbl"},
ColumnsLparen: pos(16),
Columns: []*sql.Ident{
{NamePos: pos(17), Name: "x"},
},
ColumnsRparen: pos(18),
Select: &sql.SelectStatement{
Select: pos(20),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(27), Name: "y"}},
},
},
})
AssertParseStatement(t, `INSERT INTO tbl (x) DEFAULT VALUES`, &sql.InsertStatement{
Insert: pos(0),
Into: pos(7),
Table: &sql.Ident{NamePos: pos(12), Name: "tbl"},
ColumnsLparen: pos(16),
Columns: []*sql.Ident{
{NamePos: pos(17), Name: "x"},
},
ColumnsRparen: pos(18),
Default: pos(20),
DefaultValues: pos(28),
})
AssertParseStatement(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (y ASC, z DESC) DO NOTHING`, &sql.InsertStatement{
Insert: pos(0),
Into: pos(7),
Table: &sql.Ident{NamePos: pos(12), Name: "tbl"},
ColumnsLparen: pos(16),
Columns: []*sql.Ident{
{NamePos: pos(17), Name: "x"},
},
ColumnsRparen: pos(18),
Values: pos(20),
ValueLists: []*sql.ExprList{{
Lparen: pos(27),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(28), Value: "1"},
},
Rparen: pos(29),
}},
UpsertClause: &sql.UpsertClause{
On: pos(31),
OnConflict: pos(34),
Lparen: pos(43),
Columns: []*sql.IndexedColumn{
{X: &sql.Ident{NamePos: pos(44), Name: "y"}, Asc: pos(46)},
{X: &sql.Ident{NamePos: pos(51), Name: "z"}, Desc: pos(53)},
},
Rparen: pos(57),
Do: pos(59),
DoNothing: pos(62),
},
})
AssertParseStatement(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (y) WHERE true DO UPDATE SET foo = 1, (bar, baz) = 2 WHERE false`, &sql.InsertStatement{
Insert: pos(0),
Into: pos(7),
Table: &sql.Ident{NamePos: pos(12), Name: "tbl"},
ColumnsLparen: pos(16),
Columns: []*sql.Ident{
{NamePos: pos(17), Name: "x"},
},
ColumnsRparen: pos(18),
Values: pos(20),
ValueLists: []*sql.ExprList{{
Lparen: pos(27),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(28), Value: "1"},
},
Rparen: pos(29),
}},
UpsertClause: &sql.UpsertClause{
On: pos(31),
OnConflict: pos(34),
Lparen: pos(43),
Columns: []*sql.IndexedColumn{
{X: &sql.Ident{NamePos: pos(44), Name: "y"}},
},
Rparen: pos(45),
Where: pos(47),
WhereExpr: &sql.BoolLit{ValuePos: pos(53), Value: true},
Do: pos(58),
DoUpdate: pos(61),
DoUpdateSet: pos(68),
Assignments: []*sql.Assignment{
{
Columns: []*sql.Ident{
{NamePos: pos(72), Name: "foo"},
},
Eq: pos(76),
Expr: &sql.NumberLit{ValuePos: pos(78), Value: "1"},
},
{
Lparen: pos(81),
Columns: []*sql.Ident{
{NamePos: pos(82), Name: "bar"},
{NamePos: pos(87), Name: "baz"},
},
Rparen: pos(90),
Eq: pos(92),
Expr: &sql.NumberLit{ValuePos: pos(94), Value: "2"},
},
},
UpdateWhere: pos(96),
UpdateWhereExpr: &sql.BoolLit{ValuePos: pos(102), Value: false},
},
})
AssertParseStatementError(t, `INSERT`, `1:6: expected INTO, found 'EOF'`)
AssertParseStatementError(t, `INSERT OR`, `1:9: expected ROLLBACK, REPLACE, ABORT, FAIL, or IGNORE, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO`, `1:11: expected table name, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl AS`, `1:18: expected alias, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl `, `1:16: expected VALUES, SELECT, or DEFAULT VALUES, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (`, `1:17: expected column name, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x`, `1:18: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x)`, `1:19: expected VALUES, SELECT, or DEFAULT VALUES, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES`, `1:26: expected left paren, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (`, `1:28: expected expression, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1`, `1:29: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) SELECT`, `1:26: expected expression, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) DEFAULT`, `1:27: expected VALUES, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON`, `1:33: expected CONFLICT, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (`, `1:44: expected expression, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x`, `1:45: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x) WHERE`, `1:52: expected expression, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x)`, `1:46: expected DO, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x) DO`, `1:49: expected NOTHING or UPDATE SET, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x) DO UPDATE`, `1:56: expected SET, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x) DO UPDATE SET foo`, `1:64: expected =, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x) DO UPDATE SET foo =`, `1:66: expected expression, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x) DO UPDATE SET foo = 1 WHERE`, `1:74: expected expression, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x) DO UPDATE SET (`, `1:62: expected column name, found 'EOF'`)
AssertParseStatementError(t, `INSERT INTO tbl (x) VALUES (1) ON CONFLICT (x) DO UPDATE SET (foo`, `1:65: expected comma or right paren, found 'EOF'`)
})
t.Run("Update", func(t *testing.T) {
AssertParseStatement(t, `UPDATE tbl SET x = 1, y = 2`, &sql.UpdateStatement{
Update: pos(0),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(7), Name: "tbl"},
},
Set: pos(11),
Assignments: []*sql.Assignment{
{
Columns: []*sql.Ident{{NamePos: pos(15), Name: "x"}},
Eq: pos(17),
Expr: &sql.NumberLit{ValuePos: pos(19), Value: "1"},
},
{
Columns: []*sql.Ident{{NamePos: pos(22), Name: "y"}},
Eq: pos(24),
Expr: &sql.NumberLit{ValuePos: pos(26), Value: "2"},
},
},
})
AssertParseStatement(t, `UPDATE tbl SET x = 1 WHERE y = 2`, &sql.UpdateStatement{
Update: pos(0),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(7), Name: "tbl"},
},
Set: pos(11),
Assignments: []*sql.Assignment{{
Columns: []*sql.Ident{{NamePos: pos(15), Name: "x"}},
Eq: pos(17),
Expr: &sql.NumberLit{ValuePos: pos(19), Value: "1"},
}},
Where: pos(21),
WhereExpr: &sql.BinaryExpr{
X: &sql.Ident{NamePos: pos(27), Name: "y"},
OpPos: pos(29), Op: sql.EQ,
Y: &sql.NumberLit{ValuePos: pos(31), Value: "2"},
},
})
AssertParseStatement(t, `UPDATE OR ROLLBACK tbl SET x = 1`, &sql.UpdateStatement{
Update: pos(0),
UpdateOr: pos(7),
UpdateOrRollback: pos(10),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(19), Name: "tbl"},
},
Set: pos(23),
Assignments: []*sql.Assignment{{
Columns: []*sql.Ident{{NamePos: pos(27), Name: "x"}},
Eq: pos(29),
Expr: &sql.NumberLit{ValuePos: pos(31), Value: "1"},
}},
})
AssertParseStatement(t, `UPDATE OR ABORT tbl SET x = 1`, &sql.UpdateStatement{
Update: pos(0),
UpdateOr: pos(7),
UpdateOrAbort: pos(10),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(16), Name: "tbl"},
},
Set: pos(20),
Assignments: []*sql.Assignment{{
Columns: []*sql.Ident{{NamePos: pos(24), Name: "x"}},
Eq: pos(26),
Expr: &sql.NumberLit{ValuePos: pos(28), Value: "1"},
}},
})
AssertParseStatement(t, `UPDATE OR REPLACE tbl SET x = 1`, &sql.UpdateStatement{
Update: pos(0),
UpdateOr: pos(7),
UpdateOrReplace: pos(10),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(18), Name: "tbl"},
},
Set: pos(22),
Assignments: []*sql.Assignment{{
Columns: []*sql.Ident{{NamePos: pos(26), Name: "x"}},
Eq: pos(28),
Expr: &sql.NumberLit{ValuePos: pos(30), Value: "1"},
}},
})
AssertParseStatement(t, `UPDATE OR FAIL tbl SET x = 1`, &sql.UpdateStatement{
Update: pos(0),
UpdateOr: pos(7),
UpdateOrFail: pos(10),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(15), Name: "tbl"},
},
Set: pos(19),
Assignments: []*sql.Assignment{{
Columns: []*sql.Ident{{NamePos: pos(23), Name: "x"}},
Eq: pos(25),
Expr: &sql.NumberLit{ValuePos: pos(27), Value: "1"},
}},
})
AssertParseStatement(t, `UPDATE OR IGNORE tbl SET x = 1`, &sql.UpdateStatement{
Update: pos(0),
UpdateOr: pos(7),
UpdateOrIgnore: pos(10),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(17), Name: "tbl"},
},
Set: pos(21),
Assignments: []*sql.Assignment{{
Columns: []*sql.Ident{{NamePos: pos(25), Name: "x"}},
Eq: pos(27),
Expr: &sql.NumberLit{ValuePos: pos(29), Value: "1"},
}},
})
AssertParseStatement(t, `WITH cte (x) AS (SELECT y) UPDATE tbl SET x = 1`, &sql.UpdateStatement{
WithClause: &sql.WithClause{
With: pos(0),
CTEs: []*sql.CTE{
{
TableName: &sql.Ident{NamePos: pos(5), Name: "cte"},
ColumnsLparen: pos(9),
Columns: []*sql.Ident{
{NamePos: pos(10), Name: "x"},
},
ColumnsRparen: pos(11),
As: pos(13),
SelectLparen: pos(16),
Select: &sql.SelectStatement{
Select: pos(17),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(24), Name: "y"}},
},
},
SelectRparen: pos(25),
},
},
},
Update: pos(27),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(34), Name: "tbl"},
},
Set: pos(38),
Assignments: []*sql.Assignment{{
Columns: []*sql.Ident{{NamePos: pos(42), Name: "x"}},
Eq: pos(44),
Expr: &sql.NumberLit{ValuePos: pos(46), Value: "1"},
}},
})
AssertParseStatementError(t, `UPDATE`, `1:6: expected table name, found 'EOF'`)
AssertParseStatementError(t, `UPDATE OR`, `1:9: expected ROLLBACK, REPLACE, ABORT, FAIL, or IGNORE, found 'EOF'`)
AssertParseStatementError(t, `UPDATE tbl`, `1:10: expected SET, found 'EOF'`)
AssertParseStatementError(t, `UPDATE tbl SET`, `1:14: expected column name or column list, found 'EOF'`)
AssertParseStatementError(t, `UPDATE tbl SET x = `, `1:19: expected expression, found 'EOF'`)
AssertParseStatementError(t, `UPDATE tbl SET x = 1 WHERE`, `1:26: expected expression, found 'EOF'`)
AssertParseStatementError(t, `UPDATE tbl SET x = 1 WHERE y =`, `1:30: expected expression, found 'EOF'`)
})
t.Run("Delete", func(t *testing.T) {
AssertParseStatement(t, `DELETE FROM tbl`, &sql.DeleteStatement{
Delete: pos(0),
From: pos(7),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
},
})
AssertParseStatement(t, `DELETE FROM tbl WHERE x = 1`, &sql.DeleteStatement{
Delete: pos(0),
From: pos(7),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
},
Where: pos(16),
WhereExpr: &sql.BinaryExpr{
X: &sql.Ident{NamePos: pos(22), Name: "x"},
OpPos: pos(24), Op: sql.EQ,
Y: &sql.NumberLit{ValuePos: pos(26), Value: "1"},
},
})
AssertParseStatement(t, `WITH cte (x) AS (SELECT y) DELETE FROM tbl`, &sql.DeleteStatement{
WithClause: &sql.WithClause{
With: pos(0),
CTEs: []*sql.CTE{
{
TableName: &sql.Ident{NamePos: pos(5), Name: "cte"},
ColumnsLparen: pos(9),
Columns: []*sql.Ident{
{NamePos: pos(10), Name: "x"},
},
ColumnsRparen: pos(11),
As: pos(13),
SelectLparen: pos(16),
Select: &sql.SelectStatement{
Select: pos(17),
Columns: []*sql.ResultColumn{
{Expr: &sql.Ident{NamePos: pos(24), Name: "y"}},
},
},
SelectRparen: pos(25),
},
},
},
Delete: pos(27),
From: pos(34),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(39), Name: "tbl"},
},
})
AssertParseStatement(t, `DELETE FROM tbl ORDER BY x, y LIMIT 1 OFFSET 2`, &sql.DeleteStatement{
Delete: pos(0),
From: pos(7),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
},
Order: pos(16),
OrderBy: pos(22),
OrderingTerms: []*sql.OrderingTerm{
{X: &sql.Ident{NamePos: pos(25), Name: "x"}},
{X: &sql.Ident{NamePos: pos(28), Name: "y"}},
},
Limit: pos(30),
LimitExpr: &sql.NumberLit{ValuePos: pos(36), Value: "1"},
Offset: pos(38),
OffsetExpr: &sql.NumberLit{ValuePos: pos(45), Value: "2"},
})
AssertParseStatement(t, `DELETE FROM tbl LIMIT 1`, &sql.DeleteStatement{
Delete: pos(0),
From: pos(7),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
},
Limit: pos(16),
LimitExpr: &sql.NumberLit{ValuePos: pos(22), Value: "1"},
})
AssertParseStatement(t, `DELETE FROM tbl LIMIT 1, 2`, &sql.DeleteStatement{
Delete: pos(0),
From: pos(7),
Table: &sql.QualifiedTableName{
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
},
Limit: pos(16),
LimitExpr: &sql.NumberLit{ValuePos: pos(22), Value: "1"},
OffsetComma: pos(23),
OffsetExpr: &sql.NumberLit{ValuePos: pos(25), Value: "2"},
})
AssertParseStatementError(t, `DELETE`, `1:6: expected FROM, found 'EOF'`)
AssertParseStatementError(t, `DELETE FROM`, `1:11: expected table name, found 'EOF'`)
AssertParseStatementError(t, `DELETE FROM tbl WHERE`, `1:21: expected expression, found 'EOF'`)
AssertParseStatementError(t, `DELETE FROM tbl ORDER `, `1:22: expected BY, found 'EOF'`)
AssertParseStatementError(t, `DELETE FROM tbl ORDER BY`, `1:24: expected expression, found 'EOF'`)
AssertParseStatementError(t, `DELETE FROM tbl ORDER BY x`, `1:26: expected LIMIT, found 'EOF'`)
AssertParseStatementError(t, `DELETE FROM tbl LIMIT`, `1:21: expected expression, found 'EOF'`)
AssertParseStatementError(t, `DELETE FROM tbl LIMIT 1,`, `1:24: expected expression, found 'EOF'`)
AssertParseStatementError(t, `DELETE FROM tbl LIMIT 1 OFFSET`, `1:30: expected expression, found 'EOF'`)
})
t.Run("AlterTable", func(t *testing.T) {
AssertParseStatement(t, `ALTER TABLE tbl RENAME TO new_tbl`, &sql.AlterTableStatement{
Alter: pos(0),
Table: pos(6),
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
Rename: pos(16),
RenameTo: pos(23),
NewName: &sql.Ident{NamePos: pos(26), Name: "new_tbl"},
})
AssertParseStatement(t, `ALTER TABLE tbl RENAME COLUMN col TO new_col`, &sql.AlterTableStatement{
Alter: pos(0),
Table: pos(6),
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
Rename: pos(16),
RenameColumn: pos(23),
ColumnName: &sql.Ident{NamePos: pos(30), Name: "col"},
To: pos(34),
NewColumnName: &sql.Ident{NamePos: pos(37), Name: "new_col"},
})
AssertParseStatement(t, `ALTER TABLE tbl RENAME col TO new_col`, &sql.AlterTableStatement{
Alter: pos(0),
Table: pos(6),
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
Rename: pos(16),
ColumnName: &sql.Ident{NamePos: pos(23), Name: "col"},
To: pos(27),
NewColumnName: &sql.Ident{NamePos: pos(30), Name: "new_col"},
})
AssertParseStatement(t, `ALTER TABLE tbl ADD COLUMN col TEXT PRIMARY KEY`, &sql.AlterTableStatement{
Alter: pos(0),
Table: pos(6),
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
Add: pos(16),
AddColumn: pos(20),
ColumnDef: &sql.ColumnDefinition{
Name: &sql.Ident{Name: "col", NamePos: pos(27)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(31)},
},
Constraints: []sql.Constraint{
&sql.PrimaryKeyConstraint{
Primary: pos(36),
Key: pos(44),
},
},
},
})
AssertParseStatement(t, `ALTER TABLE tbl ADD col TEXT`, &sql.AlterTableStatement{
Alter: pos(0),
Table: pos(6),
Name: &sql.Ident{NamePos: pos(12), Name: "tbl"},
Add: pos(16),
ColumnDef: &sql.ColumnDefinition{
Name: &sql.Ident{Name: "col", NamePos: pos(20)},
Type: &sql.Type{
Name: &sql.Ident{Name: "TEXT", NamePos: pos(24)},
},
},
})
AssertParseStatementError(t, `ALTER`, `1:5: expected TABLE, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE`, `1:11: expected table name, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl`, `1:15: expected ADD or RENAME, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl RENAME`, `1:22: expected COLUMN keyword or column name, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl RENAME TO`, `1:25: expected new table name, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl RENAME COLUMN`, `1:29: expected column name, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl RENAME COLUMN col`, `1:33: expected TO, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl RENAME COLUMN col TO`, `1:36: expected new column name, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl ADD`, `1:19: expected COLUMN keyword or column name, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl ADD COLUMN`, `1:26: expected column name, found 'EOF'`)
})
t.Run("Analyze", func(t *testing.T) {
AssertParseStatement(t, `ANALYZE tbl`, &sql.AnalyzeStatement{
Analyze: pos(0),
Name: &sql.Ident{NamePos: pos(8), Name: "tbl"},
})
AssertParseStatementError(t, `ANALYZE`, `1:7: expected table or index name, found 'EOF'`)
})
}
func TestParser_ParseExpr(t *testing.T) {
t.Run("Ident", func(t *testing.T) {
AssertParseExpr(t, `fooBAR_123'`, &sql.Ident{NamePos: pos(0), Name: `fooBAR_123`})
})
t.Run("StringLit", func(t *testing.T) {
AssertParseExpr(t, `'foo bar'`, &sql.StringLit{ValuePos: pos(0), Value: `foo bar`})
})
t.Run("BlobLit", func(t *testing.T) {
AssertParseExpr(t, `x'0123'`, &sql.BlobLit{ValuePos: pos(0), Value: `0123`})
})
t.Run("Integer", func(t *testing.T) {
AssertParseExpr(t, `123`, &sql.NumberLit{ValuePos: pos(0), Value: `123`})
})
t.Run("Float", func(t *testing.T) {
AssertParseExpr(t, `123.456`, &sql.NumberLit{ValuePos: pos(0), Value: `123.456`})
})
t.Run("Null", func(t *testing.T) {
AssertParseExpr(t, `NULL`, &sql.NullLit{Pos: pos(0)})
})
t.Run("Bool", func(t *testing.T) {
AssertParseExpr(t, `true`, &sql.BoolLit{ValuePos: pos(0), Value: true})
AssertParseExpr(t, `false`, &sql.BoolLit{ValuePos: pos(0), Value: false})
})
t.Run("Bind", func(t *testing.T) {
AssertParseExpr(t, `$bar`, &sql.BindExpr{NamePos: pos(0), Name: "$bar"})
})
t.Run("UnaryExpr", func(t *testing.T) {
AssertParseExpr(t, `-123`, &sql.UnaryExpr{OpPos: pos(0), Op: sql.MINUS, X: &sql.NumberLit{ValuePos: pos(1), Value: `123`}})
AssertParseExprError(t, `-`, `1:1: expected expression, found 'EOF'`)
})
t.Run("QualifiedRef", func(t *testing.T) {
AssertParseExpr(t, `tbl.col`, &sql.QualifiedRef{
Table: &sql.Ident{NamePos: pos(0), Name: "tbl"},
Dot: pos(3),
Column: &sql.Ident{NamePos: pos(4), Name: "col"},
})
AssertParseExpr(t, `"tbl"."col"`, &sql.QualifiedRef{
Table: &sql.Ident{NamePos: pos(0), Name: "tbl", Quoted: true},
Dot: pos(5),
Column: &sql.Ident{NamePos: pos(6), Name: "col", Quoted: true},
})
AssertParseExprError(t, `tbl.`, `1:4: expected column name, found 'EOF'`)
})
t.Run("Exists", func(t *testing.T) {
AssertParseExpr(t, `EXISTS (SELECT *)`, &sql.Exists{
Exists: pos(0),
Lparen: pos(7),
Select: &sql.SelectStatement{
Select: pos(8),
Columns: []*sql.ResultColumn{
{Star: pos(15)},
},
},
Rparen: pos(16),
})
AssertParseExpr(t, `NOT EXISTS (SELECT *)`, &sql.Exists{
Not: pos(0),
Exists: pos(4),
Lparen: pos(11),
Select: &sql.SelectStatement{
Select: pos(12),
Columns: []*sql.ResultColumn{
{Star: pos(19)},
},
},
Rparen: pos(20),
})
AssertParseExprError(t, `NOT`, `1:3: expected EXISTS, found 'EOF'`)
AssertParseExprError(t, `EXISTS`, `1:6: expected left paren, found 'EOF'`)
AssertParseExprError(t, `EXISTS (`, `1:8: expected SELECT or VALUES, found 'EOF'`)
AssertParseExprError(t, `EXISTS (SELECT`, `1:14: expected expression, found 'EOF'`)
AssertParseExprError(t, `EXISTS (SELECT *`, `1:16: expected right paren, found 'EOF'`)
})
t.Run("BinaryExpr", func(t *testing.T) {
AssertParseExpr(t, `1 + 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.PLUS,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 - 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.MINUS,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 * 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.STAR,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 / 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.SLASH,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 % 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.REM,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 || 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.CONCAT,
Y: &sql.NumberLit{ValuePos: pos(5), Value: "2"},
})
AssertParseExpr(t, `1 << 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.LSHIFT,
Y: &sql.NumberLit{ValuePos: pos(5), Value: "2"},
})
AssertParseExpr(t, `1 >> 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.RSHIFT,
Y: &sql.NumberLit{ValuePos: pos(5), Value: "2"},
})
AssertParseExpr(t, `1 & 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.BITAND,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 | 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.BITOR,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 < 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.LT,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 <= 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.LE,
Y: &sql.NumberLit{ValuePos: pos(5), Value: "2"},
})
AssertParseExpr(t, `1 > 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.GT,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 >= 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.GE,
Y: &sql.NumberLit{ValuePos: pos(5), Value: "2"},
})
AssertParseExpr(t, `1 = 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.EQ,
Y: &sql.NumberLit{ValuePos: pos(4), Value: "2"},
})
AssertParseExpr(t, `1 != 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.NE,
Y: &sql.NumberLit{ValuePos: pos(5), Value: "2"},
})
AssertParseExpr(t, `(1 + 2)'`, &sql.ParenExpr{
Lparen: pos(0),
X: &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(1), Value: "1"},
OpPos: pos(3), Op: sql.PLUS,
Y: &sql.NumberLit{ValuePos: pos(5), Value: "2"},
},
Rparen: pos(6),
})
AssertParseExprError(t, `(`, `1:1: expected expression, found 'EOF'`)
AssertParseExpr(t, `1 IS 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.IS,
Y: &sql.NumberLit{ValuePos: pos(5), Value: "2"},
})
AssertParseExpr(t, `1 IS NOT 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.ISNOT,
Y: &sql.NumberLit{ValuePos: pos(9), Value: "2"},
})
AssertParseExpr(t, `1 LIKE 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.LIKE,
Y: &sql.NumberLit{ValuePos: pos(7), Value: "2"},
})
AssertParseExpr(t, `1 NOT LIKE 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.NOTLIKE,
Y: &sql.NumberLit{ValuePos: pos(11), Value: "2"},
})
AssertParseExpr(t, `1 GLOB 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.GLOB,
Y: &sql.NumberLit{ValuePos: pos(7), Value: "2"},
})
AssertParseExpr(t, `1 NOT GLOB 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.NOTGLOB,
Y: &sql.NumberLit{ValuePos: pos(11), Value: "2"},
})
AssertParseExpr(t, `1 REGEXP 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.REGEXP,
Y: &sql.NumberLit{ValuePos: pos(9), Value: "2"},
})
AssertParseExpr(t, `1 NOT REGEXP 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.NOTREGEXP,
Y: &sql.NumberLit{ValuePos: pos(13), Value: "2"},
})
AssertParseExpr(t, `1 MATCH 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.MATCH,
Y: &sql.NumberLit{ValuePos: pos(8), Value: "2"},
})
AssertParseExpr(t, `1 NOT MATCH 2'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.NOTMATCH,
Y: &sql.NumberLit{ValuePos: pos(12), Value: "2"},
})
AssertParseExprError(t, `1 NOT TABLE`, `1:7: expected IN, LIKE, GLOB, REGEXP, MATCH, or BETWEEN, found 'TABLE'`)
AssertParseExpr(t, `1 IN (2, 3)'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.IN,
Y: &sql.ExprList{
Lparen: pos(5),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(6), Value: "2"},
&sql.NumberLit{ValuePos: pos(9), Value: "3"},
},
Rparen: pos(10),
},
})
AssertParseExpr(t, `1 NOT IN (2, 3)'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.NOTIN,
Y: &sql.ExprList{
Lparen: pos(9),
Exprs: []sql.Expr{
&sql.NumberLit{ValuePos: pos(10), Value: "2"},
&sql.NumberLit{ValuePos: pos(13), Value: "3"},
},
Rparen: pos(14),
},
})
AssertParseExprError(t, `1 IN 2`, `1:6: expected left paren, found 2`)
AssertParseExprError(t, `1 IN (`, `1:6: expected expression, found 'EOF'`)
AssertParseExprError(t, `1 IN (2 3`, `1:9: expected comma or right paren, found 3`)
AssertParseExpr(t, `1 BETWEEN 2 AND 3'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.BETWEEN,
Y: &sql.Range{
X: &sql.NumberLit{ValuePos: pos(10), Value: "2"},
And: pos(12),
Y: &sql.NumberLit{ValuePos: pos(16), Value: "3"},
},
})
AssertParseExpr(t, `1 NOT BETWEEN 2 AND 3'`, &sql.BinaryExpr{
X: &sql.NumberLit{ValuePos: pos(0), Value: "1"},
OpPos: pos(2), Op: sql.NOTBETWEEN,
Y: &sql.Range{
X: &sql.NumberLit{ValuePos: pos(14), Value: "2"},
And: pos(16),
Y: &sql.NumberLit{ValuePos: pos(20), Value: "3"},
},
})
AssertParseExprError(t, `1 BETWEEN`, `1:9: expected expression, found 'EOF'`)
AssertParseExprError(t, `1 BETWEEN 2`, `1:11: expected range expression, found 'EOF'`)
AssertParseExprError(t, `1 BETWEEN 2 + 3`, `1:15: expected range expression, found 'EOF'`)
AssertParseExprError(t, `1 + `, `1:4: expected expression, found 'EOF'`)
})
t.Run("Call", func(t *testing.T) {
AssertParseExpr(t, `sum()`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
})
AssertParseExpr(t, `sum(*)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Star: pos(4),
Rparen: pos(5),
})
AssertParseExpr(t, `sum(foo, 123)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Args: []sql.Expr{
&sql.Ident{NamePos: pos(4), Name: "foo"},
&sql.NumberLit{ValuePos: pos(9), Value: "123"},
},
Rparen: pos(12),
})
AssertParseExpr(t, `sum(distinct 'foo')`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Distinct: pos(4),
Args: []sql.Expr{
&sql.StringLit{ValuePos: pos(13), Value: "foo"},
},
Rparen: pos(18),
})
AssertParseExpr(t, `sum() filter (where true)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Filter: &sql.FilterClause{
Filter: pos(6),
Lparen: pos(13),
Where: pos(14),
X: &sql.BoolLit{ValuePos: pos(20), Value: true},
Rparen: pos(24),
},
})
AssertParseExpr(t, `sum() over win1`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Name: &sql.Ident{NamePos: pos(11), Name: "win1"},
},
})
AssertParseExpr(t, `sum() over (win1 partition by foo, bar order by baz ASC NULLS FIRST, biz)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Base: &sql.Ident{NamePos: pos(12), Name: "win1"},
Partition: pos(17),
PartitionBy: pos(27),
Partitions: []sql.Expr{
&sql.Ident{NamePos: pos(30), Name: "foo"},
&sql.Ident{NamePos: pos(35), Name: "bar"},
},
Order: pos(39),
OrderBy: pos(45),
OrderingTerms: []*sql.OrderingTerm{
{
X: &sql.Ident{NamePos: pos(48), Name: "baz"},
Asc: pos(52),
Nulls: pos(56),
NullsFirst: pos(62),
},
{
X: &sql.Ident{NamePos: pos(69), Name: "biz"},
},
},
Rparen: pos(72),
},
},
})
AssertParseExpr(t, `sum() over (order by baz DESC NULLS LAST)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Order: pos(12),
OrderBy: pos(18),
OrderingTerms: []*sql.OrderingTerm{
{
X: &sql.Ident{NamePos: pos(21), Name: "baz"},
Desc: pos(25),
Nulls: pos(30),
NullsLast: pos(36),
},
},
Rparen: pos(40),
},
},
})
AssertParseExpr(t, `sum() over (range foo preceding)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Range: pos(12),
X: &sql.Ident{NamePos: pos(18), Name: "foo"},
PrecedingX: pos(22),
},
Rparen: pos(31),
},
},
})
AssertParseExpr(t, `sum() over (rows between foo following and bar preceding)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Rows: pos(12),
Between: pos(17),
X: &sql.Ident{NamePos: pos(25), Name: "foo"},
FollowingX: pos(29),
And: pos(39),
Y: &sql.Ident{NamePos: pos(43), Name: "bar"},
PrecedingY: pos(47),
},
Rparen: pos(56),
},
},
})
AssertParseExpr(t, `sum() over (rows between foo following and bar following)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Rows: pos(12),
Between: pos(17),
X: &sql.Ident{NamePos: pos(25), Name: "foo"},
FollowingX: pos(29),
And: pos(39),
Y: &sql.Ident{NamePos: pos(43), Name: "bar"},
FollowingY: pos(47),
},
Rparen: pos(56),
},
},
})
AssertParseExpr(t, `sum() over (groups between unbounded preceding and unbounded following)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Groups: pos(12),
Between: pos(19),
UnboundedX: pos(27),
PrecedingX: pos(37),
And: pos(47),
UnboundedY: pos(51),
FollowingY: pos(61),
},
Rparen: pos(70),
},
},
})
AssertParseExpr(t, `sum() over (groups between current row and current row)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Groups: pos(12),
Between: pos(19),
CurrentX: pos(27),
CurrentRowX: pos(35),
And: pos(39),
CurrentY: pos(43),
CurrentRowY: pos(51),
},
Rparen: pos(54),
},
},
})
AssertParseExpr(t, `sum() over (groups current row exclude no others)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Groups: pos(12),
CurrentX: pos(19),
CurrentRowX: pos(27),
Exclude: pos(31),
ExcludeNo: pos(39),
ExcludeNoOthers: pos(42),
},
Rparen: pos(48),
},
},
})
AssertParseExpr(t, `sum() over (groups current row exclude current row)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Groups: pos(12),
CurrentX: pos(19),
CurrentRowX: pos(27),
Exclude: pos(31),
ExcludeCurrent: pos(39),
ExcludeCurrentRow: pos(47),
},
Rparen: pos(50),
},
},
})
AssertParseExpr(t, `sum() over (groups current row exclude group)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Groups: pos(12),
CurrentX: pos(19),
CurrentRowX: pos(27),
Exclude: pos(31),
ExcludeGroup: pos(39),
},
Rparen: pos(44),
},
},
})
AssertParseExpr(t, `sum() over (groups current row exclude ties)`, &sql.Call{
Name: &sql.Ident{NamePos: pos(0), Name: "sum"},
Lparen: pos(3),
Rparen: pos(4),
Over: &sql.OverClause{
Over: pos(6),
Definition: &sql.WindowDefinition{
Lparen: pos(11),
Frame: &sql.FrameSpec{
Groups: pos(12),
CurrentX: pos(19),
CurrentRowX: pos(27),
Exclude: pos(31),
ExcludeTies: pos(39),
},
Rparen: pos(43),
},
},
})
AssertParseExprError(t, `sum(`, `1:4: expected expression, found 'EOF'`)
AssertParseExprError(t, `sum(*`, `1:5: expected right paren, found 'EOF'`)
AssertParseExprError(t, `sum(foo foo`, `1:9: expected comma or right paren, found foo`)
AssertParseExprError(t, `sum() filter`, `1:12: expected left paren, found 'EOF'`)
AssertParseExprError(t, `sum() filter (`, `1:14: expected WHERE, found 'EOF'`)
AssertParseExprError(t, `sum() filter (where`, `1:19: expected expression, found 'EOF'`)
AssertParseExprError(t, `sum() filter (where true`, `1:24: expected right paren, found 'EOF'`)
AssertParseExprError(t, `sum() over`, `1:10: expected left paren, found 'EOF'`)
AssertParseExprError(t, `sum() over (partition`, `1:21: expected BY, found 'EOF'`)
AssertParseExprError(t, `sum() over (partition by`, `1:24: expected expression, found 'EOF'`)
AssertParseExprError(t, `sum() over (partition by foo foo`, `1:30: expected right paren, found foo`)
AssertParseExprError(t, `sum() over (order`, `1:17: expected BY, found 'EOF'`)
AssertParseExprError(t, `sum() over (order by`, `1:20: expected expression, found 'EOF'`)
AssertParseExprError(t, `sum() over (order by foo foo`, `1:26: expected right paren, found foo`)
AssertParseExprError(t, `sum() over (order by foo nulls foo`, `1:32: expected FIRST or LAST, found foo`)
AssertParseExprError(t, `sum() over (range between`, `1:25: expected expression, found 'EOF'`)
AssertParseExprError(t, `sum() over (range between unbounded`, `1:35: expected PRECEDING, found 'EOF'`)
AssertParseExprError(t, `sum() over (range between current`, `1:33: expected ROW, found 'EOF'`)
AssertParseExprError(t, `sum() over (range between foo`, `1:29: expected PRECEDING or FOLLOWING, found 'EOF'`)
AssertParseExprError(t, `sum() over (range between foo following`, `1:39: expected AND, found 'EOF'`)
AssertParseExprError(t, `sum() over (range between foo following and`, `1:43: expected expression, found 'EOF'`)
AssertParseExprError(t, `sum() over (range between foo following and unbounded`, `1:53: expected FOLLOWING, found 'EOF'`)
AssertParseExprError(t, `sum() over (range between foo following and current`, `1:51: expected ROW, found 'EOF'`)
AssertParseExprError(t, `sum() over (range between foo following and foo`, `1:47: expected PRECEDING or FOLLOWING, found 'EOF'`)
AssertParseExprError(t, `sum() over (range current row exclude`, `1:37: expected NO OTHERS, CURRENT ROW, GROUP, or TIES, found 'EOF'`)
AssertParseExprError(t, `sum() over (range current row exclude no`, `1:40: expected OTHERS, found 'EOF'`)
AssertParseExprError(t, `sum() over (range current row exclude current`, `1:45: expected ROW, found 'EOF'`)
AssertParseExprError(t, `sum() over (range foo following`, `1:23: expected PRECEDING, found 'FOLLOWING'`)
})
t.Run("Cast", func(t *testing.T) {
AssertParseExpr(t, `CAST (1 AS INTEGER)`, &sql.CastExpr{
Cast: pos(0),
Lparen: pos(5),
X: &sql.NumberLit{ValuePos: pos(6), Value: "1"},
As: pos(8),
Type: &sql.Type{Name: &sql.Ident{NamePos: pos(11), Name: "INTEGER"}},
Rparen: pos(18),
})
AssertParseExprError(t, `CAST`, `1:4: expected left paren, found 'EOF'`)
AssertParseExprError(t, `CAST (`, `1:6: expected expression, found 'EOF'`)
AssertParseExprError(t, `CAST (1`, `1:7: expected AS, found 'EOF'`)
AssertParseExprError(t, `CAST (1 AS`, `1:10: expected type name, found 'EOF'`)
AssertParseExprError(t, `CAST (1 AS INTEGER`, `1:18: expected right paren, found 'EOF'`)
})
t.Run("Case", func(t *testing.T) {
AssertParseExpr(t, `CASE 1 WHEN 2 THEN 3 WHEN 4 THEN 5 ELSE 6 END`, &sql.CaseExpr{
Case: pos(0),
Operand: &sql.NumberLit{ValuePos: pos(5), Value: "1"},
Blocks: []*sql.CaseBlock{
{
When: pos(7),
Condition: &sql.NumberLit{ValuePos: pos(12), Value: "2"},
Then: pos(14),
Body: &sql.NumberLit{ValuePos: pos(19), Value: "3"},
},
{
When: pos(21),
Condition: &sql.NumberLit{ValuePos: pos(26), Value: "4"},
Then: pos(28),
Body: &sql.NumberLit{ValuePos: pos(33), Value: "5"},
},
},
Else: pos(35),
ElseExpr: &sql.NumberLit{ValuePos: pos(40), Value: "6"},
End: pos(42),
})
AssertParseExpr(t, `CASE WHEN 1 THEN 2 END`, &sql.CaseExpr{
Case: pos(0),
Blocks: []*sql.CaseBlock{
{
When: pos(5),
Condition: &sql.NumberLit{ValuePos: pos(10), Value: "1"},
Then: pos(12),
Body: &sql.NumberLit{ValuePos: pos(17), Value: "2"},
},
},
End: pos(19),
})
AssertParseExprError(t, `CASE`, `1:4: expected expression, found 'EOF'`)
AssertParseExprError(t, `CASE 1`, `1:6: expected WHEN, found 'EOF'`)
AssertParseExprError(t, `CASE WHEN`, `1:9: expected expression, found 'EOF'`)
AssertParseExprError(t, `CASE WHEN 1`, `1:11: expected THEN, found 'EOF'`)
AssertParseExprError(t, `CASE WHEN 1 THEN`, `1:16: expected expression, found 'EOF'`)
AssertParseExprError(t, `CASE WHEN 1 THEN 2`, `1:18: expected WHEN, ELSE or END, found 'EOF'`)
AssertParseExprError(t, `CASE WHEN 1 THEN 2 ELSE`, `1:23: expected expression, found 'EOF'`)
AssertParseExprError(t, `CASE WHEN 1 THEN 2 ELSE 3`, `1:25: expected END, found 'EOF'`)
})
t.Run("Raise", func(t *testing.T) {
AssertParseExpr(t, `RAISE(IGNORE)`, &sql.Raise{
Raise: pos(0),
Lparen: pos(5),
Ignore: pos(6),
Rparen: pos(12),
})
AssertParseExpr(t, `RAISE(ROLLBACK, 'bad error')`, &sql.Raise{
Raise: pos(0),
Lparen: pos(5),
Rollback: pos(6),
Comma: pos(14),
Error: &sql.StringLit{ValuePos: pos(16), Value: "bad error"},
Rparen: pos(27),
})
AssertParseExpr(t, `RAISE(ABORT, 'error')`, &sql.Raise{
Raise: pos(0),
Lparen: pos(5),
Abort: pos(6),
Comma: pos(11),
Error: &sql.StringLit{ValuePos: pos(13), Value: "error"},
Rparen: pos(20),
})
AssertParseExpr(t, `RAISE(FAIL, 'error')`, &sql.Raise{
Raise: pos(0),
Lparen: pos(5),
Fail: pos(6),
Comma: pos(10),
Error: &sql.StringLit{ValuePos: pos(12), Value: "error"},
Rparen: pos(19),
})
AssertParseExprError(t, `RAISE`, `1:5: expected left paren, found 'EOF'`)
AssertParseExprError(t, `RAISE(`, `1:6: expected IGNORE, ROLLBACK, ABORT, or FAIL, found 'EOF'`)
AssertParseExprError(t, `RAISE(IGNORE`, `1:12: expected right paren, found 'EOF'`)
AssertParseExprError(t, `RAISE(ROLLBACK`, `1:14: expected comma, found 'EOF'`)
AssertParseExprError(t, `RAISE(ROLLBACK,`, `1:15: expected error message, found 'EOF'`)
})
}
func TestError_Error(t *testing.T) {
err := &sql.Error{Msg: "test"}
if got, want := err.Error(), `test`; got != want {
t.Fatalf("Error()=%s, want %s", got, want)
}
}
// ParseStatementOrFail parses a statement from s. Fail on error.
func ParseStatementOrFail(tb testing.TB, s string) sql.Statement {
tb.Helper()
stmt, err := sql.NewParser(strings.NewReader(s)).ParseStatement()
if err != nil {
tb.Fatal(err)
}
return stmt
}
// AssertParseStatement asserts the value of the first parse of s.
func AssertParseStatement(tb testing.TB, s string, want sql.Statement) {
tb.Helper()
stmt, err := sql.NewParser(strings.NewReader(s)).ParseStatement()
if err != nil {
tb.Fatal(err)
} else if diff := deep.Equal(stmt, want); diff != nil {
tb.Fatalf("mismatch:\n%s", strings.Join(diff, "\n"))
}
}
// AssertParseStatementError asserts s parses to a given error string.
func AssertParseStatementError(tb testing.TB, s string, want string) {
tb.Helper()
_, err := sql.NewParser(strings.NewReader(s)).ParseStatement()
if err == nil || err.Error() != want {
tb.Fatalf("ParseStatement()=%q, want %q", err, want)
}
}
// AssertParseExpr asserts the value of the first parse of s.
func AssertParseExpr(tb testing.TB, s string, want sql.Expr) {
tb.Helper()
stmt, err := sql.NewParser(strings.NewReader(s)).ParseExpr()
if err != nil {
tb.Fatal(err)
} else if diff := deep.Equal(stmt, want); diff != nil {
tb.Fatalf("mismatch:\n%s", strings.Join(diff, "\n"))
}
}
// AssertParseExprError asserts s parses to a given error string.
func AssertParseExprError(tb testing.TB, s string, want string) {
tb.Helper()
_, err := sql.NewParser(strings.NewReader(s)).ParseExpr()
if err == nil || err.Error() != want {
tb.Fatalf("ParseExpr()=%q, want %q", err, want)
}
}
// pos is a helper function for generating positions based on offset for one-line parsing.
func pos(offset int) sql.Pos {
return sql.Pos{Offset: offset, Line: 1, Column: offset + 1}
}
func deepEqual(a, b interface{}) string {
return strings.Join(deep.Equal(a, b), "\n")
}