test(sql): cover conflictTargetMatchesADeclaredUniqueConstraintNotJus...
This commit is contained in:
1 parent
f1061c5709
commit
83424e2d8e
1 file changed
+125
@@ -161,3 +161,128 @@ class DiskExecutorOnConflictTest {
|
|||||||
assertTrue(error.message!!.contains("no unique or primary key constraint"), error.message!!)
|
assertTrue(error.message!!.contains("no unique or primary key constraint"), error.message!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun conflictTargetMatchesADeclaredUniqueConstraintNotJustThePrimaryKey() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE, name TEXT)")
|
||||||
|
db.run("INSERT INTO users VALUES (1, 'ada@example.com', 'ada')")
|
||||||
|
val count = db.insertCount(
|
||||||
|
"INSERT INTO users VALUES (2, 'ada@example.com', 'ada2') ON CONFLICT (email) DO UPDATE SET name = excluded.name"
|
||||||
|
)
|
||||||
|
assertEquals(1, count)
|
||||||
|
assertEquals("ada2", (db.select("SELECT name FROM users WHERE email = 'ada@example.com'").rows[0][0] as Value.TextValue).value)
|
||||||
|
assertEquals(1, db.select("SELECT * FROM users").rows.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun conflictOnADifferentUnrelatedUniqueColumnStillThrows() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE, tag TEXT UNIQUE)")
|
||||||
|
db.run("INSERT INTO users VALUES (1, 'ada@example.com', 'x')")
|
||||||
|
val error = assertFailsWith<KestrelException> {
|
||||||
|
db.run("INSERT INTO users VALUES (2, 'new@example.com', 'x') ON CONFLICT (email) DO NOTHING")
|
||||||
|
}
|
||||||
|
assertTrue(error.message!!.contains("UNIQUE constraint"), error.message!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun conflictDoUpdateEnforcesNotNullOnTheResultingRow() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.seed()
|
||||||
|
val error = assertFailsWith<KestrelException> {
|
||||||
|
db.run("INSERT INTO accounts VALUES (1, 'ada', 100) ON CONFLICT (id) DO UPDATE SET name = NULL")
|
||||||
|
}
|
||||||
|
assertTrue(error.message!!.contains("NOT NULL"), error.message!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun conflictDoUpdateEnforcesCheckConstraintsOnTheResultingRow() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER CHECK (balance >= 0))")
|
||||||
|
db.run("INSERT INTO accounts VALUES (1, 100)")
|
||||||
|
val error = assertFailsWith<KestrelException> {
|
||||||
|
db.run("INSERT INTO accounts VALUES (1, 100) ON CONFLICT (id) DO UPDATE SET balance = -5")
|
||||||
|
}
|
||||||
|
assertTrue(error.message!!.contains("CHECK constraint"), error.message!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun conflictDoUpdateRunsForeignKeyValidationOnTheUpdatedRow() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE departments (id INTEGER PRIMARY KEY)")
|
||||||
|
db.run("INSERT INTO departments VALUES (1)")
|
||||||
|
db.run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept INTEGER REFERENCES departments(id))")
|
||||||
|
db.run("INSERT INTO employees VALUES (1, 1)")
|
||||||
|
val error = assertFailsWith<KestrelException> {
|
||||||
|
db.run("INSERT INTO employees VALUES (1, 1) ON CONFLICT (id) DO UPDATE SET dept = 999")
|
||||||
|
}
|
||||||
|
assertTrue(error.message!!.contains("FOREIGN KEY"), error.message!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun conflictDoUpdateCascadesToChildTablesWhenTheReferencedKeyChanges() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE departments (id INTEGER PRIMARY KEY)")
|
||||||
|
db.run("INSERT INTO departments VALUES (1)")
|
||||||
|
db.run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept INTEGER REFERENCES departments(id) ON UPDATE CASCADE)")
|
||||||
|
db.run("INSERT INTO employees VALUES (1, 1)")
|
||||||
|
db.insertCount("INSERT INTO departments VALUES (1) ON CONFLICT (id) DO UPDATE SET id = 2")
|
||||||
|
assertEquals(int(2), db.select("SELECT dept FROM employees WHERE id = 1").rows[0][0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compositePrimaryKeyConflictTargetIsRecognised() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE grades (student INTEGER, course INTEGER, score INTEGER, PRIMARY KEY (student, course))")
|
||||||
|
db.run("INSERT INTO grades VALUES (1, 10, 50)")
|
||||||
|
val count = db.insertCount(
|
||||||
|
"INSERT INTO grades VALUES (1, 10, 90) ON CONFLICT (student, course) DO UPDATE SET score = excluded.score"
|
||||||
|
)
|
||||||
|
assertEquals(1, count)
|
||||||
|
assertEquals(int(90), db.select("SELECT score FROM grades WHERE student = 1 AND course = 10").rows[0][0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun conflictDoUpdateDefaultKeywordFallsBackToColumnDefault() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, note TEXT DEFAULT 'fresh')")
|
||||||
|
db.run("INSERT INTO t VALUES (1, 'stale')")
|
||||||
|
db.insertCount("INSERT INTO t VALUES (1, 'stale') ON CONFLICT (id) DO UPDATE SET note = DEFAULT")
|
||||||
|
assertEquals("fresh", (db.select("SELECT note FROM t WHERE id = 1").rows[0][0] as Value.TextValue).value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onConflictInsideAnExplicitTransactionParticipatesInItsCommit() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.seed()
|
||||||
|
db.run("BEGIN")
|
||||||
|
db.insertCount("INSERT INTO accounts VALUES (1, 'ada', 999) ON CONFLICT (id) DO UPDATE SET balance = excluded.balance")
|
||||||
|
db.insertCount("INSERT INTO accounts VALUES (2, 'linus', 200) ON CONFLICT (id) DO NOTHING")
|
||||||
|
db.run("ROLLBACK")
|
||||||
|
assertEquals(int(100), db.select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
|
||||||
|
assertEquals(1, db.select("SELECT * FROM accounts").rows.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Durability proof unique to the disk-backed executor: a resolved conflict (one row inserted
|
||||||
|
* fresh, one row updated in place) must be readable after a real close/reopen of the backing
|
||||||
|
* files, not just correct in the live process's buffer pool - mirrors the reopen tests already
|
||||||
|
* established for SEQUENCE ([DiskExecutorSequenceTest.sequencePositionSurvivesAReopen]) and
|
||||||
|
* SAVEPOINT ([DiskExecutorSavepointTest.dataWrittenAfterASavepointRollbackSurvivesAReopenAfterCommit]).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun conflictResolutionSurvivesAReopen() {
|
||||||
|
val db = newDatabase()
|
||||||
|
db.seed()
|
||||||
|
db.insertCount(
|
||||||
|
"""
|
||||||
|
INSERT INTO accounts VALUES (1, 'ada', 999), (2, 'linus', 200)
|
||||||
|
ON CONFLICT (id) DO UPDATE SET balance = excluded.balance
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
db.reopen()
|
||||||
|
assertEquals(int(999), db.select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
|
||||||
|
assertEquals(int(200), db.select("SELECT balance FROM accounts WHERE id = 2").rows[0][0])
|
||||||
|
assertEquals(2, db.select("SELECT * FROM accounts").rows.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user