test: cover compositeForeignKeyAcceptsAFullMatch
This commit is contained in:
1 parent
9f5328b55e
commit
eb56dc4b87
1 file changed
+158
@@ -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())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user