Skip to content

Commit c32f14d

Browse files
committed
Add support for the new SQLite storage
Resolve DB_PATH, db-path.php, or a legacy .ht.sqlite database 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 secret-path storage only when no location has been recorded. Report invalid plugin database settings as command errors. Keep storage initialization out of the plugin loader and cover the storage behavior with Behat scenarios. WordPress/sqlite-database-integration#502 WordPress/sqlite-database-integration#512
1 parent 7a3e60a commit c32f14d

6 files changed

Lines changed: 218 additions & 7 deletions

File tree

‎features/bootstrap/SQLiteFeatureContext.php‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,11 @@ public function theFileShouldExist( $filename ) {
7171

7272
private function connectToDatabase() {
7373
if ( ! $this->db ) {
74-
$run_dir = $this->variables['RUN_DIR'];
75-
$db_file = $run_dir . '/wp-content/database/.ht.sqlite';
74+
$result = $this->proc( 'wp eval "echo FQDB;"' )->run_check();
75+
$db_file = trim( $result->stdout );
76+
if ( ! is_file( $db_file ) ) {
77+
throw new Exception( 'The WordPress SQLite database does not exist.' );
78+
}
7679
$this->db = new PDO( 'sqlite:' . $db_file );
7780
$this->db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
7881
}

‎features/sqlite-storage.feature‎

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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 import test_import.sql`
26+
And I run `wp sqlite export -`
27+
Then STDOUT should contain:
28+
"""
29+
Imported data
30+
"""
31+
And the wp-content/database/.ht.sqlite file should exist
32+
And the wp-content/database/db-path.php file should not exist
33+
34+
Scenario: Only import initializes fresh default storage
35+
When I run `wp sqlite export backup.sql`
36+
And I run `rm -r wp-content/database`
37+
And I try `wp sqlite export missing.sql`
38+
Then the return code should be 1
39+
And the missing.sql file should not exist
40+
When I try `wp sqlite tables`
41+
Then the return code should be 1
42+
And the wp-content/database directory should not exist
43+
When I run `mkdir wp-content/database`
44+
And I run `wp sqlite import backup.sql`
45+
Then STDOUT should contain:
46+
"""
47+
Success: Imported from 'backup.sql'.
48+
"""
49+
When I run `wp sqlite tables`
50+
Then STDOUT should contain:
51+
"""
52+
wp_options
53+
"""
54+
When I run `wp eval 'echo get_option( "blogname" );'`
55+
Then STDOUT should be:
56+
"""
57+
WP CLI Site
58+
"""
59+
60+
Scenario: Use DB_PATH without changing existing storage
61+
When I run `wp eval 'echo FQDB;'`
62+
Then save STDOUT as {DATABASE_PATH}
63+
When I run `mkdir custom-database`
64+
And I run `cp "{DATABASE_PATH}" 'custom-database/custom;database.sqlite'`
65+
And I run `wp config set DB_PATH "dirname( __FILE__ ) . '/custom-database/custom;database.sqlite'" --raw`
66+
And I run `wp sqlite export backup.sql`
67+
And I run `wp sqlite import backup.sql`
68+
Then the custom-database/db-path.php file should not exist
69+
And the custom-database/.htaccess file should not exist
70+
And the custom-database/.ht.sqlite.lock file should not exist
71+
When I run `wp sqlite export -`
72+
Then STDOUT should contain:
73+
"""
74+
WP CLI Site
75+
"""
76+
77+
Scenario: Only import creates a missing explicit database
78+
Given a SQL dump file named "test_import.sql" with content:
79+
"""
80+
CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT);
81+
INSERT INTO test_table VALUES (1, 'Imported data');
82+
"""
83+
When I run `wp config set DB_PATH '{RUN_DIR}/new.sqlite'`
84+
And I try `wp sqlite export backup.sql`
85+
Then the return code should be 1
86+
And STDERR should contain:
87+
"""
88+
The SQLite database does not exist.
89+
"""
90+
And the new.sqlite file should not exist
91+
And the backup.sql file should not exist
92+
When I try `wp sqlite tables`
93+
Then the return code should be 1
94+
And the new.sqlite file should not exist
95+
When I run `wp sqlite import test_import.sql`
96+
And I run `wp sqlite tables`
97+
Then STDOUT should contain:
98+
"""
99+
test_table
100+
"""
101+
When I run `wp sqlite export -`
102+
Then STDOUT should contain:
103+
"""
104+
Imported data
105+
"""
106+
107+
Scenario: Use the legacy FQDB override
108+
When I run `wp eval 'echo FQDB;'`
109+
Then save STDOUT as {DB_PATH}
110+
When I run `cp "{DB_PATH}" custom.sqlite`
111+
And I run `wp config set FQDB '{RUN_DIR}/custom.sqlite'`
112+
And I run `wp sqlite export backup.sql`
113+
And I run `wp sqlite import backup.sql`
114+
And I run `wp eval 'echo get_option( "blogname" );'`
115+
Then STDOUT should be:
116+
"""
117+
WP CLI Site
118+
"""
119+
120+
Scenario: Reject an invalid DB_PATH without falling back to existing storage
121+
Given a SQL dump file named "test_import.sql" with content:
122+
"""
123+
CREATE TABLE test_table (id INTEGER PRIMARY KEY);
124+
"""
125+
When I run `wp config set DB_PATH null --raw`
126+
And I try `wp sqlite import test_import.sql`
127+
Then the return code should be 1
128+
And STDERR should contain:
129+
"""
130+
Error: Could not open the SQLite database:
131+
"""
132+
When I try `wp sqlite export backup.sql`
133+
Then the return code should be 1
134+
And the backup.sql file should not exist
135+
When I try `wp sqlite tables`
136+
Then the return code should be 1
137+
When I run `wp config delete DB_PATH`
138+
And I run `wp sqlite tables`
139+
Then STDOUT should not contain:
140+
"""
141+
test_table
142+
"""

‎phpstan/stubs.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ public function __construct() {}
3434
public function query( string $sql ) {}
3535
public function get_pdo(): PDO {}
3636
}
37+
38+
class WP_SQLite_Storage {
39+
public static function with_secret_path( string $root ): self {}
40+
public function initialize(): string {}
41+
private function __construct() {}
42+
}

‎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: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,16 @@ public static function load_plugin() {
8686
if ( $old_structure ) {
8787
require_once $plugin_directory . '/php-polyfills.php';
8888
}
89-
require_once $plugin_directory . '/constants.php';
89+
try {
90+
require_once $plugin_directory . '/constants.php';
91+
} catch ( \Throwable $exception ) {
92+
// Newer plugin releases reject invalid database settings.
93+
WP_CLI::error( 'Could not open the SQLite database: ' . $exception->getMessage() );
94+
}
95+
$storage_file = $plugin_directory . '/wp-includes/sqlite/class-wp-sqlite-storage.php';
96+
if ( file_exists( $storage_file ) ) {
97+
require_once $storage_file;
98+
}
9099

91100
$mysql_on_sqlite_class_file = $plugin_directory . '/wp-includes/database/sqlite/class-wp-mysql-on-sqlite.php';
92101
$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 secret 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 = WP_SQLite_Storage::with_secret_path( 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)