Skip to content

Fix SQL mode compat issues by removing the additional probe request#333

Merged
schlessera merged 3 commits into
mainfrom
fix/sql-mode-compat-311
Jul 21, 2026
Merged

Fix SQL mode compat issues by removing the additional probe request#333
schlessera merged 3 commits into
mainfrom
fix/sql-mode-compat-311

Conversation

@schlessera

@schlessera schlessera commented Jul 21, 2026

Copy link
Copy Markdown
Member

Problem

wp db query, wp db import and the internal run_query() all adapted the session SQL mode to match WordPress (stripping modes like STRICT_TRANS_TABLES and NO_ZERO_DATE, the way wpdb does). To build that adaptation, get_sql_mode_query() called get_current_sql_modes(), which opened a second MySQL connection to run SELECT @@SESSION.sql_mode.

That probe connection was built with $args = [], discarding the caller's connection options. So when you passed a custom --host, --defaults, SSL/TLS options or a socket, the probe connected to the wrong place (or failed outright) and aborted the whole command with Failed to get current SQL modes, before your actual query ever ran.

This is the root cause of #311 and its duplicates #237, #278, #288 and #218 (each one is a different connection option the probe failed to forward, patched one at a time).

Approach

The probe is removed. Every path that needs WordPress SQL-mode compatibility now applies it through the MySQL client's --init-command on the same connection, so it runs right after connect and before any SQL is read — no second connection, so the connection options in play no longer matter.

  1. wp db query, wp db import and run_query() all adapt the session SQL mode via --init-command. Behavior is unchanged from before for the default case: statements still run under WordPress-compatible modes, so DDL against WordPress's zero-date schema (ALTER TABLE wp_blogs …, CREATE TABLE … AS SELECT from a WordPress table, importing a headerless dump) keeps working on strict servers. This is not a breaking change.
  2. The STDIN import path (SQL mode compat logic is missing for SQL from STDIN #171) is now covered too. The old adaptation was prepended to the executed batch, which a STDIN stream never has; --init-command applies on connect regardless, so a dump streamed from STDIN gets the same treatment as a file import.
  3. New --skip-sql-mode-compat flag on wp db query and wp db import, to run under the server's own SQL modes.

Why compatibility matters

WordPress schema declares datetime columns as DEFAULT '0000-00-00 00:00:00', so real dumps and tables carry zero-date values. On MySQL 5.7+/8.0 (default sql_mode includes NO_ZERO_DATE/STRICT_TRANS_TABLES), a raw statement against that schema without a relaxed session mode fails with ERROR 1067 Invalid default value. mysqldump/wp db export dumps carry their own SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO' header, but headerless dumps (compact exports, hand-written SQL, some third-party tools) and ad-hoc DDL through wp db query still need the session mode relaxed — which is what --init-command now provides, by default.

Implementation notes

  • A shared helper, apply_sql_mode_compat_init_command(), is used by query(), import() and run_query(), so the logic lives in one place.
  • The compat statement strips the incompatible modes from @@SESSION.sql_mode in a single expression. Each mode is comma-wrapped, e.g. REPLACE(CONCAT(',', @@SESSION.sql_mode, ','), ',ANSI,', ','), so a mode name that is a substring of another (ANSI inside ANSI_QUOTES) is never corrupted.
  • Composition with a caller-supplied --init-command. If the caller already set their own --init-command (via the CLI or an option file), the compat statement is composed with it into a single multi-statement --init-command — compat first, the caller's command second — rather than dropping either one. Compat first means it is always applied; a caller that re-sets sql_mode themselves still wins, since the last SET takes effect. This fixes the previous behavior, where a caller-provided --init-command on a STDIN import silently discarded the compat statement.
  • Why one --init-command and not --init-command-add. MySQL 8.4 redefines --init-command as a single statement and adds --init-command-add for additional ones, but --init-command-add is rejected by mysql 5.6/5.7/8.0 and by the MariaDB client, so it is not portable. A single --init-command carrying two ;-separated statements was verified to execute both on every CI-targeted client: mysql 5.6, 5.7, 8.0, 8.4 and mariadb 11.4. --init-command is likewise available across all of them; get_mysql_binary_path() selects the mariadb binary on MariaDB, which also supports it.

Testing

New/updated Behat scenarios in db-query.feature and db-import.feature:

  • wp db query adapts the SQL mode via --init-command by default, with no probe; --skip-sql-mode-compat disables it.
  • Regression for Unnecessary check of sql modes in query command #311: an inline query with connection options no longer triggers a failing probe.
  • wp db import applies compat via --init-command by default; --skip-sql-mode-compat disables it.
  • wp db import of a headerless zero-date dump succeeds, from both a file and STDIN (SQL mode compat logic is missing for SQL from STDIN #171).
  • wp db import keeps compat when the caller also passes --init-command.

The pre-existing db.feature / db-tables.feature scenarios that run DDL through wp db query (CREATE TABLE … AS SELECT from wp_users, ALTER TABLE wp_blogs) pass again on strict servers, which was the regression an earlier revision of this PR introduced.

Known limitation

$sql_incompatible_modes does not include NO_ZERO_IN_DATE (it predates this PR). I kept the existing mode set to keep this change scoped to the probe fix. WordPress Core's list does include it, so adding it is a reasonable follow-up.

Fixes #311.
Fixes #171.

Summary by CodeRabbit

  • New Features
    • Improved MySQL/MariaDB SQL-mode compatibility for wp db import and wp db query.
    • Added --skip-sql-mode-compat to disable automatic SQL-mode adjustments.
  • Bug Fixes
    • Eliminated separate SQL-mode probing, making debug output more consistent.
    • Ensured inline queries with connection options (for example, --defaults) no longer fail.
    • Confirmed legacy “zero-date” datetime imports succeed with expected row counts.
    • SQL-mode adjustments now compose correctly with any caller-supplied --init-command.
  • Tests
    • Expanded regression coverage for file/STDIN imports and query scenarios.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4bcc36ee-ff45-4609-a3e5-c39bc761c7e4

📥 Commits

Reviewing files that changed from the base of the PR and between 2566fc9 and ab66c6e.

📒 Files selected for processing (3)
  • features/db-import.feature
  • features/db-query.feature
  • src/DB_Command.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • features/db-query.feature
  • features/db-import.feature
  • src/DB_Command.php

📝 Walkthrough

Walkthrough

Changes

The PR changes MySQL/MariaDB SQL-mode handling. wp db import applies compatibility through --init-command for file and STDIN imports, while wp db query uses the same mechanism without a separate SQL-mode probe. Both commands support skipping compatibility.

SQL mode compatibility

Layer / File(s) Summary
Compatibility statement generation
src/DB_Command.php
Generates an optional session SQL-mode compatibility statement and composes it with caller-supplied initialization commands.
Import command integration
src/DB_Command.php, features/db-import.feature
Adds the skip option, applies compatibility before file and STDIN imports, preserves init commands, and tests zero-date imports.
Query execution behavior
src/DB_Command.php, features/db-query.feature
Applies compatibility during query execution and validates default, skipped, and connection-option flows.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WP_CLI
  participant MySQL_Client
  participant MySQL_Server
  WP_CLI->>MySQL_Client: Add SQL-mode compatibility to --init-command
  MySQL_Client->>MySQL_Server: Initialize session SQL mode
  MySQL_Client->>MySQL_Server: Execute query or import SQL
  MySQL_Server-->>WP_CLI: Return command result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: removing the extra SQL mode probe to fix compatibility handling.
Linked Issues check ✅ Passed The PR removes the separate probe for wp db query and adds STDIN-safe SQL-mode compat for db import, matching #311 and #171.
Out of Scope Changes check ✅ Passed All changes are tied to SQL-mode compatibility in db query/import and the related Behat coverage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sql-mode-compat-311

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/DB_Command.php (1)

489-574: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider documenting the SQL-mode behavior change in the command help.

The PR intentionally removes SQL-mode adaptation from wp db query, and the objectives call this out as a breaking change on strict-mode servers (justifying the 3.0.0 bump). The command's own ## OPTIONS/description doesn't mention this, so users hitting new strict-mode failures via --help/generated docs won't get a hint about the change.

📝 Suggested doc note
  * Use the `--skip-column-names` MySQL argument to exclude the headers
  * from a SELECT query. Pipe the output to remove the ASCII table
  * entirely.
  *
+ * Unlike `wp db import`, this command does not adapt the session SQL mode for
+ * WordPress compatibility; it runs under the server's own SQL modes, just like
+ * the `mysql` client itself.
+ *
  * ## OPTIONS
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/DB_Command.php` around lines 489 - 574, Update the `DB_Command`
documentation for `wp db query` to explicitly state that the command no longer
adapts SQL modes, including that queries may fail on servers enforcing strict
SQL modes. Place the note in the command description or `## OPTIONS` help so it
appears in `--help` and generated documentation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/DB_Command.php`:
- Around line 931-938: Extract the duplicated SQL-mode compatibility handling
from import() and run_query() into a shared helper that merges the compatibility
statement with a caller-provided mysql_args['init-command'] instead of silently
discarding either value. When both are present, preserve both commands and emit
a user-visible warning about the conflict; retain existing behavior when only
the compatibility statement or only the user command is provided.

---

Nitpick comments:
In `@src/DB_Command.php`:
- Around line 489-574: Update the `DB_Command` documentation for `wp db query`
to explicitly state that the command no longer adapts SQL modes, including that
queries may fail on servers enforcing strict SQL modes. Place the note in the
command description or `## OPTIONS` help so it appears in `--help` and generated
documentation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ed0878e6-0a0f-47cf-9a4d-dd3a21891761

📥 Commits

Reviewing files that changed from the base of the PR and between 8a9a130 and 8b15530.

📒 Files selected for processing (3)
  • features/db-import.feature
  • features/db-query.feature
  • src/DB_Command.php

Comment thread src/DB_Command.php
Comment on lines +931 to +938
// Adapt the session SQL mode to be WordPress-compatible via --init-command,
// so it runs on connect before any SQL is read. This covers both file and
// STDIN imports, and needs no separate probe connection.
$sql_mode_compat = $this->get_sql_mode_compat_statement( $assoc_args );
if ( '' !== $sql_mode_compat && ! isset( $mysql_args['init-command'] ) ) {
$mysql_args['init-command'] = $sql_mode_compat;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Custom --init-command silently disables WordPress SQL-mode compatibility, with no warning, and the logic is duplicated in two places.

In both import() and run_query(), if the caller already passes their own --init-command (e.g. to set time_zone or other session vars), $sql_mode_compat is computed but then dropped entirely — no merge, no warning. This reintroduces the exact strict-mode import-failure risk the PR is meant to fix, silently, for anyone combining --init-command with wp db import/internal run_query() callers. The same 4-line block is copy-pasted in both places.

♻️ Proposed fix: shared helper + user-visible warning on conflict
+	/**
+	 * Apply WordPress SQL-mode compatibility as an `--init-command`, unless the
+	 * caller already supplied their own `--init-command` (in which case warn
+	 * instead of silently dropping the compatibility statement).
+	 *
+	 * `@param` array $mysql_args Mysql args array, passed by reference.
+	 * `@param` array $assoc_args The associative argument array passed to the command.
+	 */
+	private function apply_sql_mode_compat_init_command( &$mysql_args, $assoc_args ) {
+		$sql_mode_compat = $this->get_sql_mode_compat_statement( $assoc_args );
+		if ( '' === $sql_mode_compat ) {
+			return;
+		}
+		if ( isset( $mysql_args['init-command'] ) ) {
+			WP_CLI::warning( 'A custom --init-command was provided; skipping automatic WordPress SQL-mode compatibility. Include the compatibility statement in your --init-command, or pass --skip-sql-mode-compat to silence this warning.' );
+			return;
+		}
+		$mysql_args['init-command'] = $sql_mode_compat;
+	}

Then at each call site:

-		// Adapt the session SQL mode to be WordPress-compatible via --init-command,
-		// so it runs on connect before any SQL is read. This covers both file and
-		// STDIN imports, and needs no separate probe connection.
-		$sql_mode_compat = $this->get_sql_mode_compat_statement( $assoc_args );
-		if ( '' !== $sql_mode_compat && ! isset( $mysql_args['init-command'] ) ) {
-			$mysql_args['init-command'] = $sql_mode_compat;
-		}
+		// Adapt the session SQL mode to be WordPress-compatible via --init-command,
+		// so it runs on connect before any SQL is read. This covers both file and
+		// STDIN imports, and needs no separate probe connection.
+		$this->apply_sql_mode_compat_init_command( $mysql_args, $assoc_args );

(and identically at the run_query() call site.)

Also applies to: 1942-1948

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/DB_Command.php` around lines 931 - 938, Extract the duplicated SQL-mode
compatibility handling from import() and run_query() into a shared helper that
merges the compatibility statement with a caller-provided
mysql_args['init-command'] instead of silently discarding either value. When
both are present, preserve both commands and emit a user-visible warning about
the conflict; retain existing behavior when only the compatibility statement or
only the user command is provided.

@schlessera

schlessera commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

20 of 30 Behat jobs fail. The split is clean along one axis:

  • Fail: every strict-SQL-mode server — MySQL 5.7, 8.0, 8.4 and MariaDB 11.4 (all PHP versions).
  • Pass: SQLite (all) and MySQL 5.6 (which does not enable strict mode by default).

The failures are deterministic, not flaky.

Failing scenarios

Two pre-existing scenarios fail, both with ERROR 1067 (42000) ... Invalid default value:

  1. features/db-tables.feature:236"Listing a site's tables should only list that site's tables"
    Fails on wp db query "ALTER TABLE wp_blogs AUTO_INCREMENT=21".
    ERROR 1067 (42000) at line 1: Invalid default value for 'registered'
    The ALTER TABLE re-validates the existing wp_blogs.registered column, whose schema default is '0000-00-00 00:00:00'. Under NO_ZERO_DATE/strict mode that default is rejected.

  2. features/db.feature:113"Clean up a WordPress install without dropping its database entirely but tables with prefix."
    Fails on wp db query "create table custom_table as select * from wp_users;".
    ERROR 1067 (42000) at line 1: Invalid default value for 'user_registered'
    The CREATE TABLE ... AS SELECT copies the wp_users.user_registered column, whose schema default is '0000-00-00 00:00:00', and strict mode rejects it.

Mechanism

Both scenarios run DDL through wp db query. Before this PR, wp db query adapted the session SQL mode (stripping NO_ZERO_DATE/STRICT_TRANS_TABLES/etc.), so these statements succeeded on strict servers. This PR intentionally removes that adaptation from wp db query (it now runs under the server's own modes), which is the direct cause of both failures. The affected statements are DDL against WordPress's zero-date schema (wp_blogs.registered, wp_users.user_registered), not the new import/query test scenarios added in this PR.

The new db-import.feature / db-query.feature scenarios themselves pass; the two failures are in unrelated, pre-existing scenarios that exercised DDL via wp db query.

Run: https://github.com/wp-cli/db-command/actions/runs/29804346388

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@schlessera schlessera changed the title Run wp db query under the server's own SQL modes; fix db import mode compat Fix SQL mode compat issues by removing the additional probe request Jul 21, 2026
`wp db query`, `wp db import` and the internal `run_query()` adapted the
session SQL mode to match WordPress. Building that adaptation opened a
second MySQL connection to read `@@SESSION.sql_mode`, and that probe was
built without the caller's connection options (`--host`, `--defaults`,
SSL/TLS, sockets), so it connected to the wrong server and aborted the
command before the real query ran. This is the root cause of #311, and of
#237, #278, #288 and #218.

- `wp db query` no longer adapts the SQL mode. It runs under the server's
  own modes, like the `mysql` client, so connection options are honored.
  This is a behavior change and targets the next major version.
- `wp db import` and `run_query()` keep WordPress compatibility, but apply
  it via the client's `--init-command` on the same connection rather than a
  probe. This also covers dumps streamed from STDIN (#171).
- Add a `--skip-sql-mode-compat` flag to `wp db import` to opt out.

The incompatible modes are stripped from `@@SESSION.sql_mode` in a single
statement, with each mode comma-wrapped so a substring like `ANSI` cannot
corrupt `ANSI_QUOTES`.
Applying the compatibility statement via --init-command collided with init
commands the caller already had: an explicit --init-command made the compat
step drop out, and an init command from an option file (--defaults) was
silently overridden by the CLI value.

Prepend the compatibility statement to the executed batch instead, for both
file imports and run_query(), so it composes with any caller init command.
STDIN imports have no executed batch, so they keep using --init-command,
which is what lets them get compatibility at all.
Replace the removed SQL-mode probe with a single mechanism shared by
`wp db query`, `wp db import` and the internal `run_query()`: the
WordPress-compatible session SQL mode is applied through the MySQL
client's `--init-command`, on the same connection, before any SQL runs.

This keeps `wp db query` adapting the SQL mode as it did before, so DDL
against WordPress's zero-date schema (`ALTER TABLE wp_blogs ...`,
`CREATE TABLE ... AS SELECT` from a WordPress table) keeps working on
strict servers, while still removing the second probe connection that
ignored the caller's connection options and caused #311. The STDIN
import path (#171) is covered too, since `--init-command` applies
whether or not there is an executed batch.

When the caller supplies their own `--init-command`, the compat statement
is composed with it into a single multi-statement `--init-command`
(compat first) instead of being dropped. `--init-command-add` is not used:
it is rejected by the mysql 5.6/5.7/8.0 and mariadb clients, whereas a
single multi-statement `--init-command` runs both statements on every
CI-targeted client (mysql 5.6/5.7/8.0/8.4, mariadb 11.4).

Add `--skip-sql-mode-compat` to `wp db query` as well, so a query can opt
into the server's own SQL modes.
@schlessera
schlessera force-pushed the fix/sql-mode-compat-311 branch from 2566fc9 to ab66c6e Compare July 21, 2026 12:03
@schlessera
schlessera merged commit 89ecbbf into main Jul 21, 2026
64 checks passed
@schlessera
schlessera deleted the fix/sql-mode-compat-311 branch July 21, 2026 13:01
@lkraav

lkraav commented Jul 21, 2026

Copy link
Copy Markdown

🦸 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unnecessary check of sql modes in query command SQL mode compat logic is missing for SQL from STDIN

2 participants