Skip to content

Commit 7727cfa

Browse files
committed
Implement information schema reconstructor
1 parent 9a6da9d commit 7727cfa

7 files changed

+529
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
use PHPUnit\Framework\TestCase;
4+
5+
class WP_SQLite_Information_Schema_Reconstructor_Tests extends TestCase {
6+
/** @var WP_SQLite_Driver */
7+
private $engine;
8+
9+
/** @var WP_SQLite_Information_Schema_Reconstructor */
10+
private $reconstructor;
11+
12+
/** @var PDO */
13+
private $sqlite;
14+
15+
public static function setUpBeforeClass(): void {
16+
// if ( ! defined( 'PDO_DEBUG' )) {
17+
// define( 'PDO_DEBUG', true );
18+
// }
19+
if ( ! defined( 'FQDB' ) ) {
20+
define( 'FQDB', ':memory:' );
21+
define( 'FQDBDIR', __DIR__ . '/../testdb' );
22+
}
23+
error_reporting( E_ALL & ~E_DEPRECATED );
24+
if ( ! isset( $GLOBALS['table_prefix'] ) ) {
25+
$GLOBALS['table_prefix'] = 'wptests_';
26+
}
27+
if ( ! isset( $GLOBALS['wpdb'] ) ) {
28+
$GLOBALS['wpdb'] = new stdClass();
29+
$GLOBALS['wpdb']->suppress_errors = false;
30+
$GLOBALS['wpdb']->show_errors = true;
31+
}
32+
}
33+
34+
// Before each test, we create a new database
35+
public function setUp(): void {
36+
$this->sqlite = new PDO( 'sqlite::memory:' );
37+
$this->engine = new WP_SQLite_Driver(
38+
array(
39+
'connection' => $this->sqlite,
40+
'database' => 'wp',
41+
)
42+
);
43+
44+
$builder = new WP_SQLite_Information_Schema_Builder(
45+
'wp',
46+
WP_SQLite_Driver::RESERVED_PREFIX,
47+
array( $this->engine, 'execute_sqlite_query' )
48+
);
49+
50+
$this->reconstructor = new WP_SQLite_Information_Schema_Reconstructor(
51+
$this->engine,
52+
$builder
53+
);
54+
}
55+
56+
public function testReconstructInformationSchemaTable(): void {
57+
$this->engine->get_pdo()->exec(
58+
'
59+
CREATE TABLE t (
60+
id INTEGER PRIMARY KEY AUTOINCREMENT,
61+
email TEXT NOT NULL UNIQUE,
62+
name TEXT NOT NULL,
63+
role TEXT,
64+
score REAL,
65+
priority INTEGER DEFAULT 0,
66+
data BLOB,
67+
UNIQUE (name)
68+
)
69+
'
70+
);
71+
$this->engine->get_pdo()->exec( 'CREATE INDEX idx_score ON t (score)' );
72+
$this->engine->get_pdo()->exec( 'CREATE INDEX idx_role_score ON t (role, priority)' );
73+
$result = $this->assertQuery( 'SELECT * FROM information_schema.tables WHERE table_name = "t"' );
74+
$this->assertEquals( 0, count( $result ) );
75+
76+
$this->reconstructor->ensure_correct_information_schema();
77+
$result = $this->assertQuery( 'SELECT * FROM information_schema.tables WHERE table_name = "t"' );
78+
$this->assertEquals( 1, count( $result ) );
79+
80+
$result = $this->assertQuery( 'SHOW CREATE TABLE t' );
81+
$this->assertSame(
82+
implode(
83+
"\n",
84+
array(
85+
'CREATE TABLE `t` (',
86+
' `id` int NOT NULL AUTO_INCREMENT,',
87+
' `email` text NOT NULL,',
88+
' `name` text NOT NULL,',
89+
' `role` text DEFAULT NULL,',
90+
' `score` float DEFAULT NULL,',
91+
" `priority` int DEFAULT '0',",
92+
' `data` blob DEFAULT NULL,',
93+
' PRIMARY KEY (`id`),',
94+
' KEY `idx_role_score` (`role`(100), `priority`),',
95+
' KEY `idx_score` (`score`),',
96+
' UNIQUE KEY `sqlite_autoindex_t_2` (`name`(100)),',
97+
' UNIQUE KEY `sqlite_autoindex_t_1` (`email`(100))',
98+
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci',
99+
)
100+
),
101+
$result[0]->{'Create Table'}
102+
);
103+
}
104+
105+
private function assertQuery( $sql ) {
106+
$retval = $this->engine->query( $sql );
107+
$this->assertNotFalse( $retval );
108+
return $retval;
109+
}
110+
}

