Skip to content

Commit 809d2ec

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

13 files changed

+375
-316
lines changed

tests/WP_SQLite_Driver_Metadata_Tests.php

+2-4
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

+4-6
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

+2-4
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

+129-131
Large diffs are not rendered by default.

tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php

+21-23
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/WP_SQLite_Query_Tests.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ public function testRecoverSerialized() {
456456
);
457457
$option_name = 'serialized_option';
458458
$option_value = serialize( $obj );
459-
$option_value_escaped = $this->engine->get_pdo()->quote( $option_value );
459+
$option_value_escaped = $this->engine->get_connection()->quote( $option_value );
460460
/* Note well: this is heredoc not nowdoc */
461461
$insert = <<<QUERY
462462
INSERT INTO `wp_options` (`option_name`, `option_value`, `autoload`)
@@ -482,7 +482,7 @@ public function testRecoverSerialized() {
482482
++$obj ['two'];
483483
$obj ['pi'] *= 2;
484484
$option_value = serialize( $obj );
485-
$option_value_escaped = $this->engine->get_pdo()->quote( $option_value );
485+
$option_value_escaped = $this->engine->get_connection()->quote( $option_value );
486486
/* Note well: this is heredoc not nowdoc */
487487
$insert = <<<QUERY
488488
INSERT INTO `wp_options` (`option_name`, `option_value`, `autoload`)

tests/bootstrap.php

+1
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

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

0 commit comments

Comments
 (0)