Skip to content

Add SQLite compatibility to wp db commands#299

Open
Copilot wants to merge 48 commits intomainfrom
copilot/add-sqlite-compatibility
Open

Add SQLite compatibility to wp db commands#299
Copilot wants to merge 48 commits intomainfrom
copilot/add-sqlite-compatibility

Conversation

Copy link
Contributor

Copilot AI commented Nov 20, 2025

Add SQLite compatibility to wp db commands

This PR implements SQLite support for database commands when using the SQLite Database Integration plugin. Commands now detect SQLite via DB_ENGINE constant, SQLITE_DB_DROPIN_VERSION, or db.php drop-in inspection.

Changes

  • New DB_Command_SQLite trait - Isolated SQLite operations using PDO:

    • create/drop/reset - File-based database lifecycle with sqlite3 binary
    • query - Direct PDO execution with formatted output (now supports --skip-column-names)
    • export/import - SQL dump/restore with proper identifier escaping
    • size - File size calculation
    • Database path resolution supporting FQDB, FQDBDIR, DB_FILE constants
  • Modified DB_Command methods - Detect SQLite and route accordingly:

    • create, drop, reset, query, export, import - Full SQLite implementation
    • size - SQLite file size vs MySQL information_schema query
    • check, optimize, repair, cli - Warning messages (not applicable to SQLite)
    • tables, prefix, columns, search, clean - Unchanged (work via $wpdb)
  • Test scenarios - Updated to support SQLite for --skip-column-names flag

  • Documentation (README.md) - Supported commands, configuration, detection methods

Security Updates

  • Fixed command injection vulnerabilities: All sqlite3 CLI commands now use Utils\esc_cmd to properly escape arguments
  • Added sqlite3 binary check: Preflight validation with clear error messages if sqlite3 is not installed

Recent Updates

  • Added --skip-column-names support for SQLite: The sqlite_query() method now accepts $assoc_args and respects the --skip-column-names flag
  • Replaced custom display_table() with Formatter class: Using the standard WP-CLI Formatter class for consistency with other commands
  • Updated tests: Removed MySQL-only restriction from --skip-column-names test as SQLite now supports it

Example Usage

# Works automatically when SQLite is detected
wp db query "SELECT * FROM wp_posts"
wp db query "SELECT * FROM wp_posts" --skip-column-names
wp db export backup.sql
wp db size

Technical Notes

  • Requires sqlite3 CLI binary for create/drop/reset/export/import operations
  • PDO used for query execution, enabling functionality at after_wp_config_load stage
  • SQLite identifiers escaped with double-quotes, values via PDO::quote()
  • Default database path: wp-content/database/.ht.sqlite
  • Full backward compatibility - MySQL/MariaDB code paths unchanged
  • Uses WP-CLI Formatter class for query output formatting
  • All shell commands properly escaped using Utils\esc_cmd

Fixes #234


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 4 commits November 20, 2025 16:30
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copilot AI changed the title [WIP] Add SQLite compatibility to wp db commands Add SQLite compatibility to wp db commands Nov 20, 2025
Copilot AI requested a review from swissspidy November 20, 2025 16:41
@codecov
Copy link

codecov bot commented Nov 20, 2025

Codecov Report

❌ Patch coverage is 14.96815% with 267 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/DB_Command_SQLite.php 4.23% 226 Missing ⚠️
src/DB_Command.php 47.43% 41 Missing ⚠️

📢 Thoughts on this report? Let us know!

This comment was marked as resolved.

swissspidy and others added 5 commits February 11, 2026 12:32
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI and others added 2 commits February 11, 2026 11:44
- Use Utils\esc_cmd to properly escape database path arguments
- Add is_sqlite3_available() helper to check for sqlite3 binary
- Add preflight check with clear error message if sqlite3 not found
- Addresses security concerns about unescaped shell commands

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
@swissspidy

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

