From b832700fb4929c45733cfca76dd1fcfba48c324c Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 17:13:46 +0200 Subject: [PATCH] test: cover createTableAcceptsANamedTableLevelCheckConstraint --- src/test/kotlin/kestrel/AlterTableTest.kt | 157 ++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/src/test/kotlin/kestrel/AlterTableTest.kt b/src/test/kotlin/kestrel/AlterTableTest.kt index 6a7c2eb..2009a3c 100644 --- a/src/test/kotlin/kestrel/AlterTableTest.kt +++ b/src/test/kotlin/kestrel/AlterTableTest.kt @@ -790,3 +790,160 @@ class AlterTableTest { assertTrue(error.message!!.contains("c1"), error.message!!) } + @Test + fun createTableAcceptsANamedTableLevelCheckConstraint() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER, CONSTRAINT chk_age CHECK (age > 0))") + val error = assertFailsWith { run("INSERT INTO t VALUES (1, -1)") } + assertTrue(error.message!!.contains("chk_age"), error.message!!) + } + + @Test + fun createTableAcceptsANamedTableLevelUniqueConstraint() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT, CONSTRAINT uq_email UNIQUE (email))") + run("INSERT INTO t VALUES (1, 'a@x.com')") + val error = assertFailsWith { run("INSERT INTO t VALUES (2, 'a@x.com')") } + assertTrue(error.message!!.contains("uq_email"), error.message!!) + } + + @Test + fun createTableAcceptsANamedTableLevelForeignKeyConstraint() { + run("CREATE TABLE departments (id INTEGER PRIMARY KEY)") + run( + "CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER, " + + "CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES departments (id))" + ) + run("INSERT INTO departments VALUES (1)") + val error = assertFailsWith { run("INSERT INTO employees VALUES (1, 99)") } + assertTrue(error.message!!.contains("FOREIGN KEY constraint failed"), error.message!!) + } + + @Test + fun dropConstraintRemovesANamedCheckConstraint() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER, CONSTRAINT chk_age CHECK (age > 0))") + exec("ALTER TABLE t DROP CONSTRAINT chk_age") + run("INSERT INTO t VALUES (1, -5)") + assertEquals(listOf(listOf(int(1))), select("SELECT id FROM t").rows) + } + + @Test + fun dropConstraintRemovesANamedUniqueConstraint() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT, CONSTRAINT uq_email UNIQUE (email))") + run("INSERT INTO t VALUES (1, 'a@x.com')") + exec("ALTER TABLE t DROP CONSTRAINT uq_email") + run("INSERT INTO t VALUES (2, 'a@x.com')") + assertEquals(2, select("SELECT id FROM t").rows.size) + } + + @Test + fun dropConstraintRemovesANamedForeignKeyConstraint() { + run("CREATE TABLE departments (id INTEGER PRIMARY KEY)") + run( + "CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER, " + + "CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES departments (id))" + ) + exec("ALTER TABLE employees DROP CONSTRAINT fk_dept") + run("INSERT INTO employees VALUES (1, 99)") + assertEquals(listOf(listOf(int(1))), select("SELECT id FROM employees").rows) + } + + @Test + fun dropConstraintFailsForAnUnknownName() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + val error = assertFailsWith { run("ALTER TABLE t DROP CONSTRAINT nope") } + assertTrue(error.message!!.contains("no such constraint"), error.message!!) + } + + @Test + fun dropConstraintRestrictsWhenAnotherTablesForeignKeyDependsOnTheUniqueConstraint() { + run("CREATE TABLE departments (id INTEGER PRIMARY KEY, code TEXT, CONSTRAINT uq_code UNIQUE (code))") + run( + "CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_code TEXT, " + + "CONSTRAINT fk_dept_code FOREIGN KEY (dept_code) REFERENCES departments (code))" + ) + val error = assertFailsWith { run("ALTER TABLE departments DROP CONSTRAINT uq_code") } + assertTrue(error.message!!.contains("employees"), error.message!!) + } + + @Test + fun dropConstraintCascadeRemovesTheDependentForeignKeyOnAnotherTable() { + run("CREATE TABLE departments (id INTEGER PRIMARY KEY, code TEXT, CONSTRAINT uq_code UNIQUE (code))") + run( + "CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_code TEXT, " + + "CONSTRAINT fk_dept_code FOREIGN KEY (dept_code) REFERENCES departments (code))" + ) + exec("ALTER TABLE departments DROP CONSTRAINT uq_code CASCADE") + run("INSERT INTO employees VALUES (1, 'nonexistent')") + assertEquals(listOf(listOf(int(1))), select("SELECT id FROM employees").rows) + } + + @Test + fun dropConstraintRestrictsASelfReferencingForeignKeyDependentOnItsOwnUniqueConstraint() { + run( + "CREATE TABLE t (id INTEGER PRIMARY KEY, code TEXT, parent_code TEXT, " + + "CONSTRAINT uq_code UNIQUE (code), CONSTRAINT fk_parent FOREIGN KEY (parent_code) REFERENCES t (code))" + ) + val error = assertFailsWith { run("ALTER TABLE t DROP CONSTRAINT uq_code") } + assertTrue(error.message!!.contains("FOREIGN KEY"), error.message!!) + } + + @Test + fun dropConstraintCascadeRemovesASelfReferencingForeignKeyThatDependedOnTheDroppedUniqueConstraint() { + run( + "CREATE TABLE t (id INTEGER PRIMARY KEY, code TEXT, parent_code TEXT, " + + "CONSTRAINT uq_code UNIQUE (code), CONSTRAINT fk_parent FOREIGN KEY (parent_code) REFERENCES t (code))" + ) + exec("ALTER TABLE t DROP CONSTRAINT uq_code CASCADE") + run("INSERT INTO t VALUES (1, 'a', 'nonexistent')") + assertEquals(listOf(listOf(int(1))), select("SELECT id FROM t").rows) + } + + @Test + fun dropConstraintAllowsDroppingAUniqueConstraintWhenAnotherRemainingGroupStillCoversTheSameColumns() { + run( + "CREATE TABLE departments (id INTEGER PRIMARY KEY, code TEXT, " + + "CONSTRAINT uq_code_a UNIQUE (code), CONSTRAINT uq_code_b UNIQUE (code))" + ) + run( + "CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_code TEXT, " + + "CONSTRAINT fk_dept_code FOREIGN KEY (dept_code) REFERENCES departments (code))" + ) + exec("ALTER TABLE departments DROP CONSTRAINT uq_code_a") + val error = assertFailsWith { run("INSERT INTO employees VALUES (1, 'nonexistent')") } + assertTrue(error.message!!.contains("FOREIGN KEY constraint failed"), error.message!!) + } + + // ---- ALTER COLUMN TYPE ---- + + @Test + fun changeColumnTypeWideningIntegerToRealCoercesExistingRowsWithNoExplicitUsing() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, score INTEGER)") + run("INSERT INTO t VALUES (1, 10), (2, 20)") + val result = exec("ALTER TABLE t ALTER COLUMN score TYPE REAL") + assertEquals(AlterTableResult("t"), result) + assertEquals(listOf(listOf(real(10.0)), listOf(real(20.0))), select("SELECT score FROM t ORDER BY id ASC").rows) + } + + @Test + fun changeColumnTypeWideningIntegerToDecimalCoercesExistingRows() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, amount INTEGER)") + run("INSERT INTO t VALUES (1, 42)") + exec("ALTER TABLE t ALTER COLUMN amount TYPE DECIMAL(10, 2)") + assertEquals(listOf(listOf(decimal("42.00"))), select("SELECT amount FROM t").rows) + } + + @Test + fun changeColumnTypeWithUsingConvertsTextToIntegerViaCast() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, code TEXT)") + run("INSERT INTO t VALUES (1, '100'), (2, '200')") + exec("ALTER TABLE t ALTER COLUMN code TYPE INTEGER USING CAST(code AS INTEGER)") + assertEquals(listOf(listOf(int(100)), listOf(int(200))), select("SELECT code FROM t ORDER BY id ASC").rows) + } + + @Test + fun changeColumnTypeWithUsingCanReferenceTheRowsOtherColumns() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, price INTEGER, quantity INTEGER)") + run("INSERT INTO t VALUES (1, 10, 3)") + exec("ALTER TABLE t ALTER COLUMN price TYPE INTEGER USING price * quantity") + assertEquals(listOf(listOf(int(30))), select("SELECT price FROM t").rows) + } +