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)
|
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