Visitar URL original
Handle empty $wpdb->dbname in wp_get_table_names for drop-ins like HyperDB and LudicrousDB by wprashed · Pull Request #6432 · wp-cli/wp-cli · GitHub
Skip to content

Handle empty $wpdb->dbname in wp_get_table_names for drop-ins like HyperDB and LudicrousDB - #6432

Open
wprashed wants to merge 1 commit into
wp-cli:mainfrom
wprashed:fix-empty-dbname-get-table-names
Open

wprashed wants to merge 1 commit into
wp-cli:mainfrom
wprashed:fix-empty-dbname-get-table-names

Conversation

@wprashed

@wprashed wprashed commented Oct 13, 2026 •

Copy link
Copy Markdown

Resolves wp-cli/search-replace-command#207

Summary

When database drop-ins like HyperDB or LudicrousDB are in use, $wpdb->dbname is often empty ("" or null) because the drop-in manages sharded/replica connections dynamically and does not initialize the global property.

In WP_CLI\Utils\wp_get_table_names(), queries like:

SHOW TABLES WHERE `Tables_in_` IN ('wp_posts', ...)

were constructed because esc_sql_ident( 'Tables_in_' . $wpdb->dbname ) evaluated to `Tables_in_` when $wpdb->dbname was empty. This caused MySQL to fail with:

WordPress database error Unknown column 'Tables_in_' in 'where clause' for query SHOW TABLES WHERE `Tables_in_` IN (...)

This affected commands like wp search-replace, wp db tables, wp db size, and wp db search across multisite, HyperDB, LudicrousDB, and WordPress VIP installations.

Changes

  • When $wpdb->dbname is empty, query SELECT DATABASE() to detect the active database.
  • If $dbname is still empty or cannot be resolved, fallback to SHOW TABLES and intersect the known $wp_tables list in PHP instead of failing with a malformed SQL WHERE clause.
  • Handled --all-tables-with-prefix safely when $dbname is empty.

Summary by CodeRabbit

  • Bug Fixes
    • Improved WordPress table detection when the database name is unavailable, including filtering tables by the site prefix.

@wprashed
wprashed requested a review from a team as a code owner October 13, 2026 09:49
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9ecba5e9-322f-4a93-9b03-2054c46b5ce5
📥 Commits

Reviewing files that changed from the base of the PR and between 095ae22 and 5cfd391.

📒 Files selected for processing (1)
  • php/utils-wp.php

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

📝 Walkthrough

wp_get_table_names() now resolves the database name from $wpdb->dbname, with SELECT DATABASE() as a fallback. When no database name is available, it filters returned tables for prefixed and scoped lookups.

Changes: WordPress table lookup

Layer / File(s) Summary
Database-aware table discovery
php/utils-wp.php
For prefixed table lookup, the function applies a prefix condition when it has a database name, or filters the returned table names otherwise. For scoped lookup, it queries the named database or intersects the scoped names with SHOW TABLES results when no name is available.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: schlessera

Merge Risk: ⚪ Minimal · up to 5cfd3

No actionable issue remains from this review. The database drop-in behavior was not executed, so normal validation is still appropriate before merging.

