test: cover addConstraintCheckSucceedsWhenEveryExistingRowSatisfiesIt
This commit is contained in:
1 parent
4216c1c5eb
commit
68e2600e76
1 file changed
+157
@@ -633,3 +633,160 @@ class AlterTableTest {
|
||||
assertEquals(listOf(listOf(text("pending"))), select("SELECT status FROM t").rows)
|
||||
}
|
||||
|
||||
// ---- ADD CONSTRAINT / DROP CONSTRAINT ----
|
||||
|
||||
@Test
|
||||
fun addConstraintCheckSucceedsWhenEveryExistingRowSatisfiesIt() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER)")
|
||||
run("INSERT INTO t VALUES (1, 30), (2, 40)")
|
||||
val result = exec("ALTER TABLE t ADD CONSTRAINT age_positive CHECK (age > 0)")
|
||||
assertEquals(AlterTableResult("t"), result)
|
||||
val error = assertFailsWith<KestrelException> { run("INSERT INTO t VALUES (3, -1)") }
|
||||
assertTrue(error.message!!.contains("age_positive"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintCheckRejectsWhenAnExistingRowAlreadyViolatesIt() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER)")
|
||||
run("INSERT INTO t VALUES (1, 30), (2, -5)")
|
||||
val error = assertFailsWith<KestrelException> { run("ALTER TABLE t ADD CONSTRAINT age_positive CHECK (age > 0)") }
|
||||
assertTrue(error.message!!.contains("age_positive"), error.message!!)
|
||||
// Rejected, so never installed: a fresh negative row must still be free to insert.
|
||||
run("INSERT INTO t VALUES (3, -1)")
|
||||
assertEquals(2, select("SELECT id FROM t WHERE age < 0").rows.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintCheckRejectsAReferenceToAnUnknownColumn() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER)")
|
||||
val error = assertFailsWith<KestrelException> { run("ALTER TABLE t ADD CONSTRAINT c1 CHECK (bogus > 0)") }
|
||||
assertTrue(error.message!!.contains("bogus"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintCheckNameRoundTripsThroughSqlWriterDumpAndReplay() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER)")
|
||||
run("ALTER TABLE t ADD CONSTRAINT age_positive CHECK (age > 0)")
|
||||
val dumped = SqlWriter.dump(catalog)
|
||||
assertTrue(dumped.contains("CONSTRAINT age_positive CHECK"), dumped)
|
||||
val freshCatalog = Catalog()
|
||||
val freshExecutor = Executor(freshCatalog)
|
||||
for (statement in Parser(Lexer(dumped).tokenize()).parseProgram()) freshExecutor.execute(statement)
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
freshExecutor.execute(Parser(Lexer("INSERT INTO t VALUES (5, -1)").tokenize()).parseProgram().single())
|
||||
}
|
||||
assertTrue(error.message!!.contains("age_positive"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintUniqueSucceedsWhenNoExistingRowsAreDuplicates() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT)")
|
||||
run("INSERT INTO t VALUES (1, 'a@x.com'), (2, 'b@x.com')")
|
||||
exec("ALTER TABLE t ADD CONSTRAINT uq_email UNIQUE (email)")
|
||||
run("INSERT INTO t VALUES (3, 'c@x.com')")
|
||||
val error = assertFailsWith<KestrelException> { run("INSERT INTO t VALUES (4, 'c@x.com')") }
|
||||
assertTrue(error.message!!.contains("uq_email"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintUniqueRejectsWhenExistingRowsAreAlreadyDuplicated() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT)")
|
||||
run("INSERT INTO t VALUES (1, 'a@x.com'), (2, 'a@x.com')")
|
||||
val error = assertFailsWith<KestrelException> { run("ALTER TABLE t ADD CONSTRAINT uq_email UNIQUE (email)") }
|
||||
assertTrue(error.message!!.contains("uq_email"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintUniqueIgnoresNullValuesAmongExistingRows() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, email TEXT)")
|
||||
run("INSERT INTO t VALUES (1, NULL), (2, NULL)")
|
||||
exec("ALTER TABLE t ADD CONSTRAINT uq_email UNIQUE (email)")
|
||||
run("INSERT INTO t VALUES (3, NULL)")
|
||||
assertEquals(3, select("SELECT id FROM t").rows.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintUniqueRejectsAReferenceToAnUnknownColumn() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY)")
|
||||
val error = assertFailsWith<KestrelException> { run("ALTER TABLE t ADD CONSTRAINT uq_bogus UNIQUE (bogus)") }
|
||||
assertTrue(error.message!!.contains("bogus"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintForeignKeySucceedsWhenEveryExistingRowHasAMatchOrIsNull() {
|
||||
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER)")
|
||||
run("INSERT INTO departments VALUES (1, 'eng')")
|
||||
run("INSERT INTO employees VALUES (1, 1), (2, NULL)")
|
||||
exec("ALTER TABLE employees ADD CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES departments (id)")
|
||||
val error = assertFailsWith<KestrelException> { run("INSERT INTO employees VALUES (3, 99)") }
|
||||
assertTrue(error.message!!.contains("FOREIGN KEY constraint failed"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintForeignKeyRejectsWhenAnExistingRowIsAlreadyOrphaned() {
|
||||
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER)")
|
||||
run("INSERT INTO departments VALUES (1, 'eng')")
|
||||
run("INSERT INTO employees VALUES (1, 1), (2, 99)")
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("ALTER TABLE employees ADD CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES departments (id)")
|
||||
}
|
||||
assertTrue(error.message!!.contains("FOREIGN KEY constraint failed"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintForeignKeySelfReferenceValidatesAgainstExistingSiblingRows() {
|
||||
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, manager_id INTEGER)")
|
||||
run("INSERT INTO employees VALUES (1, NULL), (2, 1), (3, 99)")
|
||||
assertFailsWith<KestrelException> {
|
||||
run("ALTER TABLE employees ADD CONSTRAINT fk_manager FOREIGN KEY (manager_id) REFERENCES employees (id)")
|
||||
}
|
||||
run("UPDATE employees SET manager_id = 1 WHERE id = 3")
|
||||
exec("ALTER TABLE employees ADD CONSTRAINT fk_manager FOREIGN KEY (manager_id) REFERENCES employees (id)")
|
||||
val error = assertFailsWith<KestrelException> { run("INSERT INTO employees VALUES (4, 100)") }
|
||||
assertTrue(error.message!!.contains("FOREIGN KEY constraint failed"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintForeignKeyRejectsWhenReferencedColumnsAreNotEnforcedUnique() {
|
||||
run("CREATE TABLE departments (id INTEGER, name TEXT)")
|
||||
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER)")
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("ALTER TABLE employees ADD CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES departments (id)")
|
||||
}
|
||||
assertTrue(error.message!!.contains("PRIMARY KEY or UNIQUE"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintForeignKeyRejectsAColumnCountMismatch() {
|
||||
// Caught by the parser itself (parseInlineForeignKey, shared with CREATE TABLE's own
|
||||
// table-level FOREIGN KEY clause) before Executor.executeAddForeignKeyConstraint's own
|
||||
// redundant arity check would even run - same as CREATE TABLE's identical redundancy.
|
||||
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER)")
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("ALTER TABLE employees ADD CONSTRAINT fk_bad FOREIGN KEY (dept_id) REFERENCES departments (id, name)")
|
||||
}
|
||||
assertTrue(error.message!!.contains("column(s)"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintForeignKeyOnDeleteCascadeAppliesAfterBeingAdded() {
|
||||
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER)")
|
||||
run("INSERT INTO departments VALUES (1, 'eng')")
|
||||
run("INSERT INTO employees VALUES (1, 1), (2, 1)")
|
||||
exec("ALTER TABLE employees ADD CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES departments (id) ON DELETE CASCADE")
|
||||
run("DELETE FROM departments WHERE id = 1")
|
||||
assertEquals(emptyList(), select("SELECT id FROM employees").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addConstraintRejectsANameAlreadyUsedByADifferentKindOfConstraintOnTheSameTable() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, age INTEGER, email TEXT)")
|
||||
run("ALTER TABLE t ADD CONSTRAINT c1 CHECK (age > 0)")
|
||||
val error = assertFailsWith<KestrelException> { run("ALTER TABLE t ADD CONSTRAINT c1 UNIQUE (email)") }
|
||||
assertTrue(error.message!!.contains("c1"), error.message!!)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user