test(sql): cover dropIndexStopsThePlannerFromPushingDownAFormerlyInde...

This commit is contained in:
dualfroz committed 2026-09-12 20:11:39 +02:00
1 parent 71ee4dfabc
commit 743ca28b0c
1 file changed
+156
@@ -957,3 +957,159 @@ class DiskExecutorTest {
assertTrue(db.catalog.table("users").secondaryIndexes.isEmpty()) assertTrue(db.catalog.table("users").secondaryIndexes.isEmpty())
} }
@Test
fun dropIndexStopsThePlannerFromPushingDownAFormerlyIndexedEquality() {
val db = newDatabase()
db.seedUsers()
db.run("CREATE INDEX idx_age ON users(age)")
db.run("DROP INDEX idx_age")
val table = db.catalog.table("users")
val scansBefore = table.scanCount
db.select("SELECT * FROM users WHERE age = 54")
assertEquals(0, table.secondaryLookupCount)
assertEquals(scansBefore + 1, table.scanCount)
}
@Test
fun droppingAUniqueIndexStopsItFromRejectingFurtherDuplicates() {
val db = newDatabase()
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)")
db.run("CREATE UNIQUE INDEX idx_email ON users(email)")
db.run("INSERT INTO users VALUES (1, 'ada@example.com')")
db.run("DROP INDEX idx_email")
db.run("INSERT INTO users VALUES (2, 'ada@example.com')")
assertEquals(2, db.select("SELECT * FROM users").rows.size)
}
@Test
fun dropIndexOnACompositeIndexRemovesTheWholeIndexNotJustOneColumn() {
val db = newDatabase()
db.seedUsers()
db.run("CREATE INDEX idx_both ON users(age, name)")
db.run("DROP INDEX idx_both")
assertTrue(db.catalog.table("users").secondaryIndexes.isEmpty())
}
@Test
fun droppingAnUnknownIndexFails() {
val db = newDatabase()
db.seedUsers()
assertFailsWith<DiskCatalogException> { db.run("DROP INDEX ghost") }
}
@Test
fun droppingAnAlreadyDroppedIndexFails() {
val db = newDatabase()
db.seedUsers()
db.run("CREATE INDEX idx_age ON users(age)")
db.run("DROP INDEX idx_age")
assertFailsWith<DiskCatalogException> { db.run("DROP INDEX idx_age") }
}
@Test
fun dropIndexIsPersistedAcrossClosingAndReopeningTheCatalog() {
val db = newDatabase()
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)")
db.run("CREATE UNIQUE INDEX idx_email ON users(email)")
db.run("INSERT INTO users VALUES (1, 'ada@example.com')")
db.run("DROP INDEX idx_email")
db.reopen()
assertTrue(db.catalog.table("users").secondaryIndexes.isEmpty())
db.run("INSERT INTO users VALUES (2, 'ada@example.com')")
assertEquals(2, db.select("SELECT * FROM users").rows.size)
}
@Test
fun dropIndexCannotRunInsideAnExplicitTransaction() {
val db = newDatabase()
db.seedUsers()
db.run("CREATE INDEX idx_age ON users(age)")
db.run("BEGIN")
assertFailsWith<DiskExecutorException> { db.run("DROP INDEX idx_age") }
db.run("ROLLBACK")
}
// --- declarative UNIQUE constraints (inline `col TYPE UNIQUE` / table-level `UNIQUE (a, b, ...)`),
// enforced by a full heap scan per write rather than a backing index, mirroring
// kestrel.engine.Executor's own uniqueConstraints handling.
@Test
fun inlineUniqueColumnRejectsADuplicateValueOnInsert() {
val db = newDatabase()
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
db.run("INSERT INTO users VALUES (1, 'ada@example.com')")
assertFailsWith<DiskTableException> { db.run("INSERT INTO users VALUES (2, 'ada@example.com')") }
assertEquals(1, db.select("SELECT * FROM users").rows.size)
}
@Test
fun inlineUniqueColumnAllowsMultipleNulls() {
val db = newDatabase()
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
db.run("INSERT INTO users VALUES (1, NULL), (2, NULL)")
assertEquals(2, db.select("SELECT * FROM users").rows.size)
}
@Test
fun tableLevelCompositeUniqueConstraintRejectsADuplicateColumnCombination() {
val db = newDatabase()
db.run("CREATE TABLE registrations (event_id INTEGER, user_id INTEGER, UNIQUE (event_id, user_id))")
db.run("INSERT INTO registrations VALUES (1, 100)")
assertFailsWith<DiskTableException> { db.run("INSERT INTO registrations VALUES (1, 100)") }
// Sharing only one of the two columns is fine: the constraint is on the pair, not either column alone.
db.run("INSERT INTO registrations VALUES (1, 200)")
assertEquals(2, db.select("SELECT * FROM registrations").rows.size)
}
@Test
fun uniqueConstraintRejectsAnUpdateThatWouldDuplicateAnotherRowsValue() {
val db = newDatabase()
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
db.run("INSERT INTO users VALUES (1, 'ada@example.com'), (2, 'grace@example.com')")
assertFailsWith<DiskTableException> { db.run("UPDATE users SET email = 'ada@example.com' WHERE id = 2") }
}
@Test
fun uniqueConstraintReferencingAnUnknownColumnIsRejectedWithAClearError() {
val db = newDatabase()
val failure = assertFailsWith<DiskExecutorException> {
db.run("CREATE TABLE bad (id INTEGER PRIMARY KEY, UNIQUE (missing_column))")
}
assertTrue(failure.message!!.contains("missing_column"))
}
@Test
fun uniqueConstraintDataAndEnforcementSurviveClosingAndReopeningTheCatalog() {
val db = newDatabase()
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
db.run("INSERT INTO users VALUES (1, 'ada@example.com')")
db.reopen()
assertFailsWith<DiskTableException> { db.run("INSERT INTO users VALUES (2, 'ada@example.com')") }
assertEquals(1, db.select("SELECT * FROM users").rows.size)
}
// --- composite (multi-column) CREATE [UNIQUE] INDEX
@Test
fun createIndexBuildsANonUniqueCompositeSecondaryIndex() {
val db = newDatabase()
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, age INTEGER, name TEXT)")
val created = db.run("CREATE INDEX idx_both ON users(age, name)").single()
assertEquals(CreateIndexResult("idx_both"), created)
val handle = db.catalog.table("users").secondaryIndexes.single()
assertEquals(listOf("age", "name"), handle.columnNames)
assertEquals(false, handle.unique)
}
@Test
fun createUniqueCompositeIndexRejectsADuplicateColumnCombinationOnInsert() {
val db = newDatabase()
db.run("CREATE TABLE registrations (event_id INTEGER, user_id INTEGER)")
db.run("CREATE UNIQUE INDEX idx_reg ON registrations(event_id, user_id)")
db.run("INSERT INTO registrations VALUES (1, 100)")
assertFailsWith<DiskTableException> { db.run("INSERT INTO registrations VALUES (1, 100)") }
// Sharing only one of the two columns is fine: the index is on the pair, not either column alone.
db.run("INSERT INTO registrations VALUES (1, 200)")
assertEquals(2, db.select("SELECT * FROM registrations").rows.size)
}