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