From 405aaafb4e97dbb250983496fe64c938892da23b Mon Sep 17 00:00:00 2001 From: lukinovec Date: Mon, 4 May 2026 11:15:51 +0200 Subject: [PATCH] Handle MySQL charset and collation Make createDatabase execute CREATE DATABASE without passing charset and collation so that if these parameters are null, the MySQL server's defaults will be used. Only add charset and collation to the statement if they're not null. --- .../MySQLDatabaseManager.php | 16 ++++- tests/TenantDatabaseManagerTest.php | 70 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/Database/TenantDatabaseManagers/MySQLDatabaseManager.php b/src/Database/TenantDatabaseManagers/MySQLDatabaseManager.php index 9747e9de..ff61a3e8 100644 --- a/src/Database/TenantDatabaseManagers/MySQLDatabaseManager.php +++ b/src/Database/TenantDatabaseManagers/MySQLDatabaseManager.php @@ -16,7 +16,21 @@ class MySQLDatabaseManager extends TenantDatabaseManager $this->validateParameter([$database, $charset, $collation]); - return $this->connection()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`"); + // MySQL defaults to the server's charset and collation + // if charset and collation are not specified. + // If charset is specified but collation is null, MySQL + // will choose a default collation for the specified charset (and vice versa). + $statement = "CREATE DATABASE `{$database}`"; + + if ($charset !== null) { + $statement .= " CHARACTER SET `{$charset}`"; + } + + if ($collation !== null) { + $statement .= " COLLATE `{$collation}`"; + } + + return $this->connection()->statement($statement); } public function deleteDatabase(TenantWithDatabase $tenant): bool diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php index f29f9a63..cd5089a2 100644 --- a/tests/TenantDatabaseManagerTest.php +++ b/tests/TenantDatabaseManagerTest.php @@ -661,6 +661,76 @@ test('sqlite database manager respects the configured path while making the data expect($tenant->database()->connection()['database'])->toBe($customPath . 'tenant.sqlite'); }); +test('newly created tenant databases use the correct charset and collation with mysql', function () { + config([ + 'tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class], + ]); + + Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) { + return $event->tenant; + })->toListener()); + + withBootstrapping(); + + $charset = fn () => DB::selectOne('SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = DATABASE()')->DEFAULT_CHARACTER_SET_NAME; + $collation = fn () => DB::selectOne('SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = DATABASE()')->DEFAULT_COLLATION_NAME; + + $defaultTenant = Tenant::create(); + + tenancy()->initialize($defaultTenant); + + // No charset or collation specified, + // defaults from the MySQL config used. + expect($charset())->toBe('utf8mb4'); + expect($collation())->toBe('utf8mb4_unicode_ci'); + + $tenantWithCharsetAndCollation = Tenant::create([ + 'tenancy_db_charset' => 'latin1', + 'tenancy_db_collation' => 'latin1_swedish_ci', + ]); + + tenancy()->initialize($tenantWithCharsetAndCollation); + + // Custom charset and collation from tenant config + expect($charset())->toBe('latin1'); + expect($collation())->toBe('latin1_swedish_ci'); + + $tenantWithNullCharsetAndCollation = Tenant::create([ + 'tenancy_db_charset' => null, + 'tenancy_db_collation' => null, + ]); + + tenancy()->initialize($tenantWithNullCharsetAndCollation); + + // Default MySQL server charset and collation + expect($charset())->toBe('utf8mb4'); + expect($collation())->toBe('utf8mb4_0900_ai_ci'); + + $tenantWithCharsetAndNullCollation = Tenant::create([ + 'tenancy_db_charset' => 'binary', + 'tenancy_db_collation' => null, + ]); + + tenancy()->initialize($tenantWithCharsetAndNullCollation); + + // Charset specified, collation is null, + // MySQL will choose a default collation for the specified charset. + expect($charset())->toBe('binary'); + expect($collation())->toBe('binary'); + + // Collation specified, charset is null, + // MySQL will choose a default charset for the specified collation. + $tenantWithCollationAndNullCharset = Tenant::create([ + 'tenancy_db_charset' => null, + 'tenancy_db_collation' => 'latin1_swedish_ci', + ]); + + tenancy()->initialize($tenantWithCollationAndNullCharset); + + expect($charset())->toBe('latin1'); + expect($collation())->toBe('latin1_swedish_ci'); +}); + // Datasets dataset('database_managers', [ ['mysql', MySQLDatabaseManager::class],