From 9c2ffa9e47ac8aac0846d5017b683ddba5ddd33e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 15:42:44 +0200 Subject: [PATCH] test(sql): cover alterSequenceRestartValueOutOfNewBoundsFails --- .../store/sql/DiskExecutorSequenceTest.kt | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorSequenceTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorSequenceTest.kt index 78035e0..88196fe 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorSequenceTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorSequenceTest.kt @@ -158,3 +158,228 @@ class DiskExecutorSequenceTest { 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 { db.run("ALTER SEQUENCE seq RESTART WITH 20") } + } + + @Test + fun alteringAnUnknownSequenceFails() { + val db = newDatabase() + assertFailsWith { 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 { 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 { + 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 { 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 { db.run("CREATE SEQUENCE seq") } + } + + @Test + fun dropSequenceIsRejectedInsideAnExplicitTransaction() { + val db = newDatabase() + db.run("CREATE SEQUENCE seq") + db.run("BEGIN") + assertFailsWith { db.run("DROP SEQUENCE seq") } + } + + @Test + fun alterSequenceIsRejectedInsideAnExplicitTransaction() { + val db = newDatabase() + db.run("CREATE SEQUENCE seq") + db.run("BEGIN") + assertFailsWith { 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 { 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 { db.run("CREATE TABLE orders (id TEXT DEFAULT NEXTVAL('seq'))") } + } + + @Test + fun currvalAsColumnDefaultIsRejected() { + val db = newDatabase() + db.run("CREATE SEQUENCE seq") + assertFailsWith { 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 { 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 { db.run("UPDATE t SET id = 2 WHERE id = NEXTVAL('seq')") } + } + + @Test + fun nextvalInACheckConstraintIsRejectedAtCreateTableTime() { + val db = newDatabase() + db.run("CREATE SEQUENCE seq") + assertFailsWith { db.run("CREATE TABLE t (id INTEGER CHECK (id = NEXTVAL('seq')))") } + } + + @Test + fun createSequenceMinvalueExceedingMaxvalueFails() { + val db = newDatabase() + assertFailsWith { db.run("CREATE SEQUENCE seq MINVALUE 10 MAXVALUE 5") } + } + + @Test + fun createSequenceIncrementByZeroFails() { + val db = newDatabase() + assertFailsWith { db.run("CREATE SEQUENCE seq INCREMENT BY 0") } + } + + @Test + fun createSequenceStartOutsideBoundsFails() { + val db = newDatabase() + assertFailsWith { 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) + } +}