From f8d57f56219a5722e3abed68c90ef5110329dd71 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 16:20:30 +0200 Subject: [PATCH] test: cover renameColumnUpdatesAnIndexAndThePlannerStillUsesIt --- 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 eb58fc9..d5b4142 100644 --- a/src/test/kotlin/kestrel/AlterTableTest.kt +++ b/src/test/kotlin/kestrel/AlterTableTest.kt @@ -313,3 +313,160 @@ class AlterTableTest { assertEquals(listOf(listOf(int(30))), select("SELECT years FROM t").rows) } + @Test + fun renameColumnUpdatesAnIndexAndThePlannerStillUsesIt() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER)") + run("INSERT INTO t VALUES (1, 30), (2, 40)") + run("CREATE INDEX idx_age ON t (age)") + run("ALTER TABLE t RENAME COLUMN age TO years") + val plan = explain("EXPLAIN SELECT id FROM t WHERE years = 30").joinToString("\n") + assertTrue(plan.contains("Index Scan using idx_age"), plan) + assertEquals(listOf(listOf(int(1))), select("SELECT id FROM t WHERE years = 30").rows) + } + + // ---- RENAME TABLE ---- + + @Test + fun renameTableRenamesAndPreservesData() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)") + run("INSERT INTO t VALUES (1, 'ada')") + val result = exec("ALTER TABLE t RENAME TO people") + assertEquals(AlterTableResult("people"), result) + assertEquals(listOf(listOf(int(1), text("ada"))), select("SELECT * FROM people").rows) + assertFailsWith { run("SELECT * FROM t") } + assertEquals(listOf("people"), catalog.tableNames) + } + + @Test + fun renameTableRejectsWhenNewNameAlreadyExistsAsTable() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + run("CREATE TABLE other (id INTEGER PRIMARY KEY)") + val error = assertFailsWith { run("ALTER TABLE t RENAME TO other") } + assertTrue(error.message!!.contains("already exists")) + } + + @Test + fun renameTableRejectsWhenNewNameAlreadyExistsAsView() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + run("CREATE TABLE other (id INTEGER PRIMARY KEY)") + run("CREATE VIEW v AS SELECT * FROM other") + val error = assertFailsWith { run("ALTER TABLE t RENAME TO v") } + assertTrue(error.message!!.contains("already exists")) + } + + @Test + fun renameTableRejectsRenamingAView() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + run("CREATE VIEW v AS SELECT * FROM t") + val error = assertFailsWith { run("ALTER TABLE v RENAME TO w") } + assertTrue(error.message!!.contains("cannot modify view")) + } + + @Test + fun renameTablePropagatesToOtherTablesForeignKeyReference() { + run("CREATE TABLE departments (id INTEGER PRIMARY KEY)") + run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id) ON DELETE CASCADE)") + run("INSERT INTO departments VALUES (1)") + run("INSERT INTO employees VALUES (1, 1)") + run("ALTER TABLE departments RENAME TO divisions") + run("INSERT INTO divisions VALUES (2)") + run("INSERT INTO employees VALUES (2, 2)") + assertFailsWith { run("INSERT INTO employees VALUES (3, 99)") } + run("DELETE FROM divisions WHERE id = 1") + assertEquals(listOf(listOf(int(2))), select("SELECT id FROM employees").rows) + } + + @Test + fun renameTableUpdatesItsOwnSelfReferencingForeignKey() { + run( + "CREATE TABLE staff (id INTEGER PRIMARY KEY, manager_id INTEGER REFERENCES staff(id))" + ) + run("INSERT INTO staff VALUES (1, NULL)") + run("ALTER TABLE staff RENAME TO personnel") + run("INSERT INTO personnel VALUES (2, 1)") + assertFailsWith { run("INSERT INTO personnel VALUES (3, 99)") } + assertEquals(2, select("SELECT * FROM personnel").rows.size) + } + + @Test + fun renameTableUpdatesAnIndexAndThePlannerStillUsesIt() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER)") + run("INSERT INTO t VALUES (1, 30), (2, 40)") + run("CREATE INDEX idx_age ON t (age)") + run("ALTER TABLE t RENAME TO people") + val plan = explain("EXPLAIN SELECT id FROM people WHERE age = 30").joinToString("\n") + assertTrue(plan.contains("Index Scan using idx_age on people"), plan) + assertEquals(listOf(listOf(int(1))), select("SELECT id FROM people WHERE age = 30").rows) + } + + @Test + fun renameTablePropagatesIntoASimpleViewDefinition() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)") + run("INSERT INTO t VALUES (1, 'ada')") + run("CREATE VIEW v AS SELECT * FROM t") + run("ALTER TABLE t RENAME TO people") + assertEquals(listOf(listOf(int(1), text("ada"))), select("SELECT * FROM v").rows) + } + + @Test + fun renameTablePropagatesIntoAViewJoinAndASubqueryExpression() { + run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)") + run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER)") + run("INSERT INTO departments VALUES (1, 'eng')") + run("INSERT INTO employees VALUES (1, 1)") + run( + "CREATE VIEW v AS SELECT e.id FROM employees e JOIN departments d ON e.dept_id = d.id " + + "WHERE d.id IN (SELECT id FROM departments)" + ) + run("ALTER TABLE departments RENAME TO divisions") + assertEquals(listOf(listOf(int(1))), select("SELECT * FROM v").rows) + } + + @Test + fun renameTablePropagatesIntoADerivedTableAndAUnion() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + run("INSERT INTO t VALUES (1), (2)") + run( + "CREATE VIEW v AS SELECT * FROM (SELECT id FROM t) sub " + + "UNION SELECT id FROM t WHERE id = 2" + ) + run("ALTER TABLE t RENAME TO renamed") + assertEquals(listOf(listOf(int(1)), listOf(int(2))), select("SELECT * FROM v ORDER BY id").rows) + } + + @Test + fun renameTableDoesNotTouchATableSourceShadowedByACteOfTheSameName() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + run("INSERT INTO t VALUES (1)") + run("CREATE VIEW v AS WITH t AS (SELECT 99 AS id FROM t) SELECT * FROM t") + run("ALTER TABLE t RENAME TO renamed") + // The CTE named "t" shadows the base table for the rest of that WITH clause, so the view + // must still yield the CTE's own row (99), not read from the (now renamed) base table. + assertEquals(listOf(listOf(int(99))), select("SELECT * FROM v").rows) + assertEquals(listOf(listOf(int(1))), select("SELECT * FROM renamed").rows) + } + + @Test + fun renameTableRenamingToItsOwnNameIsANoOp() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY)") + run("INSERT INTO t VALUES (1)") + run("ALTER TABLE t RENAME TO t") + assertEquals(listOf(listOf(int(1))), select("SELECT * FROM t").rows) + } + + // ---- DEFAULT (CREATE TABLE + INSERT) ---- + + @Test + fun insertUsesColumnDefaultWhenColumnOmittedFromColumnList() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, status TEXT DEFAULT 'pending')") + run("INSERT INTO t (id) VALUES (1)") + assertEquals(listOf(listOf(text("pending"))), select("SELECT status FROM t").rows) + } + + @Test + fun insertDoesNotUseDefaultWhenExplicitNullIsGiven() { + run("CREATE TABLE t (id INTEGER PRIMARY KEY, status TEXT DEFAULT 'pending')") + run("INSERT INTO t (id, status) VALUES (1, NULL)") + assertEquals(listOf(listOf(Value.NullValue)), select("SELECT status FROM t").rows) + } +