test: cover compositeUniqueIndexSurvivesReopenAfterCheckpointAndKeeps...
This commit is contained in:
1 parent
9c87f22cbb
commit
69d45e0362
1 file changed
+173
@@ -372,4 +372,177 @@ class PersistenceTest {
|
||||
CREATE UNIQUE INDEX idx_email ON users (email);
|
||||
.exit
|
||||
""".trimIndent()
|
||||
)
|
||||
val afterReopen = runScript(
|
||||
dir,
|
||||
"""
|
||||
INSERT INTO users VALUES (2, 'ada@example.com');
|
||||
EXPLAIN SELECT id FROM users WHERE email = 'ada@example.com';
|
||||
.schema users
|
||||
.exit
|
||||
""".trimIndent()
|
||||
)
|
||||
assertContains(afterReopen, "Error")
|
||||
assertContains(afterReopen, "UNIQUE constraint")
|
||||
assertContains(afterReopen, "Index Scan using idx_email")
|
||||
assertContains(afterReopen, "CREATE UNIQUE INDEX idx_email ON users (email);")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun compositeUniqueIndexSurvivesReopenAfterCheckpointAndKeepsEnforcingAndAccelerating() {
|
||||
val dir = tempDir()
|
||||
runScript(
|
||||
dir,
|
||||
"""
|
||||
CREATE TABLE orders (id INTEGER PRIMARY KEY, region TEXT, seq INTEGER);
|
||||
INSERT INTO orders VALUES (1, 'EU', 1);
|
||||
CREATE UNIQUE INDEX idx_region_seq ON orders (region, seq);
|
||||
.exit
|
||||
""".trimIndent()
|
||||
)
|
||||
val afterReopen = runScript(
|
||||
dir,
|
||||
"""
|
||||
INSERT INTO orders VALUES (2, 'EU', 1);
|
||||
INSERT INTO orders VALUES (3, 'US', 1);
|
||||
EXPLAIN SELECT id FROM orders WHERE region = 'EU' AND seq = 1;
|
||||
.schema orders
|
||||
.exit
|
||||
""".trimIndent()
|
||||
)
|
||||
assertContains(afterReopen, "Error")
|
||||
assertContains(afterReopen, "UNIQUE constraint")
|
||||
assertContains(afterReopen, "Index Scan using idx_region_seq")
|
||||
assertContains(afterReopen, "CREATE UNIQUE INDEX idx_region_seq ON orders (region, seq);")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun compositePrimaryKeyAndForeignKeySurviveReopenAfterCheckpoint() {
|
||||
val dir = tempDir()
|
||||
runScript(
|
||||
dir,
|
||||
"""
|
||||
CREATE TABLE orders (region TEXT, order_no INTEGER, PRIMARY KEY (region, order_no));
|
||||
CREATE TABLE line_items (region TEXT, order_no INTEGER, sku TEXT, FOREIGN KEY (region, order_no) REFERENCES orders (region, order_no) ON DELETE CASCADE);
|
||||
INSERT INTO orders VALUES ('eu', 1);
|
||||
INSERT INTO line_items VALUES ('eu', 1, 'SKU1');
|
||||
.exit
|
||||
""".trimIndent()
|
||||
)
|
||||
val duplicateRejected = runScript(dir, "INSERT INTO orders VALUES ('eu', 1);\n.exit\n")
|
||||
assertContains(duplicateRejected, "Error")
|
||||
assertContains(duplicateRejected, "UNIQUE constraint")
|
||||
|
||||
val afterCascade = runScript(
|
||||
dir,
|
||||
"""
|
||||
DELETE FROM orders WHERE region = 'eu';
|
||||
SELECT * FROM line_items;
|
||||
.exit
|
||||
""".trimIndent()
|
||||
)
|
||||
assertTrue(!afterCascade.contains("SKU1"), "cascade delete across a composite FK must survive reopen")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alterTableAddColumnSurvivesReopenAfterCheckpointAndBackfillsAndDefaultsFutureInserts() {
|
||||
val dir = tempDir()
|
||||
runScript(
|
||||
dir,
|
||||
"""
|
||||
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
|
||||
INSERT INTO users VALUES (1, 'ada'), (2, 'linus');
|
||||
ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
|
||||
INSERT INTO users (id, name) VALUES (3, 'grace');
|
||||
.exit
|
||||
""".trimIndent()
|
||||
)
|
||||
val reopened = runScript(dir, "SELECT name, status FROM users ORDER BY id ASC;\n.exit\n")
|
||||
assertContains(reopened, "ada")
|
||||
assertContains(reopened, "active")
|
||||
val statusCount = Regex("active").findAll(reopened).count()
|
||||
assertEquals(3, statusCount, "all three rows (backfilled and freshly inserted) must carry the DEFAULT")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generatedColumnSurvivesReopenAfterCheckpointAndKeepsComputingFutureInserts() {
|
||||
val dir = tempDir()
|
||||
runScript(
|
||||
dir,
|
||||
"""
|
||||
CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED);
|
||||
INSERT INTO t (a) VALUES (5);
|
||||
.exit
|
||||
""".trimIndent()
|
||||
)
|
||||
runScript(dir, "INSERT INTO t (a) VALUES (7);\n.exit\n")
|
||||
val reopened = runScript(dir, "SELECT b FROM t ORDER BY a ASC;\n.exit\n")
|
||||
assertContains(reopened, "10")
|
||||
assertContains(reopened, "14")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generatedColumnSurvivesReopenWithoutACheckpointViaWalReplay() {
|
||||
val dir = tempDir()
|
||||
val persistence = Persistence(dir)
|
||||
persistence.appendWal(
|
||||
listOf(
|
||||
"CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED);",
|
||||
"INSERT INTO t (a) VALUES (5);"
|
||||
)
|
||||
)
|
||||
val output = StringBuilder()
|
||||
Repl(output, output, Persistence(dir)).run("SELECT b FROM t WHERE a = 5;\n.exit\n".reader().buffered())
|
||||
assertContains(output.toString(), "10")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alterTableRenameAndDropColumnSurviveReopenWithoutACheckpointViaWalReplay() {
|
||||
val dir = tempDir()
|
||||
val persistence = Persistence(dir)
|
||||
persistence.appendWal(
|
||||
listOf(
|
||||
"CREATE TABLE t (id INTEGER PRIMARY KEY, bio TEXT, scratch TEXT);",
|
||||
"INSERT INTO t VALUES (1, 'mathematician', 'temp');",
|
||||
"ALTER TABLE t RENAME COLUMN bio TO biography;",
|
||||
"ALTER TABLE t DROP COLUMN scratch;"
|
||||
)
|
||||
)
|
||||
val output = StringBuilder()
|
||||
Repl(output, output, Persistence(dir)).run("SELECT biography FROM t WHERE id = 1;\n.exit\n".reader().buffered())
|
||||
assertContains(output.toString(), "mathematician")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alterTableRenameToSurvivesReopenWithoutACheckpointViaWalReplay() {
|
||||
val dir = tempDir()
|
||||
val persistence = Persistence(dir)
|
||||
persistence.appendWal(
|
||||
listOf(
|
||||
"CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);",
|
||||
"INSERT INTO t VALUES (1, 'ada');",
|
||||
"ALTER TABLE t RENAME TO people;"
|
||||
)
|
||||
)
|
||||
val output = StringBuilder()
|
||||
Repl(output, output, Persistence(dir)).run("SELECT name FROM people WHERE id = 1;\n.tables\n.exit\n".reader().buffered())
|
||||
val text = output.toString()
|
||||
assertContains(text, "ada")
|
||||
val tableLines = text.lines().map { it.trim() }
|
||||
assertTrue(tableLines.contains("people"), "renamed table must appear in .tables")
|
||||
assertTrue(tableLines.none { it.equals("t", ignoreCase = true) }, "old table name must not linger in .tables")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alterTableRenameToPropagatesIntoADependentViewAcrossACheckpointReopen() {
|
||||
val dir = tempDir()
|
||||
runScript(
|
||||
dir,
|
||||
"""
|
||||
CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);
|
||||
INSERT INTO departments VALUES (1, 'eng');
|
||||
CREATE VIEW v AS SELECT id, name FROM departments;
|
||||
ALTER TABLE departments RENAME TO divisions;
|
||||
.exit
|
||||
""".trimIndent()
|
||||
}
|
||||
Reference in new issue
Block a user