fix(db): add PRIMARY KEY to group_permissions; promote tbmp UNIQUE to PK
V63 deduplicates any phantom (group_id, permission) rows accumulated since the initial schema. V64 sets NOT NULL on permission and adds pk_group_permissions. V65 renames uq_tbmp_block_person to pk_tbmp for naming-convention consistency. Integration tests confirm each constraint via pg_catalog.pg_constraint. Closes #469 (partial). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
-- Remove duplicate (group_id, permission) rows that accumulated without a UNIQUE constraint.
|
||||||
|
-- Keeps the row with the smallest ctid (earliest physical insertion order).
|
||||||
|
DELETE FROM group_permissions a
|
||||||
|
USING group_permissions b
|
||||||
|
WHERE a.ctid < b.ctid
|
||||||
|
AND a.group_id = b.group_id
|
||||||
|
AND a.permission = b.permission;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- Add NOT NULL and PRIMARY KEY to group_permissions.
|
||||||
|
-- Requires V63 to have run first (no duplicates can remain).
|
||||||
|
--
|
||||||
|
-- After this migration, future seed migrations can use:
|
||||||
|
-- INSERT INTO group_permissions ... ON CONFLICT DO NOTHING
|
||||||
|
-- instead of the INSERT ... WHERE NOT EXISTS pattern used before V64.
|
||||||
|
ALTER TABLE group_permissions
|
||||||
|
ALTER COLUMN permission SET NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE group_permissions
|
||||||
|
ADD CONSTRAINT pk_group_permissions PRIMARY KEY (group_id, permission);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- Promote the de-facto unique constraint on transcription_block_mentioned_persons to a named PK.
|
||||||
|
-- uq_tbmp_block_person (added in V57) is backed by a B-tree index identical to a PK;
|
||||||
|
-- this rename makes the naming convention explicit (pk_* vs uq_*).
|
||||||
|
ALTER TABLE transcription_block_mentioned_persons
|
||||||
|
DROP CONSTRAINT uq_tbmp_block_person;
|
||||||
|
|
||||||
|
ALTER TABLE transcription_block_mentioned_persons
|
||||||
|
ADD CONSTRAINT pk_tbmp PRIMARY KEY (block_id, person_id);
|
||||||
@@ -399,6 +399,66 @@ class MigrationIntegrationTest {
|
|||||||
AND dc.annotation_id IS NOT NULL
|
AND dc.annotation_id IS NOT NULL
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
// ─── V63+V64: group_permissions dedup + primary key ──────────────────────
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void v64_pk_group_permissions_exists() {
|
||||||
|
Integer count = jdbc.queryForObject(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*) FROM pg_catalog.pg_constraint c
|
||||||
|
JOIN pg_catalog.pg_class t ON c.conrelid = t.oid
|
||||||
|
WHERE t.relname = 'group_permissions'
|
||||||
|
AND c.conname = 'pk_group_permissions'
|
||||||
|
AND c.contype = 'p'
|
||||||
|
""",
|
||||||
|
Integer.class);
|
||||||
|
assertThat(count).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void v64_permission_column_isNotNullable() {
|
||||||
|
Integer count = jdbc.queryForObject(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*) FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'group_permissions'
|
||||||
|
AND column_name = 'permission'
|
||||||
|
AND is_nullable = 'NO'
|
||||||
|
""",
|
||||||
|
Integer.class);
|
||||||
|
assertThat(count).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
void v64_rejectsDuplicateGroupPermission() {
|
||||||
|
UUID groupId = createUserGroup("DuplicateTestGroup-" + UUID.randomUUID());
|
||||||
|
jdbc.update("INSERT INTO group_permissions (group_id, permission) VALUES (?, 'READ_ALL')", groupId);
|
||||||
|
|
||||||
|
assertThatThrownBy(() ->
|
||||||
|
jdbc.update("INSERT INTO group_permissions (group_id, permission) VALUES (?, 'READ_ALL')", groupId)
|
||||||
|
).isInstanceOf(DataIntegrityViolationException.class);
|
||||||
|
|
||||||
|
jdbc.update("DELETE FROM group_permissions WHERE group_id = ?", groupId);
|
||||||
|
jdbc.update("DELETE FROM user_groups WHERE id = ?", groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── V65: tbmp UNIQUE promoted to PRIMARY KEY ─────────────────────────────
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void v65_pk_tbmp_exists() {
|
||||||
|
Integer count = jdbc.queryForObject(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*) FROM pg_catalog.pg_constraint c
|
||||||
|
JOIN pg_catalog.pg_class t ON c.conrelid = t.oid
|
||||||
|
WHERE t.relname = 'transcription_block_mentioned_persons'
|
||||||
|
AND c.conname = 'pk_tbmp'
|
||||||
|
AND c.contype = 'p'
|
||||||
|
""",
|
||||||
|
Integer.class);
|
||||||
|
assertThat(count).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── helpers ─────────────────────────────────────────────────────────────
|
// ─── helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private UUID createPerson(String firstName, String lastName) {
|
private UUID createPerson(String firstName, String lastName) {
|
||||||
@@ -482,4 +542,10 @@ class MigrationIntegrationTest {
|
|||||||
""", id, recipientId, docId, commentId);
|
""", id, recipientId, docId, commentId);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UUID createUserGroup(String name) {
|
||||||
|
UUID id = UUID.randomUUID();
|
||||||
|
jdbc.update("INSERT INTO user_groups (id, name) VALUES (?, ?)", id, name);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user