test: cover GeneratedColumnTest and run
This commit is contained in:
1 parent
9af294b2b7
commit
00f08c1bca
1 file changed
+158
@@ -0,0 +1,158 @@
|
||||
package kestrel
|
||||
|
||||
import kestrel.catalog.Catalog
|
||||
import kestrel.engine.Executor
|
||||
import kestrel.engine.MergeResult
|
||||
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
|
||||
|
||||
/**
|
||||
* `GENERATED ALWAYS AS (expr) STORED` on the in-memory engine - a column whose value is always
|
||||
* derived from the rest of its own row, never assigned directly, and recomputed on every
|
||||
* INSERT/UPDATE. See [kestrel.store.sql.DiskExecutorGeneratedColumnTest] for the disk engine.
|
||||
*/
|
||||
class GeneratedColumnTest {
|
||||
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)
|
||||
|
||||
// ---- computed on INSERT ----
|
||||
|
||||
@Test
|
||||
fun generatedColumnIsComputedWhenOmittedFromInsert() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("INSERT INTO t (a) VALUES (5)")
|
||||
assertEquals(listOf(listOf(int(5), int(10))), select("SELECT a, b FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generatedColumnIsComputedWhenOmittedFromPositionalInsertColumnList() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("INSERT INTO t (a, b) VALUES (5, DEFAULT)")
|
||||
assertEquals(listOf(listOf(int(5), int(10))), select("SELECT a, b FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertRejectsAnExplicitValueForAGeneratedColumn() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
val error = assertFailsWith<KestrelException> { run("INSERT INTO t (a, b) VALUES (5, 99)") }
|
||||
assertTrue(error.message!!.contains("GENERATED"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generatedColumnRecomputesFreshForEveryRowOfAMultiRowInsert() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("INSERT INTO t (a) VALUES (1), (2), (3)")
|
||||
assertEquals(
|
||||
listOf(listOf(int(1), int(2)), listOf(int(2), int(4)), listOf(int(3), int(6))),
|
||||
select("SELECT a, b FROM t ORDER BY a").rows
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generatedColumnCanReferenceMultipleOtherColumns() {
|
||||
run("CREATE TABLE t (price INTEGER, qty INTEGER, total INTEGER GENERATED ALWAYS AS (price * qty) STORED)")
|
||||
run("INSERT INTO t (price, qty) VALUES (7, 3)")
|
||||
assertEquals(listOf(listOf(int(21))), select("SELECT total FROM t").rows)
|
||||
}
|
||||
|
||||
// ---- computed on UPDATE ----
|
||||
|
||||
@Test
|
||||
fun updatingADependencyColumnRecomputesTheGeneratedColumn() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("INSERT INTO t (a) VALUES (5)")
|
||||
run("UPDATE t SET a = 9")
|
||||
assertEquals(listOf(listOf(int(18))), select("SELECT b FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateRejectsAnExplicitValueForAGeneratedColumn() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("INSERT INTO t (a) VALUES (5)")
|
||||
val error = assertFailsWith<KestrelException> { run("UPDATE t SET b = 1") }
|
||||
assertTrue(error.message!!.contains("GENERATED"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateSetToDefaultOnAGeneratedColumnRecomputesIt() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("INSERT INTO t (a) VALUES (5)")
|
||||
run("UPDATE t SET a = 9, b = DEFAULT")
|
||||
assertEquals(listOf(listOf(int(18))), select("SELECT b FROM t").rows)
|
||||
}
|
||||
|
||||
// ---- ON CONFLICT DO UPDATE ----
|
||||
|
||||
@Test
|
||||
fun onConflictDoUpdateRecomputesTheGeneratedColumn() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("INSERT INTO t (id, a) VALUES (1, 5)")
|
||||
run("INSERT INTO t (id, a) VALUES (1, 20) ON CONFLICT (id) DO UPDATE SET a = EXCLUDED.a")
|
||||
assertEquals(listOf(listOf(int(40))), select("SELECT b FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onConflictDoUpdateRejectsAnExplicitValueForAGeneratedColumn() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("INSERT INTO t (id, a) VALUES (1, 5)")
|
||||
assertFailsWith<KestrelException> {
|
||||
run("INSERT INTO t (id, a) VALUES (1, 20) ON CONFLICT (id) DO UPDATE SET b = 1")
|
||||
}
|
||||
}
|
||||
|
||||
// ---- MERGE ----
|
||||
|
||||
@Test
|
||||
fun mergeInsertComputesTheGeneratedColumn() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("CREATE TABLE s (id INTEGER, a INTEGER)")
|
||||
run("INSERT INTO s VALUES (1, 5)")
|
||||
val result = exec(
|
||||
"MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED THEN INSERT (id, a) VALUES (s.id, s.a)"
|
||||
) as MergeResult
|
||||
assertEquals(MergeResult(1, 0, 0), result)
|
||||
assertEquals(listOf(listOf(int(10))), select("SELECT b FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mergeUpdateRecomputesTheGeneratedColumn() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED)")
|
||||
run("CREATE TABLE s (id INTEGER, a INTEGER)")
|
||||
run("INSERT INTO t (id, a) VALUES (1, 5)")
|
||||
run("INSERT INTO s VALUES (1, 9)")
|
||||
exec("MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN UPDATE SET a = s.a")
|
||||
assertEquals(listOf(listOf(int(18))), select("SELECT b FROM t").rows)
|
||||
}
|
||||
|
||||
// ---- ordering with CHECK / NOT NULL ----
|
||||
|
||||
@Test
|
||||
fun checkConstraintSeesTheComputedGeneratedValueNotItsOldOne() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED CHECK (b > 0))")
|
||||
assertFailsWith<KestrelException> { run("INSERT INTO t (a) VALUES (-1)") }
|
||||
run("INSERT INTO t (a) VALUES (3)")
|
||||
assertEquals(listOf(listOf(int(6))), select("SELECT b FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notNullOnAGeneratedColumnRejectsARowWhoseComputedValueIsNull() {
|
||||
run("CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a) STORED NOT NULL)")
|
||||
assertFailsWith<KestrelException> { run("INSERT INTO t (a) VALUES (NULL)") }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user