Skip to content

Database: Unescape prepared LIKE operands in wpdb::get_table_from_query() - #14889

Open
bluefairydevelopment wants to merge 2 commits into
WordPress:trunkfrom
bluefairydevelopment:fix/57149-get-table-from-prepared-like
Open

bluefairydevelopment wants to merge 2 commits into
WordPress:trunkfrom
bluefairydevelopment:fix/57149-get-table-from-prepared-like

Conversation

@bluefairydevelopment

@bluefairydevelopment bluefairydevelopment commented Oct 26, 2026 •

Copy link
Copy Markdown

wpdb::get_table_from_query() unescapes \_ in a SHOW TABLES LIKE operand. A query built the documented way, $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ), is escaped twice: esc_like() turns _ into \_, then prepare() escapes the backslash again as a string literal, giving 'wp\\_my\\_custom\\_table'. Trunk only undoes the first step, so the table name comes back as wp\_my\_custom\_table. That breaks drop-ins such as HyperDB, LudicrousDB and BerlinDB, which route queries by table name.

This removes the string-literal escaping (\\ → \) before unescaping the underscores (\_ → _). Hand-written operands such as 'wp\_123\_%' contain no doubled backslashes and behave exactly as before; the existing data_get_escaped_table_from_show_query cases still pass. This builds on the reporter prettyboymp's patch on Trac (57149.2.diff), which replaces both \\_ and \_ with _ in one step. Both versions pass the same tests and give the same results for every table name without a backslash that the parsing pattern accepts. With backslashes they can differ, because this patch also collapses doubled backslashes that are not followed by an underscore (see "Not covered" below). This one undoes the two escaping layers in reverse order, so it doesn't depend on the backslash sitting directly before an underscore, and it adds the unit tests the ticket was missing.

Not covered: backslashes inside table names. As johnjamesjacoby noted in comment:1, MySQL allows \ in a table name, and neither trunk nor this change handles that: a name containing a literal backslash would still be mis-parsed. WordPress never creates such tables, and handling them properly would mean deciding how SHOW ... LIKE queries should be escaped across core, which is the open question in that comment. This PR keeps to the documented prepare() + esc_like() pattern, which core itself uses in maybe_create_table(), network_domain_check() and elsewhere.

Tests

  • New test_get_table_from_prepared_show_like_query, covering SHOW TABLES, SHOW TABLE STATUS and SHOW FULL TABLES with both quote styles and a trailing wildcard, plus test_get_table_from_query_built_with_prepare_and_esc_like, which builds the query with the real prepare() and esc_like(). test_get_table_from_query_built_with_prepare_esc_like_and_wildcard prepares a prefix pattern with a trailing % and removes the placeholder escape, as the query filter does, before looking up the table.
  • Without the fix: all 4 cases with prepared operands fail, returning wp\_my\_custom\_table / wp\_123\_.
  • With the fix: all pass; --group wpdb passes (666 tests, 0 failures, 0 errors) on PHP 8.5 / MariaDB 10.11; phpcs clean.

Trac ticket: https://core.trac.wordpress.org/ticket/57149

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: reading the ticket and the code, the fix, and the tests, including confirming that the tests fail without the fix and pass with it. Submitted by Blue Fairy Development, who are responsible for the change.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

…ry().

A SHOW TABLES LIKE query built with wpdb::prepare() and wpdb::esc_like()
has its backslashes escaped once more as a string literal, so the table
name came back as wp\_my\_table. Remove the string literal escaping
before unescaping the underscores.

See #57149.
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @bluefairydevelopment.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

Core Committers: Use this line as a base for the props when committing in SVN:

Props johnjamesjacoby.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@JJJ JJJ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The exact-name fix looks correct for the reported esc_like() plus prepare() case, and the existing single-escaped cases retain their behavior. One wording adjustment would help future reviews: the description says this and 57149.2.diff produce the same result for every name accepted by the parser, but the parser also accepts backslashes and this patch collapses doubled backslashes outside \\_. The documented limitation for table names containing backslashes is reasonable for this ticket; please qualify that equivalence claim to names without backslashes.

return array(
'SHOW TABLES, single quotes' => array( "SHOW TABLES LIKE 'wp\\\\_my\\\\_custom\\\\_table'", 'wp_my_custom_table' ),
'SHOW TABLES, double quotes' => array( 'SHOW TABLES LIKE "wp\\\\_my\\\\_custom\\\\_table"', 'wp_my_custom_table' ),
'SHOW TABLE STATUS, prefix wildcard' => array( "SHOW TABLE STATUS LIKE 'wp\\\\_123\\\\_%'", 'wp_123_' ),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This covers the trailing wildcard with a handwritten SQL string. The routing path builds the operand with esc_like( $prefix ) . '%' and prepare(), which temporarily replaces % with a placeholder token until the query filter removes it. Please add a case that prepares the wildcard query and runs it through that filter (or remove_placeholder_escape()) before asserting the extracted prefix. That will verify the escape and filter order used by database drop-ins.

…ape is removed.

Builds the wildcard operand with esc_like() and prepare(), then removes the
placeholder escape as the query filter does, before checking the prefix
that get_table_from_query() returns.
@bluefairydevelopment

Copy link
Copy Markdown
Author

Thanks, both done.

  • Added test_get_table_from_query_built_with_prepare_esc_like_and_wildcard. It builds the operand with esc_like( 'wp_123_' ) . '%' and prepare(), removes the placeholder escape with remove_placeholder_escape() as the query filter does, checks the query reads SHOW TABLE STATUS LIKE 'wp\\_123\\_%', and then checks that the prefix comes back as wp_123_. It fails on trunk and passes with this change.
  • Qualified the comparison with 57149.2.diff in the description to table names without backslashes.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants