fix(education): lock collection migration adoption

This commit is contained in:
2026-07-31 10:45:55 +08:00
parent c3c244a6f5
commit 2df6b3f26c
2 changed files with 34 additions and 0 deletions

View File

@@ -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,

View File

@@ -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");