From 9c87f22cbb52be9fb251f758f65a917ea82794f1 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 18:25:23 +0200 Subject: [PATCH] test: cover viewDefinitionSurvivesReopenAfterCheckpointAndStaysLiveOv... --- src/test/kotlin/kestrel/PersistenceTest.kt | 173 +++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/src/test/kotlin/kestrel/PersistenceTest.kt b/src/test/kotlin/kestrel/PersistenceTest.kt index acdc542..2c521d9 100644 --- a/src/test/kotlin/kestrel/PersistenceTest.kt +++ b/src/test/kotlin/kestrel/PersistenceTest.kt @@ -199,4 +199,177 @@ class PersistenceTest { assertContains(output.toString(), "1") assertContains(output.toString(), "2") } + + @Test + fun viewDefinitionSurvivesReopenAfterCheckpointAndStaysLiveOverNewData() { + val dir = tempDir() + runScript( + dir, + """ + CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER); + INSERT INTO users VALUES (1, 'ada', 36), (2, 'linus', 54); + CREATE VIEW adults AS SELECT id, name FROM users WHERE age >= 40; + .exit + """.trimIndent() + ) + val afterCheckpoint = runScript( + dir, + """ + SELECT name FROM adults ORDER BY id ASC; + INSERT INTO users VALUES (3, 'grace', 85); + .exit + """.trimIndent() + ) + assertContains(afterCheckpoint, "linus") + assertTrue(!afterCheckpoint.contains("ada"), "view predicate must exclude rows under the age threshold") + + val afterInsert = runScript(dir, "SELECT name FROM adults ORDER BY id ASC;\n.views\n.exit\n") + assertContains(afterInsert, "grace") + assertContains(afterInsert, "adults") + } + + @Test + fun dropViewSurvivesReopenWithoutACheckpoint() { + val dir = tempDir() + val persistence = Persistence(dir) + persistence.appendWal( + listOf( + "CREATE TABLE t (id INTEGER);", + "CREATE VIEW v AS SELECT id FROM t;", + "DROP VIEW v;" + ) + ) + val output = StringBuilder() + Repl(output, output, Persistence(dir)).run(".views\n.exit\n".reader().buffered()) + assertContains(output.toString(), "no views") + } + + @Test + fun updatesUsingScalarFunctionsAndPredicatesSurviveReopen() { + val dir = tempDir() + runScript( + dir, + """ + CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, bio TEXT); + INSERT INTO users VALUES (1, 'ada', 'mathematician'), (2, 'linus', NULL), (3, 'grace', 'admiral'); + UPDATE users SET name = UPPER(name) WHERE bio IS NOT NULL; + DELETE FROM users WHERE name IN ('linus') OR bio LIKE '%admiral'; + .exit + """.trimIndent() + ) + val reopened = runScript(dir, "SELECT name FROM users ORDER BY id ASC;\n.exit\n") + assertContains(reopened, "ADA") + assertTrue(!reopened.contains("linus"), "row matching NOT IN predicate must stay deleted") + assertTrue(!reopened.contains("grace") && !reopened.contains("GRACE"), "row matching LIKE predicate must stay deleted") + } + + @Test + fun decimalAndDateColumnsSurviveReopenAfterCheckpoint() { + val dir = tempDir() + runScript( + dir, + """ + CREATE TABLE orders (id INTEGER PRIMARY KEY, amount DECIMAL(10, 2), placed DATE); + INSERT INTO orders VALUES (1, 19.9, CAST('2024-03-01' AS DATE)); + .exit + """.trimIndent() + ) + val reopened = runScript(dir, "SELECT amount, placed FROM orders;\n.exit\n") + assertContains(reopened, "19.90") + assertContains(reopened, "2024-03-01") + } + + @Test + fun decimalAndDateColumnsSurviveReopenWithoutACheckpointViaWalReplay() { + val dir = tempDir() + val persistence = Persistence(dir) + persistence.appendWal( + listOf( + "CREATE TABLE orders (id INTEGER PRIMARY KEY, amount DECIMAL(10, 2), placed DATE);", + "INSERT INTO orders VALUES (1, CAST('42.5' AS DECIMAL(10, 2)), CAST('2024-03-01' AS DATE));" + ) + ) + val output = StringBuilder() + Repl(output, output, Persistence(dir)).run("SELECT amount, placed FROM orders;\n.exit\n".reader().buffered()) + assertContains(output.toString(), "42.50") + assertContains(output.toString(), "2024-03-01") + } + + @Test + fun foreignKeyAndCheckConstraintsSurviveReopenAfterCheckpoint() { + val dir = tempDir() + runScript( + dir, + """ + CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT); + CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id) ON DELETE CASCADE, age INTEGER CHECK (age > 0)); + INSERT INTO departments VALUES (1, 'eng'); + INSERT INTO employees VALUES (1, 1, 30); + .exit + """.trimIndent() + ) + val afterConstraintChecks = runScript( + dir, + """ + INSERT INTO employees VALUES (2, 99, 30); + .exit + """.trimIndent() + ) + assertContains(afterConstraintChecks, "Error") + assertContains(afterConstraintChecks, "FOREIGN KEY") + + val afterCascade = runScript( + dir, + """ + DELETE FROM departments WHERE id = 1; + SELECT * FROM employees; + .exit + """.trimIndent() + ) + assertContains(afterCascade, "0 rows") + } + + @Test + fun uniqueConstraintsSurviveReopenAfterCheckpoint() { + val dir = tempDir() + runScript( + dir, + """ + CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE, a INTEGER, b INTEGER, UNIQUE (a, b)); + INSERT INTO users VALUES (1, 'ada@example.com', 1, 1); + .exit + """.trimIndent() + ) + val afterReopen = runScript( + dir, + """ + INSERT INTO users VALUES (2, 'ada@example.com', 2, 2); + .exit + """.trimIndent() + ) + assertContains(afterReopen, "Error") + assertContains(afterReopen, "UNIQUE constraint") + + val afterCompositeViolation = runScript( + dir, + """ + INSERT INTO users VALUES (3, 'grace@example.com', 1, 1); + .exit + """.trimIndent() + ) + assertContains(afterCompositeViolation, "Error") + assertContains(afterCompositeViolation, "UNIQUE constraint") + } + + @Test + fun uniqueIndexSurvivesReopenAfterCheckpointAndKeepsEnforcingAndAccelerating() { + val dir = tempDir() + runScript( + dir, + """ + CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT); + INSERT INTO users VALUES (1, 'ada@example.com'); + CREATE UNIQUE INDEX idx_email ON users (email); + .exit + """.trimIndent() }