test(sql): cover createUniqueCompositeIndexAllowsMultipleRowsWithANul...
This commit is contained in:
1 parent
743ca28b0c
commit
e5569c3dc1
1 file changed
+157
@@ -1113,3 +1113,160 @@ class DiskExecutorTest {
|
||||
assertEquals(2, db.select("SELECT * FROM registrations").rows.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createUniqueCompositeIndexAllowsMultipleRowsWithANullInEitherColumn() {
|
||||
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, NULL), (1, NULL), (NULL, 100), (NULL, 100)")
|
||||
assertEquals(4, db.select("SELECT * FROM registrations").rows.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createUniqueCompositeIndexOverExistingDuplicateCombinationsFailsAndLeavesNoTraceOfTheIndex() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE registrations (event_id INTEGER, user_id INTEGER)")
|
||||
db.run("INSERT INTO registrations VALUES (1, 100), (1, 100)")
|
||||
assertFailsWith<DiskTableException> { db.run("CREATE UNIQUE INDEX idx_reg ON registrations(event_id, user_id)") }
|
||||
assertTrue(db.catalog.table("registrations").secondaryIndexes.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createUniqueCompositeIndexRejectsAnUpdateThatWouldDuplicateAnotherRowsCombination() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE registrations (id INTEGER PRIMARY KEY, 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, 1, 100), (2, 1, 200)")
|
||||
assertFailsWith<DiskTableException> { db.run("UPDATE registrations SET user_id = 100 WHERE id = 2") }
|
||||
assertEquals(int(200), db.select("SELECT user_id FROM registrations WHERE id = 2").rows.single().single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createUniqueCompositeIndexDataAndConstraintSurviveClosingAndReopeningTheCatalog() {
|
||||
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)")
|
||||
db.reopen()
|
||||
assertFailsWith<DiskTableException> { db.run("INSERT INTO registrations VALUES (1, 100)") }
|
||||
val handle = db.catalog.table("registrations").secondaryIndexes.single()
|
||||
assertEquals(listOf("event_id", "user_id"), handle.columnNames)
|
||||
assertTrue(handle.unique)
|
||||
}
|
||||
|
||||
// --- index pushdown: proven by DiskTable's access counters, not just by correct results,
|
||||
// since a full scan and an index lookup return identical rows for these queries.
|
||||
|
||||
@Test
|
||||
fun whereEqualityOnThePrimaryKeyUsesAPointLookupNotAFullScan() {
|
||||
val db = newDatabase()
|
||||
db.seedUsers()
|
||||
val table = db.catalog.table("users")
|
||||
val result = db.select("SELECT name FROM users WHERE id = 2")
|
||||
assertEquals(listOf(listOf(text("linus"))), result.rows)
|
||||
assertEquals(1, table.pointLookupCount)
|
||||
assertEquals(0, table.scanCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aMissingPrimaryKeyPointLookupReturnsNoRowsWithoutScanning() {
|
||||
val db = newDatabase()
|
||||
db.seedUsers()
|
||||
val table = db.catalog.table("users")
|
||||
val result = db.select("SELECT name FROM users WHERE id = 999")
|
||||
assertEquals(emptyList<List<Value>>(), result.rows)
|
||||
assertEquals(1, table.pointLookupCount)
|
||||
assertEquals(0, table.scanCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRangeOnThePrimaryKeyUsesARangeScanNotAFullScan() {
|
||||
val db = newDatabase()
|
||||
db.seedUsers()
|
||||
val table = db.catalog.table("users")
|
||||
val result = db.select("SELECT name FROM users WHERE id > 1 AND id <= 3 ORDER BY id")
|
||||
assertEquals(listOf(listOf(text("linus")), listOf(text("grace"))), result.rows)
|
||||
assertEquals(1, table.rangeScanCount)
|
||||
assertEquals(0, table.scanCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun equalityOnAnUnindexedColumnStillFallsBackToAFullScan() {
|
||||
val db = newDatabase()
|
||||
db.seedUsers()
|
||||
val table = db.catalog.table("users")
|
||||
val result = db.select("SELECT id FROM users WHERE name = 'linus'")
|
||||
assertEquals(listOf(listOf(int(2))), result.rows)
|
||||
assertEquals(1, table.scanCount)
|
||||
assertEquals(0, table.pointLookupCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun equalityOnASecondaryIndexedColumnUsesTheSecondaryIndexNotAFullScan() {
|
||||
val db = newDatabase()
|
||||
db.seedUsers()
|
||||
db.run("CREATE INDEX idx_age ON users(age)")
|
||||
val table = db.catalog.table("users")
|
||||
val scansBeforeSelect = table.scanCount // backfilling the new index already scanned once
|
||||
val result = db.select("SELECT name FROM users WHERE age = 54")
|
||||
assertEquals(listOf(listOf(text("linus"))), result.rows)
|
||||
assertEquals(1, table.secondaryLookupCount)
|
||||
assertEquals(scansBeforeSelect, table.scanCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun equalityOnAllColumnsOfACompositeSecondaryIndexUsesTheSecondaryIndexNotAFullScan() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, age INTEGER, name TEXT)")
|
||||
db.run("INSERT INTO users VALUES (1, 36, 'ada'), (2, 36, 'grace')")
|
||||
db.run("CREATE INDEX idx_both ON users(age, name)")
|
||||
val table = db.catalog.table("users")
|
||||
val scansBeforeSelect = table.scanCount // backfilling the new index already scanned once
|
||||
val result = db.select("SELECT id FROM users WHERE age = 36 AND name = 'grace'")
|
||||
assertEquals(listOf(listOf(int(2))), result.rows)
|
||||
assertEquals(1, table.secondaryLookupCount)
|
||||
assertEquals(scansBeforeSelect, table.scanCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun equalityOnOnlyOneColumnOfACompositeSecondaryIndexFallsBackToAFullScan() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, age INTEGER, name TEXT)")
|
||||
db.run("INSERT INTO users VALUES (1, 36, 'ada'), (2, 36, 'grace')")
|
||||
db.run("CREATE INDEX idx_both ON users(age, name)")
|
||||
val table = db.catalog.table("users")
|
||||
val scansBeforeSelect = table.scanCount
|
||||
val result = db.select("SELECT id FROM users WHERE age = 36")
|
||||
assertEquals(2, result.rows.size)
|
||||
assertEquals(0, table.secondaryLookupCount)
|
||||
assertEquals(scansBeforeSelect + 1, table.scanCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRangeOnASingleColumnSecondaryIndexUsesARangeScanNotAFullScan() {
|
||||
val db = newDatabase()
|
||||
db.seedUsers()
|
||||
db.run("CREATE INDEX idx_age ON users(age)")
|
||||
val table = db.catalog.table("users")
|
||||
val scansBeforeSelect = table.scanCount // backfilling the new index already scanned once
|
||||
val result = db.select("SELECT name FROM users WHERE age > 40 AND age <= 85 ORDER BY age")
|
||||
assertEquals(listOf(listOf(text("linus")), listOf(text("grace"))), result.rows)
|
||||
assertEquals(1, table.rangeScanCount)
|
||||
assertEquals(0, table.secondaryLookupCount)
|
||||
assertEquals(scansBeforeSelect, table.scanCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRangeOnTheFirstColumnOfACompositeSecondaryIndexUsesARangeScanNotAFullScan() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE order_lines (order_id INTEGER, line_no INTEGER, sku TEXT)")
|
||||
db.run("INSERT INTO order_lines VALUES (1, 1, 'apple'), (2, 1, 'kiwi'), (3, 1, 'mango')")
|
||||
db.run("CREATE INDEX idx_order_sku ON order_lines(order_id, sku)")
|
||||
val table = db.catalog.table("order_lines")
|
||||
val scansBeforeSelect = table.scanCount // backfilling the new index already scanned once
|
||||
val result = db.select("SELECT sku FROM order_lines WHERE order_id > 1 ORDER BY order_id")
|
||||
assertEquals(listOf(listOf(text("kiwi")), listOf(text("mango"))), result.rows)
|
||||
assertEquals(1, table.rangeScanCount)
|
||||
assertEquals(scansBeforeSelect, table.scanCount)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user