| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- package executor
- import (
- "errors"
- "strings"
- "testing"
- "github.com/danfragoso/pizzasql-next/pkg/lexer"
- "github.com/danfragoso/pizzasql-next/pkg/parser"
- "github.com/danfragoso/pizzasql-next/pkg/pgerror"
- "github.com/danfragoso/pizzasql-next/pkg/storage"
- )
- // requireCheckViolation asserts that err is a CHECK violation for constraint.
- func requireCheckViolation(t *testing.T, err error, constraint string) {
- t.Helper()
- var violation *storage.CheckViolationError
- if !errors.As(err, &violation) {
- t.Fatalf("error = %v, want CheckViolationError", err)
- }
- if constraint != "" && violation.Constraint != constraint {
- t.Fatalf("constraint = %q, want %q", violation.Constraint, constraint)
- }
- if !strings.HasPrefix(err.Error(), "CHECK constraint failed: ") {
- t.Fatalf("message = %q, want the CHECK constraint failure prefix", err.Error())
- }
- }
- // Inline, table-level, named, and unnamed CHECK declarations all persist and
- // enforce the same predicate.
- func TestCheckConstraintDeclarationForms(t *testing.T) {
- tests := []struct {
- name string
- ddl string
- constraint string // expected label on violation
- }{
- {
- name: "inline unnamed",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0))",
- },
- {
- name: "table level unnamed",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER, CHECK (age > 0))",
- },
- {
- name: "column level named",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CONSTRAINT positive_age CHECK (age > 0))",
- constraint: "positive_age",
- },
- {
- name: "table level named",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER, CONSTRAINT positive_age CHECK (age > 0))",
- constraint: "positive_age",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, tt.ddl)
- stored, err := schema.GetSchema("t")
- if err != nil {
- t.Fatalf("get schema: %v", err)
- }
- if len(stored.Checks) != 1 {
- t.Fatalf("checks = %+v, want exactly one persisted CHECK", stored.Checks)
- }
- if tt.constraint != "" && stored.Checks[0].Name != tt.constraint {
- t.Fatalf("check name = %q, want %q", stored.Checks[0].Name, tt.constraint)
- }
- if _, err := execSQL(e, "INSERT INTO t (id, age) VALUES (1, -1)"); err == nil {
- t.Fatal("insert of a violating row succeeded")
- } else {
- requireCheckViolation(t, err, tt.constraint)
- }
- execMust(t, e, "INSERT INTO t (id, age) VALUES (2, 1)")
- execMust(t, e, "INSERT INTO t (id, age) VALUES (3, NULL)")
- })
- }
- }
- // A CHECK predicate that is NULL or unknown for a row passes, matching SQL
- // three-valued logic, on INSERT and on UPDATE.
- func TestCheckConstraintNullAndUnknownPass(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER, CHECK (age > 0 AND age < 200))")
- execMust(t, e, "INSERT INTO t (id, age) VALUES (1, NULL)")
- execMust(t, e, "INSERT INTO t (id) VALUES (2)")
- execMust(t, e, "INSERT INTO t (id, age) VALUES (3, 30)")
- execMust(t, e, "UPDATE t SET age = NULL WHERE id = 3")
- res := execMust(t, e, "SELECT id, age FROM t ORDER BY id")
- if res.RowCount != 3 {
- t.Fatalf("rows = %d, want 3 nullable rows", res.RowCount)
- }
- for _, row := range res.Rows {
- if row[1] != nil {
- t.Fatalf("age = %v, want NULL", row[1])
- }
- }
- }
- // Multi-row INSERT is atomic: a violating row rejects the whole statement and
- // leaves no rows behind.
- func TestCheckConstraintMultiRowInsertIsAtomic(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0))")
- _, err := execSQL(e, "INSERT INTO t (id, age) VALUES (1, 10), (2, -5)")
- requireCheckViolation(t, err, "")
- if res := execMust(t, e, "SELECT id FROM t"); res.RowCount != 0 {
- t.Fatalf("rows after rejected multi-row insert = %d, want 0", res.RowCount)
- }
- execMust(t, e, "INSERT INTO t (id, age) VALUES (1, 10), (2, 20)")
- if res := execMust(t, e, "SELECT id FROM t ORDER BY id"); res.RowCount != 2 {
- t.Fatalf("rows = %d, want 2", res.RowCount)
- }
- }
- // UPDATE re-evaluates every CHECK predicate against the post-assignment row and
- // rolls the row back when a predicate fails.
- func TestCheckConstraintUpdateViolation(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0))")
- execMust(t, e, "INSERT INTO t (id, age) VALUES (1, 10)")
- _, err := execSQL(e, "UPDATE t SET age = -1 WHERE id = 1")
- requireCheckViolation(t, err, "")
- res := execMust(t, e, "SELECT age FROM t WHERE id = 1")
- if res.RowCount != 1 || res.Rows[0][0] != int64(10) {
- t.Fatalf("age after rejected update = %v, want 10", res.Rows)
- }
- execMust(t, e, "UPDATE t SET age = age + 5 WHERE id = 1")
- res = execMust(t, e, "SELECT age FROM t WHERE id = 1")
- if res.Rows[0][0] != int64(15) {
- t.Fatalf("age after valid update = %v, want 15", res.Rows[0][0])
- }
- }
- // ON CONFLICT DO UPDATE validates the merged row against the CHECK predicates
- // before it is staged.
- func TestCheckConstraintOnConflictDoUpdateViolation(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, qty INTEGER CHECK (qty > 0))")
- execMust(t, e, "INSERT INTO t (id, qty) VALUES (1, 5)")
- _, err := execSQL(e, "INSERT INTO t (id, qty) VALUES (1, -2) ON CONFLICT (id) DO UPDATE SET qty = excluded.qty")
- requireCheckViolation(t, err, "")
- res := execMust(t, e, "SELECT qty FROM t WHERE id = 1")
- if res.Rows[0][0] != int64(5) {
- t.Fatalf("qty after rejected upsert = %v, want 5", res.Rows[0][0])
- }
- execMust(t, e, "INSERT INTO t (id, qty) VALUES (1, 7) ON CONFLICT (id) DO UPDATE SET qty = excluded.qty")
- res = execMust(t, e, "SELECT qty FROM t WHERE id = 1")
- if res.Rows[0][0] != int64(7) {
- t.Fatalf("qty after valid upsert = %v, want 7", res.Rows[0][0])
- }
- }
- // Every CHECK on a table is enforced, and the violation names the constraint
- // that rejected the row.
- func TestCheckConstraintMultipleChecks(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, `CREATE TABLE t (
- id INTEGER PRIMARY KEY,
- age INTEGER CHECK (age > 0),
- score INTEGER,
- CONSTRAINT score_range CHECK (score >= 0 AND score <= 100)
- )`)
- stored, err := schema.GetSchema("t")
- if err != nil {
- t.Fatalf("get schema: %v", err)
- }
- if len(stored.Checks) != 2 {
- t.Fatalf("checks = %+v, want 2", stored.Checks)
- }
- _, err = execSQL(e, "INSERT INTO t (id, age, score) VALUES (1, -1, 50)")
- requireCheckViolation(t, err, "")
- _, err = execSQL(e, "INSERT INTO t (id, age, score) VALUES (2, 10, 101)")
- requireCheckViolation(t, err, "score_range")
- execMust(t, e, "INSERT INTO t (id, age, score) VALUES (3, 10, 100)")
- if res := execMust(t, e, "SELECT id FROM t"); res.RowCount != 1 {
- t.Fatalf("rows = %d, want 1", res.RowCount)
- }
- }
- // ADD COLUMN ... CHECK validates the rows that already exist, using the value
- // the constant-default backfill will write (or NULL when there is no default).
- func TestAlterTableAddColumnCheckValidatesExistingRows(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
- execMust(t, e, "INSERT INTO t (id) VALUES (1), (2)")
- // A default satisfying the predicate is backfilled and the DDL succeeds.
- execMust(t, e, "ALTER TABLE t ADD COLUMN qty INTEGER DEFAULT 5 CHECK (qty >= 0)")
- stored, err := schema.GetSchema("t")
- if err != nil {
- t.Fatalf("get schema: %v", err)
- }
- if _, ok := stored.GetColumn("qty"); !ok {
- t.Fatal("ADD COLUMN ... CHECK published no column")
- }
- if len(stored.Checks) != 1 {
- t.Fatalf("checks = %+v, want 1", stored.Checks)
- }
- res := execMust(t, e, "SELECT qty FROM t ORDER BY id")
- if len(res.Rows) != 2 || res.Rows[0][0] != int64(5) || res.Rows[1][0] != int64(5) {
- t.Fatalf("backfilled qty = %v, want [5 5]", res.Rows)
- }
- // A violation is enforced for later writes as well.
- if _, err := execSQL(e, "INSERT INTO t (id, qty) VALUES (3, -1)"); err == nil {
- t.Fatal("insert violating the ADD COLUMN check succeeded")
- } else {
- requireCheckViolation(t, err, "")
- }
- // A default that violates the predicate rejects the DDL and publishes
- // nothing: no column, no constraint, and no rewritten rows.
- _, err = execSQL(e, "ALTER TABLE t ADD COLUMN bad INTEGER DEFAULT -1 CHECK (bad >= 0)")
- requireCheckViolation(t, err, "")
- stored, err = schema.GetSchema("t")
- if err != nil {
- t.Fatalf("get schema after rejected DDL: %v", err)
- }
- if _, ok := stored.GetColumn("bad"); ok {
- t.Fatal("rejected ADD COLUMN ... CHECK left the column in the schema")
- }
- if len(stored.Checks) != 1 {
- t.Fatalf("checks after rejected DDL = %+v, want the original one", stored.Checks)
- }
- // Without a default the existing rows read NULL, which passes.
- execMust(t, e, "ALTER TABLE t ADD COLUMN note TEXT CHECK (LENGTH(note) > 0)")
- if res := execMust(t, e, "SELECT id, note FROM t ORDER BY id"); res.RowCount != 2 || res.Rows[0][1] != nil {
- t.Fatalf("rows after nullable ADD COLUMN = %v, want NULL notes", res.Rows)
- }
- }
- // A transaction that stages a row before a concurrent ADD COLUMN ... CHECK is
- // validated at commit, so the backfilled default cannot bypass the predicate.
- func TestAlterTableAddColumnCheckRejectsRowStagedBeforeDDL(t *testing.T) {
- _, schema, table := newTestDB(t)
- writer := newExec(schema, table)
- ddl := newExec(schema, table)
- execMust(t, writer, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
- execMust(t, writer, "BEGIN")
- execMust(t, writer, "INSERT INTO t (id) VALUES (1)")
- // The table is durably empty, so the ALTER accepts the constraint.
- execMust(t, ddl, "ALTER TABLE t ADD COLUMN qty INTEGER DEFAULT -1 CHECK (qty >= 0)")
- _, err := execSQL(writer, "COMMIT")
- requireCheckViolation(t, err, "")
- if got := execMust(t, ddl, "SELECT id FROM t").RowCount; got != 0 {
- t.Fatalf("rows after rejected commit = %d, want 0", got)
- }
- }
- // The persisted predicate is durable: a fresh SchemaManager loaded from the same
- // store keeps enforcing it.
- func TestCheckConstraintDurableReload(t *testing.T) {
- pool, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER, CONSTRAINT positive_age CHECK (age > 0))")
- execMust(t, e, "INSERT INTO t (id, age) VALUES (1, 10)")
- reloaded := storage.NewSchemaManager(pool, "testdb")
- reloadedTable := storage.NewTableManager(pool, reloaded, "testdb")
- reopened := newExec(reloaded, reloadedTable)
- stored, err := reloaded.GetSchema("t")
- if err != nil {
- t.Fatalf("reload schema: %v", err)
- }
- if len(stored.Checks) != 1 || stored.Checks[0].Name != "positive_age" || !strings.Contains(stored.Checks[0].Expression, "age") {
- t.Fatalf("reloaded checks = %+v, want the persisted positive_age predicate", stored.Checks)
- }
- _, err = execSQL(reopened, "INSERT INTO t (id, age) VALUES (2, -3)")
- requireCheckViolation(t, err, "positive_age")
- execMust(t, reopened, "INSERT INTO t (id, age) VALUES (3, 30)")
- execMust(t, reopened, "UPDATE t SET age = 40 WHERE id = 3")
- if _, err := execSQL(reopened, "UPDATE t SET age = -40 WHERE id = 3"); err == nil {
- t.Fatal("update violating the reloaded check succeeded")
- }
- }
- // The emulated SQLite catalog exposes the constraint in the table SQL.
- func TestCheckConstraintInSQLiteCatalog(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER, CONSTRAINT positive_age CHECK (age > 0), CHECK (id > 0))")
- res := execMust(t, e, "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 't'")
- if res.RowCount != 1 {
- t.Fatalf("sqlite_master rows = %d, want 1", res.RowCount)
- }
- sql, _ := res.Rows[0][0].(string)
- for _, want := range []string{"CONSTRAINT `positive_age` CHECK", "(id > 0)"} {
- if !strings.Contains(sql, want) {
- t.Fatalf("catalog SQL %q does not contain %q", sql, want)
- }
- }
- res = execMust(t, e, "SELECT sql FROM sqlite_schema WHERE name = 't'")
- if res.RowCount != 1 || !strings.Contains(res.Rows[0][0].(string), "positive_age") {
- t.Fatalf("sqlite_schema SQL = %v, want the named CHECK", res.Rows)
- }
- }
- // Non-deterministic and non-row predicates are rejected with feature-not-
- // supported instead of being accepted as constraints the engine cannot enforce.
- func TestCheckConstraintRejectsUnsupportedExpressions(t *testing.T) {
- tests := []struct {
- name string
- setup []string
- ddl string
- }{
- {
- name: "subquery",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > (SELECT 1)))",
- },
- {
- name: "aggregate",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (COUNT(age) > 0))",
- },
- {
- name: "window function",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (ROW_NUMBER() OVER (ORDER BY age) > 0))",
- },
- {
- name: "non-deterministic function",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (RANDOM() > 0))",
- },
- {
- name: "qualified column",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (t.age > 0))",
- },
- {
- name: "qualified column in parentheses",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK ((t.age) > 0))",
- },
- {
- name: "qualified column in cast",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (CAST(t.age AS INTEGER) > 0))",
- },
- {
- name: "unknown column",
- ddl: "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (missing > 0))",
- },
- {
- name: "alter add column subquery",
- setup: []string{"CREATE TABLE t (id INTEGER PRIMARY KEY)"},
- ddl: "ALTER TABLE t ADD COLUMN age INTEGER CHECK (age > (SELECT 1))",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- for _, sql := range tt.setup {
- execMust(t, e, sql)
- }
- if _, err := execSQL(e, tt.ddl); !errors.Is(err, pgerror.ErrFeatureNotSupported) {
- t.Fatalf("%s: error = %v, want ErrFeatureNotSupported", tt.ddl, err)
- }
- // A rejected declaration publishes nothing: no table when the DDL
- // was CREATE TABLE, and no added column or check for ALTER TABLE.
- stored, err := schema.GetSchema("t")
- if err != nil {
- if len(tt.setup) == 0 {
- return
- }
- t.Fatalf("get schema: %v", err)
- }
- if len(stored.Checks) != 0 {
- t.Fatalf("checks after rejected DDL = %+v, want none", stored.Checks)
- }
- if _, ok := stored.GetColumn("age"); ok {
- t.Fatalf("column age after rejected DDL = ok, want absent")
- }
- })
- }
- }
- // A CHECK predicate is stored as SQL text, so DROP/RENAME COLUMN refuse to leave
- // it referencing a column that no longer exists.
- func TestCheckConstraintGuardsColumnChange(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0), note TEXT)")
- for _, sql := range []string{
- "ALTER TABLE t RENAME COLUMN age TO years",
- "ALTER TABLE t DROP COLUMN age",
- } {
- if _, err := execSQL(e, sql); !errors.Is(err, pgerror.ErrFeatureNotSupported) {
- t.Fatalf("%s: error = %v, want ErrFeatureNotSupported", sql, err)
- }
- }
- // Columns the predicate does not reference can still be renamed or dropped.
- execMust(t, e, "ALTER TABLE t RENAME COLUMN note TO memo")
- execMust(t, e, "INSERT INTO t (id, age, memo) VALUES (1, 5, 'ok')")
- execMust(t, e, "ALTER TABLE t DROP COLUMN memo")
- }
- // The accepted expression surface survives the round trip through the durable
- // schema text: each predicate is re-parsed and still enforced after a reload.
- func TestCheckConstraintExpressionRoundTrip(t *testing.T) {
- tests := []struct {
- name string
- check string
- columns string
- valid string // VALUES list for a row that satisfies the predicate
- violated string // VALUES list for a row that violates it
- }{
- {"comparison and boolean", "age > 0 AND age < 200", "id, age", "(1, 10)", "(1, 500)"},
- {"LIKE", "name LIKE 'a%'", "id, name", "(1, 'abc')", "(1, 'zzz')"},
- {"CAST", "CAST(score AS INTEGER) >= 0", "id, score", "(1, 5)", "(1, -5)"},
- {"BETWEEN", "LENGTH(code) BETWEEN 1 AND 8", "id, code", "(1, 'abcd')", "(1, 'toolongcode')"},
- {"function", "LOWER(status) = 'ready'", "id, status", "(1, 'READY')", "(1, 'waiting')"},
- {"is null", "note IS NOT NULL", "id, note", "(1, 'x')", "(1, NULL)"},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- pool, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, `CREATE TABLE t (
- id INTEGER PRIMARY KEY,
- age INTEGER,
- name TEXT,
- score INTEGER,
- code TEXT,
- status TEXT,
- note TEXT,
- CHECK (`+tt.check+`)
- )`)
- execMust(t, e, "INSERT INTO t ("+tt.columns+") VALUES "+tt.valid)
- reloaded := storage.NewSchemaManager(pool, "testdb")
- reopened := newExec(reloaded, storage.NewTableManager(pool, reloaded, "testdb"))
- if _, err := execSQL(reopened, "INSERT INTO t ("+tt.columns+") VALUES "+tt.violated); err == nil {
- t.Fatalf("reloaded table accepted a row violating %s", tt.check)
- } else {
- requireCheckViolation(t, err, "")
- }
- })
- }
- }
- // The persisted predicate text must round-trip. A quoted keyword column is the
- // motivating case: FormatExpr quotes it, so CREATE TABLE, later writes, and a
- // durable reload all keep binding to the column instead of the keyword.
- func TestCheckConstraintQuotedKeywordColumn(t *testing.T) {
- pool, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, `CREATE TABLE t (id INTEGER PRIMARY KEY, "order" INTEGER CHECK ("order" > 0))`)
- stored, err := schema.GetSchema("t")
- if err != nil {
- t.Fatalf("get schema: %v", err)
- }
- if len(stored.Checks) != 1 {
- t.Fatalf("checks = %+v, want 1", stored.Checks)
- }
- if !strings.Contains(stored.Checks[0].Expression, `"order"`) {
- t.Fatalf("stored expression = %q, want the keyword column quoted", stored.Checks[0].Expression)
- }
- _, err = execSQL(e, `INSERT INTO t (id, "order") VALUES (1, -1)`)
- requireCheckViolation(t, err, "")
- execMust(t, e, `INSERT INTO t (id, "order") VALUES (1, 3)`)
- // The durable text stays evaluable after reload.
- reloaded := storage.NewSchemaManager(pool, "testdb")
- reopened := newExec(reloaded, storage.NewTableManager(pool, reloaded, "testdb"))
- _, err = execSQL(reopened, `INSERT INTO t (id, "order") VALUES (2, -2)`)
- requireCheckViolation(t, err, "")
- execMust(t, reopened, `INSERT INTO t (id, "order") VALUES (2, 4)`)
- }
- // A predicate whose formatted text cannot be re-parsed is rejected at DDL time
- // instead of being published as a constraint that fails every later write.
- func TestCheckConstraintRejectsNonRoundTrippingExpression(t *testing.T) {
- schema := &storage.Schema{Name: "t", Columns: []storage.Column{{Name: "id", Type: "INTEGER"}}}
- // FormatExpr renders a number literal verbatim; this one is not valid SQL.
- _, err := checkConstraintFor("", &parser.LiteralExpr{Type: lexer.TokenNumber, Value: "1 +"}, schema)
- if !errors.Is(err, pgerror.ErrFeatureNotSupported) {
- t.Fatalf("error = %v, want ErrFeatureNotSupported", err)
- }
- if !strings.Contains(err.Error(), "cannot be stored") {
- t.Fatalf("error = %q, want the round-trip explanation", err.Error())
- }
- }
|