diff --git a/composer.json b/composer.json index e575f01f..34fbdb20 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,8 @@ "db search", "db tables", "db size", - "db columns" + "db columns", + "db status" ] }, "autoload": { diff --git a/features/db-status.feature b/features/db-status.feature new file mode 100644 index 00000000..1702184f --- /dev/null +++ b/features/db-status.feature @@ -0,0 +1,122 @@ +Feature: Display database status overview + + Scenario: Display database status for a WordPress install + Given a WP install + + When I run `wp db status` + Then STDOUT should contain: + """ + Database Name: + """ + And STDOUT should contain: + """ + Tables: + """ + And STDOUT should contain: + """ + Total Size: + """ + And STDOUT should contain: + """ + Prefix: wp_ + """ + And STDOUT should contain: + """ + Engine: + """ + And STDOUT should contain: + """ + Charset: + """ + And STDOUT should contain: + """ + Collation: + """ + And STDOUT should contain: + """ + Check Status: + """ + + Scenario: Verify database status shows correct database name + Given a WP install + + When I run `wp db status` + Then STDOUT should contain: + """ + wp_cli_test + """ + + Scenario: Verify database status shows check status as OK + Given a WP install + + When I run `wp db status` + Then STDOUT should contain: + """ + Check Status: OK + """ + + Scenario: Run db status with MySQL defaults to check the database + Given a WP install + + When I run `wp db status --defaults` + Then STDOUT should contain: + """ + Database Name: + """ + And STDOUT should contain: + """ + Check Status: OK + """ + + Scenario: Run db status with --no-defaults to check the database + Given a WP install + + When I run `wp db status --no-defaults` + Then STDOUT should contain: + """ + Database Name: + """ + And STDOUT should contain: + """ + Check Status: OK + """ + + Scenario: Run db status with passed-in options + Given a WP install + + When I run `wp db status --dbuser=wp_cli_test` + Then STDOUT should contain: + """ + Database Name: + """ + And STDOUT should contain: + """ + Check Status: OK + """ + + When I run `wp db status --dbpass=password1` + Then STDOUT should contain: + """ + Database Name: + """ + And STDOUT should contain: + """ + Check Status: OK + """ + + When I run `wp db status --dbuser=wp_cli_test --dbpass=password1` + Then STDOUT should contain: + """ + Database Name: + """ + And STDOUT should contain: + """ + Check Status: OK + """ + + When I try `wp db status --dbuser=no_such_user` + Then the return code should not be 0 + + When I try `wp db status --dbpass=no_such_pass` + Then the return code should not be 0 + diff --git a/src/DB_Command.php b/src/DB_Command.php index 77676172..a66800b0 100644 --- a/src/DB_Command.php +++ b/src/DB_Command.php @@ -1247,6 +1247,152 @@ public function prefix() { WP_CLI::log( $wpdb->prefix ); } + /** + * Displays a quick database status overview. + * + * Shows key database information including name, table count, size, + * prefix, engine, charset, collation, and health check status. This + * command is useful for getting a quick snapshot of database health + * without needing to run multiple separate commands. + * + * ## OPTIONS + * + * [--dbuser=] + * : Username to pass to mysql. Defaults to DB_USER. + * + * [--dbpass=] + * : Password to pass to mysql. Defaults to DB_PASSWORD. + * + * [--defaults] + * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration. + * + * ## EXAMPLES + * + * $ wp db status + * Database Name: wp_cli_test + * Tables: 54 + * Total Size: 312 KB + * Prefix: wp_ + * Engine: InnoDB + * Charset: utf8mb4 + * Collation: utf8mb4_unicode_ci + * Check Status: OK + * + * @when before_wp_load + */ + public function status( $_, $assoc_args ) { + global $wpdb; + + // Get database name. + $db_name = DB_NAME; + + // Get table count. + $table_count = count( Utils\wp_get_table_names( [], [ 'scope' => 'all' ] ) ); + + // Get total database size. + $db_size_bytes = $wpdb->get_var( + $wpdb->prepare( + 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES where table_schema = %s GROUP BY table_schema;', + DB_NAME + ) + ); + + // Format size to human-readable. + if ( empty( $db_size_bytes ) || $db_size_bytes <= 0 ) { + $db_size = '0 B'; + } else { + $size_key = floor( log( $db_size_bytes ) / log( 1000 ) ); + $sizes = [ 'B', 'KB', 'MB', 'GB', 'TB' ]; + $size_format = isset( $sizes[ $size_key ] ) ? $sizes[ $size_key ] : $sizes[0]; + $divisor = pow( 1000, $size_key ); + $db_size = round( $db_size_bytes / $divisor, 2 ) . ' ' . $size_format; + } + + // Get prefix. + $prefix = $wpdb->prefix; + + // Get engine, charset, and collation from information_schema across all tables with the prefix. + $table_info = $wpdb->get_row( + $wpdb->prepare( + 'SELECT ' + . 'COUNT(DISTINCT ENGINE) AS engine_count, ' + . 'MIN(ENGINE) AS engine, ' + . 'COUNT(DISTINCT CCSA.character_set_name) AS charset_count, ' + . 'MIN(CCSA.character_set_name) AS charset, ' + . 'COUNT(DISTINCT TABLE_COLLATION) AS collation_count, ' + . 'MIN(TABLE_COLLATION) AS collation ' + . 'FROM information_schema.TABLES T ' + . 'LEFT JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY CCSA ' + . 'ON CCSA.collation_name = T.table_collation ' + . 'WHERE T.table_schema = %s ' + . 'AND T.table_name LIKE %s', + DB_NAME, + $wpdb->esc_like( $prefix ) . '%' + ) + ); + + if ( $table_info ) { + $engine_count = isset( $table_info->engine_count ) ? (int) $table_info->engine_count : 0; + $charset_count = isset( $table_info->charset_count ) ? (int) $table_info->charset_count : 0; + $collation_count = isset( $table_info->collation_count ) ? (int) $table_info->collation_count : 0; + + if ( $engine_count > 1 ) { + $engine = 'Mixed'; + } elseif ( isset( $table_info->engine ) && '' !== $table_info->engine ) { + $engine = $table_info->engine; + } else { + $engine = 'N/A'; + } + + if ( $charset_count > 1 ) { + $charset = 'Mixed'; + } elseif ( isset( $table_info->charset ) && '' !== $table_info->charset ) { + $charset = $table_info->charset; + } else { + $charset = 'N/A'; + } + + if ( $collation_count > 1 ) { + $collation = 'Mixed'; + } elseif ( isset( $table_info->collation ) && '' !== $table_info->collation ) { + $collation = $table_info->collation; + } else { + $collation = 'N/A'; + } + } else { + $engine = 'N/A'; + $charset = 'N/A'; + $collation = 'N/A'; + } + // Run database check silently to get status. + if ( $table_count > 0 ) { + $command = sprintf( + '/usr/bin/env %s%s %s', + Utils\get_sql_check_command(), + $this->get_defaults_flag_string( $assoc_args ), + '%s' + ); + list( $stdout, $stderr, $exit_code ) = self::run( + Utils\esc_cmd( $command, DB_NAME ), + array_merge( [ 'check' => true ], $assoc_args ), + false + ); + $check_status = ( 0 === $exit_code ) ? 'OK' : 'Error'; + } else { + // No tables to check; mark status as not applicable. + $check_status = 'N/A'; + } + // Output formatted status. + WP_CLI::log( sprintf( '%-18s %s', 'Database Name:', $db_name ) ); + WP_CLI::log( sprintf( '%-18s %d', 'Tables:', $table_count ) ); + WP_CLI::log( sprintf( '%-18s %s', 'Total Size:', $db_size ) ); + WP_CLI::log( sprintf( '%-18s %s', 'Prefix:', $prefix ) ); + WP_CLI::log( sprintf( '%-18s %s', 'Engine:', $engine ) ); + WP_CLI::log( sprintf( '%-18s %s', 'Charset:', $charset ) ); + WP_CLI::log( sprintf( '%-18s %s', 'Collation:', $collation ) ); + WP_CLI::log( sprintf( '%-18s %s', 'Check Status:', $check_status ) ); + } + /** * Finds a string in the database. *