diff --git a/CHANGELOG.md b/CHANGELOG.md index 656cd66..98e7472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ All notable changes to **Tiger Core** (`webtigers/tiger-core`). Format follows ## [Unreleased] +## [1.19.0] — 2026-10-01 + +### Changed + +- **Active-only UNIQUE indexes on `user.email` + `user.username` — soft-delete now frees the value + (TIGER-274).** A plain `UNIQUE(col)` spans soft-deleted (`deleted=1`) rows, so soft-deleting a user + never freed their email/username and a re-signup with that address collided on the index. Migration + `0053` moves each unique index onto a generated `_active = IF(deleted=0, col, NULL)` column — + MySQL/MariaDB allow multiple NULLs in a unique index, so soft-deleted rows drop out and the value is + reusable, while live rows stay unique. The real `email`/`username` columns are untouched and the + finders already filter `deleted=0`, so there is no behavioral change for live rows. This is the + platform-wide convention for every unique + soft-deletable column (TIGER-274). + ## [1.18.1] — 2026-09-27 ### Fixed diff --git a/library/Tiger/Version.php b/library/Tiger/Version.php index ab5241b..90367db 100644 --- a/library/Tiger/Version.php +++ b/library/Tiger/Version.php @@ -9,5 +9,5 @@ class Tiger_Version { /** Current Tiger Core version. Keep in lockstep with the git tag cut for a release. */ - const VERSION = '1.18.1'; + const VERSION = '1.19.0'; } diff --git a/migrations/0053_active_only_user_unique.php b/migrations/0053_active_only_user_unique.php new file mode 100644 index 0000000..914e7d9 --- /dev/null +++ b/migrations/0053_active_only_user_unique.php @@ -0,0 +1,33 @@ +_active = IF(deleted=0, col, NULL)` instead: MySQL/MariaDB allow multiple NULLs in a unique + * index, so deleted rows drop out (NULL) and free the value, while live rows stay unique. The real + * `email`/`username` columns are untouched — `Tiger_Model_User::findByEmail`/finders already filter + * `deleted=0`, so no application code changes. This is the convention for every unique + soft-deletable + * column platform-wide (see TIGER-274). + */ +return [ + 'up' => [ + "ALTER TABLE `user` DROP INDEX `uq_user_email`", + "ALTER TABLE `user` ADD COLUMN `email_active` VARCHAR(191) GENERATED ALWAYS AS (IF(`deleted`=0, `email`, NULL)) VIRTUAL", + "ALTER TABLE `user` ADD UNIQUE KEY `uq_user_email_active` (`email_active`)", + "ALTER TABLE `user` DROP INDEX `uq_user_username`", + "ALTER TABLE `user` ADD COLUMN `username_active` VARCHAR(64) GENERATED ALWAYS AS (IF(`deleted`=0, `username`, NULL)) VIRTUAL", + "ALTER TABLE `user` ADD UNIQUE KEY `uq_user_username_active` (`username_active`)", + ], + 'down' => [ + "ALTER TABLE `user` DROP INDEX `uq_user_email_active`", + "ALTER TABLE `user` DROP COLUMN `email_active`", + "ALTER TABLE `user` ADD UNIQUE KEY `uq_user_email` (`email`)", + "ALTER TABLE `user` DROP INDEX `uq_user_username_active`", + "ALTER TABLE `user` DROP COLUMN `username_active`", + "ALTER TABLE `user` ADD UNIQUE KEY `uq_user_username` (`username`)", + ], +];