test(sql): cover DiskCatalogTest and tempFile

This commit is contained in:
dualfroz committed 2026-09-11 13:32:34 +02:00
1 parent 910c0761fa
commit 65424efff3
1 file changed
+159
@@ -0,0 +1,159 @@
package kestrel.store.sql
import kestrel.catalog.Column
import kestrel.catalog.SequenceDefinition
import kestrel.catalog.UniqueConstraint
import kestrel.storage.ColumnType
import kestrel.storage.Row
import kestrel.storage.Value
import java.io.File
import java.nio.file.Files
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class DiskCatalogTest {
private fun tempFile(prefix: String): File = Files.createTempFile(prefix, ".db").toFile().also { it.deleteOnExit() }
private val usersColumns = listOf(
Column("id", ColumnType.Integer, notNull = true, primaryKey = true),
Column("name", ColumnType.Text, notNull = true, primaryKey = false)
)
@Test
fun creatingTheSameTableTwiceFails() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
catalog.createTable("users", usersColumns)
assertFailsWith<DiskCatalogException> { catalog.createTable("users", usersColumns) }
}
@Test
fun droppedTableIsNoLongerListedOrQueryable() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
catalog.createTable("users", usersColumns)
catalog.dropTable("users")
assertTrue(catalog.tableNames.isEmpty())
assertFailsWith<DiskCatalogException> { catalog.table("users") }
}
@Test
fun reopeningAFreshProcessSeesTablesAndRowsThroughNewObjectsEntirely() {
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 txn1 = catalog1.beginTransaction()
table1.insert(txn1, Row(listOf(Value.IntegerValue(1), Value.TextValue("alice"))))
table1.insert(txn1, Row(listOf(Value.IntegerValue(2), Value.TextValue("bob"))))
catalog1.commit(txn1)
catalog1.close()
val catalog2 = DiskCatalog.open(dataFile, walFile)
assertEquals(listOf("users"), catalog2.tableNames)
val table2 = catalog2.table("users")
val reader = catalog2.beginTransaction()
assertEquals(setOf(1L, 2L), table2.scan(reader).map { (it.second.values[0] as Value.IntegerValue).value }.toSet())
val (_, aliceRow) = table2.findByPrimaryKey(reader, Value.IntegerValue(1))!!
assertEquals("alice", (aliceRow.values[1] as Value.TextValue).value)
catalog2.commit(reader)
}
@Test
fun aCommittedWriteThatNeverFlushedTheBufferPoolSurvivesAReopen() {
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 txn = catalog1.beginTransaction()
table1.insert(txn, Row(listOf(Value.IntegerValue(1), Value.TextValue("alice"))))
catalog1.commit(txn)
// No catalog1.close(): the dirty pages may still only live in the buffer pool.
val catalog2 = DiskCatalog.open(dataFile, walFile)
val table2 = catalog2.table("users")
val reader = catalog2.beginTransaction()
assertEquals("alice", table2.findByPrimaryKey(reader, Value.IntegerValue(1))!!.second.values[1].let { (it as Value.TextValue).value })
catalog2.commit(reader)
}
@Test
fun aPrimaryKeyIndexThatSplitsSurvivesAReopenWithTheCorrectRoot() {
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 txn = catalog1.beginTransaction()
for (id in 1..300L) table1.insert(txn, Row(listOf(Value.IntegerValue(id), Value.TextValue("user$id"))))
catalog1.commit(txn)
catalog1.close()
val catalog2 = DiskCatalog.open(dataFile, walFile)
val table2 = catalog2.table("users")
val reader = catalog2.beginTransaction()
assertEquals(300, table2.scan(reader).size)
for (id in listOf(1L, 150L, 300L)) {
assertEquals("user$id", table2.findByPrimaryKey(reader, Value.IntegerValue(id))!!.second.values[1].let { (it as Value.TextValue).value })
}
val range = table2.rangeByPrimaryKey(reader, Value.IntegerValue(100), true, Value.IntegerValue(105), true)
assertEquals((100..105L).toList(), range.map { (it.second.values[0] as Value.IntegerValue).value })
catalog2.commit(reader)
}
@Test
fun aSecondaryIndexCreatedBeforeCloseSurvivesAReopen() {
val dataFile = tempFile("kestrel-cat-data")
val walFile = tempFile("kestrel-cat-wal")
val columns = usersColumns + Column("dept", ColumnType.Text, notNull = false, primaryKey = false)
val catalog1 = DiskCatalog.open(dataFile, walFile)
val table1 = catalog1.createTable("users", columns)
catalog1.createIndex("users", "idx_dept", "dept")
val txn = catalog1.beginTransaction()
table1.insert(txn, Row(listOf(Value.IntegerValue(1), Value.TextValue("alice"), Value.TextValue("eng"))))
table1.insert(txn, Row(listOf(Value.IntegerValue(2), Value.TextValue("bob"), Value.TextValue("eng"))))
catalog1.commit(txn)
catalog1.close()
val catalog2 = DiskCatalog.open(dataFile, walFile)
val table2 = catalog2.table("users")
val reader = catalog2.beginTransaction()
val engineers = table2.findBySecondary(reader, "idx_dept", Value.TextValue("eng"))
assertEquals(setOf("alice", "bob"), engineers.map { (it.second.values[1] as Value.TextValue).value }.toSet())
catalog2.commit(reader)
}
private val orderLineColumns = listOf(
Column("order_id", ColumnType.Integer, notNull = true, primaryKey = false),
Column("line_no", ColumnType.Integer, notNull = true, primaryKey = false),
Column("sku", ColumnType.Text, notNull = false, primaryKey = false)
)
@Test
fun aTableCannotDeclareBothAnInlinePrimaryKeyColumnAndACompositePrimaryKey() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
assertFailsWith<DiskCatalogException> {
catalog.createTable("users", usersColumns, compositePrimaryKey = listOf("name"))
}
}
@Test
fun aCompositePrimaryKeyReferencingAnUnknownColumnIsRejected() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
assertFailsWith<DiskCatalogException> {
catalog.createTable("order_lines", orderLineColumns, compositePrimaryKey = listOf("order_id", "missing"))
}
}
@Test
fun aCompositePrimaryKeyListingTheSameColumnTwiceIsRejected() {
val catalog = DiskCatalog.open(tempFile("kestrel-cat-data"), tempFile("kestrel-cat-wal"))
assertFailsWith<DiskCatalogException> {
catalog.createTable("order_lines", orderLineColumns, compositePrimaryKey = listOf("order_id", "order_id"))
}
}