Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions docs/content/users/extend/customization-extendibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,14 +342,23 @@ max_execution_time = 240;

You can provide additional MySQL/MariaDB configuration for a project by creating a directory called `.ddev/mysql/` and adding any number of `*.cnf` MySQL configuration files. These files will be automatically included when MySQL is started. Make sure that the section header is included in the file.

An example file in `.ddev/mysql/no_utf8mb4.cnf` might be:
A common use case is setting the server's default character set and collation. DDEV automatically replaces modern collations like `utf8mb4_0900_ai_ci` (MySQL 8.0+) and `utf8mb4_uca1400_ai_ci` (MariaDB 11.x) with the server's `collation-server` value during `ddev import-db`. Setting this explicitly ensures the substitution lands on the collation you expect:

An example file in `.ddev/mysql/utf8mb4.cnf`:

```ini
[mysqld]
collation-server = utf8mb4_unicode_ci
character-set-server = utf8mb4
```

DDEV already sets `max_allowed_packet` to 256M by default, which covers most cases. If your project works with unusually large BLOBs you can raise it further, though this is rarely needed:

An example file in `.ddev/mysql/max-packet.cnf`:

```ini
[mysqld]
server-id = 2
collation-server = utf8_general_ci
character-set-server = utf8
innodb_large_prefix=false
max_allowed_packet = 512M
```

