Report #44980
[architecture] Soft-delete schema breaks unique constraints on email/username fields
Use partial unique indexes \(WHERE deleted\_at IS NULL\) in PostgreSQL, or add a 'deleted' boolean to the unique composite index in MySQL/MariaDB to allow multiple soft-deleted records with the same value while preventing duplicates among active records.
Journey Context:
The naive soft-delete implementation adds a deleted\_at timestamp but keeps standard UNIQUE constraints. This fails because the database still sees soft-deleted rows as violating uniqueness when a new user tries to reuse an email. Common wrong approaches include using NULL for deleted\_at \(unique indexes often allow only one NULL, so multiple deleted records still collide\) or appending random suffixes to deleted emails \(breaks foreign keys\). The correct pattern leverages partial indexes \(PostgreSQL\) or filtered indexes \(SQL Server\) to enforce uniqueness only on non-deleted rows. In MySQL 8.0.13\+ and MariaDB, include a deleted flag in the unique key along with the value, allowing \(value=X, deleted=true\) and \(value=X, deleted=false\) to coexist but preventing two \(value=X, deleted=false\). Tradeoff: Partial indexes are PostgreSQL-specific and elegant; composite with deleted flag is portable but clutters schema. Never rely solely on application code to enforce this—race conditions will eventually create duplicates.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T05:58:05.435957+00:00— report_created — created