SQLite: migration 002 never dropped UNIQUE(project_id, user_id) on project_access #133
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
qwc-open/asiakirjat#133
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while fixing #126 (see PR #132). This is a separate defect and needs its own migration.
What's wrong
internal/database/migrations/sqlite/002_auth_groups.up.sqlintends to replace the per-user uniqueness onproject_accesswith one that includessource:But in SQLite the original constraint is a table-level
UNIQUE(project_id, user_id)declared insideCREATE TABLE(001_initial.up.sql). Its backing index issqlite_autoindex_project_access_1and cannot be removed withDROP INDEX. The statement above matches nothing and succeeds silently, so both constraints are live.Verified against a migrated test database —
sqlite_masterstill shows:Postgres (
DROP CONSTRAINT ... project_access_project_id_user_id_key) and MySQL (DROP INDEX uq_project_user) both do drop theirs correctly. SQLite is the default driver, so most deployments have the wrong schema.Impact
A user can hold only one
project_accessrow per project on SQLite, so multi-source grants are impossible:manualgrant and an LDAP/OAuth2 group mapping then grants them access,ProjectAccessStore.Granthits theUNIQUE(project_id, user_id)constraint. ItsON CONFLICT(project_id, user_id, source)clause targets the other index, so the violation isn't handled and the insert fails withUNIQUE constraint failed: project_access.project_id, project_access.user_id. The group access is never applied.Fix sketch
A SQLite migration that rebuilds the table without the inline
UNIQUE(project_id, user_id), in the style of the existing002_auth_groups.down.sqlrebuild:CREATE TABLE project_access_new (...)— same columns, no table-level UNIQUE, FKs preserved.DROP TABLE project_access; ALTER TABLE project_access_new RENAME TO project_access;idx_project_access_sourceand any other indexes.Existing data is safe: the surviving rows are already unique per
(project_id, user_id), which is stricter than the target constraint. Postgres and MySQL need no change, so the migration is SQLite-only.Worth a test asserting a user can hold a
manualand anldapgrant on the same project at once. PR #132 originally had that test; it could not pass against this schema, so it was narrowed to a single-row source-targeting check with a comment pointing here — restore the two-row version alongside the migration.Reported by Claude Opus 5.