test(sql): cover foreignKeyRequiresReferencedTableToExist
This commit is contained in:
1 parent
085763bb52
commit
197e8c974d
1 file changed
+161
@@ -3167,3 +3167,164 @@ class DiskExecutorTest {
|
||||
assertFailsWith<KestrelException> { db.run("UPDATE employees SET dept_id = 99 WHERE id = 1") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun foreignKeyRequiresReferencedTableToExist() {
|
||||
val db = newDatabase()
|
||||
assertFailsWith<KestrelException> {
|
||||
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, x INTEGER REFERENCES missing(id))")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun foreignKeyRequiresReferencedColumnToBeAPrimaryKey() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
assertFailsWith<KestrelException> {
|
||||
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, x TEXT REFERENCES departments(name))")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tableLevelForeignKeySyntaxIsEquivalentToColumnLevel() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
db.run(
|
||||
"CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER, " +
|
||||
"FOREIGN KEY (dept_id) REFERENCES departments(id))"
|
||||
)
|
||||
db.run("INSERT INTO departments VALUES (1, 'eng')")
|
||||
assertEquals(InsertResult(1), db.run("INSERT INTO employees VALUES (1, 1)").single())
|
||||
assertFailsWith<KestrelException> { db.run("INSERT INTO employees VALUES (2, 99)") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteIsRestrictedByDefaultWhenChildrenReferenceTheRow() {
|
||||
val db = newDatabase()
|
||||
db.seedDepartmentsAndEmployees()
|
||||
db.run("INSERT INTO employees VALUES (1, 'ada', 1)")
|
||||
val error = assertFailsWith<KestrelException> { db.run("DELETE FROM departments WHERE id = 1") }
|
||||
assertTrue(error.message!!.contains("FOREIGN KEY"))
|
||||
assertEquals(1, db.select("SELECT * FROM departments WHERE id = 1").rows.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteWithoutReferencingChildrenSucceeds() {
|
||||
val db = newDatabase()
|
||||
db.seedDepartmentsAndEmployees()
|
||||
assertEquals(DeleteResult(1), db.run("DELETE FROM departments WHERE id = 2").single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteCascadeRemovesReferencingChildren() {
|
||||
val db = newDatabase()
|
||||
db.seedCascadeDepartments()
|
||||
assertEquals(DeleteResult(1), db.run("DELETE FROM departments WHERE id = 1").single())
|
||||
assertEquals(listOf(listOf(text("grace"))), db.select("SELECT name FROM employees ORDER BY id").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onUpdateCascadeChangesChildForeignKeyWhenParentPrimaryKeyChanges() {
|
||||
val db = newDatabase()
|
||||
db.seedCascadeDepartments()
|
||||
db.run("UPDATE departments SET id = 10 WHERE id = 1")
|
||||
val depts = db.select("SELECT dept_id FROM employees ORDER BY id")
|
||||
assertEquals(listOf(listOf(int(10)), listOf(int(10)), listOf(int(2))), depts.rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onUpdateRestrictByDefaultBlocksPrimaryKeyChangeWhenChildrenReference() {
|
||||
val db = newDatabase()
|
||||
db.seedDepartmentsAndEmployees()
|
||||
db.run("INSERT INTO employees VALUES (1, 'ada', 1)")
|
||||
assertFailsWith<KestrelException> { db.run("UPDATE departments SET id = 99 WHERE id = 1") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteSetNullNullsOutReferencingChildren() {
|
||||
val db = newDatabase()
|
||||
db.seedSetNullDepartments(childNotNull = false)
|
||||
db.run("DELETE FROM departments WHERE id = 1")
|
||||
assertEquals(listOf(listOf(Value.NullValue)), db.select("SELECT dept_id FROM employees WHERE id = 1").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDeleteSetNullFailsWhenChildColumnIsNotNull() {
|
||||
val db = newDatabase()
|
||||
db.seedSetNullDepartments(childNotNull = true)
|
||||
val error = assertFailsWith<KestrelException> { db.run("DELETE FROM departments WHERE id = 1") }
|
||||
assertTrue(error.message!!.contains("NOT NULL"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selfReferencingForeignKeyIsAllowedAndCascadesThroughMultipleLevels() {
|
||||
val db = newDatabase()
|
||||
db.run(
|
||||
"CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT NOT NULL, " +
|
||||
"manager_id INTEGER REFERENCES employees(id) ON DELETE CASCADE)"
|
||||
)
|
||||
db.run(
|
||||
"INSERT INTO employees VALUES " +
|
||||
"(1, 'ceo', NULL), (2, 'vp', 1), (3, 'manager', 2), (4, 'ic', 3)"
|
||||
)
|
||||
assertEquals(DeleteResult(1), db.run("DELETE FROM employees WHERE id = 1").single())
|
||||
assertEquals(emptyList(), db.select("SELECT id FROM employees").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selfReferencingForeignKeyAllowsForwardAndBackwardSiblingReferencesInTheSameInsertBatch() {
|
||||
val db = newDatabase()
|
||||
db.run(
|
||||
"CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT NOT NULL, " +
|
||||
"manager_id INTEGER REFERENCES employees(id))"
|
||||
)
|
||||
// row 1 references row 2 (forward, not yet on disk) and row 3 references row 1 (backward) -
|
||||
// order-independence within a single multi-row INSERT statement.
|
||||
assertEquals(
|
||||
InsertResult(3),
|
||||
db.run("INSERT INTO employees VALUES (1, 'a', 2), (2, 'b', NULL), (3, 'c', 1)").single()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dropTableIsRestrictedByDefaultWhenAForeignKeyReferencesIt() {
|
||||
val db = newDatabase()
|
||||
db.seedDepartmentsAndEmployees()
|
||||
val error = assertFailsWith<KestrelException> { db.run("DROP TABLE departments") }
|
||||
assertTrue(error.message!!.contains("FOREIGN KEY"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dropTableCascadeStripsTheForeignKeyButKeepsTheReferencingTableAndItsRows() {
|
||||
val db = newDatabase()
|
||||
db.seedDepartmentsAndEmployees()
|
||||
db.run("INSERT INTO employees VALUES (1, 'ada', 1)")
|
||||
db.run("DROP TABLE departments CASCADE")
|
||||
assertEquals(listOf("employees"), db.catalog.tableNames)
|
||||
assertEquals(1, db.select("SELECT * FROM employees").rows.size)
|
||||
// the constraint is gone, so a now-dangling value is accepted
|
||||
assertEquals(InsertResult(1), db.run("INSERT INTO employees VALUES (2, 'linus', 99)").single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dropTableCascadeStrippedForeignKeySurvivesClosingAndReopeningTheCatalog() {
|
||||
val db = newDatabase()
|
||||
db.seedDepartmentsAndEmployees()
|
||||
db.run("INSERT INTO employees VALUES (1, 'ada', 1)")
|
||||
db.run("DROP TABLE departments CASCADE")
|
||||
db.reopen()
|
||||
assertEquals(listOf("employees"), db.catalog.tableNames)
|
||||
assertEquals(InsertResult(1), db.run("INSERT INTO employees VALUES (2, 'linus', 99)").single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dropTableCascadeDropsDependentViewsAndStripsForeignKeysInTheSameStatement() {
|
||||
val db = newDatabase()
|
||||
db.seedDepartmentsAndEmployees()
|
||||
db.run("CREATE VIEW deptNames AS SELECT name FROM departments")
|
||||
db.run("INSERT INTO employees VALUES (1, 'ada', 1)")
|
||||
db.run("DROP TABLE departments CASCADE")
|
||||
assertEquals(listOf("employees"), db.catalog.tableNames)
|
||||
assertTrue(db.catalog.viewNames.isEmpty())
|
||||
assertEquals(InsertResult(1), db.run("INSERT INTO employees VALUES (2, 'linus', 99)").single())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user