test: cover aTargetRowMatchedByTwoSourceRowsIsFineWhenOnlyOneClauseAc...
This commit is contained in:
1 parent
f9ab4ff6c5
commit
1798f78918
1 file changed
+156
@@ -154,3 +154,159 @@ class MergeTest {
|
||||
assertTrue(error.message!!.contains("a second time"), error.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aTargetRowMatchedByTwoSourceRowsIsFineWhenOnlyOneClauseActuallyFires() {
|
||||
setUpAccountsAndStaging()
|
||||
run("INSERT INTO accounts VALUES (1, 100)")
|
||||
run("INSERT INTO staging VALUES (1, 5), (1, 999)")
|
||||
exec(
|
||||
"MERGE INTO accounts USING staging ON accounts.id = staging.id " +
|
||||
"WHEN MATCHED AND staging.amount > 100 THEN UPDATE SET balance = accounts.balance + staging.amount"
|
||||
)
|
||||
assertEquals(listOf(listOf(int(1099))), select("SELECT balance FROM accounts").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anUnqualifiedColumnPresentOnBothSidesIsAmbiguousInOnCondition() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER)")
|
||||
run("CREATE TABLE s (id INTEGER, v INTEGER)")
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("MERGE INTO t USING s ON id = s.id WHEN MATCHED THEN UPDATE SET v = 1")
|
||||
}
|
||||
assertTrue(error.message!!.contains("ambiguous"), error.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aQualifierMatchingNeitherSideFails() {
|
||||
setUpAccountsAndStaging()
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("MERGE INTO accounts USING staging ON bogus.id = staging.id WHEN MATCHED THEN DELETE")
|
||||
}
|
||||
assertTrue(error.message!!.contains("no such table or alias"), error.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aWhenNotMatchedClauseCannotReferenceTheTargetTable() {
|
||||
setUpAccountsAndStaging()
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run(
|
||||
"MERGE INTO accounts USING staging ON accounts.id = staging.id " +
|
||||
"WHEN NOT MATCHED AND accounts.balance > 0 THEN INSERT (id, balance) VALUES (staging.id, 1)"
|
||||
)
|
||||
}
|
||||
assertTrue(error.message!!.contains("WHEN NOT MATCHED"), error.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun columnValidationRunsUpFrontEvenWhenNoSourceRowEverReachesTheBadClause() {
|
||||
setUpAccountsAndStaging()
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run(
|
||||
"MERGE INTO accounts USING staging ON accounts.id = staging.id " +
|
||||
"WHEN NOT MATCHED THEN INSERT (id, nope) VALUES (staging.id, 1)"
|
||||
)
|
||||
}
|
||||
assertTrue(error.message!!.contains("no such column"), error.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theTargetAndSourceCannotShareTheSameNameOrAlias() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY)")
|
||||
run("CREATE TABLE other (id INTEGER)")
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("MERGE INTO t USING other AS t ON t.id = t.id WHEN MATCHED THEN DELETE")
|
||||
}
|
||||
assertTrue(error.message!!.contains("target and source"), error.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sourceCanBeADerivedTableSubquery() {
|
||||
setUpAccountsAndStaging()
|
||||
run("INSERT INTO staging VALUES (1, 10), (1, 20)")
|
||||
exec(
|
||||
"MERGE INTO accounts USING (SELECT id, SUM(amount) AS total FROM staging GROUP BY id) AS agg " +
|
||||
"ON accounts.id = agg.id " +
|
||||
"WHEN NOT MATCHED THEN INSERT (id, balance) VALUES (agg.id, agg.total)"
|
||||
)
|
||||
assertEquals(listOf(listOf(int(1), int(30))), select("SELECT id, balance FROM accounts").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aDefaultKeywordInTheInsertValuesUsesTheColumnDefault() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT DEFAULT 'unlabeled')")
|
||||
run("CREATE TABLE s (id INTEGER)")
|
||||
run("INSERT INTO s VALUES (1)")
|
||||
exec("MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED THEN INSERT (id, label) VALUES (s.id, DEFAULT)")
|
||||
assertEquals(listOf(listOf(int(1), text("unlabeled"))), select("SELECT id, label FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anOmittedColumnListInsertsIntoEveryColumnInSchemaOrder() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT)")
|
||||
run("CREATE TABLE s (id INTEGER, label TEXT)")
|
||||
run("INSERT INTO s VALUES (1, 'a')")
|
||||
exec("MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED THEN INSERT VALUES (s.id, s.label)")
|
||||
assertEquals(listOf(listOf(int(1), text("a"))), select("SELECT id, label FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aNextvalDefaultStillAdvancesThroughAMergeInsert() {
|
||||
run("CREATE SEQUENCE seq")
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY DEFAULT NEXTVAL('seq'), label TEXT)")
|
||||
run("CREATE TABLE s (label TEXT)")
|
||||
run("INSERT INTO s VALUES ('a'), ('b')")
|
||||
exec("MERGE INTO t USING s ON t.label = s.label WHEN NOT MATCHED THEN INSERT (label) VALUES (s.label)")
|
||||
assertEquals(
|
||||
listOf(listOf(int(1), text("a")), listOf(int(2), text("b"))),
|
||||
select("SELECT id, label FROM t ORDER BY id").rows
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notNullViolationOnAMergeInsertFailsAndInsertsNothing() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT NOT NULL)")
|
||||
run("CREATE TABLE s (id INTEGER)")
|
||||
run("INSERT INTO s VALUES (1)")
|
||||
assertFailsWith<KestrelException> {
|
||||
run("MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED THEN INSERT (id) VALUES (s.id)")
|
||||
}
|
||||
assertEquals(emptyList(), select("SELECT * FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aForeignKeyViolationOnAMergeUpdateIsRejected() {
|
||||
run("CREATE TABLE departments (id INTEGER PRIMARY KEY)")
|
||||
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id))")
|
||||
run("CREATE TABLE moves (id INTEGER, new_dept INTEGER)")
|
||||
run("INSERT INTO departments VALUES (1)")
|
||||
run("INSERT INTO employees VALUES (1, 1)")
|
||||
run("INSERT INTO moves VALUES (1, 999)")
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("MERGE INTO employees USING moves ON employees.id = moves.id WHEN MATCHED THEN UPDATE SET dept_id = moves.new_dept")
|
||||
}
|
||||
assertTrue(error.message!!.contains("FOREIGN KEY"), error.message)
|
||||
assertEquals(listOf(listOf(int(1))), select("SELECT dept_id FROM employees").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rowTriggersFireForEachMergeDrivenAction() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT)")
|
||||
run("CREATE TABLE s (id INTEGER, label TEXT)")
|
||||
run("CREATE TABLE log (event TEXT)")
|
||||
run("INSERT INTO t VALUES (1, 'old'), (2, 'gone')")
|
||||
run("INSERT INTO s VALUES (1, 'new'), (2, 'new'), (3, 'fresh')")
|
||||
run("CREATE TRIGGER log_insert AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO log VALUES (CONCAT('insert:', NEW.label)); END")
|
||||
run("CREATE TRIGGER log_update AFTER UPDATE ON t FOR EACH ROW BEGIN INSERT INTO log VALUES (CONCAT('update:', NEW.label)); END")
|
||||
run("CREATE TRIGGER log_delete AFTER DELETE ON t FOR EACH ROW BEGIN INSERT INTO log VALUES (CONCAT('delete:', OLD.label)); END")
|
||||
exec(
|
||||
"MERGE INTO t USING s ON t.id = s.id " +
|
||||
"WHEN MATCHED AND s.id = 2 THEN DELETE " +
|
||||
"WHEN MATCHED THEN UPDATE SET label = s.label " +
|
||||
"WHEN NOT MATCHED THEN INSERT (id, label) VALUES (s.id, s.label)"
|
||||
)
|
||||
assertEquals(
|
||||
setOf(text("update:new"), text("delete:gone"), text("insert:fresh")),
|
||||
select("SELECT event FROM log").rows.map { it.single() }.toSet()
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user