diff --git a/src/backend/clients/database/SqliteDatabaseClient.test.ts b/src/backend/clients/database/SqliteDatabaseClient.test.ts index 6bdbfa4ff..647b468c0 100644 --- a/src/backend/clients/database/SqliteDatabaseClient.test.ts +++ b/src/backend/clients/database/SqliteDatabaseClient.test.ts @@ -134,8 +134,7 @@ describe('SqliteDatabaseClient — boot and migrations', { timeout: DISK_MIGRATI 'UPDATE `group` SET `handle` = ? WHERE `id` = (SELECT MIN(`id`) FROM `group`)', ['design-team'], ); - // Without COLLATE NOCASE this differs per engine: MySQL rejects it via - // utf8mb4_unicode_ci while sqlite and postgres would let it through. + // Without NOCASE this differs per engine: mysql rejects, the others accept. await expect( client.write( 'UPDATE `group` SET `handle` = ? WHERE `id` = (SELECT MAX(`id`) FROM `group`)', @@ -144,6 +143,20 @@ describe('SqliteDatabaseClient — boot and migrations', { timeout: DISK_MIGRATI ).rejects.toThrow(/UNIQUE/iu); }); + it('matches a handle case-insensitively on lookup, not just on insert', async () => { + // Index-only NOCASE would leave `WHERE handle = ?` case-sensitive here while + // mysql's utf8mb4_unicode_ci column matched -- one query, two behaviours. + await client.write( + 'UPDATE `group` SET `handle` = ? WHERE `id` = (SELECT MIN(`id`) FROM `group`)', + ['design-team'], + ); + await expect( + client.read('SELECT `handle` FROM `group` WHERE `handle` = ?', [ + 'Design-Team', + ]), + ).resolves.toEqual([{ handle: 'design-team' }]); + }); + it('runs the javascript migrations, not just the .sql ones', async () => { // The `system` user only exists because 0025 (a .dbmig.js file) ran // inside the migration VM. diff --git a/src/backend/clients/database/migrations/mysql/mysql_mig_32.sql b/src/backend/clients/database/migrations/mysql/mysql_mig_32.sql index 14ee32d66..b2da63778 100644 --- a/src/backend/clients/database/migrations/mysql/mysql_mig_32.sql +++ b/src/backend/clients/database/migrations/mysql/mysql_mig_32.sql @@ -24,7 +24,7 @@ CALL _puter_add_col('group', 'name', '`name` varchar(255) DEFAULT NULL'); CALL _puter_add_col('group', 'handle', '`handle` varchar(64) DEFAULT NULL'); CALL _puter_add_col('group', 'deleted_at', '`deleted_at` timestamp NULL DEFAULT NULL'); CALL _puter_add_col('jct_user_group', 'org_owned', '`org_owned` tinyint(1) DEFAULT NULL'); -CALL _puter_add_col('user', 'requires_password_change', '`requires_password_change` tinyint(1) DEFAULT NULL'); +CALL _puter_add_col('user', 'requires_password_change', '`requires_password_change` tinyint(1) NOT NULL DEFAULT ''0'''); DROP PROCEDURE IF EXISTS _puter_add_team_indexes; DELIMITER // diff --git a/src/backend/clients/database/migrations/postgres/postgres_mig_21.sql b/src/backend/clients/database/migrations/postgres/postgres_mig_21.sql index 9c31ad539..f88ae6fc4 100644 --- a/src/backend/clients/database/migrations/postgres/postgres_mig_21.sql +++ b/src/backend/clients/database/migrations/postgres/postgres_mig_21.sql @@ -35,4 +35,4 @@ ALTER TABLE jct_user_group ADD COLUMN IF NOT EXISTS org_owned smallint; CREATE INDEX IF NOT EXISTS idx_jct_user_group_group ON jct_user_group (group_id, user_id); -ALTER TABLE "user" ADD COLUMN IF NOT EXISTS requires_password_change smallint; +ALTER TABLE "user" ADD COLUMN IF NOT EXISTS requires_password_change smallint NOT NULL DEFAULT 0; diff --git a/src/backend/clients/database/migrations/sqlite/0077_teams.sql b/src/backend/clients/database/migrations/sqlite/0077_teams.sql index 5140e27aa..a08b434c4 100644 --- a/src/backend/clients/database/migrations/sqlite/0077_teams.sql +++ b/src/backend/clients/database/migrations/sqlite/0077_teams.sql @@ -15,16 +15,15 @@ -- You should have received a copy of the GNU Affero General Public License -- along with this program. If not, see . --- Teams: a `group` row with `kind = 'team'` owns accounts and pays for them. --- Seeded system groups keep `kind` NULL, so a team query never returns them. +-- A `group` row with `kind = 'team'` owns accounts and pays for them; seeded +-- system groups keep `kind` NULL so a team query never returns them. ALTER TABLE `group` ADD COLUMN `kind` TEXT DEFAULT NULL; ALTER TABLE `group` ADD COLUMN `name` TEXT DEFAULT NULL; -ALTER TABLE `group` ADD COLUMN `handle` TEXT DEFAULT NULL; +ALTER TABLE `group` ADD COLUMN `handle` TEXT COLLATE NOCASE DEFAULT NULL; ALTER TABLE `group` ADD COLUMN `deleted_at` TIMESTAMP DEFAULT NULL; --- NOCASE so a handle is unique the way a username is (see 0055). MySQL gets this --- from utf8mb4_unicode_ci; postgres needs a lower() index. NULLs stay distinct. +-- NOCASE on both column and index, so lookups match mysql's utf8mb4_unicode_ci. CREATE UNIQUE INDEX IF NOT EXISTS `idx_group_handle` ON `group` (`handle` COLLATE NOCASE); @@ -38,5 +37,5 @@ ALTER TABLE `jct_user_group` ADD COLUMN `org_owned` INTEGER DEFAULT NULL; CREATE INDEX IF NOT EXISTS `idx_jct_user_group_group` ON `jct_user_group` (`group_id`, `user_id`); --- Fourth `requires_*` flag, enforced by assertVerifiedAccount. Set on admin reset. -ALTER TABLE `user` ADD COLUMN `requires_password_change` INTEGER DEFAULT NULL; +-- Fourth `requires_*` flag; assertVerifiedAccount will enforce it in phase 5. +ALTER TABLE `user` ADD COLUMN `requires_password_change` INTEGER NOT NULL DEFAULT 0;