test: cover UpsertTest and run

This commit is contained in:
dualfroz committed 2026-09-10 19:08:49 +02:00
1 parent e1a2106130
commit 0f54d1fe15
1 file changed
+162
+162
View File
@@ -0,0 +1,162 @@
package kestrel
import kestrel.catalog.Catalog
import kestrel.engine.Executor
import kestrel.engine.InsertResult
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 UpsertTest {
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 exec(sql: String): StatementResult = run(sql).single()
private fun select(sql: String): SelectResult = exec(sql) as SelectResult
private fun insertCount(sql: String): Int = (exec(sql) as InsertResult).count
private fun seed() {
run("CREATE TABLE accounts (id INTEGER PRIMARY KEY, name TEXT NOT NULL, balance INTEGER NOT NULL)")
run("INSERT INTO accounts VALUES (1, 'ada', 100)")
}
@Test
fun noConflictInsertsNormallyAndReportsOneRow() {
seed()
val count = insertCount("INSERT INTO accounts VALUES (2, 'linus', 200) ON CONFLICT (id) DO NOTHING")
assertEquals(1, count)
assertEquals(2, select("SELECT * FROM accounts").rows.size)
}
@Test
fun conflictOnPrimaryKeyDoesNothingAndReportsZero() {
seed()
val count = insertCount("INSERT INTO accounts VALUES (1, 'ignored', 999) ON CONFLICT (id) DO NOTHING")
assertEquals(0, count)
assertEquals(Value.IntegerValue(100), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
assertEquals("ada", (select("SELECT name FROM accounts WHERE id = 1").rows[0][0] as Value.TextValue).value)
}
@Test
fun conflictWithBareOnConflictWithNoTargetCatchesAnyUniqueViolation() {
seed()
run("CREATE UNIQUE INDEX idxName ON accounts (name)")
val count = insertCount("INSERT INTO accounts VALUES (2, 'ada', 300) ON CONFLICT DO NOTHING")
assertEquals(0, count)
assertEquals(1, select("SELECT * FROM accounts").rows.size)
}
@Test
fun conflictDoUpdateAppliesTheAssignments() {
seed()
insertCount("INSERT INTO accounts VALUES (1, 'ada', 999) ON CONFLICT (id) DO UPDATE SET balance = 999")
assertEquals(Value.IntegerValue(999), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
assertEquals(1, select("SELECT * FROM accounts").rows.size)
}
@Test
fun conflictDoUpdateCanReferenceExcludedForTheProposedRow() {
seed()
insertCount("INSERT INTO accounts VALUES (1, 'ada', 50) ON CONFLICT (id) DO UPDATE SET balance = accounts.balance + excluded.balance")
assertEquals(Value.IntegerValue(150), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
}
@Test
fun conflictDoUpdateWithFailingWhereBehavesLikeDoNothingForThatRow() {
seed()
val count = insertCount(
"INSERT INTO accounts VALUES (1, 'ada', 999) ON CONFLICT (id) DO UPDATE SET balance = 999 WHERE excluded.balance < 0"
)
assertEquals(0, count)
assertEquals(Value.IntegerValue(100), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
}
@Test
fun conflictDoUpdateWithPassingWhereApplies() {
seed()
val count = insertCount(
"INSERT INTO accounts VALUES (1, 'ada', 999) ON CONFLICT (id) DO UPDATE SET balance = 999 WHERE excluded.balance > 0"
)
assertEquals(1, count)
assertEquals(Value.IntegerValue(999), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
}
@Test
fun multiRowInsertHandlesEachRowsConflictIndependently() {
seed()
val count = insertCount(
"""
INSERT INTO accounts VALUES (1, 'ada', 5), (2, 'linus', 200), (3, 'grace', 300)
ON CONFLICT (id) DO UPDATE SET balance = accounts.balance + excluded.balance
""".trimIndent()
)
assertEquals(3, count)
assertEquals(Value.IntegerValue(105), select("SELECT balance FROM accounts WHERE id = 1").rows[0][0])
assertEquals(Value.IntegerValue(200), select("SELECT balance FROM accounts WHERE id = 2").rows[0][0])
assertEquals(Value.IntegerValue(300), select("SELECT balance FROM accounts WHERE id = 3").rows[0][0])
}
@Test
fun laterRowInTheSameStatementSeesAnEarlierRowsInsert() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, hits INTEGER NOT NULL)")
val count = insertCount(
"""
INSERT INTO t VALUES (1, 1), (1, 1)
ON CONFLICT (id) DO UPDATE SET hits = t.hits + excluded.hits
""".trimIndent()
)
assertEquals(2, count)
assertEquals(Value.IntegerValue(2), select("SELECT hits FROM t WHERE id = 1").rows[0][0])
}
@Test
fun conflictTargetMustMatchAnExistingUniqueOrPrimaryKey() {
seed()
val error = assertFailsWith<KestrelException> {
run("INSERT INTO accounts VALUES (2, 'linus', 200) ON CONFLICT (name) DO NOTHING")
}
assertTrue(error.message!!.contains("no unique or primary key constraint"), error.message!!)
}
@Test
fun conflictTargetMatchesADeclaredUniqueConstraintNotJustThePrimaryKey() {
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE, name TEXT)")
run("INSERT INTO users VALUES (1, 'ada@example.com', 'ada')")
val count = insertCount(
"INSERT INTO users VALUES (2, 'ada@example.com', 'ada2') ON CONFLICT (email) DO UPDATE SET name = excluded.name"
)
assertEquals(1, count)
assertEquals("ada2", (select("SELECT name FROM users WHERE email = 'ada@example.com'").rows[0][0] as Value.TextValue).value)
assertEquals(1, select("SELECT * FROM users").rows.size)
}
@Test
fun conflictOnADifferentUnrelatedUniqueColumnStillThrows() {
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE, tag TEXT UNIQUE)")
run("INSERT INTO users VALUES (1, 'ada@example.com', 'x')")
val error = assertFailsWith<KestrelException> {
run("INSERT INTO users VALUES (2, 'new@example.com', 'x') ON CONFLICT (email) DO NOTHING")
}
assertTrue(error.message!!.contains("UNIQUE constraint"), error.message!!)
}
@Test
fun conflictDoUpdateEnforcesNotNullOnTheResultingRow() {
seed()
val error = assertFailsWith<KestrelException> {
run("INSERT INTO accounts VALUES (1, 'ada', 100) ON CONFLICT (id) DO UPDATE SET name = NULL")
}
assertTrue(error.message!!.contains("NOT NULL"), error.message!!)
}