Skip to content

wp db query misidentifies MySQL REPLACE() string function as REPLACE INTO write statement #313

Closed

Description

@chubes4

Bug Report

wp db query silently swallows SELECT results when the query contains the MySQL REPLACE() string function. The command outputs Success: Query succeeded. Rows affected: -1 instead of the query results.

This is because the write-query detection regex on line 541 of DB_Command.php matches the word REPLACE without distinguishing between:

  • REPLACE() — a MySQL string function used in SELECT statements
  • REPLACE INTO — a DML write statement

Reproduction

# This works (no REPLACE keyword):
$ wp db query "SELECT CONCAT('a', 'b') as result"
result
ab

# This silently fails (REPLACE string function triggers write path):
$ wp db query "SELECT REPLACE('hello world', 'world', 'there') as result"
Success: Query succeeded. Rows affected: -1

# Piping through db cli works fine (bypasses the regex):
$ echo "SELECT REPLACE('hello world', 'world', 'there') as result;" | wp db cli
result
hello there

Root Cause

The regex at line 541:

$is_row_modifying_query = isset( $assoc_args['execute'] )
    && preg_match( '/\b(UPDATE|DELETE|INSERT|REPLACE|LOAD DATA)\b/i', $assoc_args['execute'] );

\bREPLACE\b matches the REPLACE in REPLACE('hello world', ...) because \b treats the transition from E to ( as a word boundary.

When the write path is taken, the code:

  1. Appends ; SELECT ROW_COUNT(); to the query
  2. Captures stdout internally (the false third arg to self::run)
  3. Extracts only the last line as the affected row count (-1 because the prior statement was a SELECT)
  4. Prints the success message and discards the actual query results

Suggested Fix

The MySQL REPLACE write statement syntax is always REPLACE INTO. Changing the regex to match REPLACE\s+INTO instead of bare REPLACE would correctly distinguish the two:

preg_match( '/\b(UPDATE|DELETE|INSERT|REPLACE\s+INTO|LOAD DATA)\b/i', $assoc_args['execute'] );

Note: MySQL does technically allow REPLACE without INTO (the INTO keyword is optional), but this mirrors how the regex already handles INSERT — bare INSERT matches, which works because INSERT has no equivalent string function collision. For REPLACE, the collision with the string function makes the stricter match necessary.

Introduced in PR #277 (merged March 10, 2025).

Environment

WP-CLI version: 2.12.0
PHP version: 8.4
MySQL: MariaDB
OS: Linux (Debian)

Activity

  1. added a commit that references this issue on Mar 7, 2026
    f96946a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions