test: cover conflictDoUpdateEnforcesCheckConstraintsOnTheResultingRow

This commit is contained in:
dualfroz committed 2026-09-10 19:27:21 +02:00
1 parent 0f54d1fe15
commit 4682431a80
1 file changed
+72
+72
View File
@@ -160,3 +160,75 @@ class UpsertTest {
assertTrue(error.message!!.contains("NOT NULL"), error.message!!)
}
@Test
fun conflictDoUpdateEnforcesCheckConstraintsOnTheResultingRow() {
run(
"CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER CHECK (balance >= 0))"
)
run("INSERT INTO accounts VALUES (1, 100)")
val error = assertFailsWith<KestrelException> {
run("INSERT INTO accounts VALUES (1, 100) ON CONFLICT (id) DO UPDATE SET balance = -5")
}
assertTrue(error.message!!.contains("CHECK constraint"), error.message!!)
}
@Test
fun conflictDoUpdateRunsForeignKeyValidationOnTheUpdatedRow() {
run("CREATE TABLE departments (id INTEGER PRIMARY KEY)")
run("INSERT INTO departments VALUES (1)")
run(
"CREATE TABLE employees (id INTEGER PRIMARY KEY, dept INTEGER REFERENCES departments(id))"
)
run("INSERT INTO employees VALUES (1, 1)")
val error = assertFailsWith<KestrelException> {
run("INSERT INTO employees VALUES (1, 1) ON CONFLICT (id) DO UPDATE SET dept = 999")
}
assertTrue(error.message!!.contains("FOREIGN KEY"), error.message!!)
}
@Test
fun conflictDoUpdateCascadesToChildTablesWhenTheReferencedKeyChanges() {
run("CREATE TABLE departments (id INTEGER PRIMARY KEY)")
run("INSERT INTO departments VALUES (1)")
run(
"CREATE TABLE employees (id INTEGER PRIMARY KEY, dept INTEGER REFERENCES departments(id) ON UPDATE CASCADE)"
)
run("INSERT INTO employees VALUES (1, 1)")
insertCount("INSERT INTO departments VALUES (1) ON CONFLICT (id) DO UPDATE SET id = 2")
assertEquals(Value.IntegerValue(2), select("SELECT dept FROM employees WHERE id = 1").rows[0][0])
}
@Test
fun compositePrimaryKeyConflictTargetIsRecognised() {
run(
"CREATE TABLE grades (student INTEGER, course INTEGER, score INTEGER, PRIMARY KEY (student, course))"
)
run("INSERT INTO grades VALUES (1, 10, 50)")
val count = insertCount(
"INSERT INTO grades VALUES (1, 10, 90) ON CONFLICT (student, course) DO UPDATE SET score = excluded.score"
)
assertEquals(1, count)
assertEquals(Value.IntegerValue(90), select("SELECT score FROM grades WHERE student = 1 AND course = 10").rows[0][0])
}
@Test
fun conflictDoUpdateDefaultKeywordFallsBackToColumnDefault() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, note TEXT DEFAULT 'fresh')")
run("INSERT INTO t VALUES (1, 'stale')")
insertCount("INSERT INTO t VALUES (1, 'stale') ON CONFLICT (id) DO UPDATE SET note = DEFAULT")
assertEquals("fresh", (select("SELECT note FROM t WHERE id = 1").rows[0][0] as Value.TextValue).value)
}
@Test
fun tableStateAfterAConflictResolutionStillDumpsAndReplaysCleanly() {
seed()
run("INSERT INTO accounts VALUES (2, 'linus', 200) ON CONFLICT (id) DO UPDATE SET balance = excluded.balance")
val dumped = kestrel.persistence.SqlWriter.dump(catalog)
val replayCatalog = Catalog()
val replayExecutor = Executor(replayCatalog)
Parser(Lexer(dumped).tokenize()).parseProgram().forEach { replayExecutor.execute(it) }
val replaySelect = Parser(Lexer("SELECT * FROM accounts").tokenize()).parseProgram()
.map { replayExecutor.execute(it) }.single() as SelectResult
assertEquals(2, replaySelect.rows.size)
}
}