selectRaw('LOWER(email) as normalized_email') ->selectRaw('COUNT(*) as user_count') ->selectRaw("STRING_AGG(id::text || ' <' || email || '>', ', ' ORDER BY email) as users") ->where('is_placeholder', false) ->groupByRaw('LOWER(email)') ->havingRaw('COUNT(*) > 1') ->orderBy('normalized_email') ->get(); if ($duplicateEmails->isNotEmpty()) { $duplicateEmailMessage = $duplicateEmails ->take(20) ->map(fn (stdClass $duplicateEmail): string => sprintf( '%s (%d users: %s)', $duplicateEmail->normalized_email, $duplicateEmail->user_count, $duplicateEmail->users, )) ->implode('; '); $remainingDuplicateCount = $duplicateEmails->count() - 20; $remainingDuplicateMessage = $remainingDuplicateCount > 0 ? sprintf('; and %d more duplicate normalized emails', $remainingDuplicateCount) : ''; throw new RuntimeException( 'Cannot lowercase users.email because doing so would create duplicate non-placeholder user emails and violate the unique index on users.email for non-placeholder users. Resolve these case-insensitive duplicates first: '. $duplicateEmailMessage. $remainingDuplicateMessage ); } DB::table('users') ->whereRaw('email <> LOWER(email)') ->update([ 'email' => DB::raw('LOWER(email)'), ]); } /** * Reverse the migrations. */ public function down(): void { // } };