fix(education): lock collection migration adoption
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
-- EDU-010: current-tenant manual Question Collection authoring lifecycle.
|
||||
-- JAVA_READ application authority is enforced in Java; database guards protect direct mutation.
|
||||
|
||||
-- Prevent concurrent collection/membership/question mutation across adoption validation,
|
||||
-- backfill, and trigger installation while retaining ordinary reads.
|
||||
LOCK TABLE education_question_collection,
|
||||
education_question_collection_question,
|
||||
education_question
|
||||
IN SHARE ROW EXCLUSIVE MODE;
|
||||
|
||||
ALTER TABLE education_question_collection
|
||||
ADD COLUMN publication_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
ADD COLUMN authoring_version INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
@@ -675,6 +675,33 @@ class EducationFlywayMigrationIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseMigrationLocksThatBlockMembershipAndQuestionWrites() throws SQLException {
|
||||
String schema = createSchema("collection_lock_modes");
|
||||
configureFlyway(schema, false).target("4100").load().migrate();
|
||||
try (Connection locker = DriverManager.getConnection(jdbcUrl(schema), USER, PASSWORD);
|
||||
Connection writer = DriverManager.getConnection(jdbcUrl(schema), USER, PASSWORD)) {
|
||||
locker.setAutoCommit(false);
|
||||
try (var statement = locker.createStatement()) {
|
||||
statement.execute("LOCK TABLE education_question_collection, education_question_collection_question, education_question IN SHARE ROW EXCLUSIVE MODE");
|
||||
}
|
||||
assertThat(queryLong(schema, """
|
||||
SELECT count(*) FROM pg_locks lock
|
||||
JOIN pg_class relation ON relation.oid=lock.relation
|
||||
WHERE lock.pid<>pg_backend_pid() AND lock.mode='ShareRowExclusiveLock' AND lock.granted
|
||||
AND relation.relname IN ('education_question_collection','education_question_collection_question','education_question')
|
||||
""")).isEqualTo(3L);
|
||||
try (var statement = writer.createStatement()) {
|
||||
statement.execute("SET lock_timeout='250ms'");
|
||||
assertThatThrownBy(() -> statement.execute("DELETE FROM education_question_collection_question WHERE false"))
|
||||
.hasMessageContaining("lock timeout");
|
||||
assertThatThrownBy(() -> statement.execute("UPDATE education_question SET deleted=deleted WHERE false"))
|
||||
.hasMessageContaining("lock timeout");
|
||||
}
|
||||
locker.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailV4110ForHistoricalCollectionEntryDivergence() throws SQLException {
|
||||
String schema = createSchema("collection_entry_divergence");
|
||||
|
||||
Reference in New Issue
Block a user