-
Notifications
You must be signed in to change notification settings - Fork 67
Add wp db status command for database health overview
#307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
wp db status command for database health overview
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a new wp db status command, which provides a convenient overview of the database's health and configuration. The implementation is solid and consolidates information from several other commands. My review focuses on improving the new Behat tests for better robustness and refactoring parts of the new status() method to enhance code clarity, reduce duplication, and improve maintainability.
| 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 | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Behat tests are split across multiple scenarios, each testing a small part of the command's output. This makes the tests less robust as they don't verify the output structure as a whole. Combining these into a single, more comprehensive scenario would improve test clarity and strength. Using should match with regular expressions for dynamic values like table count and size will also make the assertions more precise and resilient to minor changes.
Scenario: Display database status for a WordPress install
Given a WP install
When I run `wp db status`
Then STDOUT should contain:
"""
Database Name: wp_cli_test
"""
And STDOUT should match /^Tables:\s+\d+$/m
And STDOUT should match /^Total Size:\s+[\d.]+ \wB$/m
And STDOUT should contain:
"""
Prefix: wp_
"""
And STDOUT should match /^Engine:\s+\w+$/m
And STDOUT should match /^Charset:\s+\w+$/m
And STDOUT should match /^Collation:\s+\w+$/m
And STDOUT should contain:
"""
Check Status: OK
"""
| if ( empty( $db_size_bytes ) || $db_size_bytes <= 0 ) { | ||
| $db_size = '0 B'; | ||
| } else { | ||
| $size_key = floor( log( (float) $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( (int) $db_size_bytes / $divisor, 2 ) . ' ' . $size_format; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block for formatting the database size into a human-readable string is very similar to logic within the size() method. To avoid code duplication and improve maintainability, consider extracting this logic into a private helper function that can be reused in both places. For now, the logic can be slightly simplified for better readability by inverting the conditional and using the null coalescing operator.
if ( ! empty( $db_size_bytes ) && $db_size_bytes > 0 ) {
$size_key = floor( log( (float) $db_size_bytes ) / log( 1000 ) );
$sizes = [ 'B', 'KB', 'MB', 'GB', 'TB' ];
$size_format = $sizes[ $size_key ] ?? $sizes[0];
$divisor = pow( 1000, $size_key );
$db_size = round( (int) $db_size_bytes / $divisor, 2 ) . ' ' . $size_format;
} else {
$db_size = '0 B';
}| 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 ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The block of WP_CLI::log() calls for outputting the status is repetitive. This can be refactored to be data-driven by storing the status information in an associative array and then iterating over it to generate the output. This approach is more maintainable and makes it easier to add or modify status fields in the future.
$status_items = [
'Database Name' => $db_name,
'Tables' => $table_count,
'Total Size' => $db_size,
'Prefix' => $prefix,
'Engine' => $engine,
'Charset' => $charset,
'Collation' => $collation,
'Check Status' => $check_status,
];
foreach ( $status_items as $label => $value ) {
WP_CLI::log( sprintf( '%-18s %s', $label . ':', $value ) );
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a new wp db status command that provides a consolidated view of database health and configuration information in a single command, eliminating the need to run multiple separate database commands (wp db size, wp db tables, wp db prefix, wp db check).
Changes:
- New
status()method inDB_Commandclass that queries database metadata and runs health checks - Command registration in
composer.json - Behat test coverage validating output format
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| src/DB_Command.php | Implements the status() method that retrieves database name, table count, size, prefix, engine, charset, collation, and check status, formatting output as aligned key-value pairs |
| features/db-status.feature | Adds Behat test scenarios to verify the command outputs all expected fields and displays correct values |
| composer.json | Registers the new db status command in the commands array |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ); | ||
|
|
||
| $check_status = ( 0 === $exit_code ) ? 'OK' : 'Error'; | ||
|
|
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The database check silently ignores stderr output. When the check fails (exit_code is non-zero), the user only sees "Error" but doesn't get any details about what went wrong. Consider logging or displaying the stderr output when the check fails to provide more actionable information for debugging database issues. This would be especially helpful since this command is intended for database health diagnostics.
| if ( 0 !== $exit_code && ! empty( trim( (string) $stderr ) ) ) { | |
| WP_CLI::log( sprintf( '%-18s %s', 'Check Details:', trim( (string) $stderr ) ) ); | |
| } |
| // Get engine, charset, and collation from information_schema (using a common table). | ||
| $table_info = $wpdb->get_row( | ||
| $wpdb->prepare( | ||
| 'SELECT ENGINE as engine, CCSA.character_set_name as charset, 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 ' | ||
| . 'LIMIT 1', | ||
| DB_NAME, | ||
| $wpdb->esc_like( $prefix ) . '%' | ||
| ) | ||
| ); | ||
|
|
||
| $engine = $table_info && isset( $table_info->engine ) ? $table_info->engine : 'N/A'; | ||
| $charset = $table_info && isset( $table_info->charset ) ? $table_info->charset : 'N/A'; | ||
| $collation = $table_info && isset( $table_info->collation ) ? $table_info->collation : 'N/A'; | ||
|
|
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The query for engine, charset, and collation retrieves information from only one table (the first table matching the prefix). In a multi-engine or mixed-charset database, this could be misleading. If tables have different engines or charsets, the command would only show the values from one arbitrary table. Consider either documenting this limitation in the command's docblock (e.g., "Engine, charset, and collation are determined from the first table with the configured prefix") or implementing logic to detect if multiple values exist and display "Mixed" when appropriate.
| // Get engine, charset, and collation from information_schema (using a common table). | |
| $table_info = $wpdb->get_row( | |
| $wpdb->prepare( | |
| 'SELECT ENGINE as engine, CCSA.character_set_name as charset, 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 ' | |
| . 'LIMIT 1', | |
| DB_NAME, | |
| $wpdb->esc_like( $prefix ) . '%' | |
| ) | |
| ); | |
| $engine = $table_info && isset( $table_info->engine ) ? $table_info->engine : 'N/A'; | |
| $charset = $table_info && isset( $table_info->charset ) ? $table_info->charset : 'N/A'; | |
| $collation = $table_info && isset( $table_info->collation ) ? $table_info->collation : 'N/A'; | |
| // 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'; | |
| } |
| $check_args = []; | ||
| $command = sprintf( | ||
| '/usr/bin/env %s%s %s', | ||
| Utils\get_sql_check_command(), | ||
| $this->get_defaults_flag_string( $check_args ), | ||
| '%s' | ||
| ); | ||
| list( $stdout, $stderr, $exit_code ) = self::run( | ||
| Utils\esc_cmd( $command, DB_NAME ), | ||
| [ 'check' => true ], | ||
| false | ||
| ); | ||
|
|
||
| $check_status = ( 0 === $exit_code ) ? 'OK' : 'Error'; | ||
|
|
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a database has no tables with the configured prefix, the query at lines 1304-1316 will return NULL for $table_info, and lines 1318-1320 will correctly set engine, charset, and collation to 'N/A'. However, when there are NO tables at all in the database (not even with a different prefix), this query would still return NULL but the Check Status would likely fail or behave unexpectedly since mysqlcheck needs tables to check. Consider adding a check for zero tables and either skipping the database check or displaying 'N/A' for Check Status when $table_count is 0.
| $check_args = []; | |
| $command = sprintf( | |
| '/usr/bin/env %s%s %s', | |
| Utils\get_sql_check_command(), | |
| $this->get_defaults_flag_string( $check_args ), | |
| '%s' | |
| ); | |
| list( $stdout, $stderr, $exit_code ) = self::run( | |
| Utils\esc_cmd( $command, DB_NAME ), | |
| [ 'check' => true ], | |
| false | |
| ); | |
| $check_status = ( 0 === $exit_code ) ? 'OK' : 'Error'; | |
| if ( $table_count > 0 ) { | |
| $check_args = []; | |
| $command = sprintf( | |
| '/usr/bin/env %s%s %s', | |
| Utils\get_sql_check_command(), | |
| $this->get_defaults_flag_string( $check_args ), | |
| '%s' | |
| ); | |
| list( $stdout, $stderr, $exit_code ) = self::run( | |
| Utils\esc_cmd( $command, DB_NAME ), | |
| [ 'check' => true ], | |
| false | |
| ); | |
| $check_status = ( 0 === $exit_code ) ? 'OK' : 'Error'; | |
| } else { | |
| // No tables to check; mark status as not applicable. | |
| $check_status = 'N/A'; | |
| } |
| 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 | ||
| """ |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test scenarios don't verify that the --dbuser, --dbpass, and --defaults options work with the status command. Looking at similar commands like db check (features/db-check.feature:42-138), these options are tested to ensure they properly override credentials and handle authentication failures. Consider adding test scenarios to verify these parameters work correctly with wp db status, especially since the current implementation has a bug where these parameters aren't being passed through.
| $check_args = []; | ||
| $command = sprintf( | ||
| '/usr/bin/env %s%s %s', | ||
| Utils\get_sql_check_command(), | ||
| $this->get_defaults_flag_string( $check_args ), | ||
| '%s' | ||
| ); | ||
| list( $stdout, $stderr, $exit_code ) = self::run( | ||
| Utils\esc_cmd( $command, DB_NAME ), | ||
| [ 'check' => true ], | ||
| false | ||
| ); |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty $check_args array is passed to get_defaults_flag_string(), but this array is never populated with potential dbuser or dbpass parameters that might have been passed to the command. If the status command is invoked with --dbuser or --dbpass options (similar to other db commands like check or optimize), these credentials won't be passed to the mysqlcheck command, causing authentication failures. The status method should accept $assoc_args parameters and pass them through to the check command, similar to how the check() method works at line 252-266.
| * | ||
| * @when after_wp_load | ||
| */ | ||
| public function status() { |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method signature is missing parameters. Looking at other command methods in the class, they accept $_ (positional arguments) and $assoc_args (associative arguments). The status method should have the signature public function status( $_, $assoc_args ) to be consistent with other commands in this class and to support passing options like --dbuser, --dbpass, and --defaults as documented in similar commands.
| public function status() { | |
| public function status( $_, $assoc_args ) { |
| /** | ||
| * 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. | ||
| * | ||
| * ## 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 | ||
| * |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command documentation should include OPTIONS section documenting the --dbuser, --dbpass, and --defaults parameters that are supported by other database commands. These options are standard across all db commands that interact with MySQL directly (check, optimize, repair, etc.) and users would expect them to work here as well for consistency.
| * Collation: utf8mb4_unicode_ci | ||
| * Check Status: OK | ||
| * | ||
| * @when after_wp_load |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a discrepancy between the PR description and the code. The PR description states the command "Works without loading WordPress (@when after_wp_load), suitable for automation", but having @when after_wp_load means WordPress DOES get loaded. The statement seems to be backwards. Either the annotation should be removed (to inherit after_wp_config_load from the class level), or the PR description needs correction to accurately reflect that WordPress is loaded.
| $size_key = floor( log( (float) $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( (int) $db_size_bytes / $divisor, 2 ) . ' ' . $size_format; |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type casting issue: The size calculation casts $db_size_bytes to (int) which could overflow for very large databases on 32-bit systems. Additionally, the log calculation uses (float) cast, but on line 1297 the division uses (int) cast. For consistency with the existing size command implementation (see line 1196 in the codebase), the division should cast to (int) the bytes value, or better yet, avoid casting altogether since PHP handles numeric strings correctly in arithmetic operations.
| $size_key = floor( log( (float) $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( (int) $db_size_bytes / $divisor, 2 ) . ' ' . $size_format; | |
| $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; |
Adds single command to retrieve database health and configuration info, consolidating what previously required running
wp db size,wp db tables,wp db prefix, andwp db checkseparately.Implementation
New
status()method inDB_Commandclassinformation_schema.TABLESfor size, engine, charset, collationmysqlcheckfor health statusCommand registration in
composer.jsonBehat test coverage for output validation
Output
Notes
wp db size --human-readablebehavior@when after_wp_load), suitable for automationOriginal prompt
wp db statuscommand for quick database health overview #306✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.