fix(db): add PRIMARY KEY to group_permissions and promote tbmp UNIQUE to PK (#469) #492
@@ -271,9 +271,10 @@ public class UserService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public UserGroup createGroup(GroupDTO dto) {
|
public UserGroup createGroup(GroupDTO dto) {
|
||||||
UserGroup group = new UserGroup();
|
UserGroup group = UserGroup.builder()
|
||||||
group.setName(dto.getName());
|
.name(dto.getName())
|
||||||
group.setPermissions(dto.getPermissions());
|
.permissions(dto.getPermissions() != null ? dto.getPermissions() : new HashSet<>())
|
||||||
|
.build();
|
||||||
return groupRepository.save(group);
|
return groupRepository.save(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,68 @@ 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());
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
} finally {
|
||||||
|
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 +544,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -902,4 +902,18 @@ class UserServiceTest {
|
|||||||
assertThat(result.getName()).isEqualTo("Familie");
|
assertThat(result.getName()).isEqualTo("Familie");
|
||||||
assertThat(result.getPermissions()).containsExactlyInAnyOrder("READ_ALL", "WRITE_ALL");
|
assertThat(result.getPermissions()).containsExactlyInAnyOrder("READ_ALL", "WRITE_ALL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createGroup_withNullPermissions_savesGroupWithEmptyPermissionSet() {
|
||||||
|
org.raddatz.familienarchiv.user.GroupDTO dto = new org.raddatz.familienarchiv.user.GroupDTO();
|
||||||
|
dto.setName("Leser");
|
||||||
|
dto.setPermissions(null);
|
||||||
|
|
||||||
|
UserGroup saved = UserGroup.builder().id(UUID.randomUUID()).name("Leser").build();
|
||||||
|
when(groupRepository.save(any())).thenReturn(saved);
|
||||||
|
|
||||||
|
userService.createGroup(dto);
|
||||||
|
|
||||||
|
verify(groupRepository).save(argThat(g -> g.getPermissions() != null && g.getPermissions().isEmpty()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user