// Copyright 2021 Pilosa Corp. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sql2_test import ( "reflect" "strings" "testing" "github.com/go-test/deep" sql "github.com/molecula/featurebase/v2/sql2" ) func TestExprString(t *testing.T) { if got, want := sql.ExprString(&sql.NullLit{}), "NULL"; got != want { t.Fatalf("ExprString()=%q, want %q", got, want) } else if got, want := sql.ExprString(nil), ""; got != want { t.Fatalf("ExprString()=%q, want %q", got, want) } } func TestSplitExprTree(t *testing.T) { t.Run("AND-only", func(t *testing.T) { AssertSplitExprTree(t, `x = 1 AND y = 2 AND z = 3`, []sql.Expr{ &sql.BinaryExpr{X: &sql.Ident{Name: "x"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "1"}}, &sql.BinaryExpr{X: &sql.Ident{Name: "y"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "2"}}, &sql.BinaryExpr{X: &sql.Ident{Name: "z"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "3"}}, }) }) t.Run("OR", func(t *testing.T) { AssertSplitExprTree(t, `x = 1 AND (y = 2 OR y = 3) AND z = 4`, []sql.Expr{ &sql.BinaryExpr{X: &sql.Ident{Name: "x"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "1"}}, &sql.BinaryExpr{ X: &sql.BinaryExpr{X: &sql.Ident{Name: "y"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "2"}}, Op: sql.OR, Y: &sql.BinaryExpr{X: &sql.Ident{Name: "y"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "3"}}, }, &sql.BinaryExpr{X: &sql.Ident{Name: "z"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "4"}}, }) }) t.Run("ParenExpr", func(t *testing.T) { AssertSplitExprTree(t, `x = 1 AND (y = 2 AND z = 3)`, []sql.Expr{ &sql.BinaryExpr{X: &sql.Ident{Name: "x"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "1"}}, &sql.BinaryExpr{X: &sql.Ident{Name: "y"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "2"}}, &sql.BinaryExpr{X: &sql.Ident{Name: "z"}, Op: sql.EQ, Y: &sql.NumberLit{Value: "3"}}, }) }) } func AssertSplitExprTree(tb testing.TB, s string, want []sql.Expr) { tb.Helper() if diff := deep.Equal(sql.SplitExprTree(StripExprPos(sql.MustParseExprString(s))), want); diff != nil { tb.Fatal("mismatch: \n" + strings.Join(diff, "\n")) } } func TestAlterTableStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.AlterTableStatement{ Name: &sql.Ident{Name: "foo"}, NewName: &sql.Ident{Name: "bar"}, }, `ALTER TABLE "foo" RENAME TO "bar"`) AssertStatementStringer(t, &sql.AlterTableStatement{ Name: &sql.Ident{Name: "foo"}, ColumnName: &sql.Ident{Name: "col1"}, NewColumnName: &sql.Ident{Name: "col2"}, }, `ALTER TABLE "foo" RENAME COLUMN "col1" TO "col2"`) AssertStatementStringer(t, &sql.AlterTableStatement{ Name: &sql.Ident{Name: "foo"}, ColumnDef: &sql.ColumnDefinition{ Name: &sql.Ident{Name: "bar"}, Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, }, }, `ALTER TABLE "foo" ADD COLUMN "bar" INTEGER`) } func TestAnalyzeStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.AnalyzeStatement{Name: &sql.Ident{Name: "foo"}}, `ANALYZE "foo"`) } func TestBeginStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.BeginStatement{}, `BEGIN`) AssertStatementStringer(t, &sql.BeginStatement{Deferred: pos(0)}, `BEGIN DEFERRED`) AssertStatementStringer(t, &sql.BeginStatement{Immediate: pos(0)}, `BEGIN IMMEDIATE`) AssertStatementStringer(t, &sql.BeginStatement{Exclusive: pos(0)}, `BEGIN EXCLUSIVE`) AssertStatementStringer(t, &sql.BeginStatement{Immediate: pos(0), Transaction: pos(0)}, `BEGIN IMMEDIATE TRANSACTION`) } func TestCommitStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.CommitStatement{}, `COMMIT`) AssertStatementStringer(t, &sql.CommitStatement{End: pos(0)}, `END`) AssertStatementStringer(t, &sql.CommitStatement{End: pos(0), Transaction: pos(0)}, `END TRANSACTION`) } func TestCreateIndexStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.CreateIndexStatement{ Name: &sql.Ident{Name: "foo"}, Table: &sql.Ident{Name: "bar"}, Columns: []*sql.IndexedColumn{{X: &sql.Ident{Name: "baz"}}}, }, `CREATE INDEX "foo" ON "bar" ("baz")`) AssertStatementStringer(t, &sql.CreateIndexStatement{ Unique: pos(0), IfNotExists: pos(0), Name: &sql.Ident{Name: "foo"}, Table: &sql.Ident{Name: "bar"}, Columns: []*sql.IndexedColumn{ {X: &sql.Ident{Name: "baz"}}, {X: &sql.Ident{Name: "bat"}}, }, WhereExpr: &sql.BoolLit{Value: true}, }, `CREATE UNIQUE INDEX IF NOT EXISTS "foo" ON "bar" ("baz", "bat") WHERE TRUE`) } func TestCreateTableStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.CreateTableStatement{ Name: &sql.Ident{Name: "foo"}, IfNotExists: pos(0), Columns: []*sql.ColumnDefinition{ { Name: &sql.Ident{Name: "bar"}, Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, }, { Name: &sql.Ident{Name: "baz"}, Type: &sql.Type{Name: &sql.Ident{Name: "TEXT"}}, }, }, }, `CREATE TABLE IF NOT EXISTS "foo" ("bar" INTEGER, "baz" TEXT)`) AssertStatementStringer(t, &sql.CreateTableStatement{ Name: &sql.Ident{Name: "foo"}, Columns: []*sql.ColumnDefinition{{ Name: &sql.Ident{Name: "bar"}, Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, Constraints: []sql.Constraint{ &sql.PrimaryKeyConstraint{Autoincrement: pos(0)}, &sql.NotNullConstraint{Name: &sql.Ident{Name: "nn"}}, &sql.DefaultConstraint{Name: &sql.Ident{Name: "def"}, Expr: &sql.NumberLit{Value: "123"}}, &sql.DefaultConstraint{Expr: &sql.NumberLit{Value: "456"}, Lparen: pos(0)}, &sql.UniqueConstraint{}, }, }}, }, `CREATE TABLE "foo" ("bar" INTEGER PRIMARY KEY AUTOINCREMENT CONSTRAINT "nn" NOT NULL CONSTRAINT "def" DEFAULT 123 DEFAULT (456) UNIQUE)`) AssertStatementStringer(t, &sql.CreateTableStatement{ Name: &sql.Ident{Name: "foo"}, Columns: []*sql.ColumnDefinition{{ Name: &sql.Ident{Name: "bar"}, Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, Constraints: []sql.Constraint{ &sql.ForeignKeyConstraint{ ForeignTable: &sql.Ident{Name: "x"}, ForeignColumns: []*sql.Ident{{Name: "y"}}, Args: []*sql.ForeignKeyArg{ {OnDelete: pos(0), SetNull: pos(0)}, {OnUpdate: pos(0), SetDefault: pos(0)}, {OnUpdate: pos(0), Cascade: pos(0)}, {OnUpdate: pos(0), Restrict: pos(0)}, {OnUpdate: pos(0), NoAction: pos(0)}, }, }, }, }}, }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") ON DELETE SET NULL ON UPDATE SET DEFAULT ON UPDATE CASCADE ON UPDATE RESTRICT ON UPDATE NO ACTION)`) AssertStatementStringer(t, &sql.CreateTableStatement{ Name: &sql.Ident{Name: "foo"}, Columns: []*sql.ColumnDefinition{{ Name: &sql.Ident{Name: "bar"}, Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, Constraints: []sql.Constraint{ &sql.ForeignKeyConstraint{ ForeignTable: &sql.Ident{Name: "x"}, ForeignColumns: []*sql.Ident{{Name: "y"}}, Deferrable: pos(0), InitiallyDeferred: pos(0), }, }, }}, }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") DEFERRABLE INITIALLY DEFERRED)`) AssertStatementStringer(t, &sql.CreateTableStatement{ Name: &sql.Ident{Name: "foo"}, Columns: []*sql.ColumnDefinition{{ Name: &sql.Ident{Name: "bar"}, Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}, Constraints: []sql.Constraint{ &sql.ForeignKeyConstraint{ ForeignTable: &sql.Ident{Name: "x"}, ForeignColumns: []*sql.Ident{{Name: "y"}}, NotDeferrable: pos(0), InitiallyImmediate: pos(0), }, }, }}, }, `CREATE TABLE "foo" ("bar" INTEGER REFERENCES "x" ("y") NOT DEFERRABLE INITIALLY IMMEDIATE)`) AssertStatementStringer(t, &sql.CreateTableStatement{ Name: &sql.Ident{Name: "foo"}, Columns: []*sql.ColumnDefinition{{ Name: &sql.Ident{Name: "bar"}, Type: &sql.Type{Name: &sql.Ident{Name: "DECIMAL"}, Precision: &sql.NumberLit{Value: "100"}}, }}, Constraints: []sql.Constraint{ &sql.PrimaryKeyConstraint{ Name: &sql.Ident{Name: "pk"}, Columns: []*sql.Ident{ {Name: "x"}, {Name: "y"}, }, }, &sql.UniqueConstraint{ Name: &sql.Ident{Name: "uniq"}, Columns: []*sql.Ident{ {Name: "x"}, {Name: "y"}, }, }, &sql.CheckConstraint{ Name: &sql.Ident{Name: "chk"}, Expr: &sql.BoolLit{Value: true}, }, }, }, `CREATE TABLE "foo" ("bar" DECIMAL(100), CONSTRAINT "pk" PRIMARY KEY ("x", "y"), CONSTRAINT "uniq" UNIQUE ("x", "y"), CONSTRAINT "chk" CHECK (TRUE))`) AssertStatementStringer(t, &sql.CreateTableStatement{ Name: &sql.Ident{Name: "foo"}, Columns: []*sql.ColumnDefinition{{ Name: &sql.Ident{Name: "bar"}, Type: &sql.Type{Name: &sql.Ident{Name: "DECIMAL"}, Precision: &sql.NumberLit{Value: "100"}, Scale: &sql.NumberLit{Value: "200"}}, }}, Constraints: []sql.Constraint{ &sql.ForeignKeyConstraint{ Name: &sql.Ident{Name: "fk"}, Columns: []*sql.Ident{{Name: "a"}, {Name: "b"}}, ForeignTable: &sql.Ident{Name: "x"}, ForeignColumns: []*sql.Ident{{Name: "y"}, {Name: "z"}}, }, }, }, `CREATE TABLE "foo" ("bar" DECIMAL(100,200), CONSTRAINT "fk" FOREIGN KEY ("a", "b") REFERENCES "x" ("y", "z"))`) AssertStatementStringer(t, &sql.CreateTableStatement{ Name: &sql.Ident{Name: "foo"}, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, `CREATE TABLE "foo" AS SELECT *`) } func TestCreateTriggerStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.CreateTriggerStatement{ Name: &sql.Ident{Name: "trig"}, Insert: pos(0), Table: &sql.Ident{Name: "tbl"}, Body: []sql.Statement{ &sql.DeleteStatement{Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl2"}}}, }, }, `CREATE TRIGGER "trig" INSERT ON "tbl" BEGIN DELETE FROM "tbl2"; END`) AssertStatementStringer(t, &sql.CreateTriggerStatement{ Name: &sql.Ident{Name: "trig"}, Before: pos(0), Delete: pos(0), ForEachRow: pos(0), Table: &sql.Ident{Name: "tbl"}, Body: []sql.Statement{ &sql.DeleteStatement{Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}}, }, }, `CREATE TRIGGER "trig" BEFORE DELETE ON "tbl" FOR EACH ROW BEGIN DELETE FROM "x"; END`) AssertStatementStringer(t, &sql.CreateTriggerStatement{ IfNotExists: pos(0), Name: &sql.Ident{Name: "trig"}, After: pos(0), Update: pos(0), Table: &sql.Ident{Name: "tbl"}, WhenExpr: &sql.BoolLit{Value: true}, Body: []sql.Statement{ &sql.DeleteStatement{Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}}, }, }, `CREATE TRIGGER IF NOT EXISTS "trig" AFTER UPDATE ON "tbl" WHEN TRUE BEGIN DELETE FROM "x"; END`) AssertStatementStringer(t, &sql.CreateTriggerStatement{ Name: &sql.Ident{Name: "trig"}, InsteadOf: pos(0), Update: pos(0), UpdateOf: pos(0), UpdateOfColumns: []*sql.Ident{{Name: "x"}, {Name: "y"}}, Table: &sql.Ident{Name: "tbl"}, Body: []sql.Statement{ &sql.DeleteStatement{Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}}, }, }, `CREATE TRIGGER "trig" INSTEAD OF UPDATE OF "x", "y" ON "tbl" BEGIN DELETE FROM "x"; END`) } func TestCreateViewStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.CreateViewStatement{ Name: &sql.Ident{Name: "vw"}, Columns: []*sql.Ident{ {Name: "x"}, {Name: "y"}, }, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, `CREATE VIEW "vw" ("x", "y") AS SELECT *`) AssertStatementStringer(t, &sql.CreateViewStatement{ IfNotExists: pos(0), Name: &sql.Ident{Name: "vw"}, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, `CREATE VIEW IF NOT EXISTS "vw" AS SELECT *`) } func TestDeleteStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.DeleteStatement{ Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}, Alias: &sql.Ident{Name: "tbl2"}}, }, `DELETE FROM "tbl" AS "tbl2"`) AssertStatementStringer(t, &sql.DeleteStatement{ Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}, Index: &sql.Ident{Name: "idx"}}, }, `DELETE FROM "tbl" INDEXED BY "idx"`) AssertStatementStringer(t, &sql.DeleteStatement{ Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}, NotIndexed: pos(0)}, }, `DELETE FROM "tbl" NOT INDEXED`) AssertStatementStringer(t, &sql.DeleteStatement{ Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, WhereExpr: &sql.BoolLit{Value: true}, OrderingTerms: []*sql.OrderingTerm{ {X: &sql.Ident{Name: "x"}}, {X: &sql.Ident{Name: "y"}}, }, LimitExpr: &sql.NumberLit{Value: "10"}, OffsetExpr: &sql.NumberLit{Value: "5"}, }, `DELETE FROM "tbl" WHERE TRUE ORDER BY "x", "y" LIMIT 10 OFFSET 5`) AssertStatementStringer(t, &sql.DeleteStatement{ WithClause: &sql.WithClause{ Recursive: pos(0), CTEs: []*sql.CTE{{ TableName: &sql.Ident{Name: "cte"}, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }}, }, Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, }, `WITH RECURSIVE "cte" AS (SELECT *) DELETE FROM "tbl"`) } func TestDropIndexStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.DropIndexStatement{ Name: &sql.Ident{Name: "idx"}, }, `DROP INDEX "idx"`) AssertStatementStringer(t, &sql.DropIndexStatement{ IfExists: pos(0), Name: &sql.Ident{Name: "idx"}, }, `DROP INDEX IF EXISTS "idx"`) } func TestDropTableStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.DropTableStatement{ Name: &sql.Ident{Name: "tbl"}, }, `DROP TABLE "tbl"`) AssertStatementStringer(t, &sql.DropTableStatement{ IfExists: pos(0), Name: &sql.Ident{Name: "tbl"}, }, `DROP TABLE IF EXISTS "tbl"`) } func TestDropTriggerStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.DropTriggerStatement{ Name: &sql.Ident{Name: "trig"}, }, `DROP TRIGGER "trig"`) AssertStatementStringer(t, &sql.DropTriggerStatement{ IfExists: pos(0), Name: &sql.Ident{Name: "trig"}, }, `DROP TRIGGER IF EXISTS "trig"`) } func TestDropViewStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.DropViewStatement{ Name: &sql.Ident{Name: "vw"}, }, `DROP VIEW "vw"`) AssertStatementStringer(t, &sql.DropViewStatement{ IfExists: pos(0), Name: &sql.Ident{Name: "vw"}, }, `DROP VIEW IF EXISTS "vw"`) } func TestExplainStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.ExplainStatement{ Stmt: &sql.DropViewStatement{ Name: &sql.Ident{Name: "vw"}, }, }, `EXPLAIN DROP VIEW "vw"`) AssertStatementStringer(t, &sql.ExplainStatement{ QueryPlan: pos(0), Stmt: &sql.DropViewStatement{ Name: &sql.Ident{Name: "vw"}, }, }, `EXPLAIN QUERY PLAN DROP VIEW "vw"`) } func TestInsertStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.InsertStatement{ Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT INTO "tbl" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ Table: &sql.Ident{Name: "tbl"}, Alias: &sql.Ident{Name: "x"}, DefaultValues: pos(0), }, `INSERT INTO "tbl" AS "x" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ InsertOrReplace: pos(0), Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR REPLACE INTO "tbl" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ InsertOrRollback: pos(0), Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR ROLLBACK INTO "tbl" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ InsertOrAbort: pos(0), Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR ABORT INTO "tbl" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ InsertOrFail: pos(0), Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR FAIL INTO "tbl" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ InsertOrIgnore: pos(0), Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `INSERT OR IGNORE INTO "tbl" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ Replace: pos(0), Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `REPLACE INTO "tbl" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ Table: &sql.Ident{Name: "tbl"}, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, `INSERT INTO "tbl" SELECT *`) AssertStatementStringer(t, &sql.InsertStatement{ Table: &sql.Ident{Name: "tbl"}, Columns: []*sql.Ident{ {Name: "x"}, {Name: "y"}, }, ValueLists: []*sql.ExprList{ {Exprs: []sql.Expr{&sql.NullLit{}, &sql.NullLit{}}}, {Exprs: []sql.Expr{&sql.NullLit{}, &sql.NullLit{}}}, }, }, `INSERT INTO "tbl" ("x", "y") VALUES (NULL, NULL), (NULL, NULL)`) AssertStatementStringer(t, &sql.InsertStatement{ WithClause: &sql.WithClause{ CTEs: []*sql.CTE{ { TableName: &sql.Ident{Name: "cte"}, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, { TableName: &sql.Ident{Name: "cte2"}, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, }, }, Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), }, `WITH "cte" AS (SELECT *), "cte2" AS (SELECT *) INSERT INTO "tbl" DEFAULT VALUES`) AssertStatementStringer(t, &sql.InsertStatement{ Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), UpsertClause: &sql.UpsertClause{ DoNothing: pos(0), }, }, `INSERT INTO "tbl" DEFAULT VALUES ON CONFLICT DO NOTHING`) AssertStatementStringer(t, &sql.InsertStatement{ Table: &sql.Ident{Name: "tbl"}, DefaultValues: pos(0), UpsertClause: &sql.UpsertClause{ Columns: []*sql.IndexedColumn{ {X: &sql.Ident{Name: "x"}, Asc: pos(0)}, {X: &sql.Ident{Name: "y"}, Desc: pos(0)}, }, WhereExpr: &sql.BoolLit{Value: true}, Assignments: []*sql.Assignment{ {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, {Columns: []*sql.Ident{{Name: "y"}, {Name: "z"}}, Expr: &sql.NumberLit{Value: "200"}}, }, UpdateWhereExpr: &sql.BoolLit{Value: false}, }, }, `INSERT INTO "tbl" DEFAULT VALUES ON CONFLICT ("x" ASC, "y" DESC) WHERE TRUE DO UPDATE SET "x" = 100, ("y", "z") = 200 WHERE FALSE`) } func TestReleaseStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.ReleaseStatement{Name: &sql.Ident{Name: "x"}}, `RELEASE "x"`) AssertStatementStringer(t, &sql.ReleaseStatement{Savepoint: pos(0), Name: &sql.Ident{Name: "x"}}, `RELEASE SAVEPOINT "x"`) } func TestRollbackStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.RollbackStatement{}, `ROLLBACK`) AssertStatementStringer(t, &sql.RollbackStatement{Transaction: pos(0)}, `ROLLBACK TRANSACTION`) AssertStatementStringer(t, &sql.RollbackStatement{SavepointName: &sql.Ident{Name: "x"}}, `ROLLBACK TO "x"`) AssertStatementStringer(t, &sql.RollbackStatement{Savepoint: pos(0), SavepointName: &sql.Ident{Name: "x"}}, `ROLLBACK TO SAVEPOINT "x"`) } func TestSavepointStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.SavepointStatement{Name: &sql.Ident{Name: "x"}}, `SAVEPOINT "x"`) } func TestSelectStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{ {Expr: &sql.Ident{Name: "x"}, Alias: &sql.Ident{Name: "y"}}, {Expr: &sql.Ident{Name: "z"}}, }, }, `SELECT "x" AS "y", "z"`) AssertStatementStringer(t, &sql.SelectStatement{ Distinct: pos(0), Columns: []*sql.ResultColumn{ {Expr: &sql.Ident{Name: "x"}}, }, }, `SELECT DISTINCT "x"`) AssertStatementStringer(t, &sql.SelectStatement{ All: pos(0), Columns: []*sql.ResultColumn{ {Expr: &sql.Ident{Name: "x"}}, }, }, `SELECT ALL "x"`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, WhereExpr: &sql.BoolLit{Value: true}, GroupByExprs: []sql.Expr{&sql.Ident{Name: "x"}, &sql.Ident{Name: "y"}}, HavingExpr: &sql.Ident{Name: "z"}, }, `SELECT * FROM "tbl" WHERE TRUE GROUP BY "x", "y" HAVING "z"`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.ParenSource{ X: &sql.SelectStatement{Columns: []*sql.ResultColumn{{Star: pos(0)}}}, Alias: &sql.Ident{Name: "tbl"}, }, }, `SELECT * FROM (SELECT *) AS "tbl"`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.ParenSource{ X: &sql.SelectStatement{Columns: []*sql.ResultColumn{{Star: pos(0)}}}, }, }, `SELECT * FROM (SELECT *)`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, Windows: []*sql.Window{ { Name: &sql.Ident{Name: "win1"}, Definition: &sql.WindowDefinition{ Base: &sql.Ident{Name: "base"}, Partitions: []sql.Expr{&sql.Ident{Name: "x"}, &sql.Ident{Name: "y"}}, OrderingTerms: []*sql.OrderingTerm{ {X: &sql.Ident{Name: "x"}, Asc: pos(0), NullsFirst: pos(0)}, {X: &sql.Ident{Name: "y"}, Desc: pos(0), NullsLast: pos(0)}, }, Frame: &sql.FrameSpec{ Range: pos(0), UnboundedX: pos(0), PrecedingX: pos(0), }, }, }, { Name: &sql.Ident{Name: "win2"}, Definition: &sql.WindowDefinition{ Base: &sql.Ident{Name: "base2"}, }, }, }, }, `SELECT * FROM "tbl" WINDOW "win1" AS ("base" PARTITION BY "x", "y" ORDER BY "x" ASC NULLS FIRST, "y" DESC NULLS LAST RANGE UNBOUNDED PRECEDING), "win2" AS ("base2")`) AssertStatementStringer(t, &sql.SelectStatement{ WithClause: &sql.WithClause{ CTEs: []*sql.CTE{{ TableName: &sql.Ident{Name: "cte"}, Columns: []*sql.Ident{ {Name: "x"}, {Name: "y"}, }, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }}, }, ValueLists: []*sql.ExprList{ {Exprs: []sql.Expr{&sql.NumberLit{Value: "1"}, &sql.NumberLit{Value: "2"}}}, {Exprs: []sql.Expr{&sql.NumberLit{Value: "3"}, &sql.NumberLit{Value: "4"}}}, }, }, `WITH "cte" ("x", "y") AS (SELECT *) VALUES (1, 2), (3, 4)`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Union: pos(0), Compound: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, `SELECT * UNION SELECT *`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Union: pos(0), UnionAll: pos(0), Compound: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, `SELECT * UNION ALL SELECT *`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Intersect: pos(0), Compound: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, `SELECT * INTERSECT SELECT *`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Except: pos(0), Compound: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }, `SELECT * EXCEPT SELECT *`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, OrderingTerms: []*sql.OrderingTerm{ {X: &sql.Ident{Name: "x"}}, {X: &sql.Ident{Name: "y"}}, }, }, `SELECT * ORDER BY "x", "y"`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, LimitExpr: &sql.NumberLit{Value: "1"}, OffsetExpr: &sql.NumberLit{Value: "2"}, }, `SELECT * LIMIT 1 OFFSET 2`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.JoinClause{ X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, Operator: &sql.JoinOperator{Comma: pos(0)}, Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, }, }, `SELECT * FROM "x", "y"`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.JoinClause{ X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, Operator: &sql.JoinOperator{}, Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, Constraint: &sql.OnConstraint{X: &sql.BoolLit{Value: true}}, }, }, `SELECT * FROM "x" JOIN "y" ON TRUE`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.JoinClause{ X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, Operator: &sql.JoinOperator{Natural: pos(0), Inner: pos(0)}, Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, Constraint: &sql.UsingConstraint{ Columns: []*sql.Ident{{Name: "a"}, {Name: "b"}}, }, }, }, `SELECT * FROM "x" NATURAL INNER JOIN "y" USING ("a", "b")`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.JoinClause{ X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, Operator: &sql.JoinOperator{Left: pos(0)}, Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, }, }, `SELECT * FROM "x" LEFT JOIN "y"`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.JoinClause{ X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, Operator: &sql.JoinOperator{Left: pos(0), Outer: pos(0)}, Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, }, }, `SELECT * FROM "x" LEFT OUTER JOIN "y"`) AssertStatementStringer(t, &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, Source: &sql.JoinClause{ X: &sql.QualifiedTableName{Name: &sql.Ident{Name: "x"}}, Operator: &sql.JoinOperator{Cross: pos(0)}, Y: &sql.QualifiedTableName{Name: &sql.Ident{Name: "y"}}, }, }, `SELECT * FROM "x" CROSS JOIN "y"`) } func TestUpdateStatement_String(t *testing.T) { AssertStatementStringer(t, &sql.UpdateStatement{ Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, Assignments: []*sql.Assignment{ {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, {Columns: []*sql.Ident{{Name: "y"}}, Expr: &sql.NumberLit{Value: "200"}}, }, WhereExpr: &sql.BoolLit{Value: true}, }, `UPDATE "tbl" SET "x" = 100, "y" = 200 WHERE TRUE`) AssertStatementStringer(t, &sql.UpdateStatement{ UpdateOrRollback: pos(0), Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, Assignments: []*sql.Assignment{ {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, }, }, `UPDATE OR ROLLBACK "tbl" SET "x" = 100`) AssertStatementStringer(t, &sql.UpdateStatement{ UpdateOrAbort: pos(0), Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, Assignments: []*sql.Assignment{ {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, }, }, `UPDATE OR ABORT "tbl" SET "x" = 100`) AssertStatementStringer(t, &sql.UpdateStatement{ UpdateOrReplace: pos(0), Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, Assignments: []*sql.Assignment{ {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, }, }, `UPDATE OR REPLACE "tbl" SET "x" = 100`) AssertStatementStringer(t, &sql.UpdateStatement{ UpdateOrFail: pos(0), Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, Assignments: []*sql.Assignment{ {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, }, }, `UPDATE OR FAIL "tbl" SET "x" = 100`) AssertStatementStringer(t, &sql.UpdateStatement{ UpdateOrIgnore: pos(0), Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, Assignments: []*sql.Assignment{ {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, }, }, `UPDATE OR IGNORE "tbl" SET "x" = 100`) AssertStatementStringer(t, &sql.UpdateStatement{ WithClause: &sql.WithClause{ CTEs: []*sql.CTE{{ TableName: &sql.Ident{Name: "cte"}, Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{{Star: pos(0)}}, }, }}, }, Table: &sql.QualifiedTableName{Name: &sql.Ident{Name: "tbl"}}, Assignments: []*sql.Assignment{ {Columns: []*sql.Ident{{Name: "x"}}, Expr: &sql.NumberLit{Value: "100"}}, }, }, `WITH "cte" AS (SELECT *) UPDATE "tbl" SET "x" = 100`) } func TestIdent_String(t *testing.T) { AssertExprStringer(t, &sql.Ident{Name: "foo"}, `"foo"`) AssertExprStringer(t, &sql.Ident{Name: "foo \" bar"}, `"foo "" bar"`) } func TestStringLit_String(t *testing.T) { AssertExprStringer(t, &sql.StringLit{Value: "foo"}, `'foo'`) AssertExprStringer(t, &sql.StringLit{Value: "foo ' bar"}, `'foo '' bar'`) } func TestNumberLit_String(t *testing.T) { AssertExprStringer(t, &sql.NumberLit{Value: "123.45"}, `123.45`) } func TestBlobLit_String(t *testing.T) { AssertExprStringer(t, &sql.BlobLit{Value: "0123abcd"}, `x'0123abcd'`) } func TestBoolLit_String(t *testing.T) { AssertExprStringer(t, &sql.BoolLit{Value: true}, `TRUE`) AssertExprStringer(t, &sql.BoolLit{Value: false}, `FALSE`) } func TestNullLit_String(t *testing.T) { AssertExprStringer(t, &sql.NullLit{}, `NULL`) } func TestBindExpr_String(t *testing.T) { AssertExprStringer(t, &sql.BindExpr{Name: "foo"}, `$foo`) } func TestParenExpr_String(t *testing.T) { AssertExprStringer(t, &sql.ParenExpr{X: &sql.NullLit{}}, `(NULL)`) } func TestUnaryExpr_String(t *testing.T) { AssertExprStringer(t, &sql.UnaryExpr{Op: sql.PLUS, X: &sql.NumberLit{Value: "100"}}, `+100`) AssertExprStringer(t, &sql.UnaryExpr{Op: sql.MINUS, X: &sql.NumberLit{Value: "100"}}, `-100`) AssertNodeStringerPanic(t, &sql.UnaryExpr{X: &sql.NumberLit{Value: "100"}}, `sql.UnaryExpr.String(): invalid op ILLEGAL`) } func TestBinaryExpr_String(t *testing.T) { AssertExprStringer(t, &sql.BinaryExpr{Op: sql.PLUS, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 + 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.MINUS, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 - 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.STAR, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 * 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.SLASH, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 / 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.REM, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 % 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.CONCAT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 || 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.BETWEEN, X: &sql.NumberLit{Value: "1"}, Y: &sql.Range{X: &sql.NumberLit{Value: "2"}, Y: &sql.NumberLit{Value: "3"}}}, `1 BETWEEN 2 AND 3`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTBETWEEN, X: &sql.NumberLit{Value: "1"}, Y: &sql.BinaryExpr{Op: sql.AND, X: &sql.NumberLit{Value: "2"}, Y: &sql.NumberLit{Value: "3"}}}, `1 NOT BETWEEN 2 AND 3`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.LSHIFT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 << 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.RSHIFT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 >> 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.BITAND, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 & 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.BITOR, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 | 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.LT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 < 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.LE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 <= 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.GT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 > 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.GE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 >= 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.EQ, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 = 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 != 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.IS, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 IS 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.ISNOT, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 IS NOT 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.IN, X: &sql.NumberLit{Value: "1"}, Y: &sql.ExprList{Exprs: []sql.Expr{&sql.NumberLit{Value: "2"}}}}, `1 IN (2)`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTIN, X: &sql.NumberLit{Value: "1"}, Y: &sql.ExprList{Exprs: []sql.Expr{&sql.NumberLit{Value: "2"}}}}, `1 NOT IN (2)`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.LIKE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 LIKE 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTLIKE, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 NOT LIKE 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.GLOB, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 GLOB 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTGLOB, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 NOT GLOB 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.MATCH, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 MATCH 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTMATCH, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 NOT MATCH 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.REGEXP, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 REGEXP 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.NOTREGEXP, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 NOT REGEXP 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.AND, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 AND 2`) AssertExprStringer(t, &sql.BinaryExpr{Op: sql.OR, X: &sql.NumberLit{Value: "1"}, Y: &sql.NumberLit{Value: "2"}}, `1 OR 2`) AssertNodeStringerPanic(t, &sql.BinaryExpr{}, `sql.BinaryExpr.String(): invalid op ILLEGAL`) } func TestCastExpr_String(t *testing.T) { AssertExprStringer(t, &sql.CastExpr{X: &sql.NumberLit{Value: "1"}, Type: &sql.Type{Name: &sql.Ident{Name: "INTEGER"}}}, `CAST(1 AS INTEGER)`) } func TestCaseExpr_String(t *testing.T) { AssertExprStringer(t, &sql.CaseExpr{ Operand: &sql.Ident{Name: "foo"}, Blocks: []*sql.CaseBlock{ {Condition: &sql.NumberLit{Value: "1"}, Body: &sql.BoolLit{Value: true}}, {Condition: &sql.NumberLit{Value: "2"}, Body: &sql.BoolLit{Value: false}}, }, ElseExpr: &sql.NullLit{}, }, `CASE "foo" WHEN 1 THEN TRUE WHEN 2 THEN FALSE ELSE NULL END`) AssertExprStringer(t, &sql.CaseExpr{ Blocks: []*sql.CaseBlock{ {Condition: &sql.NumberLit{Value: "1"}, Body: &sql.BoolLit{Value: true}}, }, }, `CASE WHEN 1 THEN TRUE END`) } func TestExprList_String(t *testing.T) { AssertExprStringer(t, &sql.ExprList{Exprs: []sql.Expr{&sql.NullLit{}}}, `(NULL)`) AssertExprStringer(t, &sql.ExprList{Exprs: []sql.Expr{&sql.NullLit{}, &sql.NullLit{}}}, `(NULL, NULL)`) } func TestQualifiedRef_String(t *testing.T) { AssertExprStringer(t, &sql.QualifiedRef{Table: &sql.Ident{Name: "tbl"}, Column: &sql.Ident{Name: "col"}}, `"tbl"."col"`) AssertExprStringer(t, &sql.QualifiedRef{Table: &sql.Ident{Name: "tbl"}, Star: pos(0)}, `"tbl".*`) } func TestCall_String(t *testing.T) { AssertExprStringer(t, &sql.Call{Name: &sql.Ident{Name: "foo"}}, `foo()`) AssertExprStringer(t, &sql.Call{Name: &sql.Ident{Name: "foo"}, Star: pos(0)}, `foo(*)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Distinct: pos(0), Args: []sql.Expr{ &sql.NullLit{}, &sql.NullLit{}, }, }, `foo(DISTINCT NULL, NULL)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Filter: &sql.FilterClause{ X: &sql.BoolLit{Value: true}, }, }, `foo() FILTER (WHERE TRUE)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Name: &sql.Ident{Name: "win"}, }, }, `foo() OVER "win"`) t.Run("FrameSpec", func(t *testing.T) { AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Definition: &sql.WindowDefinition{ Frame: &sql.FrameSpec{ Rows: pos(0), X: &sql.NullLit{}, PrecedingX: pos(0), ExcludeNoOthers: pos(0), }, }, }, }, `foo() OVER (ROWS NULL PRECEDING EXCLUDE NO OTHERS)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Definition: &sql.WindowDefinition{ Frame: &sql.FrameSpec{ Groups: pos(0), CurrentRowX: pos(0), ExcludeCurrentRow: pos(0), }, }, }, }, `foo() OVER (GROUPS CURRENT ROW EXCLUDE CURRENT ROW)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Definition: &sql.WindowDefinition{ Frame: &sql.FrameSpec{ Rows: pos(0), UnboundedX: pos(0), PrecedingX: pos(0), Between: pos(0), CurrentRowY: pos(0), }, }, }, }, `foo() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Definition: &sql.WindowDefinition{ Frame: &sql.FrameSpec{ Rows: pos(0), X: &sql.NullLit{}, PrecedingX: pos(0), Between: pos(0), CurrentRowY: pos(0), }, }, }, }, `foo() OVER (ROWS BETWEEN NULL PRECEDING AND CURRENT ROW)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Definition: &sql.WindowDefinition{ Frame: &sql.FrameSpec{ Range: pos(0), X: &sql.NullLit{}, FollowingX: pos(0), Between: pos(0), Y: &sql.BoolLit{Value: true}, PrecedingY: pos(0), ExcludeGroup: pos(0), }, }, }, }, `foo() OVER (RANGE BETWEEN NULL FOLLOWING AND TRUE PRECEDING EXCLUDE GROUP)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Definition: &sql.WindowDefinition{ Frame: &sql.FrameSpec{ Range: pos(0), CurrentRowX: pos(0), Between: pos(0), Y: &sql.BoolLit{Value: true}, FollowingY: pos(0), ExcludeTies: pos(0), }, }, }, }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND TRUE FOLLOWING EXCLUDE TIES)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Definition: &sql.WindowDefinition{ Frame: &sql.FrameSpec{ Range: pos(0), CurrentRowX: pos(0), Between: pos(0), CurrentRowY: pos(0), }, }, }, }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND CURRENT ROW)`) AssertExprStringer(t, &sql.Call{ Name: &sql.Ident{Name: "foo"}, Over: &sql.OverClause{ Definition: &sql.WindowDefinition{ Frame: &sql.FrameSpec{ Range: pos(0), CurrentRowX: pos(0), Between: pos(0), UnboundedY: pos(0), FollowingY: pos(0), }, }, }, }, `foo() OVER (RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)`) }) } func TestRaise_String(t *testing.T) { AssertExprStringer(t, &sql.Raise{Rollback: pos(0), Error: &sql.StringLit{Value: "err"}}, `RAISE(ROLLBACK, 'err')`) AssertExprStringer(t, &sql.Raise{Abort: pos(0), Error: &sql.StringLit{Value: "err"}}, `RAISE(ABORT, 'err')`) AssertExprStringer(t, &sql.Raise{Fail: pos(0), Error: &sql.StringLit{Value: "err"}}, `RAISE(FAIL, 'err')`) AssertExprStringer(t, &sql.Raise{Ignore: pos(0)}, `RAISE(IGNORE)`) } func TestExists_String(t *testing.T) { AssertExprStringer(t, &sql.Exists{ Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{ {Star: pos(0)}, }, }, }, `EXISTS (SELECT *)`) AssertExprStringer(t, &sql.Exists{ Not: pos(0), Exists: pos(0), Select: &sql.SelectStatement{ Columns: []*sql.ResultColumn{ {Star: pos(0)}, }, }, }, `NOT EXISTS (SELECT *)`) } func AssertExprStringer(tb testing.TB, expr sql.Expr, s string) { tb.Helper() if str := expr.String(); str != s { tb.Fatalf("String()=%s, expected %s", str, s) } else if _, err := sql.NewParser(strings.NewReader(str)).ParseExpr(); err != nil { tb.Fatalf("cannot parse string: %s; err=%s", str, err) } } func AssertStatementStringer(tb testing.TB, stmt sql.Statement, s string) { tb.Helper() if str := stmt.String(); str != s { tb.Fatalf("String()=%s, expected %s", str, s) } else if _, err := sql.NewParser(strings.NewReader(str)).ParseStatement(); err != nil { tb.Fatalf("cannot parse string: %s; err=%s", str, err) } } func AssertNodeStringerPanic(tb testing.TB, node sql.Node, msg string) { tb.Helper() var r interface{} func() { defer func() { r = recover() }() _ = node.String() }() if r == nil { tb.Fatal("expected node stringer to panic") } else if r != msg { tb.Fatalf("recover()=%s, want %s", r, msg) } } // StripPos removes the position data from a node and its children. // This function returns the root argument passed in. func StripPos(root sql.Node) sql.Node { zero := reflect.ValueOf(sql.Pos{}) _, _ = sql.Walk(sql.VisitFunc(func(node sql.Node) (sql.Node, error) { value := reflect.Indirect(reflect.ValueOf(node)) for i := 0; i < value.NumField(); i++ { if field := value.Field(i); field.Type() == zero.Type() { field.Set(zero) } } return node, nil }), root) return root } func StripExprPos(root sql.Expr) sql.Expr { StripPos(root) return root }