test: cover index access and join execution

This commit is contained in:
dualfroz committed 2026-08-28 09:48:17 +02:00
1 parent 99227e6c78
commit bf8e197760
2 files changed
+233

No files matched your search

+115
View File
@@ -0,0 +1,115 @@
package kestrel
import kestrel.catalog.Catalog
import kestrel.engine.ExplainResult
import kestrel.engine.Executor
import kestrel.engine.SelectResult
import kestrel.engine.StatementResult
import kestrel.sql.lexer.Lexer
import kestrel.sql.parser.Parser
import kestrel.storage.Value
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class IndexTest {
private val catalog = Catalog()
private val executor = Executor(catalog)
private fun run(sql: String): List<StatementResult> =
Parser(Lexer(sql).tokenize()).parseProgram().map { executor.execute(it) }
private fun select(sql: String): SelectResult = run(sql).single() as SelectResult
private fun explain(sql: String): List<String> = (run(sql).single() as ExplainResult).plan
private fun seed() {
run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER)")
run(
"INSERT INTO users VALUES " +
"(1, 'ada', 36), (2, 'linus', 54), (3, 'grace', 85), (4, 'don', 54), (5, 'edsger', 40)"
)
}
@Test
fun equalityLookupMatchesFullScan() {
seed()
val scanned = select("SELECT name FROM users WHERE age = 54").rows
run("CREATE INDEX idxAge ON users (age)")
val indexed = select("SELECT name FROM users WHERE age = 54").rows
assertEquals(scanned, indexed)
assertEquals(2, indexed.size)
}
@Test
fun rangeLookupMatchesFullScan() {
seed()
val scanned = select("SELECT name FROM users WHERE age >= 40 AND age < 85").rows
run("CREATE INDEX idxAge ON users (age)")
val indexed = select("SELECT name FROM users WHERE age >= 40 AND age < 85").rows
assertEquals(scanned, indexed)
}
@Test
fun plannerChoosesIndexForEquality() {
seed()
assertTrue(explain("EXPLAIN SELECT name FROM users WHERE age = 54").any { it.contains("Seq Scan") })
run("CREATE INDEX idxAge ON users (age)")
val plan = explain("EXPLAIN SELECT name FROM users WHERE age = 54")
assertTrue(plan.any { it.contains("Index Scan using idxAge") }, "expected index scan, got $plan")
}
@Test
fun plannerChoosesIndexForRange() {
seed()
run("CREATE INDEX idxAge ON users (age)")
val plan = explain("EXPLAIN SELECT name FROM users WHERE age > 50")
assertTrue(plan.any { it.contains("Index Scan using idxAge") && it.contains("age > 50") }, "got $plan")
}
@Test
fun plannerFallsBackToScanWithoutIndex() {
seed()
val plan = explain("EXPLAIN SELECT name FROM users WHERE age > 50")
assertTrue(plan.any { it.contains("Seq Scan on users") }, "got $plan")
}
@Test
fun indexStaysCorrectAfterUpdateAndDelete() {
seed()
run("CREATE INDEX idxAge ON users (age)")
run("UPDATE users SET age = 54 WHERE name = 'ada'")
run("DELETE FROM users WHERE name = 'linus'")
val indexed = select("SELECT name FROM users WHERE age = 54").rows
.map { (it.single() as Value.TextValue).value }
assertEquals(setOf("ada", "don"), indexed.toSet())
}
@Test
fun dropIndexRemovesItFromPlanner() {
seed()
run("CREATE INDEX idxAge ON users (age)")
run("DROP INDEX idxAge")
val plan = explain("EXPLAIN SELECT name FROM users WHERE age = 54")
assertTrue(plan.any { it.contains("Seq Scan on users") }, "got $plan")
}
@Test
fun duplicateIndexIsRejected() {
seed()
run("CREATE INDEX idxAge ON users (age)")
assertFailsWith<KestrelException> { run("CREATE INDEX idxAge ON users (id)") }
}
@Test
fun indexOnUnknownColumnIsRejected() {
seed()
assertFailsWith<KestrelException> { run("CREATE INDEX bad ON users (missing)") }
}
@Test
fun droppingUnknownIndexIsRejected() {
assertFailsWith<KestrelException> { run("DROP INDEX ghost") }
}
}
+118
View File
@@ -0,0 +1,118 @@
package kestrel
import kestrel.catalog.Catalog
import kestrel.engine.ExplainResult
import kestrel.engine.Executor
import kestrel.engine.SelectResult
import kestrel.engine.StatementResult
import kestrel.sql.lexer.Lexer
import kestrel.sql.parser.Parser
import kestrel.storage.Value
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class JoinTest {
private val catalog = Catalog()
private val executor = Executor(catalog)
private fun run(sql: String): List<StatementResult> =
Parser(Lexer(sql).tokenize()).parseProgram().map { executor.execute(it) }
private fun select(sql: String): SelectResult = run(sql).single() as SelectResult
private fun explain(sql: String): List<String> = (run(sql).single() as ExplainResult).plan
private fun text(value: String) = Value.TextValue(value)
private fun int(value: Long) = Value.IntegerValue(value)
private fun seed() {
run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
run("INSERT INTO users VALUES (1, 'ada'), (2, 'linus'), (3, 'grace')")
run("CREATE TABLE orders (id INTEGER PRIMARY KEY, userId INTEGER, total INTEGER)")
run("INSERT INTO orders VALUES (10, 1, 100), (11, 1, 40), (12, 2, 70)")
}
@Test
fun innerJoinMatchesOnCondition() {
seed()
val result = select(
"SELECT u.name, o.total FROM users u INNER JOIN orders o ON u.id = o.userId ORDER BY o.total ASC"
)
assertEquals(listOf("name", "total"), result.columns)
assertEquals(
listOf(listOf(text("ada"), int(40)), listOf(text("linus"), int(70)), listOf(text("ada"), int(100))),
result.rows
)
}
@Test
fun leftJoinKeepsUnmatchedRowsWithNulls() {
seed()
val result = select(
"SELECT u.name, o.total FROM users u LEFT JOIN orders o ON u.id = o.userId ORDER BY o.total ASC"
)
assertEquals(listOf(text("grace"), Value.NullValue), result.rows[0])
assertEquals(listOf(text("ada"), int(40)), result.rows[1])
assertEquals(listOf(text("linus"), int(70)), result.rows[2])
assertEquals(listOf(text("ada"), int(100)), result.rows[3])
}
@Test
fun hashJoinAndNestedLoopProduceSameResult() {
seed()
val hashed = select(
"SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.userId ORDER BY o.total ASC"
).rows
val nested = select(
"SELECT u.name, o.total FROM users u JOIN orders o ON u.id >= o.userId AND u.id <= o.userId ORDER BY o.total ASC"
).rows
assertEquals(hashed, nested)
}
@Test
fun plannerUsesHashJoinForEquality() {
seed()
val plan = explain("EXPLAIN SELECT u.name FROM users u JOIN orders o ON u.id = o.userId")
assertTrue(plan.any { it.contains("Hash Join") }, "got $plan")
}
@Test
fun plannerUsesNestedLoopForInequality() {
seed()
val plan = explain("EXPLAIN SELECT u.name FROM users u JOIN orders o ON u.id < o.userId")
assertTrue(plan.any { it.contains("Nested Loop") }, "got $plan")
}
@Test
fun threeTableJoinResolvesColumns() {
seed()
run("CREATE TABLE items (orderId INTEGER, label TEXT)")
run("INSERT INTO items VALUES (10, 'book'), (11, 'pen'), (12, 'lamp')")
val result = select(
"SELECT u.name, i.label FROM users u " +
"JOIN orders o ON u.id = o.userId " +
"JOIN items i ON o.id = i.orderId ORDER BY i.label ASC"
)
assertEquals(
listOf(listOf(text("ada"), text("book")), listOf(text("linus"), text("lamp")), listOf(text("ada"), text("pen"))),
result.rows
)
}
@Test
fun unqualifiedColumnResolvesAcrossJoin() {
seed()
val result = select("SELECT name, total FROM users u JOIN orders o ON u.id = o.userId WHERE total > 50 ORDER BY total ASC")
assertEquals(listOf(listOf(text("linus"), int(70)), listOf(text("ada"), int(100))), result.rows)
}
@Test
fun ambiguousColumnIsRejected() {
seed()
assertFailsWith<KestrelException> {
select("SELECT id FROM users u JOIN orders o ON u.id = o.userId")
}
}
}