Database migration - add member_role_id to access level tables
## Context | | | |---|---| | **Phase** | 1 of 6 | | **Parallel with** | — | | **Blocked by** | — | | **Unblocks** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594877+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594878+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594879+ | ## Summary Add `member_role_id` column to the three protected branch access level tables. This is the foundational database change that all other issues in this epic depend on. ## Background Protected branch access is controlled via three join tables, each storing an "access level row" that is currently one of: a fixed role (`access_level` integer), a user (`user_id`), a group (`group_id`), or a deploy key (`deploy_key_id` — push only). To support custom roles, a new mutually exclusive type — `member_role_id` — must be added to all three tables. ## Tables to migrate - `protected_branch_merge_access_levels` - `protected_branch_push_access_levels` - `protected_branch_unprotect_access_levels` ## Changes required ### Migration Write a standard GitLab migration (in `db/migrate/`) adding to each of the three tables: ```ruby add_column :protected_branch_merge_access_levels, :member_role_id, :bigint add_column :protected_branch_push_access_levels, :member_role_id, :bigint add_column :protected_branch_unprotect_access_levels, :member_role_id, :bigint ``` ### Foreign key constraints Add FK constraints to `member_roles.id` on each table: ```ruby add_concurrent_foreign_key :protected_branch_merge_access_levels, :member_roles, column: :member_role_id, on_delete: :restrict add_concurrent_foreign_key :protected_branch_push_access_levels, :member_roles, column: :member_role_id, on_delete: :restrict add_concurrent_foreign_key :protected_branch_unprotect_access_levels, :member_roles, column: :member_role_id, on_delete: :restrict ``` > **Note on `ON DELETE` behaviour:** The FK is proposed as `ON DELETE RESTRICT` (prevent deletion of a `MemberRole` if it is referenced by any protected branch access level row). The alternative is `ON DELETE SET NULL` (nullify `member_role_id`, leaving the row in place but reverting it to an ambiguous state). Please confirm with the team which behaviour is correct before merging. Both options and their tradeoffs should be discussed in this issue. > > - `RESTRICT`: Protects data integrity — admins must explicitly remove the protected branch rule before deleting the custom role. Safer but more friction. > - `SET NULL`: Silently degrades — the access level row becomes a role-type entry with `access_level` whatever was set at creation time, potentially granting broader access than intended. ### Indexes ```ruby add_concurrent_index :protected_branch_merge_access_levels, :member_role_id add_concurrent_index :protected_branch_push_access_levels, :member_role_id add_concurrent_index :protected_branch_unprotect_access_levels, :member_role_id ``` ### No backfill needed This is a new feature column. All existing rows will have `member_role_id = NULL`. ### Post-migration Update `db/structure.sql` (handled automatically by Rails migration runner). ## Testing - Migration spec verifying columns, indexes, and FK constraints exist on all three tables after running the migration - Verify `db:migrate` and `db:rollback` both succeed cleanly ## Dependencies None — this is the first issue in the epic and unblocks all backend work. ## Labels
issue