test: cover TruncateTest and run

This commit is contained in:
dualfroz committed 2026-09-10 17:53:46 +02:00
1 parent 628235ed19
commit 464777ed2c
1 file changed
+209
+209
View File
@@ -0,0 +1,209 @@
package kestrel
import kestrel.catalog.Catalog
import kestrel.engine.Executor
import kestrel.engine.SelectResult
import kestrel.engine.StatementResult
import kestrel.engine.TruncateTableResult
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 TruncateTest {
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 int(value: Long) = Value.IntegerValue(value)
@Test
fun truncateRemovesEveryRowButKeepsTheTableAndItsSchema() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT NOT NULL)")
run("INSERT INTO t VALUES (1, 'a'), (2, 'b')")
val result = exec("TRUNCATE TABLE t") as TruncateTableResult
assertEquals(listOf("t"), result.tableNames)
assertEquals(emptyList(), select("SELECT * FROM t").rows)
run("INSERT INTO t VALUES (1, 'fresh')")
assertEquals(listOf(listOf(int(1))), select("SELECT id FROM t").rows)
}
@Test
fun tableKeywordIsOptional() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY)")
run("INSERT INTO t VALUES (1)")
exec("TRUNCATE t")
assertEquals(emptyList(), select("SELECT * FROM t").rows)
}
@Test
fun multipleTablesInOneStatementAreAllTruncated() {
run("CREATE TABLE a (id INTEGER PRIMARY KEY)")
run("CREATE TABLE b (id INTEGER PRIMARY KEY)")
run("INSERT INTO a VALUES (1)")
run("INSERT INTO b VALUES (1)")
val result = exec("TRUNCATE TABLE a, b") as TruncateTableResult
assertEquals(setOf("a", "b"), result.tableNames.toSet())
assertEquals(emptyList(), select("SELECT * FROM a").rows)
assertEquals(emptyList(), select("SELECT * FROM b").rows)
}
@Test
fun truncatingAnUnknownTableFails() {
assertFailsWith<KestrelException> { run("TRUNCATE TABLE nope") }
}
@Test
fun truncatingAViewFailsWithAViewSpecificMessage() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY)")
run("CREATE VIEW v AS SELECT * FROM t")
val error = assertFailsWith<KestrelException> { run("TRUNCATE TABLE v") }
assertTrue(error.message!!.contains("view"), error.message)
}
@Test
fun continueIdentityIsTheDefaultAndLeavesTheSequencePositionAlone() {
run("CREATE SEQUENCE seq")
run("CREATE TABLE t (id INTEGER PRIMARY KEY DEFAULT NEXTVAL('seq'), label TEXT)")
run("INSERT INTO t (label) VALUES ('a'), ('b')")
exec("TRUNCATE TABLE t")
run("INSERT INTO t (label) VALUES ('c')")
assertEquals(listOf(listOf(int(3))), select("SELECT id FROM t").rows)
}
@Test
fun continueIdentityIsAlsoTheResultOfWritingItOutExplicitly() {
run("CREATE SEQUENCE seq")
run("CREATE TABLE t (id INTEGER PRIMARY KEY DEFAULT NEXTVAL('seq'), label TEXT)")
run("INSERT INTO t (label) VALUES ('a')")
exec("TRUNCATE TABLE t CONTINUE IDENTITY")
run("INSERT INTO t (label) VALUES ('b')")
assertEquals(listOf(listOf(int(2))), select("SELECT id FROM t").rows)
}
@Test
fun restartIdentityResetsTheOwningSequenceBackToItsStartValue() {
run("CREATE SEQUENCE seq")
run("CREATE TABLE t (id INTEGER PRIMARY KEY DEFAULT NEXTVAL('seq'), label TEXT)")
run("INSERT INTO t (label) VALUES ('a'), ('b'), ('c')")
exec("TRUNCATE TABLE t RESTART IDENTITY")
run("INSERT INTO t (label) VALUES ('fresh')")
assertEquals(listOf(listOf(int(1))), select("SELECT id FROM t").rows)
}
@Test
fun restartIdentityIgnoresAColumnWhoseDefaultIsNotNextval() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY, label TEXT DEFAULT 'x')")
run("INSERT INTO t VALUES (1, 'a')")
exec("TRUNCATE TABLE t RESTART IDENTITY")
run("INSERT INTO t VALUES (2, DEFAULT)")
assertEquals(listOf(listOf(int(2))), select("SELECT id FROM t").rows)
}
@Test
fun truncateIsRestrictedByDefaultWhenAnotherTableHasAForeignKeyIntoIt() {
run("CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id))")
run("INSERT INTO departments VALUES (1, 'eng')")
run("INSERT INTO employees VALUES (1, 1)")
val error = assertFailsWith<KestrelException> { run("TRUNCATE TABLE departments") }
assertTrue(error.message!!.contains("employees"), error.message)
assertEquals(listOf(listOf(int(1))), select("SELECT id FROM departments").rows)
}
@Test
fun truncateRestrictionAppliesEvenWhenTheForeignKeyDeclaresOnDeleteCascade() {
// TRUNCATE consults no per-row ON DELETE action at all - RESTRICT blocks regardless of it.
run("CREATE TABLE departments (id INTEGER PRIMARY KEY)")
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id) ON DELETE CASCADE)")
run("INSERT INTO departments VALUES (1)")
run("INSERT INTO employees VALUES (1, 1)")
assertFailsWith<KestrelException> { run("TRUNCATE TABLE departments") }
}
@Test
fun truncatingBothTablesInTheSameStatementNeedsNoCascade() {
run("CREATE TABLE departments (id INTEGER PRIMARY KEY)")
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id))")
run("INSERT INTO departments VALUES (1)")
run("INSERT INTO employees VALUES (1, 1)")
val result = exec("TRUNCATE TABLE departments, employees") as TruncateTableResult
assertEquals(setOf("departments", "employees"), result.tableNames.toSet())
}
@Test
fun truncateCascadeAlsoEmptiesEveryTransitiveReferencer() {
run("CREATE TABLE departments (id INTEGER PRIMARY KEY)")
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, dept_id INTEGER REFERENCES departments(id))")
run("CREATE TABLE timesheets (id INTEGER PRIMARY KEY, employee_id INTEGER REFERENCES employees(id))")
run("INSERT INTO departments VALUES (1)")
run("INSERT INTO employees VALUES (1, 1)")
run("INSERT INTO timesheets VALUES (1, 1)")
val result = exec("TRUNCATE TABLE departments CASCADE") as TruncateTableResult
assertEquals(setOf("departments", "employees", "timesheets"), result.tableNames.toSet())
assertEquals(emptyList(), select("SELECT * FROM timesheets").rows)
}
@Test
fun selfReferencingForeignKeyDoesNotBlockTruncatingItsOwnTable() {
run("CREATE TABLE employees (id INTEGER PRIMARY KEY, manager_id INTEGER REFERENCES employees(id))")
run("INSERT INTO employees VALUES (1, NULL), (2, 1)")
exec("TRUNCATE TABLE employees")
assertEquals(emptyList(), select("SELECT * FROM employees").rows)
}
@Test
fun truncateNeverFiresRowLevelTriggersUnlikeAWhereLessDelete() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY)")
run("CREATE TABLE audit (n INTEGER)")
run("INSERT INTO audit VALUES (0)")
run("CREATE TRIGGER log_delete AFTER DELETE ON t FOR EACH ROW BEGIN UPDATE audit SET n = n + 1; END")
run("INSERT INTO t VALUES (1), (2), (3)")
exec("TRUNCATE TABLE t")
assertEquals(listOf(listOf(int(0))), select("SELECT n FROM audit").rows)
assertEquals(emptyList(), select("SELECT * FROM t").rows)
}
@Test
fun rollbackAfterTruncateRestoresTheRows() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY)")
run("INSERT INTO t VALUES (1), (2)")
run("BEGIN")
exec("TRUNCATE TABLE t")
run("ROLLBACK")
assertEquals(listOf(listOf(int(1)), listOf(int(2))), select("SELECT id FROM t").rows)
}
@Test
fun rollbackAfterTruncateRestartIdentityAlsoRestoresTheSequencePosition() {
run("CREATE SEQUENCE seq")
run("CREATE TABLE t (id INTEGER PRIMARY KEY DEFAULT NEXTVAL('seq'), label TEXT)")
run("INSERT INTO t (label) VALUES ('a'), ('b')")
run("BEGIN")
exec("TRUNCATE TABLE t RESTART IDENTITY")
run("ROLLBACK")
// Both the rows (1, 2) AND the sequence's advanced position come back - if RESTART IDENTITY's
// reset had survived the ROLLBACK, this INSERT would collide with the just-restored id 1.
run("INSERT INTO t (label) VALUES ('c')")
assertEquals(listOf(int(1), int(2), int(3)), select("SELECT id FROM t").rows.map { it.single() })
}
@Test
fun truncateInsideATransactionCanStillBeCommitted() {
run("CREATE TABLE t (id INTEGER PRIMARY KEY)")
run("INSERT INTO t VALUES (1)")
run("BEGIN")
exec("TRUNCATE TABLE t")
run("COMMIT")
assertEquals(emptyList(), select("SELECT * FROM t").rows)
}
}