test: cover compositeForeignKeyAcceptsAFullMatch

This commit is contained in:
dualfroz committed 2026-09-08 20:00:04 +02:00
1 parent 9f5328b55e
commit eb56dc4b87
1 file changed
+158
+158
View File
@@ -156,3 +156,161 @@ class CompositeKeyTest {
}
}
@Test
fun compositeForeignKeyAcceptsAFullMatch() {
seedOrdersAndLineItems()
val result = exec("INSERT INTO line_items VALUES ('eu', 1, 'SKU1', 3)")
assertEquals(InsertResult(1), result)
}
@Test
fun compositeForeignKeyMatchSimpleSkipsCheckWhenAnyLocalColumnIsNull() {
seedOrdersAndLineItems()
val result = exec("INSERT INTO line_items VALUES ('eu', NULL, 'SKU1', 3)")
assertEquals(InsertResult(1), result)
}
@Test
fun compositeForeignKeyOnDeleteCascadeRemovesMatchingChildren() {
seedOrdersAndLineItems()
run("INSERT INTO line_items VALUES ('eu', 1, 'SKU1', 3), ('us', 1, 'SKU2', 4)")
run("DELETE FROM orders WHERE region = 'eu'")
val remaining = select("SELECT sku FROM line_items")
assertEquals(listOf(listOf(Value.TextValue("SKU2"))), remaining.rows)
}
@Test
fun compositeForeignKeyOnUpdateCascadePropagatesBothColumnsTogether() {
seedOrdersAndLineItems()
run("INSERT INTO line_items VALUES ('eu', 1, 'SKU1', 3)")
run("UPDATE orders SET order_no = 7 WHERE region = 'eu'")
val updated = select("SELECT region, order_no FROM line_items WHERE sku = 'SKU1'")
assertEquals(listOf(listOf(Value.TextValue("eu"), int(7))), updated.rows)
}
@Test
fun compositeForeignKeyOnDeleteRestrictBlocksByDefaultWithoutCascade() {
run("CREATE TABLE orders (region TEXT, order_no INTEGER, PRIMARY KEY (region, order_no))")
run(
"CREATE TABLE line_items (region TEXT, order_no INTEGER, sku TEXT, " +
"FOREIGN KEY (region, order_no) REFERENCES orders (region, order_no))"
)
run("INSERT INTO orders VALUES ('eu', 1)")
run("INSERT INTO line_items VALUES ('eu', 1, 'SKU1')")
assertFailsWith<KestrelException> { run("DELETE FROM orders WHERE region = 'eu'") }
}
@Test
fun compositeForeignKeyOnDeleteSetNullNullsEveryLocalColumn() {
run("CREATE TABLE orders (region TEXT, order_no INTEGER, PRIMARY KEY (region, order_no))")
run(
"CREATE TABLE line_items (region TEXT, order_no INTEGER, sku TEXT, " +
"FOREIGN KEY (region, order_no) REFERENCES orders (region, order_no) ON DELETE SET NULL)"
)
run("INSERT INTO orders VALUES ('eu', 1)")
run("INSERT INTO line_items VALUES ('eu', 1, 'SKU1')")
run("DELETE FROM orders WHERE region = 'eu'")
val row = select("SELECT region, order_no FROM line_items WHERE sku = 'SKU1'").rows.single()
assertEquals(listOf(Value.NullValue, Value.NullValue), row)
}
@Test
fun rollbackAfterCompositeCascadeDeleteRestoresBothTables() {
seedOrdersAndLineItems()
run("INSERT INTO line_items VALUES ('eu', 1, 'SKU1', 3)")
run("BEGIN")
run("DELETE FROM orders WHERE region = 'eu'")
run("ROLLBACK")
assertEquals(2, select("SELECT * FROM orders").rows.size)
assertEquals(1, select("SELECT * FROM line_items").rows.size)
}
// ---- composite FOREIGN KEY referencing a composite UNIQUE (not the PRIMARY KEY) ----
@Test
fun compositeForeignKeyMayReferenceACompositeUniqueConstraintInsteadOfThePrimaryKey() {
run(
"CREATE TABLE products (id INTEGER PRIMARY KEY, brand TEXT, model TEXT, UNIQUE (brand, model))"
)
run(
"CREATE TABLE reviews (id INTEGER PRIMARY KEY, brand TEXT, model TEXT, stars INTEGER, " +
"FOREIGN KEY (brand, model) REFERENCES products (brand, model))"
)
run("INSERT INTO products VALUES (1, 'acme', 'x100')")
val result = exec("INSERT INTO reviews VALUES (1, 'acme', 'x100', 5)")
assertEquals(InsertResult(1), result)
assertFailsWith<KestrelException> {
run("INSERT INTO reviews VALUES (2, 'acme', 'missing', 4)")
}
}
// ---- self-referencing composite FOREIGN KEY ----
@Test
fun selfReferencingCompositeForeignKeySeedableInASingleMultiRowInsert() {
run(
"CREATE TABLE edges (" +
"tenant TEXT, node INTEGER, parent_tenant TEXT, parent_node INTEGER, " +
"PRIMARY KEY (tenant, node), " +
"FOREIGN KEY (parent_tenant, parent_node) REFERENCES edges (tenant, node))"
)
// The root's parent columns are NULL (no parent); child references the root, all seeded
// together in one INSERT so the child must see its not-yet-committed sibling.
run(
"INSERT INTO edges VALUES " +
"('t1', 1, NULL, NULL), ('t1', 2, 't1', 1), ('t1', 3, 't1', 2)"
)
assertEquals(3, select("SELECT * FROM edges").rows.size)
}
// ---- SqlWriter round-trip ----
@Test
fun createTableWithCompositePrimaryKeyRoundTripsThroughSqlWriter() {
val statement = parseOne(
"CREATE TABLE line_items (order_id INTEGER, sku TEXT, qty INTEGER, PRIMARY KEY (order_id, sku))"
)
run(SqlWriter.write(statement))
run("INSERT INTO line_items VALUES (1, 'A', 3)")
assertFailsWith<KestrelException> { run("INSERT INTO line_items VALUES (1, 'A', 9)") }
}
@Test
fun createTableWithCompositeForeignKeyRoundTripsThroughSqlWriter() {
run(SqlWriter.write(parseOne("CREATE TABLE orders (region TEXT, order_no INTEGER, PRIMARY KEY (region, order_no))")))
run(
SqlWriter.write(
parseOne(
"CREATE TABLE line_items (region TEXT, order_no INTEGER, sku TEXT, " +
"FOREIGN KEY (region, order_no) REFERENCES orders (region, order_no) ON DELETE CASCADE)"
)
)
)
run("INSERT INTO orders VALUES ('eu', 1)")
run("INSERT INTO line_items VALUES ('eu', 1, 'SKU1')")
run("DELETE FROM orders WHERE region = 'eu'")
assertTrue(select("SELECT * FROM line_items").rows.isEmpty())
}
@Test
fun dumpAndReplayPreservesCompositePrimaryKeyAndForeignKey() {
seedOrdersAndLineItems()
run("INSERT INTO line_items VALUES ('eu', 1, 'SKU1', 3)")
val dump = SqlWriter.dump(catalog)
val freshCatalog = Catalog()
val freshExecutor = Executor(freshCatalog)
Parser(Lexer(dump).tokenize()).parseProgram().forEach { freshExecutor.execute(it) }
assertFailsWith<KestrelException> {
Parser(Lexer("INSERT INTO orders VALUES ('eu', 1)").tokenize()).parseProgram()
.forEach { freshExecutor.execute(it) }
}
Parser(Lexer("DELETE FROM orders WHERE region = 'eu'").tokenize()).parseProgram()
.forEach { freshExecutor.execute(it) }
val remaining = freshExecutor.execute(
Parser(Lexer("SELECT * FROM line_items").tokenize()).parseProgram().single()
) as SelectResult
assertTrue(remaining.rows.isEmpty())
}
}