Skip to content

Commit cfe43e7

Browse files
Copilotswissspidy
andauthored
Add wpdb fallback for wp db query when mysql/mariadb binary is unavailable
Agent-Logs-Url: https://github.com/wp-cli/db-command/sessions/8996fc65-2840-4792-8cf4-ddc700a7c040 Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
1 parent 5c54007 commit cfe43e7

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

features/db-query.feature

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,29 @@ Feature: Query the database with WordPress' MySQL config
119119
"""
120120
ANSI
121121
"""
122+
123+
@require-mysql-or-mariadb
124+
Scenario: Database querying falls back to wpdb when mysql binary is unavailable
125+
Given a WP install
126+
And a fake-bin/mysql file:
127+
"""
128+
#!/bin/sh
129+
exit 127
130+
"""
131+
And a fake-bin/mariadb file:
132+
"""
133+
#!/bin/sh
134+
exit 127
135+
"""
136+
137+
When I run `chmod +x fake-bin/mysql fake-bin/mariadb`
138+
And I run `env PATH={RUN_DIR}/fake-bin:$PATH wp db query "SELECT COUNT(ID) FROM wp_users;" --debug`
139+
Then STDOUT should be:
140+
"""
141+
COUNT(ID)
142+
1
143+
"""
144+
And STDERR should contain:
145+
"""
146+
MySQL/MariaDB binary not available, falling back to wpdb.
147+
"""

src/DB_Command.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,25 @@ public function query( $args, $assoc_args ) {
591591
return;
592592
}
593593

594+
if ( ! $this->is_mysql_binary_available() ) {
595+
// Get the query from args or STDIN.
596+
$query = '';
597+
if ( ! empty( $args ) ) {
598+
$query = $args[0];
599+
} else {
600+
$query = stream_get_contents( STDIN );
601+
}
602+
603+
if ( empty( $query ) ) {
604+
WP_CLI::error( 'No query specified.' );
605+
}
606+
607+
WP_CLI::debug( 'MySQL/MariaDB binary not available, falling back to wpdb.', 'db' );
608+
$this->maybe_load_wpdb();
609+
$this->wpdb_query( $query, $assoc_args );
610+
return;
611+
}
612+
594613
$command = sprintf(
595614
'/usr/bin/env %s%s --no-auto-rehash',
596615
$this->get_mysql_command(),
@@ -2348,4 +2367,118 @@ protected function get_current_sql_modes( $assoc_args ) {
23482367
private function get_mysql_command() {
23492368
return 'mariadb' === Utils\get_db_type() ? 'mariadb' : 'mysql';
23502369
}
2370+
2371+
/**
2372+
* Check if the mysql or mariadb binary is available.
2373+
*
2374+
* @return bool True if the binary is available, false otherwise.
2375+
*/
2376+
protected function is_mysql_binary_available() {
2377+
static $available = null;
2378+
2379+
if ( null === $available ) {
2380+
$binary = $this->get_mysql_command();
2381+
$result = \WP_CLI\Process::create( "/usr/bin/env {$binary} --version", null, null )->run();
2382+
$available = 0 === $result->return_code;
2383+
}
2384+
2385+
return $available;
2386+
}
2387+
2388+
/**
2389+
* Load WordPress's wpdb if not already available.
2390+
*
2391+
* Loads the minimal required WordPress files to make $wpdb available,
2392+
* including any db.php drop-in (e.g., HyperDB or other custom drivers).
2393+
*/
2394+
protected function maybe_load_wpdb() {
2395+
global $wpdb;
2396+
2397+
if ( isset( $wpdb ) ) {
2398+
return;
2399+
}
2400+
2401+
if ( ! defined( 'WPINC' ) ) {
2402+
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
2403+
define( 'WPINC', 'wp-includes' );
2404+
}
2405+
2406+
if ( ! defined( 'WP_CONTENT_DIR' ) ) {
2407+
define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
2408+
}
2409+
2410+
// Load required WordPress files if not already loaded.
2411+
if ( ! function_exists( 'add_action' ) ) {
2412+
$required_files = [
2413+
ABSPATH . WPINC . '/compat.php',
2414+
ABSPATH . WPINC . '/plugin.php',
2415+
// Defines `wp_debug_backtrace_summary()` as used by wpdb.
2416+
ABSPATH . WPINC . '/functions.php',
2417+
ABSPATH . WPINC . '/class-wpdb.php',
2418+
];
2419+
2420+
foreach ( $required_files as $required_file ) {
2421+
if ( file_exists( $required_file ) ) {
2422+
require_once $required_file;
2423+
}
2424+
}
2425+
}
2426+
2427+
// Load db.php drop-in if it exists (e.g., HyperDB or other custom drivers).
2428+
$db_dropin_path = WP_CONTENT_DIR . '/db.php';
2429+
if ( file_exists( $db_dropin_path ) && ! $this->is_sqlite() ) {
2430+
require_once $db_dropin_path;
2431+
}
2432+
2433+
// If $wpdb is still not set (e.g. no drop-in), create a new instance using the DB credentials from wp-config.php.
2434+
if ( ! isset( $GLOBALS['wpdb'] ) && class_exists( 'wpdb' ) ) {
2435+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2436+
$wpdb = new wpdb( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );
2437+
}
2438+
}
2439+
2440+
/**
2441+
* Execute a query against the database using wpdb.
2442+
*
2443+
* Used as a fallback when the mysql/mariadb binary is not available.
2444+
*
2445+
* @param string $query SQL query to execute.
2446+
* @param array $assoc_args Associative arguments.
2447+
*/
2448+
protected function wpdb_query( $query, $assoc_args = [] ) {
2449+
global $wpdb;
2450+
2451+
if ( ! isset( $wpdb ) || ! ( $wpdb instanceof wpdb ) ) {
2452+
WP_CLI::error( 'WordPress database (wpdb) is not available. Please install MySQL or MariaDB client tools.' );
2453+
}
2454+
2455+
$skip_column_names = Utils\get_flag_value( $assoc_args, 'skip-column-names', false );
2456+
2457+
$is_row_modifying_query = preg_match( '/\b(UPDATE|DELETE|INSERT|REPLACE(?!\s*\()|LOAD DATA)\b/i', $query );
2458+
2459+
if ( $is_row_modifying_query ) {
2460+
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2461+
$affected_rows = $wpdb->query( $query );
2462+
if ( false === $affected_rows ) {
2463+
// phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
2464+
WP_CLI::error( 'Query failed: ' . strip_tags( $wpdb->last_error ) );
2465+
}
2466+
WP_CLI::success( "Query succeeded. Rows affected: {$affected_rows}" );
2467+
} else {
2468+
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2469+
$results = $wpdb->get_results( $query, ARRAY_A );
2470+
2471+
if ( $wpdb->last_error ) {
2472+
// phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
2473+
WP_CLI::error( 'Query failed: ' . strip_tags( $wpdb->last_error ) );
2474+
}
2475+
2476+
if ( empty( $results ) ) {
2477+
return;
2478+
}
2479+
2480+
$headers = array_keys( $results[0] );
2481+
$this->display_query_results( $headers, $results, $skip_column_names );
2482+
}
2483+
}
23512484
}

0 commit comments

Comments
 (0)