Skip to content
This repository was archived by the owner on Jun 2, 2025. It is now read-only.

Commit b0009d6

Browse files
committed
Extract SQLite connection management to WP_SQLite_Connection
1 parent 94e582c commit b0009d6

12 files changed

+381
-314
lines changed

tests/WP_SQLite_Driver_Metadata_Tests.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,8 @@ public static function setUpBeforeClass(): void {
3131
public function setUp(): void {
3232
$this->sqlite = new PDO( 'sqlite::memory:' );
3333
$this->engine = new WP_SQLite_Driver(
34-
array(
35-
'connection' => $this->sqlite,
36-
'database' => 'wp',
37-
)
34+
new WP_SQLite_Connection( array( 'pdo' => $this->sqlite ) ),
35+
'wp'
3836
);
3937
}
4038

tests/WP_SQLite_Driver_Query_Tests.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,8 @@ public function setUp(): void {
4343

4444
$this->sqlite = new PDO( 'sqlite::memory:' );
4545
$this->engine = new WP_SQLite_Driver(
46-
array(
47-
'connection' => $this->sqlite,
48-
'database' => 'wp',
49-
)
46+
new WP_SQLite_Connection( array( 'pdo' => $this->sqlite ) ),
47+
'wp'
5048
);
5149

5250
$translator = $this->engine;
@@ -458,7 +456,7 @@ public function testRecoverSerialized() {
458456
);
459457
$option_name = 'serialized_option';
460458
$option_value = serialize( $obj );
461-
$option_value_escaped = $this->engine->get_pdo()->quote( $option_value );
459+
$option_value_escaped = $this->engine->get_connection()->quote( $option_value );
462460
/* Note well: this is heredoc not nowdoc */
463461
$insert = <<<QUERY
464462
INSERT INTO `wp_options` (`option_name`, `option_value`, `autoload`)
@@ -484,7 +482,7 @@ public function testRecoverSerialized() {
484482
++$obj ['two'];
485483
$obj ['pi'] *= 2;
486484
$option_value = serialize( $obj );
487-
$option_value_escaped = $this->engine->get_pdo()->quote( $option_value );
485+
$option_value_escaped = $this->engine->get_connection()->quote( $option_value );
488486
/* Note well: this is heredoc not nowdoc */
489487
$insert = <<<QUERY
490488
INSERT INTO `wp_options` (`option_name`, `option_value`, `autoload`)

tests/WP_SQLite_Driver_Tests.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,8 @@ public function setUp(): void {
3232
$this->sqlite = new PDO( 'sqlite::memory:' );
3333

3434
$this->engine = new WP_SQLite_Driver(
35-
array(
36-
'connection' => $this->sqlite,
37-
'database' => 'wp',
38-
)
35+
new WP_SQLite_Connection( array( 'pdo' => $this->sqlite ) ),
36+
'wp'
3937
);
4038
$this->engine->query(
4139
"CREATE TABLE _options (

tests/WP_SQLite_Driver_Translation_Tests.php

Lines changed: 129 additions & 131 deletions
Large diffs are not rendered by default.

tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,14 @@ function wp_get_db_schema() {
5757
public function setUp(): void {
5858
$this->sqlite = new PDO( 'sqlite::memory:' );
5959
$this->engine = new WP_SQLite_Driver(
60-
array(
61-
'connection' => $this->sqlite,
62-
'database' => 'wp',
63-
)
60+
new WP_SQLite_Connection( array( 'pdo' => $this->sqlite ) ),
61+
'wp'
6462
);
6563

6664
$builder = new WP_SQLite_Information_Schema_Builder(
6765
'wp',
6866
WP_SQLite_Driver::RESERVED_PREFIX,
69-
array( $this->engine, 'execute_sqlite_query' )
67+
$this->engine->get_connection()
7068
);
7169

7270
$this->reconstructor = new WP_SQLite_Information_Schema_Reconstructor(
@@ -76,7 +74,7 @@ public function setUp(): void {
7674
}
7775

7876
public function testReconstructTable(): void {
79-
$this->engine->get_pdo()->exec(
77+
$this->engine->get_connection()->query(
8078
'
8179
CREATE TABLE t (
8280
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -90,8 +88,8 @@ public function testReconstructTable(): void {
9088
)
9189
'
9290
);
93-
$this->engine->get_pdo()->exec( 'CREATE INDEX idx_score ON t (score)' );
94-
$this->engine->get_pdo()->exec( 'CREATE INDEX idx_role_score ON t (role, priority)' );
91+
$this->engine->get_connection()->query( 'CREATE INDEX idx_score ON t (score)' );
92+
$this->engine->get_connection()->query( 'CREATE INDEX idx_role_score ON t (role, priority)' );
9593
$result = $this->assertQuery( 'SELECT * FROM information_schema.tables WHERE table_name = "t"' );
9694
$this->assertEquals( 0, count( $result ) );
9795

@@ -126,7 +124,7 @@ public function testReconstructTable(): void {
126124

127125
public function testReconstructWpTable(): void {
128126
// Create a WP table with any columns.
129-
$this->engine->get_pdo()->exec( 'CREATE TABLE wp_posts ( id INTEGER )' );
127+
$this->engine->get_connection()->query( 'CREATE TABLE wp_posts ( id INTEGER )' );
130128

131129
// Reconstruct the information schema.
132130
$this->reconstructor->ensure_correct_information_schema();
@@ -176,18 +174,18 @@ public function testReconstructWpTable(): void {
176174
}
177175

178176
public function testReconstructTableFromMysqlDataTypesCache(): void {
179-
$pdo = $this->engine->get_pdo();
177+
$connection = $this->engine->get_connection();
180178

181-
$pdo->exec( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL );
182-
$pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'id', 'int unsigned')" );
183-
$pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'name', 'varchar(255)')" );
184-
$pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'description', 'text')" );
185-
$pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'shape', 'geomcollection')" );
186-
$pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_name', 'KEY')" );
187-
$pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_description', 'FULLTEXT')" );
188-
$pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_shape', 'SPATIAL')" );
179+
$connection->query( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL );
180+
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'id', 'int unsigned')" );
181+
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'name', 'varchar(255)')" );
182+
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'description', 'text')" );
183+
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'shape', 'geomcollection')" );
184+
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_name', 'KEY')" );
185+
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_description', 'FULLTEXT')" );
186+
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_shape', 'SPATIAL')" );
189187

190-
$this->engine->get_pdo()->exec(
188+
$connection->query(
191189
'
192190
CREATE TABLE t (
193191
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -197,9 +195,9 @@ public function testReconstructTableFromMysqlDataTypesCache(): void {
197195
)
198196
'
199197
);
200-
$this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_name ON t (name)' );
201-
$this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_description ON t (description)' );
202-
$this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_shape ON t (shape)' );
198+
$connection->query( 'CREATE INDEX t__idx_name ON t (name)' );
199+
$connection->query( 'CREATE INDEX t__idx_description ON t (description)' );
200+
$connection->query( 'CREATE INDEX t__idx_shape ON t (shape)' );
203201

204202
$this->reconstructor->ensure_correct_information_schema();
205203
$result = $this->assertQuery( 'SHOW CREATE TABLE t' );
@@ -224,7 +222,7 @@ public function testReconstructTableFromMysqlDataTypesCache(): void {
224222
}
225223

226224
public function testDefaultValues(): void {
227-
$this->engine->get_pdo()->exec(
225+
$this->engine->get_connection()->query(
228226
"
229227
CREATE TABLE t (
230228
col1 text DEFAULT abc,

tests/bootstrap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
require_once __DIR__ . '/../wp-includes/sqlite/class-wp-sqlite-token.php';
1515
require_once __DIR__ . '/../wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php';
1616
require_once __DIR__ . '/../wp-includes/sqlite/class-wp-sqlite-translator.php';
17+
require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-connection.php';
1718
require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-configurator.php';
1819
require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver.php';
1920
require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php';

tests/tools/dump-sqlite-query.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@
88
require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-token.php';
99
require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-parser.php';
1010
require_once __DIR__ . '/../../wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php';
11+
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-connection.php';
1112
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-configurator.php';
1213
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php';
1314
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php';
1415
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php';
1516
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php';
1617

1718
$driver = new WP_SQLite_Driver(
18-
array(
19-
'path' => ':memory:',
20-
'database' => 'wp',
21-
)
19+
new WP_SQLite_Connection( array( 'path' => ':memory:' ) ),
20+
'wp'
2221
);
2322

2423
$query = "SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.t1_id WHERE t1.name = 'abc'";
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<?php declare(strict_types = 1);
2+
3+
/*
4+
* The SQLite connection uses PDO. Enable PDO function calls:
5+
* phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
6+
*/
7+
8+
/**
9+
* SQLite connection.
10+
*
11+
* This class configures and encapsulates the connection to an SQLite database.
12+
* It requires PDO with the SQLite driver, and currently, it is only a simple
13+
* wrapper that leaks some of the PDO APIs (returns PDOStatement values, etc.).
14+
* In the future, we may abstract it away from PDO and support SQLite3 as well.
15+
*/
16+
class WP_SQLite_Connection {
17+
/**
18+
* The default timeout in seconds for SQLite to wait for a writable lock.
19+
*/
20+
const DEFAULT_SQLITE_TIMEOUT = 10;
21+
22+
/**
23+
* The supported SQLite journal modes.
24+
*
25+
* See: https://www.sqlite.org/pragma.html#pragma_journal_mode
26+
*/
27+
const SQLITE_JOURNAL_MODES = array(
28+
'DELETE',
29+
'TRUNCATE',
30+
'PERSIST',
31+
'MEMORY',
32+
'WAL',
33+
'OFF',
34+
);
35+
36+
/**
37+
* The PDO connection for SQLite.
38+
*
39+
* @var PDO
40+
*/
41+
private $pdo;
42+
43+
/**
44+
* A query logger callback.
45+
*
46+
* @var callable(string, array): void
47+
*/
48+
private $query_logger;
49+
50+
/**
51+
* Constructor.
52+
*
53+
* Set up an SQLite connection.
54+
*
55+
* @param array $options {
56+
* An array of options.
57+
*
58+
* @type string|null $path Optional. SQLite database path.
59+
* For in-memory database, use ':memory:'.
60+
* Must be set when PDO instance is not provided.
61+
* @type PDO|null $pdo Optional. PDO instance with SQLite connection.
62+
* If not provided, a new PDO instance will be created.
63+
* @type int|null $timeout Optional. SQLite timeout in seconds.
64+
* The time to wait for a writable lock.
65+
* @type string|null $journal_mode Optional. SQLite journal mode.
66+
* }
67+
*
68+
* @throws InvalidArgumentException When some connection options are invalid.
69+
* @throws PDOException When the driver initialization fails.
70+
*/
71+
public function __construct( array $options ) {
72+
// Setup PDO connection.
73+
if ( isset( $options['pdo'] ) && $options['pdo'] instanceof PDO ) {
74+
$this->pdo = $options['pdo'];
75+
} else {
76+
if ( ! isset( $options['path'] ) || ! is_string( $options['path'] ) ) {
77+
throw new InvalidArgumentException( 'Option "path" is required when "connection" is not provided.' );
78+
}
79+
$this->pdo = new PDO( 'sqlite:' . $options['path'] );
80+
}
81+
82+
// Throw exceptions on error.
83+
$this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
84+
85+
// Configure SQLite timeout.
86+
if ( isset( $options['timeout'] ) && is_int( $options['timeout'] ) ) {
87+
$timeout = $options['timeout'];
88+
} else {
89+
$timeout = self::DEFAULT_SQLITE_TIMEOUT;
90+
}
91+
$this->pdo->setAttribute( PDO::ATTR_TIMEOUT, $timeout );
92+
93+
// Return all values (except null) as strings.
94+
$this->pdo->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true );
95+
96+
// Configure SQLite journal mode.
97+
$journal_mode = $options['journal_mode'] ?? null;
98+
if ( $journal_mode && in_array( $journal_mode, self::SQLITE_JOURNAL_MODES, true ) ) {
99+
$this->query( 'PRAGMA journal_mode = ' . $journal_mode );
100+
}
101+
}
102+
103+
/**
104+
* Execute a query in SQLite.
105+
*
106+
* @param string $sql The query to execute.
107+
* @param array $params The query parameters.
108+
* @throws PDOException When the query execution fails.
109+
* @return PDOStatement The PDO statement object.
110+
*/
111+
public function query( string $sql, array $params = array() ): PDOStatement {
112+
if ( $this->query_logger ) {
113+
( $this->query_logger )( $sql, $params );
114+
}
115+
$stmt = $this->pdo->prepare( $sql );
116+
$stmt->execute( $params );
117+
return $stmt;
118+
}
119+
120+
/**
121+
* Returns the ID of the last inserted row.
122+
*
123+
* @return string The ID of the last inserted row.
124+
*/
125+
public function get_last_insert_id(): string {
126+
return $this->pdo->lastInsertId();
127+
}
128+
129+
/**
130+
* Quote a value for use in a query.
131+
*
132+
* @param mixed $value The value to quote.
133+
* @param int $type The type of the value.
134+
* @return string The quoted value.
135+
*/
136+
public function quote( $value, int $type = PDO::PARAM_STR ): string {
137+
return $this->pdo->quote( $value, $type );
138+
}
139+
140+
/**
141+
* Get the PDO object.
142+
*
143+
* @return PDO
144+
*/
145+
public function get_pdo(): PDO {
146+
return $this->pdo;
147+
}
148+
149+
/**
150+
* Set a logger for the queries.
151+
*
152+
* @param callable(string, array): void $logger A query logger callback.
153+
*/
154+
public function set_query_logger( callable $logger ): void {
155+
$this->query_logger = $logger;
156+
}
157+
}

0 commit comments

Comments
 (0)