tests/bootstrap.php

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver.php';
1919
require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php';
2020
require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php';
21+
require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php';
2122

2223
/**
2324
* Polyfills for WordPress functions

tests/tools/dump-sqlite-query.php

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php';
1313
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php';
1414
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php';
15+
require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php';
1516

1617
$driver = new WP_SQLite_Driver(
1718
array(

wp-includes/sqlite-ast/class-wp-sqlite-configurator.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ class WP_SQLite_Configurator {
2525
*/
2626
private $information_schema_builder;
2727

28+
/**
29+
* A service for reconstructing the MySQL INFORMATION_SCHEMA tables in SQLite.
30+
*
31+
* @var WP_SQLite_Information_Schema_Reconstructor
32+
*/
33+
private $information_schema_reconstructor;
34+
2835
/**
2936
* Constructor.
3037
*
@@ -35,8 +42,12 @@ public function __construct(
3542
WP_SQLite_Driver $driver,
3643
WP_SQLite_Information_Schema_Builder $information_schema_builder
3744
) {
38-
$this->driver = $driver;
39-
$this->information_schema_builder = $information_schema_builder;
45+
$this->driver = $driver;
46+
$this->information_schema_builder = $information_schema_builder;
47+
$this->information_schema_reconstructor = new WP_SQLite_Information_Schema_Reconstructor(
48+
$driver,
49+
$information_schema_builder
50+
);
4051
}
4152

4253
/**
@@ -64,6 +75,7 @@ public function ensure_database_configured(): void {
6475
public function configure_database(): void {
6576
$this->ensure_global_variables_table();
6677
$this->information_schema_builder->ensure_information_schema_tables();
78+
$this->information_schema_reconstructor->ensure_correct_information_schema();
6779
$this->save_current_driver_version();
6880
}
6981

wp-includes/sqlite-ast/class-wp-sqlite-driver.php

+21-9
Original file line numberDiff line numberDiff line change
@@ -655,15 +655,7 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo
655655

656656
try {
657657
// Parse the MySQL query.
658-
$lexer = new WP_MySQL_Lexer( $query );
659-
$tokens = $lexer->remaining_tokens();
660-
661-
$parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens );
662-
$ast = $parser->parse();
663-
664-
if ( null === $ast ) {
665-
throw $this->new_driver_exception( 'Failed to parse the MySQL query.' );
666-
}
658+
$ast = $this->parse_query( $query );
667659

668660
// Handle transaction commands.
669661

@@ -735,6 +727,26 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo
735727
}
736728
}
737729

730+
/**
731+
* Parse a MySQL query into an AST.
732+
*
733+
* @param string $query The MySQL query to parse.
734+
* @return WP_Parser_Node The AST representing the parsed query.
735+
* @throws WP_SQLite_Driver_Exception When the query parsing fails.
736+
*/
737+
public function parse_query( string $query ): WP_Parser_Node {
738+
$lexer = new WP_MySQL_Lexer( $query );
739+
$tokens = $lexer->remaining_tokens();
740+
741+
$parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens );
742+
$ast = $parser->parse();
743+
744+
if ( null === $ast ) {
745+
throw $this->new_driver_exception( 'Failed to parse the MySQL query.' );
746+
}
747+
return $ast;
748+
}
749+
738750
/**
739751
* Get results of the last query.
740752
*

0 commit comments

Comments
 (0)