@swissspidy swissspidy marked this pull request as ready for review February 11, 2026 13:00
@swissspidy swissspidy requested a review from a team as a code owner February 11, 2026 13:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +1644 to 1646
// Don't bother warning for term relationships (which is just 3 int columns) or SQLite.
} elseif ( ! preg_match( '/_term_relationships$/', $table ) && ! $this->is_sqlite() ) {
WP_CLI::warning( $primary_keys ? "No text columns for table '$table' - skipped." : "No primary key or text columns for table '$table' - skipped." );
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

In search(), the new condition && ! $this->is_sqlite() is inside the per-table loop. is_sqlite() can hit the filesystem (drop-in inspection), so calling it repeatedly can add avoidable overhead when searching many tables. Cache the result once at the start of search() (e.g. $is_sqlite = $this->is_sqlite();) and use that variable here.

Copilot uses AI. Check for mistakes.
static $available = null;

if ( null === $available ) {
$result = \WP_CLI\Process::create( 'which sqlite3', null, null )->run();
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

is_sqlite3_available() uses which sqlite3 to detect the binary. which isn’t reliably available across environments (e.g. some minimal containers, Windows), so this check can incorrectly report sqlite3 as unavailable. Consider checking availability by executing sqlite3 --version (via /usr/bin/env sqlite3) and checking the return code, or otherwise using a more portable command-existence check.

Suggested change
$result = \WP_CLI\Process::create( 'which sqlite3', null, null )->run();
$result = \WP_CLI\Process::create( '/usr/bin/env sqlite3 --version', null, null )->run();

Copilot uses AI. Check for mistakes.
if ( ! file_exists( $db_path ) ) {
WP_CLI::error( 'Database does not exist.' );
}

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

sqlite_export() invokes the sqlite3 CLI but doesn’t perform the same preflight check used by sqlite_create()/sqlite_reset(). If sqlite3 isn’t installed, this will fail later with a generic “Could not export database” error. Please call the sqlite3 availability check up front and emit the clearer error message before attempting any export steps.

Suggested change
if ( ! $this->is_sqlite3_available() ) {
WP_CLI::error( 'The sqlite3 binary could not be found. Please install sqlite3 to use the export command.' );
}

Copilot uses AI. Check for mistakes.
Comment on lines +288 to +289
$temp_db = tempnam( sys_get_temp_dir(), 'temp.db' );
$export_db = tempnam( sys_get_temp_dir(), 'export.db' );
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

$export_db is created via tempnam() but never used. This leaves an unnecessary temporary file on disk and makes the export logic harder to follow. Remove this variable (or use it if it was intended for a later step).

Suggested change
$temp_db = tempnam( sys_get_temp_dir(), 'temp.db' );
$export_db = tempnam( sys_get_temp_dir(), 'export.db' );
$temp_db = tempnam( sys_get_temp_dir(), 'temp.db' );

Copilot uses AI. Check for mistakes.
Comment on lines +289 to +291
$export_db = tempnam( sys_get_temp_dir(), 'export.db' );

copy( $db_path, $temp_db );
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The export temp file creation/copy isn’t checked for failure (tempnam() can return false; copy() can fail). If either fails, subsequent sqlite3 commands will run against an invalid path and produce confusing errors. Please validate these return values and bail with a clear error, cleaning up any created temp files.

Suggested change
$export_db = tempnam( sys_get_temp_dir(), 'export.db' );
copy( $db_path, $temp_db );
if ( false === $temp_db ) {
WP_CLI::error( 'Could not create temporary database file for export.' );
}
$export_db = tempnam( sys_get_temp_dir(), 'export.db' );
if ( false === $export_db ) {
// Clean up any previously created temporary database file.
@unlink( $temp_db );
WP_CLI::error( 'Could not create temporary export file.' );
}
if ( ! @copy( $db_path, $temp_db ) ) {
// Clean up temporary files if the copy operation fails.
@unlink( $temp_db );
@unlink( $export_db );
WP_CLI::error( 'Could not copy database to temporary file for export.' );
}

Copilot uses AI. Check for mistakes.
if ( ! file_exists( $db_path ) ) {
WP_CLI::error( 'Database does not exist.' );
}

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

sqlite_import() invokes the sqlite3 CLI but doesn’t check for sqlite3 availability first. For consistency with create/reset (and to match the PR’s “preflight validation” goal), perform the sqlite3 binary check at the start and show the clearer error message before doing any temp-file work.

Suggested change
if ( ! $this->is_sqlite3_available() ) {
WP_CLI::error( 'The sqlite3 CLI binary could not be found. Please ensure it is installed and available on your PATH.' );
}

Copilot uses AI. Check for mistakes.
Comment on lines +436 to +447

$command = Utils\esc_cmd(
implode( ' ', array_fill( 0, count( $command_parts ), '%s' ) ),
...$command_parts
);

$command .= ' < ' . escapeshellarg( $import_file );

WP_CLI::debug( "Running shell command: {$command}", 'db' );

$result = \WP_CLI\Process::create( $command, null, null )->run();

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The import command uses shell redirection ($command .= ' < ...'). This relies on a shell being available and makes the command line harder to reason about. Prefer using sqlite3’s built-in .read command (passed as an argument) or providing the SQL file contents via the process’ STDIN, so the import works without shell-specific redirection.

Suggested change
$command = Utils\esc_cmd(
implode( ' ', array_fill( 0, count( $command_parts ), '%s' ) ),
...$command_parts
);
$command .= ' < ' . escapeshellarg( $import_file );
WP_CLI::debug( "Running shell command: {$command}", 'db' );
$result = \WP_CLI\Process::create( $command, null, null )->run();
$command_parts[] = '.read';
$command_parts[] = $import_file;
// Build debug-friendly command string without using shell redirection.
$debug_command = Utils\esc_cmd(
implode( ' ', array_fill( 0, count( $command_parts ), '%s' ) ),
...$command_parts
);
WP_CLI::debug( "Running shell command: {$debug_command}", 'db' );
$result = \WP_CLI\Process::create( $command_parts, null, null )->run();

Copilot uses AI. Check for mistakes.
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.

Add SQLite compatibility to wp db commands

3 participants