test(sql): cover insertUpdateDeleteAgainstAViewAreRejected

This commit is contained in:
dualfroz committed 2026-09-13 14:22:39 +02:00
1 parent 8533598961
commit bac0b33caf
1 file changed
+156
@@ -2856,3 +2856,159 @@ class DiskExecutorTest {
assertFailsWith<DiskCatalogException> { db.run("DROP VIEW users") } assertFailsWith<DiskCatalogException> { db.run("DROP VIEW users") }
} }
@Test
fun insertUpdateDeleteAgainstAViewAreRejected() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW adults AS SELECT id, name FROM users WHERE age >= 18")
assertFailsWith<DiskCatalogException> { db.run("INSERT INTO adults VALUES (9, 'x')") }
assertFailsWith<DiskCatalogException> { db.run("UPDATE adults SET name = 'x'") }
assertFailsWith<DiskCatalogException> { db.run("DELETE FROM adults") }
}
@Test
fun creatingAnIndexOnAViewIsRejected() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW adults AS SELECT id, name FROM users WHERE age >= 18")
assertFailsWith<DiskCatalogException> { db.run("CREATE INDEX idx ON adults (id)") }
}
@Test
fun mutuallyRecursiveViewsFailInsteadOfOverflowingTheStack() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW a AS SELECT id FROM users")
db.run("CREATE VIEW b AS SELECT id FROM a")
db.run("CREATE OR REPLACE VIEW a AS SELECT id FROM b")
assertFailsWith<DiskExecutorException> { db.run("SELECT * FROM a") }
}
@Test
fun selfReferencingViewFailsCleanly() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW a AS SELECT id FROM users")
db.run("CREATE OR REPLACE VIEW a AS SELECT id FROM a")
assertFailsWith<DiskExecutorException> { db.run("SELECT * FROM a") }
}
@Test
fun dropTableWithADependentViewFailsByDefaultAndAlsoWithExplicitRestrict() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW adults AS SELECT id FROM users WHERE age >= 18")
assertFailsWith<DiskCatalogException> { db.run("DROP TABLE users") }
assertFailsWith<DiskCatalogException> { db.run("DROP TABLE users RESTRICT") }
assertEquals(listOf("users"), db.catalog.tableNames)
assertEquals(listOf("adults"), db.catalog.viewNames)
}
@Test
fun dropViewWithADependentViewFailsByDefault() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW adults AS SELECT id FROM users WHERE age >= 18")
db.run("CREATE VIEW seniorAdults AS SELECT id FROM adults WHERE id > 0")
assertFailsWith<DiskCatalogException> { db.run("DROP VIEW adults") }
assertEquals(listOf("adults", "seniorAdults"), db.catalog.viewNames)
}
@Test
fun dropViewCascadeDropsATransitiveChainOfDependentViews() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW a AS SELECT id FROM users WHERE age >= 18")
db.run("CREATE VIEW b AS SELECT id FROM a WHERE id > 0")
db.run("CREATE VIEW c AS SELECT id FROM b WHERE id > 0")
db.run("DROP VIEW a CASCADE")
assertTrue(db.catalog.viewNames.isEmpty())
}
@Test
fun dropTableWithADiamondOfDependentViewsIsBlockedByEveryDirectDependent() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW b AS SELECT id FROM users WHERE age >= 18")
db.run("CREATE VIEW c AS SELECT id FROM users WHERE age >= 21")
assertFailsWith<DiskCatalogException> { db.run("DROP TABLE users") }
assertEquals(listOf("users"), db.catalog.tableNames)
assertEquals(listOf("b", "c"), db.catalog.viewNames)
}
@Test
fun dropTableCascadeDropsTheTableAndItsDependentView() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW adults AS SELECT id FROM users WHERE age >= 18")
db.run("DROP TABLE users CASCADE")
assertTrue(db.catalog.tableNames.isEmpty())
assertTrue(db.catalog.viewNames.isEmpty())
}
@Test
fun dropTableCascadeHandlesADiamondOfDependentViewsWithoutFailing() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW b AS SELECT id FROM users WHERE age >= 18")
db.run("CREATE VIEW c AS SELECT id FROM users WHERE age >= 21")
db.run("CREATE VIEW d AS SELECT b.id FROM b JOIN c ON b.id = c.id")
db.run("DROP TABLE users CASCADE")
assertTrue(db.catalog.tableNames.isEmpty())
assertTrue(db.catalog.viewNames.isEmpty())
}
@Test
fun dropTableCascadeDroppedViewsSurviveClosingAndReopeningTheCatalog() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW adults AS SELECT id FROM users WHERE age >= 18")
db.run("DROP TABLE users CASCADE")
db.reopen()
assertTrue(db.catalog.tableNames.isEmpty())
assertTrue(db.catalog.viewNames.isEmpty())
}
@Test
fun columnTypeOfAViewIsInferredFromActualData() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW names AS SELECT name FROM users")
val result = db.select("SELECT name FROM names WHERE name = 'ada'")
assertEquals(listOf(listOf(text("ada"))), result.rows)
}
@Test
fun viewDefinitionSurvivesClosingAndReopeningTheCatalog() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW adults AS SELECT id, name FROM users WHERE age >= 40")
db.reopen()
assertEquals(listOf("adults"), db.catalog.viewNames)
val result = db.select("SELECT name FROM adults ORDER BY id ASC")
assertEquals(listOf(text("linus"), text("grace"), text("alan")), result.rows.map { it.single() })
db.run("INSERT INTO users VALUES (5, 'margaret', 61, 'navy')")
db.reopen()
val afterInsertAndReopen = db.select("SELECT name FROM adults ORDER BY id ASC")
assertEquals(
listOf(text("linus"), text("grace"), text("alan"), text("margaret")),
afterInsertAndReopen.rows.map { it.single() }
)
}
@Test
fun viewOverViewAndDropViewSurviveReopen() {
val db = newDatabase()
db.seedUsersForViews()
db.run("CREATE VIEW adults AS SELECT id, name, age FROM users WHERE age >= 40")
db.run("CREATE VIEW seniors AS SELECT id, name FROM adults WHERE age >= 60")
db.reopen()
assertEquals(listOf(listOf(text("grace"))), db.select("SELECT name FROM seniors").rows)
db.run("DROP VIEW seniors")
db.reopen()
assertEquals(listOf("adults"), db.catalog.viewNames)
assertFailsWith<KestrelException> { db.run("SELECT * FROM seniors") }
}