test(sql): cover columnLevelCheckAcceptsSatisfyingRowsAndRejectsViola...
This commit is contained in:
1 parent
bac0b33caf
commit
085763bb52
1 file changed
+155
@@ -3012,3 +3012,158 @@ class DiskExecutorTest {
|
|||||||
assertFailsWith<KestrelException> { db.run("SELECT * FROM seniors") }
|
assertFailsWith<KestrelException> { db.run("SELECT * FROM seniors") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun columnLevelCheckAcceptsSatisfyingRowsAndRejectsViolatingOnes() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0))")
|
||||||
|
assertEquals(InsertResult(1), db.run("INSERT INTO t VALUES (1, 30)").single())
|
||||||
|
val error = assertFailsWith<KestrelException> { db.run("INSERT INTO t VALUES (2, -5)") }
|
||||||
|
assertTrue(error.message!!.contains("CHECK"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun checkPassesWhenExpressionIsUnknownBecauseOfNull() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0))")
|
||||||
|
// three-valued logic: NULL > 0 is UNKNOWN, not FALSE, so the row is accepted
|
||||||
|
assertEquals(InsertResult(1), db.run("INSERT INTO t (id) VALUES (1)").single())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun tableLevelCheckCanSpanMultipleColumns() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE ranges (id INTEGER PRIMARY KEY, low INTEGER, high INTEGER, CHECK (low <= high))")
|
||||||
|
assertEquals(InsertResult(1), db.run("INSERT INTO ranges VALUES (1, 1, 10)").single())
|
||||||
|
assertFailsWith<KestrelException> { db.run("INSERT INTO ranges VALUES (2, 10, 1)") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multipleCheckConstraintsAllMustBeSatisfied() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run(
|
||||||
|
"CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age >= 0), " +
|
||||||
|
"score INTEGER CHECK (score BETWEEN 0 AND 100))"
|
||||||
|
)
|
||||||
|
assertEquals(InsertResult(1), db.run("INSERT INTO t VALUES (1, 30, 90)").single())
|
||||||
|
assertFailsWith<KestrelException> { db.run("INSERT INTO t VALUES (2, 30, 150)") }
|
||||||
|
assertFailsWith<KestrelException> { db.run("INSERT INTO t VALUES (3, -1, 90)") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun checkIsEnforcedOnUpdateToo() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0))")
|
||||||
|
db.run("INSERT INTO t VALUES (1, 30)")
|
||||||
|
assertFailsWith<KestrelException> { db.run("UPDATE t SET age = -1 WHERE id = 1") }
|
||||||
|
assertEquals(UpdateResult(1), db.run("UPDATE t SET age = 40 WHERE id = 1").single())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun checkRejectsAggregateFunctions() {
|
||||||
|
val db = newDatabase()
|
||||||
|
assertFailsWith<KestrelException> {
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, amount INTEGER CHECK (SUM(amount) > 0))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun checkRejectsSubqueries() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE other (id INTEGER PRIMARY KEY)")
|
||||||
|
assertFailsWith<KestrelException> {
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY CHECK (id IN (SELECT id FROM other)))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun checkRejectsUnknownColumn() {
|
||||||
|
val db = newDatabase()
|
||||||
|
assertFailsWith<KestrelException> {
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, CHECK (nope > 0))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun checkCanReferenceStringAndCallExpressions() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT CHECK (LENGTH(name) > 0))")
|
||||||
|
assertEquals(InsertResult(1), db.run("INSERT INTO t VALUES (1, 'ada')").single())
|
||||||
|
assertFailsWith<KestrelException> { db.run("INSERT INTO t VALUES (2, '')") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun checkDefinitionSurvivesClosingAndReopeningTheCatalog() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER CHECK (age > 0), CHECK (id > 0))")
|
||||||
|
db.run("INSERT INTO t VALUES (1, 30)")
|
||||||
|
db.reopen()
|
||||||
|
|
||||||
|
assertEquals(InsertResult(1), db.run("INSERT INTO t VALUES (2, 40)").single())
|
||||||
|
assertFailsWith<KestrelException> { db.run("INSERT INTO t VALUES (3, -1)") }
|
||||||
|
assertFailsWith<KestrelException> { db.run("UPDATE t SET age = -5 WHERE id = 1") }
|
||||||
|
|
||||||
|
db.reopen()
|
||||||
|
assertFailsWith<KestrelException> { db.run("INSERT INTO t VALUES (4, -1)") }
|
||||||
|
assertEquals(2, db.select("SELECT * FROM t").rows.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Database.seedDepartmentsAndEmployees() {
|
||||||
|
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
|
||||||
|
run(
|
||||||
|
"CREATE TABLE employees (" +
|
||||||
|
"id INTEGER PRIMARY KEY, name TEXT NOT NULL, dept_id INTEGER REFERENCES departments(id))"
|
||||||
|
)
|
||||||
|
run("INSERT INTO departments VALUES (1, 'eng'), (2, 'ops')")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Database.seedCascadeDepartments() {
|
||||||
|
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
|
||||||
|
run(
|
||||||
|
"CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT NOT NULL, " +
|
||||||
|
"dept_id INTEGER REFERENCES departments(id) ON DELETE CASCADE ON UPDATE CASCADE)"
|
||||||
|
)
|
||||||
|
run("INSERT INTO departments VALUES (1, 'eng'), (2, 'ops')")
|
||||||
|
run("INSERT INTO employees VALUES (1, 'ada', 1), (2, 'linus', 1), (3, 'grace', 2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Database.seedSetNullDepartments(childNotNull: Boolean) {
|
||||||
|
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
|
||||||
|
val childColumn = if (childNotNull) "dept_id INTEGER NOT NULL" else "dept_id INTEGER"
|
||||||
|
run(
|
||||||
|
"CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT NOT NULL, " +
|
||||||
|
"$childColumn REFERENCES departments(id) ON DELETE SET NULL)"
|
||||||
|
)
|
||||||
|
run("INSERT INTO departments VALUES (1, 'eng')")
|
||||||
|
run("INSERT INTO employees VALUES (1, 'ada', 1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun insertRejectsForeignKeyViolation() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.seedDepartmentsAndEmployees()
|
||||||
|
val error = assertFailsWith<KestrelException> { db.run("INSERT INTO employees VALUES (1, 'ada', 99)") }
|
||||||
|
assertTrue(error.message!!.contains("FOREIGN KEY"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun insertAllowsNullForeignKeyValue() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.seedDepartmentsAndEmployees()
|
||||||
|
assertEquals(InsertResult(1), db.run("INSERT INTO employees VALUES (1, 'ada', NULL)").single())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun insertSucceedsWhenReferencedRowExists() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.seedDepartmentsAndEmployees()
|
||||||
|
assertEquals(InsertResult(1), db.run("INSERT INTO employees VALUES (1, 'ada', 1)").single())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateRejectsForeignKeyViolationOnChildColumn() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.seedDepartmentsAndEmployees()
|
||||||
|
db.run("INSERT INTO employees VALUES (1, 'ada', 1)")
|
||||||
|
assertFailsWith<KestrelException> { db.run("UPDATE employees SET dept_id = 99 WHERE id = 1") }
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user