Pre-merge checks · Passed 5
Passed 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 clearly and specifically describes the main change: handling an empty $wpdb->dbname in wp_get_table_names for database drop-ins such as HyperDB and LudicrousDB.
Linked Issues check Passed Issue [#207] reports the malformed SHOW TABLES WHERE Tables_in_ IN (...) query during wp search-replace. The change resolves the database name with $wpdb->dbname or SELECT DATABASE(). When n…
Out of Scope Changes check Passed The reviewed change is limited to wp_get_table_names() in php/utils-wp.php. Database-name resolution, safe table queries, and PHP filtering directly support issue [#207] and the stated pull reques…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copilot AI 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.

Note

Copilot was unable to run its full agentic suite in this review.

Copilot review overview

3 open findings
What changed in this PR

Updates WordPress table discovery to handle environments where $wpdb->dbname is unavailable (e.g., DB drop-ins), improving compatibility across alternate database layers.

Changes:

  • Derive database name at runtime via SELECT DATABASE() when $wpdb->dbname is empty.
  • Add fallbacks to filter table lists in PHP when DB name can’t be determined.
  • Add a safer fallback path for fetching specific WP tables when DB name is unknown.
File Description
php/​utils-wp.php Adds $dbname fallbacks and PHP-side filtering when table metadata queries can’t reference Tables_in_<dbname>.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread php/utils-wp.php
Comment on lines 507 to +520
if ( empty( $tables_sql ) ) {
$tables_sql = $wpdb->prepare( 'SHOW TABLES LIKE %s', esc_like( $wpdb->get_blog_prefix() ) . '%' );
} else {
$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $wpdb->dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
$dbname = ! empty( $wpdb->dbname ) ? $wpdb->dbname : (string) $wpdb->get_var( 'SELECT DATABASE()' );
if ( ! empty( $dbname ) ) {
$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
}
}

// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
$tables = $wpdb->get_col( $tables_sql, 0 );

if ( ! empty( $tables_sql ) && empty( $dbname ) ) {
$blog_prefix = $wpdb->get_blog_prefix();
Comment thread php/utils-wp.php
Comment on lines +559 to +561
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is safe.
$all_tables = (array) $wpdb->get_col( 'SHOW TABLES', 0 );
$tables = array_values( array_intersect( $wp_tables, $all_tables ) );
Comment thread php/utils-wp.php
array_filter(
$tables,
function ( $table ) use ( $blog_prefix ) {
return 0 === strpos( $table, $blog_prefix );
@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
php/utils-wp.php 87.50% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@softglazee softglazee 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.

Patch Testing Report

Patch tested: #6432 at 5cfd391, compared with main at a1735b0

Environment

  • WordPress: 7.1.3, single site, then the same site converted to a subdirectory multisite with a second site (/two/)
  • PHP: 8.5.9 CLI
  • Server: n/a (WP-CLI only)
  • Database: MariaDB 11.8.9
  • Browser: n/a (command line test)
  • OS: Alpine Linux 3.24.1 (Docker)
  • Theme: Twenty Twenty-Five 1.5 (default)
  • MU Plugins: None
  • Plugins (drop-ins, one at a time; both sample configs point to the same database, one writer and one reader):
    • HyperDB 1.9 from wordpress.org: unmodified db.php and the sample db-config.php
    • LudicrousDB 5.3.1 from GitHub: unmodified drop-ins/db.php and drop-ins/db-config.php, plugin folder in wp-content/plugins/ludicrousdb
  • WP-CLI: search-replace-command main at 5a62210, db-command v3.0.1. To switch the framework I ran composer install in a search-replace-command clone, mounted a wp-cli clone over vendor/wp-cli/wp-cli, and checked out main or this PR in it.
  • WP_DEBUG: false for HyperDB, true for LudicrousDB. HyperDB 1.9 still loads wp-includes/wp-db.php. With WP_DEBUG true on WordPress 7.1.3 that is a fatal error before any command runs (Call to undefined function wp_kses() from _deprecated_file()).

Steps taken

  1. On main, both drop-ins leave $wpdb->dbname empty, and SELECT DATABASE() returns the right name:
$ wp eval 'global $wpdb; echo "class=" . get_class($wpdb) . " dbname=" . var_export($wpdb->dbname, true) . " DATABASE()=" . var_export($wpdb->get_var("SELECT DATABASE()"), true) . "\n";'
class=hyperdb dbname=NULL DATABASE()='wp_cli_test_pr6432'
class=LudicrousDB dbname=NULL DATABASE()='wp_cli_test_pr6432'
  1. On main, wp search-replace finds no tables and still exits 0. The error is printed twice (table list, then the views lookup):
$ wp search-replace foozle barzle --dry-run
WordPress database error Unknown column 'Tables_in_' in 'WHERE' for query SHOW TABLES WHERE `Tables_in_` IN ('wp_users', 'wp_usermeta', 'wp_posts', 'wp_comments', 'wp_links', 'wp_options', 'wp_postmeta', 'wp_terms', 'wp_term_taxonomy', 'wp_term_relationships', 'wp_termmeta', 'wp_commentmeta') made by WP_CLI\Utils\wp_get_table_names
WordPress database error Unknown column 'Tables_in_' in 'WHERE' for query SHOW TABLES WHERE `Tables_in_` IN ('wp_users', 'wp_usermeta', 'wp_posts', 'wp_comments', 'wp_links', 'wp_options', 'wp_postmeta', 'wp_terms', 'wp_term_taxonomy', 'wp_term_relationships', 'wp_termmeta', 'wp_commentmeta') made by WP_CLI\Utils\wp_get_table_names
Success: 0 replacements to be made.
  1. Ran the commands below on main without a drop-in, on main with each drop-in, and on this PR with each drop-in. foozle is in one post (and its revision) and in blogdescription. The database also has a custom table wp_custom, a table without the prefix other_table, and a view wp_view_posts. "DB error" means the Unknown column 'Tables_in_' error above.

Single site:

Command main, no drop-in main + HyperDB or LudicrousDB this PR + HyperDB or LudicrousDB
wp db tables 12 tables DB error, no tables 12 tables
wp db tables --scope=global 2 tables DB error, no tables 2 tables
wp db tables 'wp_p*' 2 tables DB error, Error: Couldn't find any tables matching: wp_p* 2 tables
wp db size --tables 12 rows DB error, header only 12 rows
wp db search foozle 5 matches DB error, 0 matches 5 matches
wp search-replace foozle barzle --dry-run 5 DB error twice, 0 5
wp search-replace foozle barzle wp_posts --dry-run 4 DB error, Error: Couldn't find any tables matching: wp_posts 4
wp search-replace foozle barzle --all-tables-with-prefix --dry-run 6, view skipped 6, plus DB error from the views query 6, view skipped
wp search-replace foozle barzle --all-tables --dry-run 7 7 7
wp search-replace foozle barzle (real run, then reverted) 5, rows changed 0, nothing changed 5, rows changed
wp_get_table_names() with all-tables-with-prefix + views-only wp_view_posts DB error, empty wp_view_posts
wp_get_table_names() with all-tables-with-prefix + base-tables-only 13 tables DB error, empty 13 tables

Multisite:

Command main, no drop-in main + HyperDB or LudicrousDB this PR + HyperDB or LudicrousDB
wp db tables --network 28 tables DB error, no tables 28 tables
wp db tables --url=<two> --scope=blog 10 tables DB error, no tables 10 tables
wp search-replace foozle barzle --network --dry-run 10 DB error twice, 0 10
wp search-replace foozle barzle --url=<two> --dry-run 5 DB error twice, 0 5
wp search-replace foozle barzle --url=<two> --all-tables-with-prefix --dry-run 6 6, plus DB error from the views query 6
wp search-replace foozle barzle --network (real run, then reverted) 10 0 10

HyperDB and LudicrousDB gave the same results in every row. With this PR, the full output of every command above, with either drop-in, is identical to main without a drop-in. I compared the saved outputs with diff, and only the class name in the first wp eval line differs.

  1. On a normal site without a drop-in (WP_DEBUG true), the output of this PR is identical to main for all the single site commands, with no PHP warnings.
  2. ✅ Patch is solving the problem: it fixes wp-cli/search-replace-command#207 with HyperDB and LudicrousDB, for every caller of wp_get_table_names() I tried, on single site and multisite.
  3. ❌ Patch is failing: on a normal site with lower_case_table_names=1 and an uppercase prefix, --all-tables-with-prefix returns no tables (see Additional Notes).

Expected result

  • With a drop-in that leaves $wpdb->dbname empty, the commands find the same tables as without a drop-in, and nothing changes for normal sites.

Additional Notes

  • Regression: --all-tables-with-prefix returns nothing with lower_case_table_names=1 and an uppercase prefix. This is on a normal site, no drop-in.

    In the --all-tables-with-prefix branch, $dbname is only set in the else path. On the plain SHOW TABLES LIKE path it is never set, and $tables_sql is never empty at that point. So ! empty( $tables_sql ) && empty( $dbname ) is always true there, and the new PHP filter runs on every --all-tables-with-prefix call, not only when the database name is missing.

    Usually that only repeats what LIKE already did. But SHOW TABLES LIKE is case-insensitive when lower_case_table_names is 1 (the default on Windows), and strpos() is not. With $table_prefix = 'WP_' the server stores the tables as wp_posts and so on, and the filter removes all of them.

    Steps: start MariaDB with --lower-case-table-names=1, run wp config create ... --dbprefix=WP_, then wp core install ..., then:

    $ wp db tables --all-tables-with-prefix --format=csv
    

    main:

    wp_commentmeta,wp_comments,wp_links,wp_options,wp_postmeta,wp_posts,wp_term_relationships,wp_term_taxonomy,wp_termmeta,wp_terms,wp_usermeta,wp_users
    

    This PR: an empty line.

    $ wp search-replace http://lctn.test http://lctn.example --all-tables-with-prefix --dry-run --format=count
    

    main: 8. This PR: 0.

    wp db clean uses all-tables-with-prefix too. On this PR, wp db clean --yes prints Success: Tables dropped. and all 12 tables are still there.

    The default scope is not affected: wp db tables lists 12 tables and wp search-replace without flags finds 8 replacements on both.

    About the first Copilot comment: no notice is raised, because empty() does not warn on an undefined variable (I ran with WP_DEBUG true, and PHPStan passes). But that missing $dbname is the cause of this problem.

  • Minor: order of the last fallback. The SHOW TABLES plus array_intersect() fallback only runs when $wpdb->dbname is empty and SELECT DATABASE() returns nothing. With both real drop-ins SELECT DATABASE() returned the name, so they never reach it. On a connection with no database selected, SHOW TABLES fails as well:

    $ mariadb -h wpcli-db -u wp_cli_test -ppassword1 --skip-ssl -e "SELECT DATABASE(); SHOW TABLES;"
    DATABASE()
    NULL
    --------------
    SHOW TABLES
    --------------
    
    ERROR 1046 (3D000) at line 1: No database selected
    

    To reach it anyway, I used a test-only db.php that extends wpdb, sets dbname to '' and returns null for SELECT DATABASE(). The tables were right, but in $wp_tables order instead of sorted:

    $ wp db tables --format=csv
    wp_users,wp_usermeta,wp_posts,wp_comments,wp_links,wp_options,wp_postmeta,wp_terms,wp_term_taxonomy,wp_term_relationships,wp_termmeta,wp_commentmeta,wp_blogs,wp_blogmeta,wp_signups,wp_site,wp_sitemeta,wp_registration_log
    

    The comment in the other branch says the sorted order is a BC change since 1.5.0, and the legacy SQLite filter below uses array_intersect( $tables, $wp_tables ). Swapping the arguments keeps the sorted order. Since this fallback can only help a drop-in that answers those two queries from different connections, removing it would also be fine.

  • Other bot comments:

    • Copilot, narrowing the fallback to SHOW TABLES LIKE '<prefix>%': I would not do that. $wpdb->tables() returns CUSTOM_USER_TABLE and CUSTOM_USER_META_TABLE when they are defined, and those do not have to use the prefix.
    • Copilot, str_starts_with(): WP-CLI supports PHP 7.2.24+, and CI tests WordPress 4.9, which has no polyfill for it. strpos() is right here.
    • CodeRabbit: no actionable comments. It notes the drop-in behaviour was not executed, which is what the runs above cover.

Support Content

  • Suggested change for both points above:
Suggested change to php/utils-wp.php
@@ -504,19 +504,23 @@ function wp_get_table_names( $args, $assoc_args = [] ) {
 		$tables = $wpdb->get_col( $tables_sql, 0 );
 
 	} elseif ( get_flag_value( $assoc_args, 'all-tables-with-prefix' ) ) {
+		$filter_by_prefix = false;
 		if ( empty( $tables_sql ) ) {
 			$tables_sql = $wpdb->prepare( 'SHOW TABLES LIKE %s', esc_like( $wpdb->get_blog_prefix() ) . '%' );
 		} else {
 			$dbname = ! empty( $wpdb->dbname ) ? $wpdb->dbname : (string) $wpdb->get_var( 'SELECT DATABASE()' );
 			if ( ! empty( $dbname ) ) {
 				$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
+			} else {
+				$filter_by_prefix = true;
 			}
 		}
 
 		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
 		$tables = $wpdb->get_col( $tables_sql, 0 );
 
-		if ( ! empty( $tables_sql ) && empty( $dbname ) ) {
+		// Only filter in PHP when the prefix condition could not be added to the query.
+		if ( $filter_by_prefix ) {
 			$blog_prefix = $wpdb->get_blog_prefix();
 			$tables      = array_values(
 				array_filter(
@@ -558,7 +562,7 @@ function wp_get_table_names( $args, $assoc_args = [] ) {
 			// Fallback when database name cannot be determined (e.g. drop-ins like HyperDB or LudicrousDB).
 			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is safe.
 			$all_tables = (array) $wpdb->get_col( 'SHOW TABLES', 0 );
-			$tables     = array_values( array_intersect( $wp_tables, $all_tables ) );
+			$tables     = array_values( array_intersect( $all_tables, $wp_tables ) );
 		} else {
 			// Note: BC change 1.5.0, tables are sorted (via TABLES view).
 			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- uses esc_sql_ident() and $wpdb->_escape().
  • Suggested test. The PR has no test, and Codecov reports 2 lines not covered. This scenario for features/utils-wp.feature adds a db.php drop-in that clears $wpdb->dbname, like HyperDB and LudicrousDB do. It needs @require-mysql because SQLite already uses wp-content/db.php.
Behat scenario
  # `wp db query` does not yet work on SQLite, and SQLite already uses wp-content/db.php.
  @require-mysql
  Scenario: Get WP table names when a db.php drop-in leaves $wpdb->dbname empty
    Given a WP installation
    And I run `wp db query "CREATE TABLE wp_xx_posts ( id int );"`
    And I run `wp db query "CREATE VIEW wp_posts_view AS ( SELECT ID from wp_posts );"`
    And a wp-content/db.php file:
      """
      <?php
      // Like HyperDB and LudicrousDB, this drop-in does not set $wpdb->dbname.
      class Empty_Dbname_DB extends wpdb {
          public function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) {
              parent::__construct( $dbuser, $dbpassword, $dbname, $dbhost );
              $this->dbname = '';
          }
      }
      $wpdb = new Empty_Dbname_DB( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );
      """
    And a table_names.php file:
      """
      <?php
      WP_CLI::add_command( 'get_table_names', function ( $args, $assoc_args ) {
          echo implode( PHP_EOL, WP_CLI\Utils\wp_get_table_names( $args, $assoc_args ) ) . PHP_EOL;
      } );
      """

    When I run `wp --require=table_names.php get_table_names`
    Then STDOUT should be:
      """
      wp_commentmeta
      wp_comments
      wp_links
      wp_options
      wp_postmeta
      wp_posts
      wp_term_relationships
      wp_term_taxonomy
      wp_termmeta
      wp_terms
      wp_usermeta
      wp_users
      """

    When I run `wp --require=table_names.php get_table_names 'wp_post*'`
    Then STDOUT should be:
      """
      wp_postmeta
      wp_posts
      """

    When I run `wp --require=table_names.php get_table_names --all-tables-with-prefix --base-tables-only 'wp_*posts'`
    Then STDOUT should be:
      """
      wp_posts
      wp_xx_posts
      """

    When I run `wp --require=table_names.php get_table_names --all-tables-with-prefix --views-only`
    Then STDOUT should be:
      """
      wp_posts_view
      """
  • Test results on MariaDB 11.8.9. I ran these scenarios with vendor/bin/behat directly, because the tags skip @require-mysql on MariaDB:
Test main this PR this PR + suggested change
New scenario above fails: The command unexpectedly produced STDERR output (the Tables_in_ error) passes, 13 steps passes, 13 steps
"Get WP table names for single site install" passes, 72 steps passes, 72 steps passes, 72 steps
"Get WP table names for multisite install" passes, 102 steps passes, 102 steps passes, 102 steps
composer phpunit 634 tests 634 tests 634 tests

PHPUnit gives the same result on all three: OK, but there were issues! with 1 warning and 5 skipped tests, so nothing changes there.

vendor/bin/run-behat-tests features/utils-wp.feature (9 scenarios after the tag filter) passes on main and this PR on MariaDB, and with the suggested change on SQLite. vendor/bin/phpcs php/utils-wp.php and composer phpstan pass with the suggested change.

With the suggested change, the lower_case_table_names=1 site lists the 12 tables and finds 8 replacements again, wp db clean --yes drops the tables again, the fallback returns sorted tables, and the HyperDB and LudicrousDB multisite runs are still identical to main without a drop-in.

Comment thread php/utils-wp.php
Comment on lines 507 to +519
if ( empty( $tables_sql ) ) {
$tables_sql = $wpdb->prepare( 'SHOW TABLES LIKE %s', esc_like( $wpdb->get_blog_prefix() ) . '%' );
} else {
$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $wpdb->dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
$dbname = ! empty( $wpdb->dbname ) ? $wpdb->dbname : (string) $wpdb->get_var( 'SELECT DATABASE()' );
if ( ! empty( $dbname ) ) {
$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
}
}

// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
$tables = $wpdb->get_col( $tables_sql, 0 );

if ( ! empty( $tables_sql ) && empty( $dbname ) ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

$dbname is only set in the else path, so on the plain SHOW TABLES LIKE path this PHP filter runs on every --all-tables-with-prefix call. With lower_case_table_names=1 and an uppercase prefix it removes all tables, because SHOW TABLES LIKE is case-insensitive there and strpos() is not. This filters only when the prefix condition could not be added to the query:

Suggested change
if ( empty( $tables_sql ) ) {
$tables_sql = $wpdb->prepare( 'SHOW TABLES LIKE %s', esc_like( $wpdb->get_blog_prefix() ) . '%' );
} else {
$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $wpdb->dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
$dbname = ! empty( $wpdb->dbname ) ? $wpdb->dbname : (string) $wpdb->get_var( 'SELECT DATABASE()' );
if ( ! empty( $dbname ) ) {
$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
}
}
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
$tables = $wpdb->get_col( $tables_sql, 0 );
if ( ! empty( $tables_sql ) && empty( $dbname ) ) {
$filter_by_prefix = false;
if ( empty( $tables_sql ) ) {
$tables_sql = $wpdb->prepare( 'SHOW TABLES LIKE %s', esc_like( $wpdb->get_blog_prefix() ) . '%' );
} else {
$dbname = ! empty( $wpdb->dbname ) ? $wpdb->dbname : (string) $wpdb->get_var( 'SELECT DATABASE()' );
if ( ! empty( $dbname ) ) {
$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
} else {
$filter_by_prefix = true;
}
}
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
$tables = $wpdb->get_col( $tables_sql, 0 );
// Only filter in PHP when the prefix condition could not be added to the query.
if ( $filter_by_prefix ) {

Comment thread php/utils-wp.php
// Fallback when database name cannot be determined (e.g. drop-ins like HyperDB or LudicrousDB).
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is safe.
$all_tables = (array) $wpdb->get_col( 'SHOW TABLES', 0 );
$tables = array_values( array_intersect( $wp_tables, $all_tables ) );

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With the arguments this way round, the fallback returns the tables in $wp_tables order instead of sorted. Swapping them keeps the sorted order, like the legacy SQLite filter below:

Suggested change
$tables = array_values( array_intersect( $wp_tables, $all_tables ) );
$tables = array_values( array_intersect( $all_tables, $wp_tables ) );

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.

WordPress database error Unknown column 'Tables_in_' during search-replace

3 participants