test(sql): cover aCompositePrimaryKeyThatSplitsSurvivesAReopenWithThe...

This commit is contained in:
dualfroz committed 2026-09-11 13:55:17 +02:00
1 parent 65424efff3
commit 97acea8145
1 file changed
+194
@@ -157,3 +157,197 @@ class DiskCatalogTest {
}
}
@Test
fun aCompositePrimaryKeyThatSplitsSurvivesAReopenWithTheCorrectRootAndPointLookupsStillWork() {
val dataFile = tempFile("kestrel-cat-data")
val walFile = tempFile("kestrel-cat-wal")
val catalog1 = DiskCatalog.open(dataFile, walFile)
val table1 = catalog1.createTable("order_lines", orderLineColumns, compositePrimaryKey = listOf("order_id", "line_no"))
val txn = catalog1.beginTransaction()
for (orderId in 1..50L) {
for (lineNo in 1..6L) {
table1.insert(txn, Row(listOf(Value.IntegerValue(orderId), Value.IntegerValue(lineNo), Value.TextValue("sku-$orderId-$lineNo"))))
}
}
catalog1.commit(txn)
catalog1.close()
val catalog2 = DiskCatalog.open(dataFile, walFile)
val table2 = catalog2.table("order_lines")
val reader = catalog2.beginTransaction()
assertEquals(300, table2.scan(reader).size)
val found = table2.findByPrimaryKey(reader, listOf(Value.IntegerValue(25), Value.IntegerValue(4)))!!
assertEquals("sku-25-4", (found.second.values[2] as Value.TextValue).value)
assertEquals(null, table2.findByPrimaryKey(reader, listOf(Value.IntegerValue(25), Value.IntegerValue(99))))
catalog2.commit(reader)
}
@Test
fun anAbortedTransactionsWritesAreInvisibleAfterAReopen() {
val dataFile = tempFile("kestrel-cat-data")
val walFile = tempFile("kestrel-cat-wal")
val catalog1 = DiskCatalog.open(dataFile, walFile)
val table1 = catalog1.createTable("users", usersColumns)
val committed = catalog1.beginTransaction()
table1.insert(committed, Row(listOf(Value.IntegerValue(1), Value.TextValue("alice"))))
catalog1.commit(committed)
val doomed = catalog1.beginTransaction()
table1.insert(doomed, Row(listOf(Value.IntegerValue(2), Value.TextValue("bob"))))
catalog1.abort(doomed)
catalog1.close()
val catalog2 = DiskCatalog.open(dataFile, walFile)
val table2 = catalog2.table("users")
val reader = catalog2.beginTransaction()
assertEquals(listOf(1L), table2.scan(reader).map { (it.second.values[0] as Value.IntegerValue).value })
catalog2.commit(reader)
}
@Test
fun aUniqueConstraintReferencingAnUnknownColumnIsRejected() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
assertFailsWith<DiskCatalogException> {
catalog.createTable("users", usersColumns, uniqueConstraints = listOf(UniqueConstraint(null, listOf("missing"))))
}
}
@Test
fun aUniqueIndexAndItsEnforcementSurviveAReopen() {
val dataFile = tempFile("kestrel-cat-data")
val walFile = tempFile("kestrel-cat-wal")
val columns = usersColumns + Column("email", ColumnType.Text, notNull = false, primaryKey = false)
val catalog1 = DiskCatalog.open(dataFile, walFile)
val table1 = catalog1.createTable("users", columns)
catalog1.createIndex("users", "idx_email", "email", unique = true)
val txn = catalog1.beginTransaction()
table1.insert(txn, Row(listOf(Value.IntegerValue(1), Value.TextValue("alice"), Value.TextValue("a@x.com"))))
catalog1.commit(txn)
catalog1.close()
val catalog2 = DiskCatalog.open(dataFile, walFile)
val table2 = catalog2.table("users")
assertTrue(table2.secondaryIndexes.single().unique)
val reader = catalog2.beginTransaction()
assertFailsWith<DiskTableException> {
table2.insert(reader, Row(listOf(Value.IntegerValue(2), Value.TextValue("bob"), Value.TextValue("a@x.com"))))
}
catalog2.abort(reader)
}
@Test
fun aUniqueIndexCreatedOverPreExistingDuplicatesFailsWithoutLeavingAPartialDirectoryEntry() {
val dataFile = tempFile("kestrel-cat-data")
val walFile = tempFile("kestrel-cat-wal")
val columns = usersColumns + Column("email", ColumnType.Text, notNull = false, primaryKey = false)
val catalog = DiskCatalog.open(dataFile, walFile)
val table = catalog.createTable("users", columns)
val txn = catalog.beginTransaction()
table.insert(txn, Row(listOf(Value.IntegerValue(1), Value.TextValue("alice"), Value.TextValue("dup@x.com"))))
table.insert(txn, Row(listOf(Value.IntegerValue(2), Value.TextValue("bob"), Value.TextValue("dup@x.com"))))
catalog.commit(txn)
assertFailsWith<DiskTableException> { catalog.createIndex("users", "idx_email", "email", unique = true) }
assertTrue(table.secondaryIndexes.isEmpty())
catalog.close()
val reopened = DiskCatalog.open(dataFile, walFile)
assertTrue(reopened.table("users").secondaryIndexes.isEmpty())
}
@Test
fun aCompositeUniqueIndexAndItsEnforcementSurviveAReopenWithPointLookupsStillWorking() {
val dataFile = tempFile("kestrel-cat-data")
val walFile = tempFile("kestrel-cat-wal")
val catalog1 = DiskCatalog.open(dataFile, walFile)
val table1 = catalog1.createTable("order_lines", orderLineColumns)
catalog1.createIndex("order_lines", "idx_order_sku", listOf("order_id", "sku"), unique = true)
val txn = catalog1.beginTransaction()
table1.insert(txn, Row(listOf(Value.IntegerValue(1), Value.IntegerValue(1), Value.TextValue("widget"))))
catalog1.commit(txn)
catalog1.close()
val catalog2 = DiskCatalog.open(dataFile, walFile)
val table2 = catalog2.table("order_lines")
val handle = table2.secondaryIndexes.single()
assertEquals(listOf("order_id", "sku"), handle.columnNames)
assertTrue(handle.unique)
val reader = catalog2.beginTransaction()
assertFailsWith<DiskTableException> {
table2.insert(reader, Row(listOf(Value.IntegerValue(1), Value.IntegerValue(2), Value.TextValue("widget"))))
}
val found = table2.findBySecondary(reader, "idx_order_sku", listOf(Value.IntegerValue(1), Value.TextValue("widget")))
assertEquals(1, found.size)
catalog2.abort(reader)
}
private val wideTextPadding = "x".repeat(200)
private fun DiskCatalog.buildWideTable(name: String): DiskTable {
val table = createTable(name, usersColumns)
createIndex(name, "idx_${name}_name", "name")
val txn = beginTransaction()
for (id in 1..60L) table.insert(txn, Row(listOf(Value.IntegerValue(id), Value.TextValue("$wideTextPadding-$id"))))
commit(txn)
return table
}
@Test
fun droppingATableReclaimsItsPagesForANewTableInsteadOfGrowingTheFile() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
catalog.buildWideTable("wide")
val peak = catalog.allocatedPageCount()
catalog.dropTable("wide")
catalog.buildWideTable("wide2")
assertTrue(
catalog.allocatedPageCount() <= peak,
"expected the dropped table's reclaimed heap/index pages to be reused by the new table instead of " +
"growing the file further (peak=$peak, now=${catalog.allocatedPageCount()})"
)
}
@Test
fun droppingASecondaryIndexReclaimsItsPagesForANewIndexOnTheSameTable() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
val table = catalog.createTable("wide", usersColumns)
val txn = catalog.beginTransaction()
for (id in 1..60L) table.insert(txn, Row(listOf(Value.IntegerValue(id), Value.TextValue("$wideTextPadding-$id"))))
catalog.commit(txn)
catalog.createIndex("wide", "idx_name", "name")
val peak = catalog.allocatedPageCount()
catalog.dropIndex("idx_name")
catalog.createIndex("wide", "idx_name_2", "name")
assertTrue(
catalog.allocatedPageCount() <= peak,
"expected the dropped index's reclaimed pages to be reused by the new index instead of growing the " +
"file further (peak=$peak, now=${catalog.allocatedPageCount()})"
)
assertEquals("idx_name_2", table.secondaryIndexes.single().name)
}
@Test
fun droppingASequenceReclaimsItsCounterPageForANewSequence() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
catalog.createSequence(SequenceDefinition("seq1", incrementBy = 1, minValue = 1, maxValue = Long.MAX_VALUE, startValue = 1, cycle = false))
val peak = catalog.allocatedPageCount()
catalog.dropSequence("seq1")
catalog.createSequence(SequenceDefinition("seq2", incrementBy = 1, minValue = 1, maxValue = Long.MAX_VALUE, startValue = 1, cycle = false))
assertTrue(
catalog.allocatedPageCount() <= peak,
"expected the dropped sequence's counter page to be reused instead of growing the file further " +
"(peak=$peak, now=${catalog.allocatedPageCount()})"
)
}
}