test(sql): cover alterSequenceRestartValueOutOfNewBoundsFails

This commit is contained in:
dualfroz committed 2026-09-12 15:42:44 +02:00
1 parent 5d31d6daa0
commit 9c2ffa9e47
1 file changed
+225
@@ -158,3 +158,228 @@ class DiskExecutorSequenceTest {
assertEquals(listOf(listOf(int(1)), listOf(int(101))), db.select("SELECT id FROM t ORDER BY id").rows) assertEquals(listOf(listOf(int(1)), listOf(int(101))), db.select("SELECT id FROM t ORDER BY id").rows)
} }
@Test
fun alterSequenceRestartValueOutOfNewBoundsFails() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq MAXVALUE 10")
assertFailsWith<DiskExecutorException> { db.run("ALTER SEQUENCE seq RESTART WITH 20") }
}
@Test
fun alteringAnUnknownSequenceFails() {
val db = newDatabase()
assertFailsWith<DiskCatalogException> { db.run("ALTER SEQUENCE nope INCREMENT BY 2") }
}
@Test
fun currvalReturnsLastNextvalInThisSession() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER, again INTEGER)")
db.run("INSERT INTO t VALUES (NEXTVAL('seq'), CURRVAL('seq'))")
assertEquals(listOf(listOf(int(1), int(1))), db.select("SELECT id, again FROM t").rows)
}
@Test
fun currvalBeforeAnyNextvalInThisSessionFails() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
assertFailsWith<DiskExecutorException> { db.run("INSERT INTO t VALUES (CURRVAL('seq'))") }
}
@Test
fun currvalIsScopedPerExecutorInstanceNotPerCatalog() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
db.run("INSERT INTO t VALUES (NEXTVAL('seq'))")
val secondExecutor = DiskExecutor(db.catalog)
assertFailsWith<DiskExecutorException> {
parse("INSERT INTO t VALUES (CURRVAL('seq'))").map { secondExecutor.execute(it) }
}
}
@Test
fun alterSequenceInvalidatesRememberedCurrvalForThatSession() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
db.run("INSERT INTO t VALUES (NEXTVAL('seq'))")
db.run("ALTER SEQUENCE seq INCREMENT BY 1")
assertFailsWith<DiskExecutorException> { db.run("INSERT INTO t VALUES (CURRVAL('seq'))") }
}
@Test
fun rollbackDoesNotUndoANextvalAdvance() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
db.run("BEGIN")
db.run("INSERT INTO t VALUES (NEXTVAL('seq'))")
db.run("ROLLBACK")
db.run("INSERT INTO t VALUES (NEXTVAL('seq'))")
// The rolled-back row never landed, but NEXTVAL's advance to 1 survives - the surviving
// insert gets 2, not a reused 1.
assertEquals(listOf(listOf(int(2))), db.select("SELECT id FROM t").rows)
}
@Test
fun createSequenceIsRejectedInsideAnExplicitTransaction() {
val db = newDatabase()
db.run("BEGIN")
assertFailsWith<DiskExecutorException> { db.run("CREATE SEQUENCE seq") }
}
@Test
fun dropSequenceIsRejectedInsideAnExplicitTransaction() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("BEGIN")
assertFailsWith<DiskExecutorException> { db.run("DROP SEQUENCE seq") }
}
@Test
fun alterSequenceIsRejectedInsideAnExplicitTransaction() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("BEGIN")
assertFailsWith<DiskExecutorException> { db.run("ALTER SEQUENCE seq INCREMENT BY 2") }
}
@Test
fun nextvalAsColumnDefaultActsLikeAnAutoIncrement() {
val db = newDatabase()
db.run("CREATE SEQUENCE order_ids")
db.run("CREATE TABLE orders (id INTEGER PRIMARY KEY DEFAULT NEXTVAL('order_ids'), item TEXT)")
db.run("INSERT INTO orders (item) VALUES ('a'), ('b'), ('c')")
assertEquals(
listOf(listOf(int(1), Value.TextValue("a")), listOf(int(2), Value.TextValue("b")), listOf(int(3), Value.TextValue("c"))),
db.select("SELECT id, item FROM orders ORDER BY id").rows
)
}
@Test
fun createTableWithNextvalDefaultRequiresTheSequenceToAlreadyExist() {
val db = newDatabase()
assertFailsWith<DiskCatalogException> { db.run("CREATE TABLE orders (id INTEGER DEFAULT NEXTVAL('nope'))") }
}
@Test
fun createTableDoesNotConsumeASequenceValueJustToValidateTheDefault() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE orders (id INTEGER DEFAULT NEXTVAL('seq'))")
db.run("INSERT INTO orders (id) VALUES (DEFAULT)")
// If CREATE TABLE's own validation had already called NEXTVAL once, this would be 2.
assertEquals(listOf(listOf(int(1))), db.select("SELECT id FROM orders").rows)
}
@Test
fun nextvalDefaultOnANonIntegerColumnFails() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
assertFailsWith<DiskExecutorException> { db.run("CREATE TABLE orders (id TEXT DEFAULT NEXTVAL('seq'))") }
}
@Test
fun currvalAsColumnDefaultIsRejected() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
assertFailsWith<DiskExecutorException> { db.run("CREATE TABLE orders (id INTEGER DEFAULT CURRVAL('seq'))") }
}
@Test
fun addColumnWithNextvalDefaultIsRejected() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
assertFailsWith<DiskExecutorException> { db.run("ALTER TABLE t ADD COLUMN seq_id INTEGER DEFAULT NEXTVAL('seq')") }
}
@Test
fun nextvalIsUsableInUpdateSet() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
db.run("INSERT INTO t VALUES (NULL)")
db.run("UPDATE t SET id = NEXTVAL('seq')")
assertEquals(listOf(listOf(int(1))), db.select("SELECT id FROM t").rows)
}
@Test
fun nextvalInAWhereClauseIsRejected() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
db.run("INSERT INTO t VALUES (1)")
// Thrown by the shared Evaluator (no SequenceAccess given for a WHERE clause), not this
// executor's own code - see kestrel.engine.Evaluator.evaluateNextValue.
assertFailsWith<KestrelException> { db.run("UPDATE t SET id = 2 WHERE id = NEXTVAL('seq')") }
}
@Test
fun nextvalInACheckConstraintIsRejectedAtCreateTableTime() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
assertFailsWith<DiskExecutorException> { db.run("CREATE TABLE t (id INTEGER CHECK (id = NEXTVAL('seq')))") }
}
@Test
fun createSequenceMinvalueExceedingMaxvalueFails() {
val db = newDatabase()
assertFailsWith<DiskExecutorException> { db.run("CREATE SEQUENCE seq MINVALUE 10 MAXVALUE 5") }
}
@Test
fun createSequenceIncrementByZeroFails() {
val db = newDatabase()
assertFailsWith<DiskExecutorException> { db.run("CREATE SEQUENCE seq INCREMENT BY 0") }
}
@Test
fun createSequenceStartOutsideBoundsFails() {
val db = newDatabase()
assertFailsWith<DiskExecutorException> { db.run("CREATE SEQUENCE seq MINVALUE 10 MAXVALUE 20 START WITH 5") }
}
@Test
fun noMinvalueAndNoMaxvalueOnAlterRecomputeTheDefaultBound() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq MINVALUE 1 MAXVALUE 3")
db.run("ALTER SEQUENCE seq NO MAXVALUE")
db.run("CREATE TABLE t (id INTEGER)")
// Was capped at 3 before the ALTER; NO MAXVALUE lifts it back to the ascending default.
db.run("INSERT INTO t VALUES (NEXTVAL('seq')), (NEXTVAL('seq')), (NEXTVAL('seq')), (NEXTVAL('seq'))")
assertEquals(4, db.select("SELECT id FROM t").rows.size)
}
/**
* The core new guarantee this session adds over the in-memory engine's own sequence tests:
* [DiskCatalog.nextValue] writes the advanced counter straight through [DiskManager][kestrel.store.page.DiskManager],
* bypassing the WAL entirely - a reopen must still see the advance, not just a live process.
*/
@Test
fun sequencePositionSurvivesAReopen() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
db.run("INSERT INTO t VALUES (NEXTVAL('seq')), (NEXTVAL('seq'))")
db.reopen()
db.run("INSERT INTO t VALUES (NEXTVAL('seq'))")
assertEquals(listOf(listOf(int(1)), listOf(int(2)), listOf(int(3))), db.select("SELECT id FROM t ORDER BY id").rows)
}
/** Same durability guarantee as [sequencePositionSurvivesAReopen], but for a RESTART WITH position. */
@Test
fun alterSequenceRestartPositionSurvivesAReopen() {
val db = newDatabase()
db.run("CREATE SEQUENCE seq")
db.run("CREATE TABLE t (id INTEGER)")
db.run("INSERT INTO t VALUES (NEXTVAL('seq'))")
db.run("ALTER SEQUENCE seq RESTART WITH 1000")
db.reopen()
db.run("INSERT INTO t VALUES (NEXTVAL('seq'))")
assertEquals(listOf(listOf(int(1)), listOf(int(1000))), db.select("SELECT id FROM t ORDER BY id").rows)
}
}