From aaa90ad4ab91841d011845195575adc5794c65ed Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 13 Sep 2026 16:01:15 +0200 Subject: [PATCH] test(sql): cover renameColumnUpdatesAUniqueConstraint --- .../kestrel/store/sql/DiskExecutorTest.kt | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt index 8c24af0..7feeca4 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt @@ -3646,3 +3646,162 @@ class DiskExecutorTest { assertTrue(db.select("SELECT * FROM employees").rows.isEmpty()) } + @Test + fun renameColumnUpdatesAUniqueConstraint() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT UNIQUE)") + db.run("INSERT INTO t VALUES (1, 'a@x.com')") + db.run("ALTER TABLE t RENAME COLUMN email TO contact_email") + assertFailsWith { db.run("INSERT INTO t VALUES (2, 'a@x.com')") } + } + + @Test + fun renameColumnUpdatesACheckConstraintAndStillEnforcesIt() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0))") + db.run("ALTER TABLE t RENAME COLUMN age TO years") + assertFailsWith { db.run("INSERT INTO t VALUES (1, -5)") } + db.run("INSERT INTO t VALUES (1, 30)") + assertEquals(listOf(listOf(int(30))), db.select("SELECT years FROM t").rows) + } + + @Test + fun renameColumnUpdatesASecondaryIndexAndPushdownStillWorks() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER)") + db.run("INSERT INTO t VALUES (1, 30), (2, 40)") + db.run("CREATE INDEX idx_age ON t (age)") + db.run("ALTER TABLE t RENAME COLUMN age TO years") + + val table = db.catalog.table("t") + val result = db.select("SELECT id FROM t WHERE years = 30") + assertEquals(listOf(listOf(int(1))), result.rows) + assertEquals(1, table.secondaryLookupCount) + assertEquals(listOf("years"), table.secondaryIndexes.single().columnNames) + } + + @Test + fun renameColumnDefinitionSurvivesClosingAndReopeningTheCatalog() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, bio TEXT)") + db.run("INSERT INTO t VALUES (1, 'mathematician')") + db.run("ALTER TABLE t RENAME COLUMN bio TO biography") + db.reopen() + assertEquals(listOf("id", "biography"), db.select("SELECT * FROM t").columns) + assertEquals(listOf(listOf(int(1), text("mathematician"))), db.select("SELECT * FROM t").rows) + } + + // ---- ALTER TABLE RENAME TO ---- + + @Test + fun renameTableRenamesAndPreservesData() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)") + db.run("INSERT INTO t VALUES (1, 'ada')") + val result = db.run("ALTER TABLE t RENAME TO people").single() + assertEquals(AlterTableResult("people"), result) + assertEquals(listOf(listOf(int(1), text("ada"))), db.select("SELECT * FROM people").rows) + assertFailsWith { db.run("SELECT * FROM t") } + assertEquals(listOf("people"), db.catalog.tableNames) + } + + @Test + fun renameTableRejectsWhenNewNameAlreadyExistsAsTable() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + db.run("CREATE TABLE other (id INTEGER PRIMARY KEY)") + val error = assertFailsWith { db.run("ALTER TABLE t RENAME TO other") } + assertTrue(error.message!!.contains("already exists")) + } + + @Test + fun renameTableRejectsWhenNewNameAlreadyExistsAsView() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + db.run("CREATE TABLE other (id INTEGER PRIMARY KEY)") + db.run("CREATE VIEW v AS SELECT * FROM other") + val error = assertFailsWith { db.run("ALTER TABLE t RENAME TO v") } + assertTrue(error.message!!.contains("already exists")) + } + + @Test + fun renameTableRejectsRenamingAView() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + db.run("CREATE VIEW v AS SELECT * FROM t") + val error = assertFailsWith { db.run("ALTER TABLE v RENAME TO w") } + assertTrue(error.message!!.contains("cannot modify view")) + } + + @Test + fun renameTablePropagatesToOtherTablesForeignKeyReference() { + val db = newDatabase() + db.run("CREATE TABLE departments (id INTEGER PRIMARY KEY)") + db.run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id) ON DELETE CASCADE)") + db.run("INSERT INTO departments VALUES (1)") + db.run("INSERT INTO employees VALUES (1, 1)") + db.run("ALTER TABLE departments RENAME TO divisions") + db.run("INSERT INTO divisions VALUES (2)") + db.run("INSERT INTO employees VALUES (2, 2)") + assertFailsWith { db.run("INSERT INTO employees VALUES (3, 99)") } + db.run("DELETE FROM divisions WHERE id = 1") + assertEquals(listOf(listOf(int(2))), db.select("SELECT id FROM employees").rows) + } + + @Test + fun renameTableUpdatesItsOwnSelfReferencingForeignKey() { + val db = newDatabase() + db.run("CREATE TABLE staff (id INTEGER PRIMARY KEY, manager_id INTEGER REFERENCES staff(id))") + db.run("INSERT INTO staff VALUES (1, NULL)") + db.run("ALTER TABLE staff RENAME TO personnel") + db.run("INSERT INTO personnel VALUES (2, 1)") + assertFailsWith { db.run("INSERT INTO personnel VALUES (3, 99)") } + assertEquals(2, db.select("SELECT * FROM personnel").rows.size) + } + + @Test + fun renameTableUpdatesASecondaryIndexAndPushdownStillWorks() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER)") + db.run("INSERT INTO t VALUES (1, 30), (2, 40)") + db.run("CREATE INDEX idx_age ON t (age)") + db.run("ALTER TABLE t RENAME TO people") + + val table = db.catalog.table("people") + val result = db.select("SELECT id FROM people WHERE age = 30") + assertEquals(listOf(listOf(int(1))), result.rows) + assertEquals(1, table.secondaryLookupCount) + } + + @Test + fun renameTablePropagatesIntoASimpleViewDefinition() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)") + db.run("INSERT INTO t VALUES (1, 'ada')") + db.run("CREATE VIEW v AS SELECT * FROM t") + db.run("ALTER TABLE t RENAME TO people") + assertEquals(listOf(listOf(int(1), text("ada"))), db.select("SELECT * FROM v").rows) + } + + @Test + fun renameTableRenamingToItsOwnNameIsANoOp() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + db.run("INSERT INTO t VALUES (1)") + db.run("ALTER TABLE t RENAME TO t") + assertEquals(listOf(listOf(int(1))), db.select("SELECT * FROM t").rows) + } + + @Test + fun renameTableDefinitionAndViewSurviveClosingAndReopeningTheCatalog() { + val db = newDatabase() + db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)") + db.run("INSERT INTO t VALUES (1, 'ada')") + db.run("CREATE VIEW v AS SELECT * FROM t") + db.run("ALTER TABLE t RENAME TO people") + db.reopen() + assertEquals(listOf("people"), db.catalog.tableNames) + assertEquals(listOf(listOf(int(1), text("ada"))), db.select("SELECT * FROM v").rows) + assertFailsWith { db.run("SELECT * FROM t") } + } +