test: cover rejectsGeneratedExpressionReferencingANonexistentColumn

This commit is contained in:
dualfroz committed 2026-09-09 12:08:04 +02:00
1 parent 00f08c1bca
commit 0cddb29e0e
1 file changed
+158
@@ -156,3 +156,161 @@ class GeneratedColumnTest {
assertFailsWith<KestrelException> { run("INSERT INTO t (a) VALUES (NULL)") }
}
// ---- validation at CREATE TABLE ----
@Test
fun rejectsGeneratedExpressionReferencingANonexistentColumn() {
val error = assertFailsWith<KestrelException> {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (missing) STORED)")
}
assertTrue(error.message!!.contains("no such column"))
}
@Test
fun rejectsGeneratedExpressionContainingAnAggregate() {
assertFailsWith<KestrelException> {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (COUNT(a)) STORED)")
}
}
@Test
fun rejectsGeneratedExpressionContainingASubquery() {
run("CREATE TABLE other (x INTEGER)")
assertFailsWith<KestrelException> {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS ((SELECT x FROM other)) STORED)")
}
}
@Test
fun rejectsGeneratedExpressionContainingNextval() {
run("CREATE SEQUENCE seq")
assertFailsWith<KestrelException> {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (NEXTVAL('seq')) STORED)")
}
}
@Test
fun rejectsAGeneratedColumnReferencingAnotherGeneratedColumn() {
val error = assertFailsWith<KestrelException> {
run(
"CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED, " +
"c INTEGER GENERATED ALWAYS AS (b + 1) STORED)"
)
}
assertTrue(error.message!!.contains("GENERATED"))
}
// ---- RENAME COLUMN / DROP COLUMN interaction ----
@Test
fun renameColumnPropagatesIntoADependentGeneratedExpression() {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
run("ALTER TABLE t RENAME COLUMN a TO a2")
run("INSERT INTO t (a2) VALUES (5)")
assertEquals(listOf(listOf(int(10))), select("SELECT b FROM t").rows)
}
@Test
fun dropColumnRestrictsWhenAGeneratedColumnDependsOnIt() {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
val error = assertFailsWith<KestrelException> { run("ALTER TABLE t DROP COLUMN a") }
assertTrue(error.message!!.contains("GENERATED"))
}
@Test
fun dropColumnCascadeRemovesTheDependentGeneratedColumnToo() {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED, c TEXT)")
run("INSERT INTO t (a, c) VALUES (5, 'x')")
run("ALTER TABLE t DROP COLUMN a CASCADE")
val result = select("SELECT * FROM t")
assertEquals(listOf("c"), result.columns)
assertEquals(listOf(listOf(text("x"))), result.rows)
}
// ---- ALTER TABLE ADD COLUMN GENERATED ALWAYS AS ----
@Test
fun alterTableAddColumnGeneratedBackfillsEachExistingRowFromItsOwnOtherColumns() {
run("CREATE TABLE t (a INTEGER)")
run("INSERT INTO t (a) VALUES (1), (2), (3)")
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))),
select("SELECT a, b FROM t ORDER BY a ASC").rows
)
}
@Test
fun alterTableAddColumnGeneratedWorksOnAnEmptyTable() {
run("CREATE TABLE t (a INTEGER)")
run("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (a * 2) STORED")
run("INSERT INTO t (a) VALUES (5)")
assertEquals(listOf(listOf(int(10))), select("SELECT b FROM t").rows)
}
@Test
fun afterAlterTableAddColumnGeneratedAnInsertStillRejectsAnExplicitValue() {
run("CREATE TABLE t (a INTEGER)")
run("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (a * 2) STORED")
val error = assertFailsWith<KestrelException> { run("INSERT INTO t (a, b) VALUES (5, 99)") }
assertTrue(error.message!!.contains("GENERATED"))
}
@Test
fun afterAlterTableAddColumnGeneratedUpdatingTheDependencyColumnRecomputesIt() {
run("CREATE TABLE t (a INTEGER)")
run("INSERT INTO t (a) VALUES (5)")
run("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (a * 2) STORED")
run("UPDATE t SET a = 9")
assertEquals(listOf(listOf(int(18))), select("SELECT b FROM t").rows)
}
@Test
fun alterTableAddColumnGeneratedNotNullSkipsTheDefaultRequirementOnANonEmptyTable() {
run("CREATE TABLE t (a INTEGER)")
run("INSERT INTO t (a) VALUES (1), (2)")
// A plain NOT NULL column with no DEFAULT would be rejected here (see the DEFAULT-less
// ADD COLUMN rule tested elsewhere) - GENERATED has its own per-row value to backfill with
// instead, so it needs no DEFAULT at all even though the table already has rows.
run("ALTER TABLE t ADD COLUMN b INTEGER NOT NULL GENERATED ALWAYS AS (a * 2) STORED")
assertEquals(listOf(listOf(int(2)), listOf(int(4))), select("SELECT b FROM t ORDER BY a ASC").rows)
}
@Test
fun alterTableAddColumnGeneratedNotNullRejectsARowWhoseBackfilledValueIsNull() {
run("CREATE TABLE t (a INTEGER)")
run("INSERT INTO t (a) VALUES (NULL)")
val error = assertFailsWith<KestrelException> {
run("ALTER TABLE t ADD COLUMN b INTEGER NOT NULL GENERATED ALWAYS AS (a) STORED")
}
assertTrue(error.message!!.contains("NOT NULL"))
}
@Test
fun alterTableAddColumnGeneratedRejectsReferencingANonexistentColumn() {
run("CREATE TABLE t (a INTEGER)")
val error = assertFailsWith<KestrelException> {
run("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (missing) STORED")
}
assertTrue(error.message!!.contains("no such column"))
}
@Test
fun alterTableAddColumnGeneratedRejectsReferencingAnAlreadyExistingGeneratedColumn() {
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
val error = assertFailsWith<KestrelException> {
run("ALTER TABLE t ADD COLUMN c INTEGER GENERATED ALWAYS AS (b + 1) STORED")
}
assertTrue(error.message!!.contains("GENERATED"))
}
@Test
fun alterTableAddColumnGeneratedRejectsAnAggregate() {
run("CREATE TABLE t (a INTEGER)")
assertFailsWith<KestrelException> {
run("ALTER TABLE t ADD COLUMN b INTEGER GENERATED ALWAYS AS (COUNT(a)) STORED")
}
}
private fun text(value: String) = Value.TextValue(value)
}