Skip to content

Commit 5824b36

Browse files
committed
Add support for the new SQLite storage
Resolve DB_PATH or db-path.php at runtime, retaining FQDB compatibility for older integration plugin versions. Export and table listing check that the database exists before opening it. Import uses the configured location and initializes managed storage only when no location has been recorded. Keep storage initialization out of the plugin loader and cover the storage behavior with Behat scenarios. WordPress/sqlite-database-integration#502
1 parent 7a3e60a commit 5824b36

6 files changed

Lines changed: 288 additions & 7 deletions

File tree

‎features/bootstrap/SQLiteFeatureContext.php‎

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,60 @@ class SQLiteFeatureContext extends WPCLIFeatureContext implements Context {
1313
private $db;
1414
private $comment_injection_table_name;
1515

16+
/**
17+
* Install WordPress with the integration plugin under test.
18+
*
19+
* @param string $subdir Installation subdirectory.
20+
* @param string $version WordPress version.
21+
*/
22+
public function install_wp( $subdir = '', $version = '' ): void {
23+
if ( 'sqlite' !== getenv( 'WP_CLI_TEST_DBTYPE' ) ) {
24+
parent::install_wp( $subdir, $version );
25+
return;
26+
}
27+
28+
// The upstream installation cache copies only the fixed .ht.sqlite path.
29+
// Install SQLite sites directly so managed storage is also preserved.
30+
$subdir = $this->replace_variables( $subdir );
31+
$this->create_run_dir();
32+
$this->download_wp( $subdir, $version );
33+
34+
$plugin_directory = getenv( 'WP_CLI_TEST_SQLITE_PLUGIN_DIR' );
35+
if ( $plugin_directory ) {
36+
if ( ! is_file( $plugin_directory . '/db.copy' ) ) {
37+
throw new Exception( 'WP_CLI_TEST_SQLITE_PLUGIN_DIR must point to an integration plugin directory containing db.copy.' );
38+
}
39+
$run_dir = $this->variables['RUN_DIR'] . '/' . $subdir;
40+
$target = $run_dir . '/wp-content/mu-plugins/sqlite-database-integration';
41+
self::remove_dir( $target );
42+
mkdir( $target );
43+
// Plugin source checkouts link to the shared driver package.
44+
$this->proc( \WP_CLI\Utils\esc_cmd( 'cp -RL %s/. %s', $plugin_directory, $target ) )->run_check();
45+
file_put_contents(
46+
$run_dir . '/wp-content/db.php',
47+
str_replace(
48+
[ "'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'", '{SQLITE_PLUGIN}', '/plugins/' ],
49+
[ "__DIR__ . '/mu-plugins/sqlite-database-integration'", 'sqlite-database-integration/load.php', '/mu-plugins/' ],
50+
file_get_contents( $target . '/db.copy' )
51+
)
52+
);
53+
}
54+
55+
$this->create_config( $subdir, "define( 'DISABLE_WP_CRON', true );" );
56+
$this->proc(
57+
'wp core install',
58+
[
59+
'url' => 'https://example.com',
60+
'title' => 'WP CLI Site',
61+
'admin_user' => 'admin',
62+
'admin_email' => 'admin@example.com',
63+
'admin_password' => 'password1',
64+
'skip-email' => true,
65+
],
66+
$subdir
67+
)->run_check();
68+
}
69+
1670
/**
1771
* @Given /^a SQL dump file named "([^"]*)" with content:$/
1872
*/
@@ -71,8 +125,11 @@ public function theFileShouldExist( $filename ) {
71125

72126
private function connectToDatabase() {
73127
if ( ! $this->db ) {
74-
$run_dir = $this->variables['RUN_DIR'];
75-
$db_file = $run_dir . '/wp-content/database/.ht.sqlite';
128+
$result = $this->proc( 'wp eval "echo FQDB;"' )->run_check();
129+
$db_file = trim( $result->stdout );
130+
if ( ! is_file( $db_file ) ) {
131+
throw new Exception( 'The WordPress SQLite database does not exist.' );
132+
}
76133
$this->db = new PDO( 'sqlite:' . $db_file );
77134
$this->db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
78135
}

‎features/sqlite-storage.feature‎

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
@require-sqlite
2+
Feature: SQLite database storage
3+
Commands use the same database as WordPress before its database drop-in loads.
4+
5+
Background:
6+
Given a WP installation
7+
8+
Scenario: Use an existing legacy database without migrating storage
9+
Given a SQL dump file named "test_import.sql" with content:
10+
"""
11+
CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT);
12+
INSERT INTO test_table VALUES (1, 'Imported data');
13+
"""
14+
When I run `wp eval 'echo FQDB;'`
15+
Then save STDOUT as {DATABASE_PATH}
16+
When I run `cp "{DATABASE_PATH}" original.sqlite`
17+
And I run `rm -r wp-content/database`
18+
And I run `mkdir wp-content/database`
19+
And I run `mv original.sqlite wp-content/database/.ht.sqlite`
20+
And I run `wp sqlite export backup.sql`
21+
Then the backup.sql file should contain:
22+
"""
23+
WP CLI Site
24+
"""
25+
When I run `wp sqlite tables`
26+
Then STDOUT should contain:
27+
"""
28+
wp_options
29+
"""
30+
When I run `wp sqlite import test_import.sql`
31+
And I run `wp sqlite export -`
32+
Then STDOUT should contain:
33+
"""
34+
Imported data
35+
"""
36+
And the wp-content/database/.ht.sqlite file should exist
37+
And the wp-content/database/db-path.php file should not exist
38+
39+
Scenario: Only import initializes fresh default storage
40+
When I run `wp sqlite export backup.sql`
41+
And I run `rm -r wp-content/database`
42+
And I try `wp sqlite export missing.sql`
43+
Then the return code should be 1
44+
And the missing.sql file should not exist
45+
When I try `wp sqlite tables`
46+
Then the return code should be 1
47+
And the wp-content/database directory should not exist
48+
When I run `mkdir wp-content/database`
49+
And I run `wp sqlite import backup.sql`
50+
Then STDOUT should contain:
51+
"""
52+
Success: Imported from 'backup.sql'.
53+
"""
54+
When I run `wp sqlite tables`
55+
Then STDOUT should contain:
56+
"""
57+
wp_options
58+
"""
59+
When I run `wp eval 'echo get_option( "blogname" );'`
60+
Then STDOUT should be:
61+
"""
62+
WP CLI Site
63+
"""
64+
65+
Scenario: Use DB_PATH without changing existing storage
66+
When I run `wp eval 'echo FQDB;'`
67+
Then save STDOUT as {DATABASE_PATH}
68+
When I run `mkdir custom-database`
69+
And I run `cp "{DATABASE_PATH}" 'custom-database/custom;database.sqlite'`
70+
And I run `wp config set DB_PATH "dirname( __FILE__ ) . '/custom-database/custom;database.sqlite'" --raw`
71+
And I run `wp config set FQDB '{RUN_DIR}/unused.sqlite'`
72+
And I run `wp sqlite export backup.sql`
73+
And I run `wp sqlite import backup.sql`
74+
And I run `wp sqlite tables`
75+
Then STDOUT should contain:
76+
"""
77+
wp_options
78+
"""
79+
And the unused.sqlite file should not exist
80+
And the custom-database/db-path.php file should not exist
81+
And the custom-database/.htaccess file should not exist
82+
And the custom-database/.ht.sqlite.lock file should not exist
83+
When I run `wp sqlite export -`
84+
Then STDOUT should contain:
85+
"""
86+
WP CLI Site
87+
"""
88+
89+
Scenario: Only import creates a missing explicit database
90+
Given a SQL dump file named "test_import.sql" with content:
91+
"""
92+
CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT);
93+
INSERT INTO test_table VALUES (1, 'Imported data');
94+
"""
95+
When I run `wp config set DB_PATH '{RUN_DIR}/new.sqlite'`
96+
And I try `wp sqlite export backup.sql`
97+
Then the return code should be 1
98+
And STDERR should contain:
99+
"""
100+
The SQLite database does not exist.
101+
"""
102+
And the new.sqlite file should not exist
103+
And the backup.sql file should not exist
104+
When I try `wp sqlite tables`
105+
Then the return code should be 1
106+
And the new.sqlite file should not exist
107+
When I run `wp sqlite import test_import.sql`
108+
And I run `wp sqlite tables`
109+
Then STDOUT should contain:
110+
"""
111+
test_table
112+
"""
113+
When I run `wp sqlite export -`
114+
Then STDOUT should contain:
115+
"""
116+
Imported data
117+
"""
118+
119+
Scenario: Use the legacy FQDB override
120+
When I run `wp eval 'echo FQDB;'`
121+
Then save STDOUT as {DB_PATH}
122+
When I run `cp "{DB_PATH}" custom.sqlite`
123+
And I run `wp config set FQDB '{RUN_DIR}/custom.sqlite'`
124+
And I run `wp sqlite export backup.sql`
125+
And I run `wp sqlite import backup.sql`
126+
And I run `wp sqlite tables`
127+
Then STDOUT should contain:
128+
"""
129+
wp_options
130+
"""
131+
When I run `wp eval 'echo FQDB;'`
132+
Then STDOUT should be:
133+
"""
134+
{RUN_DIR}/custom.sqlite
135+
"""
136+
When I run `wp eval 'echo get_option( "blogname" );'`
137+
Then STDOUT should be:
138+
"""
139+
WP CLI Site
140+
"""
141+
142+
Scenario: Reject an invalid DB_PATH without falling back to existing storage
143+
Given a SQL dump file named "test_import.sql" with content:
144+
"""
145+
CREATE TABLE test_table (id INTEGER PRIMARY KEY);
146+
"""
147+
When I run `wp config set DB_PATH null --raw`
148+
And I try `wp sqlite import test_import.sql`
149+
Then the return code should be 1
150+
And STDERR should contain:
151+
"""
152+
The SQLite database path is invalid.
153+
"""
154+
When I try `wp sqlite export backup.sql`
155+
Then the return code should be 1
156+
And the backup.sql file should not exist
157+
When I try `wp sqlite tables`
158+
Then the return code should be 1
159+
When I run `wp config delete DB_PATH`
160+
And I run `wp sqlite tables`
161+
Then STDOUT should not contain:
162+
"""
163+
test_table
164+
"""

‎phpstan/stubs.php‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ public function get_connection(): WP_SQLite_Connection {}
3030
}
3131

3232
class WP_SQLite_Translator {
33-
public function __construct() {}
33+
public function __construct( ?PDO $pdo = null ) {}
3434
public function query( string $sql ) {}
3535
public function get_pdo(): PDO {}
3636
}
37+
38+
class WP_SQLite_Storage {
39+
public function __construct( ?string $database_root = null, ?string $database_path = null ) {}
40+
public function initialize(): string {}
41+
}

‎src/Import.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Import {
2020

2121
public function __construct() {
2222
SQLiteDatabaseIntegrationLoader::load_plugin();
23-
$this->driver = SQLiteDriverFactory::create_driver();
23+
$this->driver = SQLiteDriverFactory::create_driver( true );
2424
}
2525

2626
/**

‎src/SQLiteDatabaseIntegrationLoader.php‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ public static function load_plugin() {
8787
require_once $plugin_directory . '/php-polyfills.php';
8888
}
8989
require_once $plugin_directory . '/constants.php';
90+
$storage_file = $plugin_directory . '/wp-includes/sqlite/class-wp-sqlite-storage.php';
91+
if ( file_exists( $storage_file ) ) {
92+
require_once $storage_file;
93+
}
9094

9195
$mysql_on_sqlite_class_file = $plugin_directory . '/wp-includes/database/sqlite/class-wp-mysql-on-sqlite.php';
9296
$mysql_on_sqlite_loader_file = $plugin_directory . '/wp-includes/database/load.php';

‎src/SQLiteDriverFactory.php‎

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,38 @@
33
namespace Automattic\WP_CLI\SQLite;
44

55
use PDO;
6+
use RuntimeException;
7+
use WP_CLI;
68
use WP_MySQL_On_SQLite;
79
use WP_SQLite_Connection;
810
use WP_SQLite_Driver;
11+
use WP_SQLite_Storage;
912
use WP_SQLite_Translator;
1013

1114
class SQLiteDriverFactory {
1215
/**
1316
* Create an instance of the SQLite driver.
1417
*
18+
* @param bool $create_database_if_missing Allow imports to create a database when missing.
19+
*
1520
* @return WP_MySQL_On_SQLite|WP_SQLite_Driver|WP_SQLite_Translator
1621
*/
17-
public static function create_driver() {
22+
public static function create_driver( $create_database_if_missing = false ) {
23+
try {
24+
$database_path = self::get_database_path( $create_database_if_missing );
25+
} catch ( \Throwable $exception ) {
26+
WP_CLI::error( 'Could not open the SQLite database: ' . $exception->getMessage() );
27+
}
28+
if ( ! $create_database_if_missing && ! is_file( $database_path ) ) {
29+
WP_CLI::error( 'The SQLite database does not exist.' );
30+
}
31+
1832
$db_name = defined( 'DB_NAME' ) && '' !== DB_NAME ? DB_NAME : 'database_name_here';
1933

2034
if ( class_exists( WP_MySQL_On_SQLite::class ) ) {
2135
$database_dsn = sprintf(
2236
'mysql-on-sqlite:path=%s;dbname=%s',
23-
str_replace( ';', ';;', FQDB ),
37+
str_replace( ';', ';;', $database_path ),
2438
str_replace( ';', ';;', $db_name )
2539
);
2640
$driver = new WP_MySQL_On_SQLite( $database_dsn );
@@ -35,9 +49,46 @@ public static function create_driver() {
3549

3650
$connection = new WP_SQLite_Connection(
3751
array(
38-
'path' => FQDB,
52+
'path' => $database_path,
3953
)
4054
);
4155
return new WP_SQLite_Driver( $connection, $db_name );
4256
}
57+
58+
/**
59+
* Resolve configuration without initializing existing storage.
60+
*
61+
* @param bool $create_database_if_missing Allow initialization when no managed path is recorded.
62+
* @return string
63+
*/
64+
private static function get_database_path( $create_database_if_missing ) {
65+
if ( defined( 'DB_PATH' ) ) {
66+
$database_path = DB_PATH;
67+
} elseif ( defined( 'FQDB' ) ) {
68+
// Older plugin releases and explicit legacy settings use FQDB.
69+
$database_path = FQDB;
70+
} else {
71+
$path_file = rtrim( FQDBDIR, '/\\' ) . '/db-path.php';
72+
$legacy_path = rtrim( FQDBDIR, '/\\' ) . '/.ht.sqlite';
73+
if ( file_exists( $path_file ) ) {
74+
// Use include so unreadable files can be handled on PHP 7.
75+
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Failure is checked below.
76+
$database_path = @include $path_file;
77+
if ( false === $database_path ) {
78+
throw new RuntimeException( 'Failed to read the SQLite database path file.' );
79+
}
80+
} elseif ( is_file( $legacy_path ) ) {
81+
$database_path = $legacy_path;
82+
} elseif ( $create_database_if_missing && class_exists( WP_SQLite_Storage::class ) ) {
83+
$database_path = ( new WP_SQLite_Storage( FQDBDIR ) )->initialize();
84+
} else {
85+
throw new RuntimeException( 'No SQLite database path is configured.' );
86+
}
87+
}
88+
89+
if ( ! is_string( $database_path ) || '' === $database_path || false !== strpos( $database_path, "\0" ) ) {
90+
throw new RuntimeException( 'The SQLite database path is invalid.' );
91+
}
92+
return $database_path;
93+
}
4394
}

0 commit comments

Comments
 (0)