To load the new configuration, run [`ddev restart`](../usage/commands.md#restart).
Expand Down
14 changes: 8 additions & 6 deletions pkg/ddevapp/ddevapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -1036,21 +1036,23 @@ func (app *DdevApp) ImportDB(dumpFile string, extractPath string, progress bool,
// Case for reading from file
// The Perl regex does three things:
// 1. Strips sandbox mode comments, CREATE DATABASE, and USE statements from the dump
// 2. Replaces MariaDB 11.x modern collation (utf8mb4_uca1400_ai_ci) with compatible fallback (utf8mb4_unicode_ci)
// 3. Replaces MySQL 8.0+ modern collation (utf8mb4_0900_ai_ci) with compatible fallback (utf8mb4_unicode_ci)
// 2. Replaces MariaDB 11.x modern collation (utf8mb4_uca1400_ai_ci) with server's default collation
// 3. Replaces MySQL 8.0+ modern collation (utf8mb4_0900_ai_ci) with server's default collation
// The collation replacements skip INSERT and VALUES lines to avoid corrupting data that mentions these collations
// The PIPESTATUS check ensures we catch and report errors from the mysql command
inContainerCommand = []string{"bash", "-c", fmt.Sprintf(`set -eu -o pipefail; %[1]s -e "%[2]s"; pv %[3]s/*.*sql | perl -p -e 's/^(\/\*.*999999.*enable the sandbox mode *|CREATE DATABASE \/\*|USE %[4]s)[^;]*(;|\*\/)//; unless (/^\s*(INSERT\s+INTO|VALUES)/i) { s/COLLATE[= ]utf8mb4_uca1400_ai_ci/COLLATE utf8mb4_unicode_ci/gi; s/COLLATE[= ]utf8mb4_0900_ai_ci/COLLATE utf8mb4_unicode_ci/gi; }' | %[1]s %[5]s; status=${PIPESTATUS[2]}; if [ $status -ne 0 ]; then echo "Database import command failed" >&2; exit 1; fi`, dbClientCmd, preImportSQL, insideContainerImportPath, "`", targetDB)}
// DDEV_REPLACED_COLLATION is queried from the server and used via $ENV{DDEV_REPLACED_COLLATION} in Perl
inContainerCommand = []string{"bash", "-c", fmt.Sprintf(`set -eu -o pipefail; DDEV_REPLACED_COLLATION=$(%[1]s -sN -e "SELECT @@collation_server" </dev/null 2>/dev/null || echo "utf8mb4_unicode_ci"); export DDEV_REPLACED_COLLATION; %[1]s -e "%[2]s"; pv %[3]s/*.*sql | perl -p -e 's/^(\/\*.*999999.*enable the sandbox mode *|CREATE DATABASE \/\*|USE %[4]s)[^;]*(;|\*\/)//; unless (/^\s*(INSERT\s+INTO|VALUES)/i) { s/COLLATE[= ]utf8mb4_uca1400_ai_ci/COLLATE $ENV{DDEV_REPLACED_COLLATION}/gi; s/COLLATE[= ]utf8mb4_0900_ai_ci/COLLATE $ENV{DDEV_REPLACED_COLLATION}/gi; }' | %[1]s %[5]s; status=${PIPESTATUS[2]}; if [ $status -ne 0 ]; then echo "Database import command failed" >&2; exit 1; fi`, dbClientCmd, preImportSQL, insideContainerImportPath, "`", targetDB)}

// Alternate case where we are reading from stdin
// The Perl regex does three things:
// 1. Strips CREATE DATABASE and USE statements from the dump
// 2. Replaces MariaDB 11.x modern collation (utf8mb4_uca1400_ai_ci) with compatible fallback (utf8mb4_unicode_ci)
// 3. Replaces MySQL 8.0+ modern collation (utf8mb4_0900_ai_ci) with compatible fallback (utf8mb4_unicode_ci)
// 2. Replaces MariaDB 11.x modern collation (utf8mb4_uca1400_ai_ci) with server's default collation
// 3. Replaces MySQL 8.0+ modern collation (utf8mb4_0900_ai_ci) with server's default collation
// The collation replacements skip INSERT and VALUES lines to avoid corrupting data that mentions these collations
// The PIPESTATUS check ensures we catch and report errors from the mysql command even when reading from stdin
// DDEV_REPLACED_COLLATION is queried from the server and used via $ENV{DDEV_REPLACED_COLLATION} in Perl
if dumpFile == "" && extractPath == "" {
inContainerCommand = []string{"bash", "-c", fmt.Sprintf(`set -eu -o pipefail; %[1]s -e "%[2]s"; perl -p -e 's/^(CREATE DATABASE \/\*|USE %[3]s)[^;]*;//; unless (/^\s*(INSERT\s+INTO|VALUES)/i) { s/COLLATE[= ]utf8mb4_uca1400_ai_ci/COLLATE utf8mb4_unicode_ci/gi; s/COLLATE[= ]utf8mb4_0900_ai_ci/COLLATE utf8mb4_unicode_ci/gi; }' | %[1]s %[4]s; status=${PIPESTATUS[1]}; if [ $status -ne 0 ]; then echo "Database import command failed" >&2; exit 1; fi`, dbClientCmd, preImportSQL, "`", targetDB)}
inContainerCommand = []string{"bash", "-c", fmt.Sprintf(`set -eu -o pipefail; DDEV_REPLACED_COLLATION=$(%[1]s -sN -e "SELECT @@collation_server" </dev/null 2>/dev/null || echo "utf8mb4_unicode_ci"); export DDEV_REPLACED_COLLATION; %[1]s -e "%[2]s"; perl -p -e 's/^(CREATE DATABASE \/\*|USE %[3]s)[^;]*;//; unless (/^\s*(INSERT\s+INTO|VALUES)/i) { s/COLLATE[= ]utf8mb4_uca1400_ai_ci/COLLATE $ENV{DDEV_REPLACED_COLLATION}/gi; s/COLLATE[= ]utf8mb4_0900_ai_ci/COLLATE $ENV{DDEV_REPLACED_COLLATION}/gi; }' | %[1]s %[4]s; status=${PIPESTATUS[1]}; if [ $status -ne 0 ]; then echo "Database import command failed" >&2; exit 1; fi`, dbClientCmd, preImportSQL, "`", targetDB)}
}

case nodeps.Postgres:
Expand Down
6 changes: 3 additions & 3 deletions pkg/ddevapp/ddevapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1518,7 +1518,7 @@ func TestDdevImportDB(t *testing.T) {
})
assert.NoError(err, "Failed to check modern_mariadb collation, stderr=%s", stderr)
assert.NotContains(out, "utf8mb4_uca1400_ai_ci", "Modern MariaDB collation should have been replaced")
assert.Contains(out, "utf8mb4_unicode_ci", "Modern MariaDB collation should be replaced with utf8mb4_unicode_ci")
assert.Contains(out, "utf8mb4_unicode_520_ci", "Modern MariaDB collation should be replaced with utf8mb4_unicode_520_ci")

// Verify modern MySQL 8.0+ collation was replaced
out, stderr, err = app.Exec(&ddevapp.ExecOpts{
Expand All @@ -1527,7 +1527,7 @@ func TestDdevImportDB(t *testing.T) {
})
assert.NoError(err, "Failed to check modern_mysql collation, stderr=%s", stderr)
assert.NotContains(out, "utf8mb4_0900_ai_ci", "Modern MySQL collation should have been replaced")
assert.Contains(out, "utf8mb4_unicode_ci", "Modern MySQL collation should be replaced with utf8mb4_unicode_ci")
assert.Contains(out, "utf8mb4_unicode_520_ci", "Modern MySQL collation should be replaced with utf8mb4_unicode_520_ci")

// Test legacy collations (should be preserved as-is)
legacyCollationsFile := filepath.Join(origDir, "testdata", t.Name(), dbType, "legacy_collations.sql")
Expand Down Expand Up @@ -1697,7 +1697,7 @@ func TestDdevAllDatabases(t *testing.T) {

//Use a smaller list if GOTEST_SHORT
if os.Getenv("GOTEST_SHORT") != "" {
dbVersions = []string{"postgres:18", "postgres:17", "mariadb:11.4", "mariadb:10.11", "mariadb:10.6", "mysql:8.0", "mysql:8.4", "mysql:5.7"}
dbVersions = []string{"postgres:18", "postgres:17", "mariadb:11.8", "mariadb:11.4", "mariadb:10.11", "mariadb:10.6", "mysql:8.0", "mysql:8.4", "mysql:5.7"}
t.Logf("Using limited set of database servers because GOTEST_SHORT is set (%v)", dbVersions)
}

Expand Down
5 changes: 2 additions & 3 deletions pkg/ddevapp/mysql_config_assets/character-set.cnf.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#ddev-generated
[mysqld]
collation-server = utf8_general_ci
character-set-server = utf8
innodb_large_prefix=false
collation-server = utf8mb4_unicode_ci
character-set-server = utf8mb4
5 changes: 5 additions & 0 deletions pkg/ddevapp/mysql_config_assets/max-packet.cnf.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#ddev-generated
# DDEV already sets max_allowed_packet to 256M by default.
# Raise this only if you need to work with unusually large BLOBs.
[mysqld]
max_allowed_packet = 512M