From b50f24109e25ab8c32abacdeb39d4b2d3d7b422f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 16:04:58 +0200 Subject: [PATCH] test(sql): cover onConflictOnATemporaryTableIsRejected --- .../sql/DiskExecutorTemporaryTableTest.kt | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorTemporaryTableTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorTemporaryTableTest.kt index d77f42b..3caa64b 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorTemporaryTableTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorTemporaryTableTest.kt @@ -156,3 +156,132 @@ class DiskExecutorTemporaryTableTest { db.run("INSERT INTO scratch (id) VALUES (1)") assertEquals(listOf(int(1), int(10), int(20)), db.select("SELECT * FROM scratch").rows.single()) + assertFailsWith { db.run("INSERT INTO scratch (id, doubled) VALUES (2, 99)") } + } + + @Test + fun onConflictOnATemporaryTableIsRejected() { + val db = newDatabase() + db.run("CREATE TEMP TABLE scratch (id INTEGER PRIMARY KEY)") + db.run("INSERT INTO scratch VALUES (1)") + assertFailsWith { db.run("INSERT INTO scratch VALUES (1) ON CONFLICT (id) DO NOTHING") } + } + + @Test + fun foreignKeyOnATemporaryTableIsRejectedAtCreateTime() { + val db = newDatabase() + db.run("CREATE TABLE parent (id INTEGER PRIMARY KEY)") + assertFailsWith { + db.run("CREATE TEMP TABLE child (id INTEGER, parent_id INTEGER REFERENCES parent(id))") + } + } + + @Test + fun createIndexAndCreateTriggerOnATemporaryTableAreRejected() { + val db = newDatabase() + db.run("CREATE TEMP TABLE scratch (id INTEGER)") + assertFailsWith { db.run("CREATE INDEX scratch_idx ON scratch(id)") } + assertFailsWith { + db.run("CREATE TRIGGER scratch_trg AFTER INSERT ON scratch FOR EACH ROW INSERT INTO scratch VALUES (0)") + } + } + + @Test + fun aTemporaryTableJoinsAndCorrelatesWithARealDiskTable() { + val db = newDatabase() + db.run("CREATE TABLE orders (id INTEGER PRIMARY KEY, total INTEGER)") + db.run("INSERT INTO orders VALUES (1, 100), (2, 200)") + db.run("CREATE TEMP TABLE flagged (order_id INTEGER)") + db.run("INSERT INTO flagged VALUES (2)") + + val joined = db.select( + "SELECT orders.id, orders.total FROM orders JOIN flagged ON orders.id = flagged.order_id" + ) + assertEquals(listOf(listOf(int(2), int(200))), joined.rows) + + val correlated = db.select( + "SELECT id FROM orders WHERE EXISTS (SELECT 1 FROM flagged WHERE flagged.order_id = orders.id)" + ) + assertEquals(listOf(listOf(int(2))), correlated.rows) + } + + @Test + fun rollbackUndoesWritesToATemporaryTableEvenThoughItNeverTouchesTheWal() { + val db = newDatabase() + db.run("CREATE TEMP TABLE scratch (id INTEGER)") + db.run("INSERT INTO scratch VALUES (1)") + + db.run("BEGIN") + db.run("INSERT INTO scratch VALUES (2), (3)") + db.run("DELETE FROM scratch WHERE id = 1") + assertEquals(2, db.rowCount("SELECT * FROM scratch")) + assertEquals(RollbackResult, db.run("ROLLBACK").single()) + + assertEquals(1, db.rowCount("SELECT * FROM scratch")) + assertEquals(listOf(int(1)), db.select("SELECT * FROM scratch").rows.single()) + } + + @Test + fun commitKeepsWritesToATemporaryTable() { + val db = newDatabase() + db.run("CREATE TEMP TABLE scratch (id INTEGER)") + db.run("BEGIN") + db.run("INSERT INTO scratch VALUES (1)") + assertEquals(CommitResult, db.run("COMMIT").single()) + assertEquals(1, db.rowCount("SELECT * FROM scratch")) + } + + @Test + fun rollbackToSavepointUndoesOnlyTemporaryTableWritesAfterIt() { + val db = newDatabase() + db.run("CREATE TEMP TABLE scratch (id INTEGER)") + db.run("BEGIN") + db.run("INSERT INTO scratch VALUES (1)") + assertEquals(SavepointResult("mark"), db.run("SAVEPOINT mark").single()) + db.run("INSERT INTO scratch VALUES (2)") + assertEquals(2, db.rowCount("SELECT * FROM scratch")) + + assertEquals(RollbackToSavepointResult("mark"), db.run("ROLLBACK TO SAVEPOINT mark").single()) + assertEquals(1, db.rowCount("SELECT * FROM scratch")) + assertEquals(CommitResult, db.run("COMMIT").single()) + assertEquals(1, db.rowCount("SELECT * FROM scratch")) + } + + @Test + fun rollbackAlsoUndoesARealDiskTableWriteInTheSameTransactionAsATemporaryTableWrite() { + val db = newDatabase() + db.run("CREATE TABLE regular (id INTEGER PRIMARY KEY)") + db.run("CREATE TEMP TABLE scratch (id INTEGER)") + db.run("BEGIN") + db.run("INSERT INTO regular VALUES (1)") + db.run("INSERT INTO scratch VALUES (1)") + db.run("ROLLBACK") + assertEquals(0, db.rowCount("SELECT * FROM regular")) + assertEquals(0, db.rowCount("SELECT * FROM scratch")) + } + + @Test + fun returningWorksOnATemporaryTable() { + val db = newDatabase() + db.run("CREATE TEMP TABLE scratch (id INTEGER PRIMARY KEY, note TEXT)") + val inserted = db.run("INSERT INTO scratch VALUES (1, 'a') RETURNING id, note").single() as InsertResult + assertEquals(SelectResult(listOf("id", "note"), listOf(listOf(int(1), text("a")))), inserted.returning) + + val updated = db.run("UPDATE scratch SET note = 'b' WHERE id = 1 RETURNING note").single() as UpdateResult + assertEquals(SelectResult(listOf("note"), listOf(listOf(text("b")))), updated.returning) + + val deleted = db.run("DELETE FROM scratch WHERE id = 1 RETURNING id").single() as DeleteResult + assertEquals(SelectResult(listOf("id"), listOf(listOf(int(1)))), deleted.returning) + } + + @Test + fun dropTableRemovesATemporaryTableWithoutAffectingCatalogTables() { + val db = newDatabase() + db.run("CREATE TABLE regular (id INTEGER)") + db.run("CREATE TEMP TABLE scratch (id INTEGER)") + db.run("DROP TABLE scratch") + assertEquals(listOf("regular"), db.catalog.tableNames) + assertFalse(db.catalog.tableNames.contains("scratch")) + assertTrue(db.catalog.tableOrNull("regular") != null) + } +}