test(sql): cover alterSequenceRestartValueOutOfNewBoundsFails
This commit is contained in:
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user