// Copyright 2021 Molecula Corp. All rights reserved. package parser_test import ( "reflect" "strings" "testing" "time" "github.com/featurebasedb/featurebase/v3/sql3/parser" "github.com/go-test/deep" ) func TestExprString(t *testing.T) { if got, want := parser.ExprString(&parser.NullLit{}), "NULL"; got != want { t.Fatalf("ExprString()=%q, want %q", got, want) } else if got, want := parser.ExprString(nil), ""; got != want { t.Fatalf("ExprString()=%q, want %q", got, want) } } func TestSplitExprTree(t *testing.T) { t.Run("AND-only", func(t *testing.T) { AssertSplitExprTree(t, `x = 1 AND y = 2 AND z = 3`, []parser.Expr{ &parser.BinaryExpr{X: &parser.Ident{Name: "x"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "1"}}, &parser.BinaryExpr{X: &parser.Ident{Name: "y"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "2"}}, &parser.BinaryExpr{X: &parser.Ident{Name: "z"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "3"}}, }) }) t.Run("OR", func(t *testing.T) { AssertSplitExprTree(t, `x = 1 AND (y = 2 OR y = 3) AND z = 4`, []parser.Expr{ &parser.BinaryExpr{X: &parser.Ident{Name: "x"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "1"}}, &parser.BinaryExpr{ X: &parser.BinaryExpr{X: &parser.Ident{Name: "y"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "2"}}, Op: parser.OR, Y: &parser.BinaryExpr{X: &parser.Ident{Name: "y"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "3"}}, }, &parser.BinaryExpr{X: &parser.Ident{Name: "z"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "4"}}, }) }) t.Run("ParenExpr", func(t *testing.T) { AssertSplitExprTree(t, `x = 1 AND (y = 2 AND z = 3)`, []parser.Expr{ &parser.BinaryExpr{X: &parser.Ident{Name: "x"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "1"}}, &parser.BinaryExpr{X: &parser.Ident{Name: "y"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "2"}}, &parser.BinaryExpr{X: &parser.Ident{Name: "z"}, Op: parser.EQ, Y: &parser.IntegerLit{Value: "3"}}, }) }) } func AssertSplitExprTree(tb testing.TB, s string, want []parser.Expr) { tb.Helper() if diff := deep.Equal(parser.SplitExprTree(StripExprPos(parser.MustParseExprString(s))), want); diff != nil { tb.Fatal("mismatch: \n" + strings.Join(diff, "\n")) } } func TestAlterTableStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.AlterTableStatement{ Name: &parser.Ident{Name: "foo"}, RenameColumn: pos(0), OldColumnName: &parser.Ident{Name: "col1"}, NewColumnName: &parser.Ident{Name: "col2"}, }, `ALTER TABLE foo RENAME COLUMN col1 TO col2`) AssertStatementStringer(t, &parser.AlterTableStatement{ Name: &parser.Ident{Name: "foo"}, AddColumn: pos(0), ColumnDef: &parser.ColumnDefinition{ Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}, }, }, `ALTER TABLE foo ADD COLUMN bar INTEGER`) AssertStatementStringer(t, &parser.AlterTableStatement{ Name: &parser.Ident{Name: "foo"}, DropColumn: pos(0), DropColumnName: &parser.Ident{Name: "bar"}, }, `ALTER TABLE foo DROP COLUMN bar`) } /*func TestAnalyzeStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.AnalyzeStatement{Name: &parser.Ident{Name: "foo"}}, `ANALYZE "foo"`) }*/ func TestBeginStatement_String(t *testing.T) { t.Skip("BEGIN is currently disabled in the parser") AssertStatementStringer(t, &parser.BeginStatement{}, `BEGIN`) AssertStatementStringer(t, &parser.BeginStatement{Deferred: pos(0)}, `BEGIN DEFERRED`) AssertStatementStringer(t, &parser.BeginStatement{Immediate: pos(0)}, `BEGIN IMMEDIATE`) AssertStatementStringer(t, &parser.BeginStatement{Exclusive: pos(0)}, `BEGIN EXCLUSIVE`) AssertStatementStringer(t, &parser.BeginStatement{Immediate: pos(0), Transaction: pos(0)}, `BEGIN IMMEDIATE TRANSACTION`) } func TestCommitStatement_String(t *testing.T) { t.Skip("COMMIT is currently disabled in the parser") AssertStatementStringer(t, &parser.CommitStatement{}, `COMMIT`) AssertStatementStringer(t, &parser.CommitStatement{End: pos(0)}, `END`) AssertStatementStringer(t, &parser.CommitStatement{End: pos(0), Transaction: pos(0)}, `END TRANSACTION`) } func TestCreateIndexStatement_String(t *testing.T) { t.Skip("CREATE INDEX is currently disabled in the parser") AssertStatementStringer(t, &parser.CreateIndexStatement{ Name: &parser.Ident{Name: "foo"}, Table: &parser.Ident{Name: "bar"}, Columns: []*parser.IndexedColumn{{X: &parser.Ident{Name: "baz"}}}, }, `CREATE INDEX "foo" ON "bar" ("baz")`) AssertStatementStringer(t, &parser.CreateIndexStatement{ Unique: pos(0), IfNotExists: pos(0), Name: &parser.Ident{Name: "foo"}, Table: &parser.Ident{Name: "bar"}, Columns: []*parser.IndexedColumn{ {X: &parser.Ident{Name: "baz"}}, {X: &parser.Ident{Name: "bat"}}, }, WhereExpr: &parser.BoolLit{Value: true}, }, `CREATE UNIQUE INDEX IF NOT EXISTS "foo" ON "bar" ("baz", "bat") WHERE TRUE`) } func TestCreateDatabaseStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.CreateDatabaseStatement{ Name: &parser.Ident{Name: "db1"}, }, `CREATE DATABASE db1`) AssertStatementStringer(t, &parser.CreateDatabaseStatement{ Name: &parser.Ident{Name: "db1"}, IfNotExists: pos(0), }, `CREATE DATABASE IF NOT EXISTS db1`) AssertStatementStringer(t, &parser.CreateDatabaseStatement{ Name: &parser.Ident{Name: "db1"}, With: pos(10), Options: []parser.DatabaseOption{ &parser.UnitsOption{ Units: pos(0), Expr: &parser.IntegerLit{Value: "4"}, }, }, }, `CREATE DATABASE db1 WITH UNITS 4`) } func TestAlterDatabaseStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.AlterDatabaseStatement{ Name: &parser.Ident{Name: "db1"}, Option: &parser.UnitsOption{ Units: pos(0), Expr: &parser.IntegerLit{Value: "4"}, }, }, `ALTER DATABASE db1 WITH UNITS 4`) } func TestCreateTableStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, IfNotExists: pos(0), Columns: []*parser.ColumnDefinition{ { Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}, }, { Name: &parser.Ident{Name: "baz"}, Type: &parser.Type{Name: &parser.Ident{Name: "STRING"}}, }, }, }, `CREATE TABLE IF NOT EXISTS foo (bar INTEGER, baz STRING)`) AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, IfNotExists: pos(0), Columns: []*parser.ColumnDefinition{ { Name: &parser.Ident{Name: "boolcol"}, Type: &parser.Type{Name: &parser.Ident{Name: "BOOL"}}, }, { Name: &parser.Ident{Name: "decimalcol"}, Type: &parser.Type{Name: &parser.Ident{Name: "DECIMAL"}}, Constraints: []parser.Constraint{ &parser.MinConstraint{Expr: &parser.IntegerLit{Value: "100.25"}}, &parser.MaxConstraint{Expr: &parser.IntegerLit{Value: "1000.75"}}, }, }, { Name: &parser.Ident{Name: "idcol"}, Type: &parser.Type{Name: &parser.Ident{Name: "ID"}}, Constraints: []parser.Constraint{ &parser.CacheTypeConstraint{ CacheTypeValue: "RANKED", Size: pos(0), SizeExpr: &parser.IntegerLit{Value: "10000"}, }, }, }, { Name: &parser.Ident{Name: "idsetcol"}, Type: &parser.Type{Name: &parser.Ident{Name: "IDSET"}}, Constraints: []parser.Constraint{ &parser.CacheTypeConstraint{ CacheTypeValue: "RANKED", Size: pos(0), SizeExpr: &parser.IntegerLit{Value: "10000"}, }, }, }, { Name: &parser.Ident{Name: "intcol"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}, Constraints: []parser.Constraint{ &parser.MinConstraint{Expr: &parser.IntegerLit{Value: "100"}}, &parser.MaxConstraint{Expr: &parser.IntegerLit{Value: "1000"}}, }, }, { Name: &parser.Ident{Name: "stringcol"}, Type: &parser.Type{Name: &parser.Ident{Name: "STRING"}}, Constraints: []parser.Constraint{ &parser.CacheTypeConstraint{ CacheTypeValue: "RANKED", Size: pos(0), SizeExpr: &parser.IntegerLit{Value: "10000"}, }, }, }, { Name: &parser.Ident{Name: "stringsetcol"}, Type: &parser.Type{Name: &parser.Ident{Name: "STRINGSET"}}, Constraints: []parser.Constraint{ &parser.CacheTypeConstraint{ CacheTypeValue: "RANKED", Size: pos(0), SizeExpr: &parser.IntegerLit{Value: "10000"}, }, }, }, { Name: &parser.Ident{Name: "timestampcol"}, Type: &parser.Type{Name: &parser.Ident{Name: "TIMESTAMP"}}, Constraints: []parser.Constraint{ &parser.TimeUnitConstraint{ Expr: &parser.StringLit{Value: "s"}, }, }, }, }, }, `CREATE TABLE IF NOT EXISTS foo (`+ `boolcol BOOL, `+ `decimalcol DECIMAL MIN 100.25 MAX 1000.75, `+ `idcol ID CACHETYPE RANKED SIZE 10000, `+ `idsetcol IDSET CACHETYPE RANKED SIZE 10000, `+ `intcol INTEGER MIN 100 MAX 1000, `+ `stringcol STRING CACHETYPE RANKED SIZE 10000, `+ `stringsetcol STRINGSET CACHETYPE RANKED SIZE 10000, `+ `timestampcol TIMESTAMP TIMEUNIT 's'`+ `)`) } func OLDTestCreateTableStatement_String(t *testing.T) { t.Skip("These tests refer to an older version of CREATE TABLE") AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, IfNotExists: pos(0), Columns: []*parser.ColumnDefinition{ { Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}, }, { Name: &parser.Ident{Name: "baz"}, Type: &parser.Type{Name: &parser.Ident{Name: "TEXT"}}, }, }, }, `CREATE TABLE IF NOT EXISTS "foo" ("bar" INTEGER, "baz" TEXT)`) AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, Columns: []*parser.ColumnDefinition{{ Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}, Constraints: []parser.Constraint{ &parser.PrimaryKeyConstraint{Autoincrement: pos(0)}, &parser.NotNullConstraint{Name: &parser.Ident{Name: "nn"}}, &parser.DefaultConstraint{Name: &parser.Ident{Name: "def"}, Expr: &parser.IntegerLit{Value: "123"}}, &parser.DefaultConstraint{Expr: &parser.IntegerLit{Value: "456"}, Lparen: pos(0)}, &parser.UniqueConstraint{}, }, }}, }, `CREATE TABLE "foo" ("bar" INTEGER PRIMARY KEY AUTOINCREMENT CONSTRAINT "nn" NOT NULL CONSTRAINT "def" DEFAULT 123 DEFAULT (456) UNIQUE)`) AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, Columns: []*parser.ColumnDefinition{{ Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}, Constraints: []parser.Constraint{ &parser.ForeignKeyConstraint{ ForeignTable: &parser.Ident{Name: "x"}, ForeignColumns: []*parser.Ident{{Name: "y"}}, Args: []*parser.ForeignKeyArg{ {OnDelete: pos(0), SetNull: pos(0)}, {OnUpdate: pos(0), SetDefault: pos(0)}, {OnUpdate: pos(0), Cascade: pos(0)}, {OnUpdate: pos(0), Restrict: pos(0)}, {OnUpdate: pos(0), NoAction: pos(0)}, }, }, }, }}, }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") ON DELETE SET NULL ON UPDATE SET DEFAULT ON UPDATE CASCADE ON UPDATE RESTRICT ON UPDATE NO ACTION)`) AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, Columns: []*parser.ColumnDefinition{{ Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}, Constraints: []parser.Constraint{ &parser.ForeignKeyConstraint{ ForeignTable: &parser.Ident{Name: "x"}, ForeignColumns: []*parser.Ident{{Name: "y"}}, Deferrable: pos(0), InitiallyDeferred: pos(0), }, }, }}, }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") DEFERRABLE INITIALLY DEFERRED)`) AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, Columns: []*parser.ColumnDefinition{{ Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}, Constraints: []parser.Constraint{ &parser.ForeignKeyConstraint{ ForeignTable: &parser.Ident{Name: "x"}, ForeignColumns: []*parser.Ident{{Name: "y"}}, NotDeferrable: pos(0), InitiallyImmediate: pos(0), }, }, }}, }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") NOT DEFERRABLE INITIALLY IMMEDIATE)`) AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, Columns: []*parser.ColumnDefinition{{ Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "DECIMAL"}, Precision: &parser.IntegerLit{Value: "100"}}, }}, Constraints: []parser.Constraint{ &parser.PrimaryKeyConstraint{ Name: &parser.Ident{Name: "pk"}, Columns: []*parser.Ident{ {Name: "x"}, {Name: "y"}, }, }, &parser.UniqueConstraint{ Name: &parser.Ident{Name: "uniq"}, Columns: []*parser.Ident{ {Name: "x"}, {Name: "y"}, }, }, &parser.CheckConstraint{ Name: &parser.Ident{Name: "chk"}, Expr: &parser.BoolLit{Value: true}, }, }, }, `CREATE TABLE "foo" ("bar" DECIMAL(100), CONSTRAINT "pk" PRIMARY KEY ("x", "y"), CONSTRAINT "uniq" UNIQUE ("x", "y"), CONSTRAINT "chk" CHECK (TRUE))`) AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, Columns: []*parser.ColumnDefinition{{ Name: &parser.Ident{Name: "bar"}, Type: &parser.Type{Name: &parser.Ident{Name: "DECIMAL"}, Precision: &parser.IntegerLit{Value: "100"}, Scale: &parser.IntegerLit{Value: "200"}}, }}, Constraints: []parser.Constraint{ &parser.ForeignKeyConstraint{ Name: &parser.Ident{Name: "fk"}, Columns: []*parser.Ident{{Name: "a"}, {Name: "b"}}, ForeignTable: &parser.Ident{Name: "x"}, ForeignColumns: []*parser.Ident{{Name: "y"}, {Name: "z"}}, }, }, }, `CREATE TABLE "foo" ("bar" DECIMAL(100,200), CONSTRAINT "fk" FOREIGN KEY ("a", "b") REFERENCES "x" ("y", "z"))`) AssertStatementStringer(t, &parser.CreateTableStatement{ Name: &parser.Ident{Name: "foo"}, Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, }, }, `CREATE TABLE "foo" AS SELECT *`) } func TestCreateFunctionStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.CreateFunctionStatement{ Name: &parser.Ident{Name: "func"}, Parameters: []*parser.ParameterDefinition{ { Name: &parser.Variable{Name: "@param1"}, Type: &parser.Type{Name: &parser.Ident{Name: "int"}}, }, }, ReturnType: &parser.Type{Name: &parser.Ident{Name: "int"}}, }, `CREATE FUNCTION func (@param1 int) RETURNS int AS BEGIN END`) AssertStatementStringer(t, &parser.CreateFunctionStatement{ IfNotExists: pos(0), Name: &parser.Ident{Name: "func"}, Parameters: []*parser.ParameterDefinition{ { Name: &parser.Variable{Name: "@param1"}, Type: &parser.Type{Name: &parser.Ident{Name: "int"}}, }, }, ReturnType: &parser.Type{Name: &parser.Ident{Name: "int"}}, }, `CREATE FUNCTION IF NOT EXISTS func (@param1 int) RETURNS int AS BEGIN END`) } func TestCreateViewStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.CreateViewStatement{ Name: &parser.Ident{Name: "vw"}, // Columns: []*parser.Ident{ // {Name: "x"}, // {Name: "y"}, // }, Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, }, //}, `CREATE VIEW vw (x, y) AS SELECT *`) }, `CREATE VIEW vw AS SELECT *`) AssertStatementStringer(t, &parser.CreateViewStatement{ IfNotExists: pos(0), Name: &parser.Ident{Name: "vw"}, Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, }, }, `CREATE VIEW IF NOT EXISTS vw AS SELECT *`) } func TestAlterViewStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.AlterViewStatement{ Name: &parser.Ident{Name: "vw"}, // Columns: []*parser.Ident{ // {Name: "x"}, // {Name: "y"}, // }, Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, }, //}, `CREATE VIEW vw (x, y) AS SELECT *`) }, `ALTER VIEW vw AS SELECT *`) } func TestDeleteStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.DeleteStatement{ TableName: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}, Alias: &parser.Ident{Name: "tbl2"}, As: pos(0)}, }, `DELETE FROM tbl AS tbl2`) // AssertStatementStringer(t, &sql.DeleteStatement{ // Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}, Index: &sql.Ident{Name: "idx"}}, // }, `DELETE FROM "tbl" INDEXED BY "idx"`) // AssertStatementStringer(t, &sql.DeleteStatement{ // Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}, NotIndexed: pos(0)}, // }, `DELETE FROM "tbl" NOT INDEXED`) /*AssertStatementStringer(t, &parser.DeleteStatement{ Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, WhereExpr: &parser.BoolLit{Value: true}, OrderingTerms: []*parser.OrderingTerm{ {X: &parser.Ident{Name: "x"}}, {X: &parser.Ident{Name: "y"}}, }, LimitExpr: &parser.IntegerLit{Value: "10"}, OffsetExpr: &parser.IntegerLit{Value: "5"}, }, `DELETE FROM "tbl" WHERE TRUE ORDER BY "x", "y" LIMIT 10 OFFSET 5`)*/ // AssertStatementStringer(t, &sql.DeleteStatement{ // WithClause: &sql.WithClause{ // Recursive: pos(0), // CTEs: []*sql.CTE{{ // TableName: &sql.Ident{Name: "cte"}, // Select: &sql.SelectStatement{ // Columns: []*sql.ResultColumn{{Star: pos(0)}}, // }, // }}, // }, // Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, // }, `WITH RECURSIVE "cte" AS (SELECT *) DELETE FROM "tbl"`) } func TestDropIndexStatement_String(t *testing.T) { t.Skip("DROP INDEX is currently disabled in the parser") AssertStatementStringer(t, &parser.DropIndexStatement{ Name: &parser.Ident{Name: "idx"}, }, `DROP INDEX "idx"`) AssertStatementStringer(t, &parser.DropIndexStatement{ IfExists: pos(0), Name: &parser.Ident{Name: "idx"}, }, `DROP INDEX IF EXISTS "idx"`) } func TestDropDatabaseStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.DropDatabaseStatement{ Name: &parser.Ident{Name: "db"}, }, `DROP DATABASE db`) AssertStatementStringer(t, &parser.DropDatabaseStatement{ IfExists: pos(0), Name: &parser.Ident{Name: "db"}, }, `DROP DATABASE IF EXISTS db`) } func TestDropTableStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.DropTableStatement{ Name: &parser.Ident{Name: "tbl"}, }, `DROP TABLE tbl`) AssertStatementStringer(t, &parser.DropTableStatement{ IfExists: pos(0), Name: &parser.Ident{Name: "tbl"}, }, `DROP TABLE IF EXISTS tbl`) } func TestDropTriggerStatement_String(t *testing.T) { t.Skip("DROP TRIGGER is currently disabled in the parser") AssertStatementStringer(t, &parser.DropFunctionStatement{ Name: &parser.Ident{Name: "trig"}, }, `DROP TRIGGER "trig"`) AssertStatementStringer(t, &parser.DropFunctionStatement{ IfExists: pos(0), Name: &parser.Ident{Name: "trig"}, }, `DROP TRIGGER IF EXISTS "trig"`) } func TestDropViewStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.DropViewStatement{ Name: &parser.Ident{Name: "vw"}, }, `DROP VIEW vw`) AssertStatementStringer(t, &parser.DropViewStatement{ IfExists: pos(0), Name: &parser.Ident{Name: "vw"}, }, `DROP VIEW IF EXISTS vw`) } func TestExplainStatement_String(t *testing.T) { t.Skip("These are currently disabled in the parser") AssertStatementStringer(t, &parser.ExplainStatement{ Stmt: &parser.DropViewStatement{ Name: &parser.Ident{Name: "vw"}, }, }, `EXPLAIN DROP VIEW "vw"`) AssertStatementStringer(t, &parser.ExplainStatement{ QueryPlan: pos(0), Stmt: &parser.DropViewStatement{ Name: &parser.Ident{Name: "vw"}, }, }, `EXPLAIN QUERY PLAN DROP VIEW "vw"`) } func TestInsertStatement_String(t *testing.T) { /*AssertStatementStringer(t, &parser.InsertStatement{ Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT INTO "tbl" DEFAULT VALUES`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ Table: &parser.Ident{Name: "tbl"}, Alias: &parser.Ident{Name: "x"}, DefaultValues: pos(0), }, `INSERT INTO "tbl" AS "x" DEFAULT VALUES`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ InsertOrReplace: pos(0), Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR REPLACE INTO "tbl" DEFAULT VALUES`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ InsertOrRollback: pos(0), Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR ROLLBACK INTO "tbl" DEFAULT VALUES`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ InsertOrAbort: pos(0), Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR ABORT INTO "tbl" DEFAULT VALUES`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ InsertOrFail: pos(0), Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR FAIL INTO "tbl" DEFAULT VALUES`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ InsertOrIgnore: pos(0), Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR IGNORE INTO "tbl" DEFAULT VALUES`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ Replace: pos(0), Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `REPLACE INTO "tbl" DEFAULT VALUES`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ Table: &parser.Ident{Name: "tbl"}, Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, }, }, `INSERT INTO "tbl" SELECT *`)*/ AssertStatementStringer(t, &parser.InsertStatement{ Table: &parser.Ident{Name: "tbl"}, Columns: []*parser.Ident{ {Name: "x"}, {Name: "y"}, }, TupleList: []*parser.ExprList{ { Exprs: []parser.Expr{&parser.NullLit{}, &parser.NullLit{}}, }, }, }, `INSERT INTO tbl (x, y) VALUES (NULL, NULL)`) AssertStatementStringer(t, &parser.InsertStatement{ Table: &parser.Ident{Name: "tbl"}, Columns: []*parser.Ident{ {Name: "x"}, {Name: "y"}, }, TupleList: []*parser.ExprList{ { Exprs: []parser.Expr{&parser.IntegerLit{Value: "1"}, &parser.IntegerLit{Value: "2"}}, }, { Exprs: []parser.Expr{&parser.IntegerLit{Value: "3"}, &parser.IntegerLit{Value: "4"}}, }, }, }, `INSERT INTO tbl (x, y) VALUES (1, 2), (3, 4)`) // AssertStatementStringer(t, &sql.InsertStatement{ // WithClause: &sql.WithClause{ // CTEs: []*sql.CTE{ // { // TableName: &sql.Ident{Name: "cte"}, // Select: &sql.SelectStatement{ // Columns: []*sql.ResultColumn{{Star: pos(0)}}, // }, // }, // { // TableName: &sql.Ident{Name: "cte2"}, // Select: &sql.SelectStatement{ // Columns: []*sql.ResultColumn{{Star: pos(0)}}, // }, // }, // }, // }, // Table: &sql.Ident{Name: "tbl"}, // DefaultValues: pos(0), // }, `WITH "cte" AS (SELECT *), "cte2" AS (SELECT *) INSERT INTO "tbl" DEFAULT VALUES`) /*AssertStatementStringer(t, &parser.InsertStatement{ Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), UpsertClause: &parser.UpsertClause{ DoNothing: pos(0), }, }, `INSERT INTO "tbl" DEFAULT VALUES ON CONFLICT DO NOTHING`)*/ /*AssertStatementStringer(t, &parser.InsertStatement{ Table: &parser.Ident{Name: "tbl"}, DefaultValues: pos(0), UpsertClause: &parser.UpsertClause{ Columns: []*parser.IndexedColumn{ {X: &parser.Ident{Name: "x"}, Asc: pos(0)}, {X: &parser.Ident{Name: "y"}, Desc: pos(0)}, }, WhereExpr: &parser.BoolLit{Value: true}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, {Columns: []*parser.Ident{{Name: "y"}, {Name: "z"}}, Expr: &parser.IntegerLit{Value: "200"}}, }, UpdateWhereExpr: &parser.BoolLit{Value: false}, }, }, `INSERT INTO "tbl" DEFAULT VALUES ON CONFLICT ("x" ASC, "y" DESC) WHERE TRUE DO UPDATE SET "x" = 100, ("y", "z") = 200 WHERE FALSE`)*/ // Testing upsert clause separately until it is enabled in Insert. { upsertast := parser.UpsertClause{ DoNothing: pos(0), } upsertsql := `ON CONFLICT DO NOTHING` if upsertast.String() != upsertsql { t.Fatalf("parser.UpsertClause.String()=%q, want %q", upsertast.String(), upsertsql) } upsertast = parser.UpsertClause{ Columns: []*parser.IndexedColumn{ {X: &parser.Ident{Name: "x"}, Asc: pos(0)}, {X: &parser.Ident{Name: "y"}, Desc: pos(0)}, }, WhereExpr: &parser.BoolLit{Value: true}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, {Columns: []*parser.Ident{{Name: "y"}, {Name: "z"}}, Expr: &parser.IntegerLit{Value: "200"}}, }, UpdateWhereExpr: &parser.BoolLit{Value: false}, } upsertsql = "ON CONFLICT (x ASC, y DESC) WHERE TRUE DO UPDATE SET x = 100, (y, z) = 200 WHERE FALSE" if upsertast.String() != upsertsql { t.Fatalf("parser.UpsertClause.String()=%q, want %q", upsertast.String(), upsertsql) } if upsertast.Clone().String() != upsertsql { t.Fatalf("parser.UpsertClause.Clone().String()=%q, want %q", upsertast.Clone().String(), upsertsql) } } } // Test Bulk Insert for CSV format func TestBulkInsertStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.BulkInsertStatement{ Table: &parser.Ident{Name: "tbl"}, Columns: []*parser.Ident{ {Name: "string"}, {Name: "int"}, {Name: "decimal"}, {Name: "timestamp"}, }, MapList: []*parser.BulkInsertMapDefinition{ {Name: &parser.Ident{Name: "string"}, Type: &parser.Type{Name: &parser.Ident{Name: "STRING"}}, MapExpr: &parser.Ident{Name: "1"}}, {Name: &parser.Ident{Name: "int"}, Type: &parser.Type{Name: &parser.Ident{Name: "INT"}}, MapExpr: &parser.Ident{Name: "2"}}, {Name: &parser.Ident{Name: "decimal"}, Type: &parser.Type{Name: &parser.Ident{Name: "DECIMAL"}, Scale: &parser.IntegerLit{Value: "2"}}, MapExpr: &parser.Ident{Name: "3"}}, {Name: &parser.Ident{Name: "timestamp"}, Type: &parser.Type{Name: &parser.Ident{Name: "TIMESTAMP"}}, MapExpr: &parser.Ident{Name: "4"}}, }, TransformList: []parser.Expr{ &parser.CaseExpr{ //Operand: &parser.Ident{Name: "foo"}, Blocks: []*parser.CaseBlock{ {Condition: &parser.BinaryExpr{Op: parser.EQ, X: &parser.Variable{Name: "@0", VariableIndex: 0}, Y: &parser.StringLit{Value: "Texas"}}, Body: &parser.StringLit{Value: "TX"}}, {Condition: &parser.BinaryExpr{Op: parser.EQ, X: &parser.Variable{Name: "@0", VariableIndex: 0}, Y: &parser.StringLit{Value: "Mass"}}, Body: &parser.StringLit{Value: "MA"}}, }, ElseExpr: &parser.NullLit{}, }, &parser.BinaryExpr{Op: parser.STAR, X: &parser.Variable{Name: "@1", VariableIndex: 1}, Y: &parser.IntegerLit{Value: "10"}}, &parser.Variable{Name: "@2", VariableIndex: 2}, &parser.SysVariable{Token: parser.CURRENT_TIMESTAMP}, }, DataSource: &parser.StringLit{Value: "csvdata.csv"}, BatchSize: &parser.IntegerLit{Value: "100000"}, Format: &parser.StringLit{Value: "CSV"}, Input: &parser.StringLit{Value: "FILE"}, RowsLimit: &parser.IntegerLit{Value: "1000000"}, HeaderRow: &parser.BoolLit{Value: false}, AllowMissingValues: &parser.BoolLit{Value: true}, }, `BULK INSERT INTO tbl(string, int, decimal, timestamp) MAP (1 STRING, 2 INT, 3 DECIMAL(2), 4 TIMESTAMP) TRANSFORM (CASE WHEN @0 = 'Texas' THEN 'TX' WHEN @0 = 'Mass' THEN 'MA' ELSE NULL END, @1 * 10, @2, CURRENT_TIMESTAMP) FROM 'csvdata.csv' WITH FORMAT 'CSV' INPUT 'FILE' HEADER_ROW BATCHSIZE 100000 ROWSLIMIT 1000000 ALLOW_MISSING_VALUES `) } func TestReleaseStatement_String(t *testing.T) { t.Skip("RELEASE is currently disabled in the parser") AssertStatementStringer(t, &parser.ReleaseStatement{Name: &parser.Ident{Name: "x"}}, `RELEASE "x"`) AssertStatementStringer(t, &parser.ReleaseStatement{Savepoint: pos(0), Name: &parser.Ident{Name: "x"}}, `RELEASE SAVEPOINT "x"`) } func TestRollbackStatement_String(t *testing.T) { t.Skip("ROLLBACK is currently disabled in the parser") AssertStatementStringer(t, &parser.RollbackStatement{}, `ROLLBACK`) AssertStatementStringer(t, &parser.RollbackStatement{Transaction: pos(0)}, `ROLLBACK TRANSACTION`) AssertStatementStringer(t, &parser.RollbackStatement{SavepointName: &parser.Ident{Name: "x"}}, `ROLLBACK TO "x"`) AssertStatementStringer(t, &parser.RollbackStatement{Savepoint: pos(0), SavepointName: &parser.Ident{Name: "x"}}, `ROLLBACK TO SAVEPOINT "x"`) } func TestSavepointStatement_String(t *testing.T) { t.Skip("SAVEPOINT is currently disabled in the parser") AssertStatementStringer(t, &parser.SavepointStatement{Name: &parser.Ident{Name: "x"}}, `SAVEPOINT "x"`) } func TestSelectStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{ {Expr: &parser.Ident{Name: "x"}, Alias: &parser.Ident{Name: "y"}, As: pos(0)}, {Expr: &parser.Ident{Name: "z"}}, }, }, `SELECT x AS y, z`) AssertStatementStringer(t, &parser.SelectStatement{ Distinct: pos(0), Columns: []*parser.ResultColumn{ {Expr: &parser.Ident{Name: "x"}}, }, }, `SELECT DISTINCT x`) AssertStatementStringer(t, &parser.SelectStatement{ Top: pos(0), TopExpr: &parser.IntegerLit{Value: "10"}, Columns: []*parser.ResultColumn{ {Expr: &parser.Ident{Name: "x"}}, }, }, `SELECT TOP(10) x`) AssertStatementStringer(t, &parser.SelectStatement{ TopN: pos(0), TopExpr: &parser.IntegerLit{Value: "10"}, Columns: []*parser.ResultColumn{ {Expr: &parser.Ident{Name: "x"}}, }, }, `SELECT TOPN(10) x`) // AssertStatementStringer(t, &sql.SelectStatement{ // All: pos(0), // Columns: []*sql.ResultColumn{ // {Expr: &sql.Ident{Name: "x"}}, // }, // }, `SELECT ALL "x"`) AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, WhereExpr: &parser.BoolLit{Value: true}, GroupByExprs: []parser.Expr{&parser.Ident{Name: "x"}, &parser.Ident{Name: "y"}}, HavingExpr: &parser.Ident{Name: "z"}, }, `SELECT * FROM tbl WHERE TRUE GROUP BY x, y HAVING z`) AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.ParenSource{ X: &parser.SelectStatement{Columns: []*parser.ResultColumn{{Star: pos(0)}}}, Alias: &parser.Ident{Name: "tbl"}, }, }, `SELECT * FROM (SELECT *) AS tbl`) AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.ParenSource{ X: &parser.SelectStatement{Columns: []*parser.ResultColumn{{Star: pos(0)}}}, }, }, `SELECT * FROM (SELECT *)`) // AssertStatementStringer(t, &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, // Windows: []*parser.Window{ // { // Name: &parser.Ident{Name: "win1"}, // Definition: &parser.WindowDefinition{ // Base: &parser.Ident{Name: "base"}, // Partitions: []parser.Expr{&parser.Ident{Name: "x"}, &parser.Ident{Name: "y"}}, // OrderingTerms: []*parser.OrderingTerm{ // {X: &parser.Ident{Name: "x"}, Asc: pos(0), NullsFirst: pos(0)}, // {X: &parser.Ident{Name: "y"}, Desc: pos(0), NullsLast: pos(0)}, // }, // Frame: &parser.FrameSpec{ // Range: pos(0), // UnboundedX: pos(0), // PrecedingX: pos(0), // }, // }, // }, // { // Name: &parser.Ident{Name: "win2"}, // Definition: &parser.WindowDefinition{ // Base: &parser.Ident{Name: "base2"}, // }, // }, // }, // }, `SELECT * FROM tbl WINDOW win1 AS (base PARTITION BY x, y ORDER BY x ASC NULLS FIRST, y DESC NULLS LAST RANGE UNBOUNDED PRECEDING), win2 AS (base2)`) // AssertStatementStringer(t, &sql.SelectStatement{ // WithClause: &sql.WithClause{ // CTEs: []*sql.CTE{{ // TableName: &sql.Ident{Name: "cte"}, // Columns: []*sql.Ident{ // {Name: "x"}, // {Name: "y"}, // }, // Select: &sql.SelectStatement{ // Columns: []*sql.ResultColumn{{Star: pos(0)}}, // }, // }}, // }, // ValueLists: []*sql.ExprList{ // {Exprs: []sql.Expr{&sql.NumberLit{Value: "1"}, &sql.NumberLit{Value: "2"}}}, // {Exprs: []sql.Expr{&sql.NumberLit{Value: "3"}, &sql.NumberLit{Value: "4"}}}, // }, // }, `WITH "cte" ("x", "y") AS (SELECT *) VALUES (1, 2), (3, 4)`) // AssertStatementStringer(t, &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // Union: pos(0), // Compound: &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // }, // }, `SELECT * UNION SELECT *`) // AssertStatementStringer(t, &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // Union: pos(0), // UnionAll: pos(0), // Compound: &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // }, // }, `SELECT * UNION ALL SELECT *`) // AssertStatementStringer(t, &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // Intersect: pos(0), // Compound: &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // }, // }, `SELECT * INTERSECT SELECT *`) // AssertStatementStringer(t, &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // Except: pos(0), // Compound: &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // }, // }, `SELECT * EXCEPT SELECT *`) AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, OrderingTerms: []*parser.OrderingTerm{ {X: &parser.Ident{Name: "x"}}, {X: &parser.Ident{Name: "y"}}, }, }, `SELECT * ORDER BY x, y`) AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.JoinClause{ X: &parser.QualifiedTableName{Name: &parser.Ident{Name: "x"}}, Operator: &parser.JoinOperator{Comma: pos(0)}, Y: &parser.QualifiedTableName{Name: &parser.Ident{Name: "y"}}, }, }, `SELECT * FROM x, y`) AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.JoinClause{ X: &parser.QualifiedTableName{Name: &parser.Ident{Name: "x"}}, Operator: &parser.JoinOperator{}, Y: &parser.QualifiedTableName{Name: &parser.Ident{Name: "y"}}, Constraint: &parser.OnConstraint{X: &parser.BoolLit{Value: true}}, }, }, `SELECT * FROM x JOIN y ON TRUE`) AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.JoinClause{ X: &parser.QualifiedTableName{Name: &parser.Ident{Name: "x"}}, Operator: &parser.JoinOperator{Left: pos(0)}, Y: &parser.QualifiedTableName{Name: &parser.Ident{Name: "y"}}, }, }, `SELECT * FROM x LEFT JOIN y`) AssertStatementStringer(t, &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.JoinClause{ X: &parser.QualifiedTableName{Name: &parser.Ident{Name: "x"}}, Operator: &parser.JoinOperator{Left: pos(0), Outer: pos(0)}, Y: &parser.QualifiedTableName{Name: &parser.Ident{Name: "y"}}, }, }, `SELECT * FROM x LEFT OUTER JOIN y`) // AssertStatementStringer(t, &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // Source: &parser.JoinClause{ // X: &parser.QualifiedTableName{Name: &parser.Ident{Name: "x"}}, // Operator: &parser.JoinOperator{Cross: pos(0)}, // Y: &parser.QualifiedTableName{Name: &parser.Ident{Name: "y"}}, // }, // }, `SELECT * FROM x CROSS JOIN y`) // Test SELECT with WITH clause only upto SQL comparison, skip AssertStatementSanity() until parser can handle WITH clauses. { selectast := parser.SelectStatement{ WithClause: &parser.WithClause{ CTEs: []*parser.CTE{ { TableName: &parser.Ident{Name: "cte"}, Columns: []*parser.Ident{ {Name: "col1"}, {Name: "col2"}, }, Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{ {Expr: &parser.Ident{Name: "col1"}}, {Expr: &parser.Ident{Name: "col2"}}, }, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "table"}}, }, As: parser.Pos{Column: 1}, }}, }, Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "cte"}}, } selectsql := `WITH cte (col1, col2) AS (SELECT col1, col2 FROM table) SELECT * FROM cte` if s := selectast.String(); s != selectsql { t.Fatalf("parser.SelectStatement.String()=%q, want %q", s, selectsql) } if s := selectast.Clone().String(); s != selectsql { t.Fatalf("parser.SelectStatement.Clone().String()=%q, want %q", s, selectsql) } } // Test SelectStatement.HasWildcard() { selectast := &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, } if !selectast.HasWildcard() { t.Fatalf("parser.SelectStatement.HasWildcard()=%v, want %v", false, true) } selectast = &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Expr: &parser.QualifiedRef{Star: pos(0)}}}, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, } if !selectast.HasWildcard() { t.Fatalf("parser.SelectStatement.HasWildcard()=%v, want %v", false, true) } selectast = &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Expr: &parser.Ident{Name: "col"}}}, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, } if selectast.HasWildcard() { t.Fatalf("parser.SelectStatement.HasWildcard()=%v, want %v", true, false) } } } func TestSources_String(t *testing.T) { // Test helper functions for QualifiedTableName { qtast := parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}} if s := qtast.TableName(); s != "tbl" { t.Fatalf("parser.QualifiedTableName.TableName()=%v, want %v", s, "tbl") } if !qtast.MatchesTablenameOrAlias("tbl") { t.Fatalf("parser.QualifiedTableName.MatchesTablenameOrAlias()=%v, want %v", false, true) } qtast = parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}, Alias: &parser.Ident{Name: "t1"}} if qtast.SourceFromAlias("t1") != qtast.SourceFromAlias("tbl") { t.Fatalf("parser.QualifiedTableName.SourceFromAlias()=%v, want %v", qtast.SourceFromAlias("t1"), qtast.SourceFromAlias("tbl")) } qtast = parser.QualifiedTableName{ Name: &parser.Ident{Name: "tbl"}, Alias: &parser.Ident{Name: "t1"}, OutputColumns: []*parser.SourceOutputColumn{ {TableName: "tbl", ColumnName: "col1", ColumnIndex: 1}, {TableName: "tbl", ColumnName: "col2", ColumnIndex: 2}, }, } if n := len(qtast.PossibleOutputColumns()); n != 2 { t.Fatalf("len(parser.QualifiedTableName.PossibleOutputColumns())=%v, want %v", n, 2) } if c, _ := qtast.OutputColumnNamed("col1"); c.ColumnName != "col1" { t.Fatalf("parser.QualifiedTableName.OutputColumnNamed()=%v, want %v", c.ColumnName, "col1") } if c, _ := qtast.OutputColumnNamed("col99"); c != nil { t.Fatalf("parser.QualifiedTableName.OutputColumnNamed()=%v, want %v", c, nil) } if c, _ := qtast.OutputColumnQualifierNamed("tbl", "col1"); c.ColumnName != "col1" { t.Fatalf("parser.QualifiedTableName.OutputColumnQualifierNamed()=%v, want %v", c.ColumnName, "col1") } if c, _ := qtast.OutputColumnQualifierNamed("t1", "col1"); c.ColumnName != "col1" { t.Fatalf("parser.QualifiedTableName.OutputColumnQualifierNamed()=%v, want %v", c.ColumnName, "col1") } if c, _ := qtast.OutputColumnQualifierNamed("t9", "col99"); c != nil { t.Fatalf("parser.QualifiedTableName.OutputColumnQualifierNamed()=%v, want %v", c, nil) } } // Test helper functions for JoinClause { jcast := parser.JoinClause{ X: &parser.QualifiedTableName{ Name: &parser.Ident{Name: "tbl1"}, Alias: &parser.Ident{Name: "t1"}, OutputColumns: []*parser.SourceOutputColumn{ {TableName: "tbl1", ColumnName: "col1", ColumnIndex: 1}, {TableName: "tbl1", ColumnName: "col2", ColumnIndex: 2}, }, }, Y: &parser.QualifiedTableName{ Name: &parser.Ident{Name: "tbl2"}, Alias: &parser.Ident{Name: "t2"}, OutputColumns: []*parser.SourceOutputColumn{ {TableName: "tbl2", ColumnName: "col3", ColumnIndex: 1}, {TableName: "tbl2", ColumnName: "col4", ColumnIndex: 2}, }, }, } if n := len(jcast.PossibleOutputColumns()); n != 4 { t.Fatalf("len(parser.JoinClause.PossibleOutputColumns())=%v, want %v", n, 4) } if c, _ := jcast.OutputColumnNamed("col1"); c.ColumnName != "col1" { t.Fatalf("parser.JoinClause.OutputColumnNamed()=%v, want %v", c.ColumnName, "col1") } if c, _ := jcast.OutputColumnNamed("col3"); c.ColumnName != "col3" { t.Fatalf("parser.JoinClause.OutputColumnNamed()=%v, want %v", c.ColumnName, "col3") } if c, _ := jcast.OutputColumnNamed("col99"); c != nil { t.Fatalf("parser.JoinClause.OutputColumnNamed()=%v, want %v", c, nil) } if c, _ := jcast.OutputColumnQualifierNamed("tbl1", "col1"); c.ColumnName != "col1" { t.Fatalf("parser.JoinClause.OutputColumnQualifierNamed()=%v, want %v", c.ColumnName, "col1") } if c, _ := jcast.OutputColumnQualifierNamed("t2", "col3"); c.ColumnName != "col3" { t.Fatalf("parser.JoinClause.OutputColumnQualifierNamed()=%v, want %v", c.ColumnName, "col3") } if c, _ := jcast.OutputColumnQualifierNamed("t1", "col3"); c != nil { t.Fatalf("parser.JoinClause.OutputColumnQualifierNamed()=%v, want %v", c, nil) } if c, _ := jcast.OutputColumnQualifierNamed("t2", "col1"); c != nil { t.Fatalf("parser.JoinClause.OutputColumnQualifierNamed()=%v, want %v", c, nil) } if s := jcast.SourceFromAlias("t1"); s != jcast.X { t.Fatalf("parser.JoinClause.SourceFromAlias()=%v, want %v", s, jcast.X) } if s := jcast.SourceFromAlias("t2"); s != jcast.Y { t.Fatalf("parser.JoinClause.SourceFromAlias()=%v, want %v", s, jcast.Y) } if s := jcast.SourceFromAlias("t3"); s != nil { t.Fatalf("parser.JoinClause.SourceFromAlias()=%v, want %v", s, nil) } } // test ParenSource helper functions { psast := parser.ParenSource{ X: &parser.QualifiedTableName{ Name: &parser.Ident{Name: "tbl1"}, OutputColumns: []*parser.SourceOutputColumn{ {TableName: "tbl1", ColumnName: "col1", ColumnIndex: 1}, {TableName: "tbl1", ColumnName: "col2", ColumnIndex: 2}, }, }, Alias: &parser.Ident{Name: "t1"}, } if s := psast.SourceFromAlias("t1"); s.String() != psast.String() { t.Fatalf("parser.ParenSource.SourceFromAlias()=%v, want %v", s, psast) } if s := psast.SourceFromAlias("t3"); s != nil { t.Fatalf("parser.ParenSource.SourceFromAlias()=%v, want %v", s, nil) } if n := len(psast.PossibleOutputColumns()); n != 2 { t.Fatalf("len(parser.JoinClause.PossibleOutputColumns())=%v, want %v", n, 2) } if c, _ := psast.OutputColumnNamed("col1"); c.ColumnName != "col1" { t.Fatalf("parser.JoinClause.OutputColumnNamed()=%v, want %v", c.ColumnName, "col1") } if c, _ := psast.OutputColumnNamed("col99"); c != nil { t.Fatalf("parser.JoinClause.OutputColumnNamed()=%v, want %v", c, nil) } if c, _ := psast.OutputColumnQualifierNamed("t1", "col1"); c.ColumnName != "col1" { t.Fatalf("parser.JoinClause.OutputColumnQualifierNamed()=%v, want %v", c.ColumnName, "col1") } if c, _ := psast.OutputColumnQualifierNamed("t1", "col3"); c != nil { t.Fatalf("parser.JoinClause.OutputColumnQualifierNamed()=%v, want %v", c, nil) } } // Test select statement source helper functions { selectast := &parser.SelectStatement{ Columns: []*parser.ResultColumn{ {Expr: &parser.Ident{Name: "col1"}}, {Expr: &parser.Ident{Name: "col2"}}, }, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "table"}}, } if s := selectast.SourceFromAlias("table"); s != nil { t.Fatalf("parser.SelectStatement.SourceFromAlias()=%v, want %v", s, nil) } if n := len(selectast.PossibleOutputColumns()); n != 2 { t.Fatalf("len(parser.SelectStatement.PossibleOutputColumns())=%v, want %v", n, 2) } if c, _ := selectast.OutputColumnNamed("col1"); c.ColumnName != "col1" { t.Fatalf("parser.SelectStatement.OutputColumnNamed()=%v, want %v", c.ColumnName, "col1") } if c, _ := selectast.OutputColumnNamed("col99"); c != nil { t.Fatalf("parser.SelectStatement.OutputColumnNamed()=%v, want %v", c, nil) } if c, _ := selectast.OutputColumnQualifierNamed("table", "col1"); c != nil { t.Fatalf("parser.SelectStatement.OutputColumnQualifierNamed()=%v, want %v", c, nil) } } } func TestUpdateStatement_String(t *testing.T) { AssertStatementStringer(t, &parser.UpdateStatement{ Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, {Columns: []*parser.Ident{{Name: "y"}}, Expr: &parser.IntegerLit{Value: "200"}}, }, WhereExpr: &parser.BoolLit{Value: true}, }, `UPDATE tbl SET x = 100, y = 200 WHERE TRUE`) AssertStatementStringer(t, &parser.UpdateStatement{ UpdateOrRollback: pos(0), Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, }, }, `UPDATE OR ROLLBACK tbl SET x = 100`) AssertStatementStringer(t, &parser.UpdateStatement{ UpdateOrAbort: pos(0), Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, }, }, `UPDATE OR ABORT tbl SET x = 100`) AssertStatementStringer(t, &parser.UpdateStatement{ UpdateOrReplace: pos(0), Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, }, }, `UPDATE OR REPLACE tbl SET x = 100`) AssertStatementStringer(t, &parser.UpdateStatement{ UpdateOrFail: pos(0), Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, }, }, `UPDATE OR FAIL tbl SET x = 100`) AssertStatementStringer(t, &parser.UpdateStatement{ UpdateOrIgnore: pos(0), Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, }, }, `UPDATE OR IGNORE tbl SET x = 100`) // AssertStatementStringer(t, &parser.UpdateStatement{ // WithClause: &parser.WithClause{ // CTEs: []*parser.CTE{{ // TableName: &parser.Ident{Name: "cte"}, // Select: &parser.SelectStatement{ // Columns: []*parser.ResultColumn{{Star: pos(0)}}, // }, // As: parser.Pos{Column: 1}, // }}, // }, // Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, // Assignments: []*parser.Assignment{ // {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, // }, // }, `WITH cte AS (SELECT *) UPDATE tbl SET x = 100`) // Testing UPDATE with WITH clause only upto SQL comparison until parser can handle WITH clauses. { updateast := parser.UpdateStatement{ WithClause: &parser.WithClause{ Recursive: parser.Pos{Column: 1}, CTEs: []*parser.CTE{{ TableName: &parser.Ident{Name: "cte1"}, Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "table"}}, }, As: parser.Pos{Column: 1}, }, { TableName: &parser.Ident{Name: "cte2"}, Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{{Star: pos(0)}}, Source: &parser.QualifiedTableName{Name: &parser.Ident{Name: "cte1"}}, }, As: parser.Pos{Column: 1}, }}, }, Table: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}}, Assignments: []*parser.Assignment{ {Columns: []*parser.Ident{{Name: "x"}}, Expr: &parser.IntegerLit{Value: "100"}}, }, } updatesql := `WITH RECURSIVE cte1 AS (SELECT * FROM table), cte2 AS (SELECT * FROM cte1) UPDATE tbl SET x = 100` if updateast.String() != updatesql { t.Fatalf("parser.UpdateStatement.String()=%q, want %q", updateast.String(), updatesql) } if updateast.Clone().String() != updatesql { t.Fatalf("parser.UpdateStatement.Clone().String()=%q, want %q", updateast.Clone().String(), updatesql) } } } func TestIdent_String(t *testing.T) { AssertExprStringer(t, &parser.Ident{Name: "foo", Quoted: true}, `"foo"`) AssertExprStringer(t, &parser.Ident{Name: "foo \" bar", Quoted: true}, `"foo "" bar"`) } func TestStringLit_String(t *testing.T) { AssertExprStringer(t, &parser.StringLit{Value: "foo"}, `'foo'`) AssertExprStringer(t, &parser.StringLit{Value: "foo ' bar"}, `'foo '' bar'`) } func TestNumberLit_String(t *testing.T) { AssertExprStringer(t, &parser.IntegerLit{Value: "123.45"}, `123.45`) } func TestBoolLit_String(t *testing.T) { AssertExprStringer(t, &parser.BoolLit{Value: true}, `TRUE`) AssertExprStringer(t, &parser.BoolLit{Value: false}, `FALSE`) } func TestNullLit_String(t *testing.T) { AssertExprStringer(t, &parser.NullLit{}, `NULL`) } // test Date literal type. DateLit.String() will return a quoted string. func TestDateLit_String(t *testing.T) { dl := &parser.DateLit{Value: time.Unix(0, 0).UTC()} AssertExprStringer(t, dl, `'1970-01-01T00:00:00Z'`) } // test SetLiteralExpr. func TestSetLiteralExpr_String(t *testing.T) { sl := &parser.SetLiteralExpr{ Lbracket: pos(0), Rbracket: pos(0), Members: []parser.Expr{ &parser.StringLit{Value: "val1"}, &parser.StringLit{Value: "val2"}, }, } AssertExprStringer(t, sl, `['val1', 'val2']`) } // test TupleLiteralExpr. func TestTupleLiteralExpr_String(t *testing.T) { sl := &parser.TupleLiteralExpr{ Lbrace: pos(0), Rbrace: pos(0), Members: []parser.Expr{ &parser.StringLit{Value: "val1"}, &parser.StringLit{Value: "val2"}, }, } AssertExprStringer(t, sl, `{'val1', 'val2'}`) } // Test string literal to timestamp conversion func TestStringLit_ConvertToTimestamp(t *testing.T) { // string value in RFC3339 format sl := &parser.StringLit{Value: "2023-03-24T10:06:01Z"} AssertExprStringer(t, sl.ConvertToTimestamp(), `'2023-03-24T10:06:01Z'`) // string value in RFC3339Nano format // DateLit.String() uses time.RFC3339 format, because of that the nano part // will be truncated in the string. sl = &parser.StringLit{Value: "2023-03-24T10:06:01.100000Z"} AssertExprStringer(t, sl.ConvertToTimestamp(), `'2023-03-24T10:06:01Z'`) // string value in common date format sl = &parser.StringLit{Value: "2023-03-24"} AssertExprStringer(t, sl.ConvertToTimestamp(), `'2023-03-24T00:00:00Z'`) // string value contains a bad date sl = &parser.StringLit{Value: "2023-13-32"} dl := sl.ConvertToTimestamp() if dl != nil { t.Fatalf("StringLit('2023-13-32').ConvertToTimestamp()=%q, want %q", dl.String(), "nil") } } // test System Variable type. func TestSysVariable_String(t *testing.T) { // test CURRENT_DATE sv := &parser.SysVariable{Token: parser.CURRENT_DATE} AssertExprStringer(t, sv, parser.CURRENT_DATE.String()) // test CURRENTTIMESTAMP sv = &parser.SysVariable{Token: parser.CURRENT_TIMESTAMP} AssertExprStringer(t, sv, parser.CURRENT_TIMESTAMP.String()) // test CURRENT_TIMESTAMP's data type and expect it to be timestamp type if sv.DataType() != parser.NewDataTypeTimestamp() { t.Fatalf("SysVariable(CURRENT_TIMESTAMP).DataType()=%q, want %q", sv.DataType().TypeDescription(), parser.NewDataTypeTimestamp().TypeDescription()) } // test CURRENT_TIMESTAMP's name and expect it to be CURRENT_TIMESTAMP if sv.Name() != sv.String() { t.Fatalf("SysVariable(CURRENT_TIMESTAMP).Name()=%q, want %q", sv.String(), sv.Name()) } } func TestParenExpr_String(t *testing.T) { AssertExprStringer(t, &parser.ParenExpr{X: &parser.NullLit{}}, `(NULL)`) } func TestUnaryExpr_String(t *testing.T) { AssertExprStringer(t, &parser.UnaryExpr{Op: parser.PLUS, X: &parser.IntegerLit{Value: "100"}}, `+100`) AssertExprStringer(t, &parser.UnaryExpr{Op: parser.MINUS, X: &parser.IntegerLit{Value: "100"}}, `-100`) AssertExprStringer(t, &parser.UnaryExpr{Op: parser.BITNOT, X: &parser.BoolLit{Value: true}}, `!TRUE`) AssertNodeStringerPanic(t, &parser.UnaryExpr{X: &parser.IntegerLit{Value: "100"}}, `sql.UnaryExpr.String(): invalid op ILLEGAL`) } func TestBinaryExpr_String(t *testing.T) { AssertExprStringer(t, &parser.BinaryExpr{Op: parser.PLUS, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 + 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.MINUS, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 - 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.STAR, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 * 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.SLASH, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 / 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.REM, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 % 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.CONCAT, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 || 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.BETWEEN, X: &parser.IntegerLit{Value: "1"}, Y: &parser.Range{X: &parser.IntegerLit{Value: "2"}, Y: &parser.IntegerLit{Value: "3"}}}, `1 BETWEEN 2 AND 3`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.NOTBETWEEN, X: &parser.IntegerLit{Value: "1"}, Y: &parser.BinaryExpr{Op: parser.AND, X: &parser.IntegerLit{Value: "2"}, Y: &parser.IntegerLit{Value: "3"}}}, `1 NOT BETWEEN 2 AND 3`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.LSHIFT, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 << 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.RSHIFT, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 >> 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.BITAND, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 & 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.BITOR, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 | 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.LT, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 < 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.LE, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 <= 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.GT, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 > 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.GE, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 >= 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.EQ, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 = 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.NE, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 != 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.IS, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 IS 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.ISNOT, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 IS NOT 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.IN, X: &parser.IntegerLit{Value: "1"}, Y: &parser.ExprList{Exprs: []parser.Expr{&parser.IntegerLit{Value: "2"}}}}, `1 IN (2)`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.NOTIN, X: &parser.IntegerLit{Value: "1"}, Y: &parser.ExprList{Exprs: []parser.Expr{&parser.IntegerLit{Value: "2"}}}}, `1 NOT IN (2)`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.LIKE, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 LIKE 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.NOTLIKE, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 NOT LIKE 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.GLOB, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 GLOB 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.NOTGLOB, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 NOT GLOB 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.MATCH, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 MATCH 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.NOTMATCH, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 NOT MATCH 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.REGEXP, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 REGEXP 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.NOTREGEXP, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 NOT REGEXP 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.AND, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 AND 2`) AssertExprStringer(t, &parser.BinaryExpr{Op: parser.OR, X: &parser.IntegerLit{Value: "1"}, Y: &parser.IntegerLit{Value: "2"}}, `1 OR 2`) AssertNodeStringerPanic(t, &parser.BinaryExpr{}, `sql.BinaryExpr.String(): invalid op ILLEGAL`) } func TestCastExpr_String(t *testing.T) { AssertExprStringer(t, &parser.CastExpr{X: &parser.IntegerLit{Value: "1"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}}, `CAST (1 AS INTEGER)`) } func TestCaseExpr_String(t *testing.T) { AssertExprStringer(t, &parser.CaseExpr{ Operand: &parser.Ident{Name: "foo"}, Blocks: []*parser.CaseBlock{ {Condition: &parser.IntegerLit{Value: "1"}, Body: &parser.BoolLit{Value: true}}, {Condition: &parser.IntegerLit{Value: "2"}, Body: &parser.BoolLit{Value: false}}, }, ElseExpr: &parser.NullLit{}, }, `CASE foo WHEN 1 THEN TRUE WHEN 2 THEN FALSE ELSE NULL END`) AssertExprStringer(t, &parser.CaseExpr{ Blocks: []*parser.CaseBlock{ {Condition: &parser.IntegerLit{Value: "1"}, Body: &parser.BoolLit{Value: true}}, }, }, `CASE WHEN 1 THEN TRUE END`) } func TestExprList_String(t *testing.T) { AssertExprStringer(t, &parser.ExprList{Exprs: []parser.Expr{&parser.NullLit{}}}, `(NULL)`) AssertExprStringer(t, &parser.ExprList{Exprs: []parser.Expr{&parser.NullLit{}, &parser.NullLit{}}}, `(NULL, NULL)`) } func TestQualifiedRef_String(t *testing.T) { AssertExprStringer(t, &parser.QualifiedRef{Table: &parser.Ident{Name: "tbl"}, Column: &parser.Ident{Name: "col"}}, `tbl.col`) AssertExprStringer(t, &parser.QualifiedRef{Table: &parser.Ident{Name: "tbl"}, Star: pos(0)}, `tbl.*`) } func TestCall_String(t *testing.T) { AssertExprStringer(t, &parser.Call{Name: &parser.Ident{Name: "foo"}}, `foo()`) AssertExprStringer(t, &parser.Call{Name: &parser.Ident{Name: "foo"}, Star: pos(0)}, `foo(*)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Distinct: pos(0), Args: []parser.Expr{ &parser.NullLit{}, &parser.NullLit{}, }, }, `foo(DISTINCT NULL, NULL)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Filter: &parser.FilterClause{ X: &parser.BoolLit{Value: true}, }, }, `foo() FILTER (WHERE TRUE)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Name: &parser.Ident{Name: "win"}, }, }, `foo() OVER win`) t.Run("FrameSpec", func(t *testing.T) { AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Definition: &parser.WindowDefinition{ Frame: &parser.FrameSpec{ Rows: pos(0), X: &parser.NullLit{}, PrecedingX: pos(0), ExcludeNoOthers: pos(0), }, }, }, }, `foo() OVER (ROWS NULL PRECEDING EXCLUDE NO OTHERS)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Definition: &parser.WindowDefinition{ Frame: &parser.FrameSpec{ Groups: pos(0), CurrentRowX: pos(0), ExcludeCurrentRow: pos(0), }, }, }, }, `foo() OVER (GROUPS CURRENT ROW EXCLUDE CURRENT ROW)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Definition: &parser.WindowDefinition{ Frame: &parser.FrameSpec{ Rows: pos(0), UnboundedX: pos(0), PrecedingX: pos(0), Between: pos(0), CurrentRowY: pos(0), }, }, }, }, `foo() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Definition: &parser.WindowDefinition{ Frame: &parser.FrameSpec{ Rows: pos(0), X: &parser.NullLit{}, PrecedingX: pos(0), Between: pos(0), CurrentRowY: pos(0), }, }, }, }, `foo() OVER (ROWS BETWEEN NULL PRECEDING AND CURRENT ROW)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Definition: &parser.WindowDefinition{ Frame: &parser.FrameSpec{ Range: pos(0), X: &parser.NullLit{}, FollowingX: pos(0), Between: pos(0), Y: &parser.BoolLit{Value: true}, PrecedingY: pos(0), ExcludeGroup: pos(0), }, }, }, }, `foo() OVER (RANGE BETWEEN NULL FOLLOWING AND TRUE PRECEDING EXCLUDE GROUP)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Definition: &parser.WindowDefinition{ Frame: &parser.FrameSpec{ Range: pos(0), CurrentRowX: pos(0), Between: pos(0), Y: &parser.BoolLit{Value: true}, FollowingY: pos(0), ExcludeTies: pos(0), }, }, }, }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND TRUE FOLLOWING EXCLUDE TIES)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Definition: &parser.WindowDefinition{ Frame: &parser.FrameSpec{ Range: pos(0), CurrentRowX: pos(0), Between: pos(0), CurrentRowY: pos(0), }, }, }, }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND CURRENT ROW)`) AssertExprStringer(t, &parser.Call{ Name: &parser.Ident{Name: "foo"}, Over: &parser.OverClause{ Definition: &parser.WindowDefinition{ Frame: &parser.FrameSpec{ Range: pos(0), CurrentRowX: pos(0), Between: pos(0), UnboundedY: pos(0), FollowingY: pos(0), }, }, }, }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)`) }) } func TestExists_String(t *testing.T) { AssertExprStringer(t, &parser.Exists{ Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{ {Star: pos(0)}, }, }, }, `EXISTS (SELECT *)`) AssertExprStringer(t, &parser.Exists{ Not: pos(0), Exists: pos(0), Select: &parser.SelectStatement{ Columns: []*parser.ResultColumn{ {Star: pos(0)}, }, }, }, `NOT EXISTS (SELECT *)`) } func AssertExprStringer(tb testing.TB, expr parser.Expr, s string) { tb.Helper() if str := expr.String(); str != s { tb.Fatalf("String()=%s, expected %s", str, s) } else if _, err := parser.NewParser(strings.NewReader(str)).ParseExpr(); err != nil { tb.Fatalf("cannot parse string: %s; err=%s", str, err) } else { AssertExprSanity(tb, expr, s) } } // AssertExprSanity checks that an expression can be cloned, and that // the clone's string matches the string we're given. This is a // kind of round-trip test both for AssertExprStringer and // AssertParseExpr, although they approach it from different directions. func AssertExprSanity(tb testing.TB, expr parser.Expr, s string) { _, err := parser.Walk(parser.VisitFunc(func(node parser.Node) (parser.Node, error) { return node, nil }), expr) if err != nil { tb.Fatalf("walking expression %q: %v", s, err) } clone := parser.CloneExpr(expr) cloneStr := clone.String() // We case-smash keywords a lot, and trying to match them all exhaustively sucks. // This means this test alone won't catch some hypothetical case smashing // errors on provided data, but those should be getting tested directly anyway. if !strings.EqualFold(s, cloneStr) { tb.Fatalf("expression %q cloned to %q", s, cloneStr) } } // AssertStatementSanity checks that a statement can be cloned, and that // the clone's string matches the string we're given. This is a // kind of round-trip test both for AssertStatementStringer and // AssertParseStatement, although they approach it from different // directions. Similarly, this checks that, if a statement has a source, // SourceList for that source produces at least one source, which // logically it should. This lets us get coverage of the Source logic // across a broad variety of statements. func AssertStatementSanity(tb testing.TB, stmt parser.Statement, s string) { _, err := parser.Walk(parser.VisitFunc(func(node parser.Node) (parser.Node, error) { return node, nil }), stmt) if err != nil { tb.Fatalf("walking expression %q: %v", s, err) } clone := parser.CloneStatement(stmt) cloneStr := clone.String() // We case-smash keywords a lot, and trying to match them all exhaustively sucks. // This means this test alone won't catch some hypothetical case smashing // errors on provided data, but those should be getting tested directly anyway. if !strings.EqualFold(s, cloneStr) { tb.Fatalf("statement %q cloned to %q", s, cloneStr) } src := parser.StatementSource(stmt) if src != nil { list := parser.SourceList(src) if len(list) == 0 { tb.Fatalf("statement %q has a source, but that source has a total of 0 sources", s) } } } func AssertStatementStringer(tb testing.TB, stmt parser.Statement, s string) { tb.Helper() if str := stmt.String(); str != s { tb.Fatalf("String()=%s, expected %s", str, s) } else if _, err := parser.NewParser(strings.NewReader(str)).ParseStatement(); err != nil { tb.Fatalf("cannot parse string: %s; err=%s", str, err) } else { AssertStatementSanity(tb, stmt, s) } } func AssertNodeStringerPanic(tb testing.TB, node parser.Node, msg string) { tb.Helper() var r interface{} func() { defer func() { r = recover() }() _ = node.String() }() if r == nil { tb.Fatal("expected node stringer to panic") } else if r != msg { tb.Fatalf("recover()=%s, want %s", r, msg) } } // StripPos removes the position data from a node and its children. // This function returns the root argument passed in. func StripPos(root parser.Node) parser.Node { zero := reflect.ValueOf(parser.Pos{}) _, _ = parser.Walk(parser.VisitFunc(func(node parser.Node) (parser.Node, error) { value := reflect.Indirect(reflect.ValueOf(node)) for i := 0; i < value.NumField(); i++ { if field := value.Field(i); field.Type() == zero.Type() { field.Set(zero) } } return node, nil }), root) return root } func StripExprPos(root parser.Expr) parser.Expr { StripPos(root) return root }