From 161fc029bd955bbd966789ec9832b7e47a6f2b61 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 19:31:34 +0200 Subject: [PATCH] test(sql): cover generatedColumnSurvivesAReopen --- .../sql/DiskExecutorGeneratedColumnTest.kt | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorGeneratedColumnTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorGeneratedColumnTest.kt index a267257..9e5bf3b 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorGeneratedColumnTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorGeneratedColumnTest.kt @@ -160,3 +160,82 @@ class DiskExecutorGeneratedColumnTest { assertEquals(listOf(listOf(Value.TextValue("x"))), result.rows) } + @Test + fun generatedColumnSurvivesAReopen() { + val db = newDatabase() + db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)") + db.run("INSERT INTO t (a) VALUES (5)") + db.reopen() + assertEquals(listOf(listOf(int(5), int(10))), db.select("SELECT a, b FROM t").rows) + // The GENERATED expression itself (not just data already computed before the reopen) must + // have round-tripped through DirectoryEntryCodec - proven by inserting a NEW row after the + // reopen and getting a freshly computed value, not a stale/missing one. + db.run("INSERT INTO t (a) VALUES (7)") + assertEquals(listOf(listOf(int(14))), db.select("SELECT b FROM t WHERE a = 7").rows) + } + + // ---- ALTER TABLE ADD COLUMN GENERATED ALWAYS AS ---- + + @Test + fun alterTableAddColumnGeneratedBackfillsEachExistingRowFromItsOwnOtherColumns() { + val db = newDatabase() + db.run("CREATE TABLE t (a INTEGER)") + db.run("INSERT INTO t (a) VALUES (1), (2), (3)") + db.run("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (a * 2) STORED") + assertEquals( + listOf(listOf(int(1), int(2)), listOf(int(2), int(4)), listOf(int(3), int(6))), + db.select("SELECT a, b FROM t ORDER BY a ASC").rows + ) + } + + @Test + fun afterAlterTableAddColumnGeneratedAnInsertStillRejectsAnExplicitValue() { + val db = newDatabase() + db.run("CREATE TABLE t (a INTEGER)") + db.run("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (a * 2) STORED") + val error = assertFailsWith { db.run("INSERT INTO t (a, b) VALUES (5, 99)") } + assertTrue(error.message!!.contains("GENERATED")) + } + + @Test + fun alterTableAddColumnGeneratedNotNullSkipsTheDefaultRequirementOnANonEmptyTable() { + val db = newDatabase() + db.run("CREATE TABLE t (a INTEGER)") + db.run("INSERT INTO t (a) VALUES (1), (2)") + db.run("ALTER TABLE t ADD COLUMN b INTEGER NOT NULL GENERATED ALWAYS AS (a * 2) STORED") + assertEquals(listOf(listOf(int(2)), listOf(int(4))), db.select("SELECT b FROM t ORDER BY a ASC").rows) + } + + @Test + fun alterTableAddColumnGeneratedNotNullRejectsARowWhoseBackfilledValueIsNull() { + val db = newDatabase() + db.run("CREATE TABLE t (a INTEGER)") + db.run("INSERT INTO t (a) VALUES (NULL)") + val error = assertFailsWith { + db.run("ALTER TABLE t ADD COLUMN b INTEGER NOT NULL GENERATED ALWAYS AS (a) STORED") + } + assertTrue(error.message!!.contains("NOT NULL")) + } + + @Test + fun alterTableAddColumnGeneratedRejectsReferencingAnAlreadyExistingGeneratedColumn() { + val db = newDatabase() + db.run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)") + val error = assertFailsWith { + db.run("ALTER TABLE t ADD COLUMN c INTEGER GENERATED ALWAYS AS (b + 1) STORED") + } + assertTrue(error.message!!.contains("GENERATED")) + } + + @Test + fun alterTableAddColumnGeneratedSurvivesAReopenAndBackfilledRowsKeepTheirValue() { + val db = newDatabase() + db.run("CREATE TABLE t (a INTEGER)") + db.run("INSERT INTO t (a) VALUES (5)") + db.run("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (a * 2) STORED") + db.reopen() + assertEquals(listOf(listOf(int(5), int(10))), db.select("SELECT a, b FROM t").rows) + db.run("INSERT INTO t (a) VALUES (7)") + assertEquals(listOf(listOf(int(14))), db.select("SELECT b FROM t WHERE a = 7").rows) + } +}