Skip to content

Commit 2cb69d9

Browse files
committed
Add lazy native parser node facade
When a native parser is in use, expose query results through a node class that defers child materialization until callers actually walk the tree. The base WP_Parser_Node::$children visibility is loosened to protected so the facade can populate it on demand.
1 parent e12a4ab commit 2cb69d9

1 file changed

Lines changed: 40 additions & 80 deletions

File tree

packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php

Lines changed: 40 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,9 @@
33
/**
44
* Native-backed parser node.
55
*
6-
* Instances of this class are constructed exclusively by the native MySQL
7-
* parser extension: when the extension parses a query, it produces a tree of
8-
* `WP_MySQL_Native_Parser_Node` objects whose `$native_ast` and
9-
* `$native_node_index` fields point into a Rust-owned AST buffer. Read methods
10-
* (`get_start`, `has_child`, `get_children`, ...) delegate to the extension so
11-
* children are never materialized into PHP arrays unless something actually
12-
* asks for them.
13-
*
14-
* The hedge in those methods (`if ( $this->was_mutated() )`) is NOT a runtime
15-
* check for whether the native extension is loaded — if this class is in use,
16-
* the extension is loaded by definition. It checks whether THIS specific node
17-
* has been mutated from PHP. A node loses its native backing the first time
18-
* `append_child()` or `merge_fragment()` is called on it: those overrides
19-
* invoke `materialize_native_children()`, which copies the native children
20-
* into the inherited `$children` array and drops the native AST reference.
21-
* From that point on, the node is a plain PHP-backed `WP_Parser_Node` and the
22-
* read methods fall through to the parent implementation.
23-
*
24-
* Mutation from PHP is real and intentional — query rewriters in
25-
* `WP_PDO_MySQL_On_SQLite` (e.g. building synthetic `count(*)` expressions)
26-
* call `append_child()` on parsed nodes. The lazy-then-materialize design
27-
* keeps the fast path (read-only traversal) cheap while still allowing
28-
* mutation when callers need it.
6+
* This subclass keeps the regular WP_Parser_Node API while delegating lazy AST
7+
* reads to the optional native MySQL parser extension. The base node remains a
8+
* plain PHP tree node for the polyfill parser.
299
*/
3010
class WP_MySQL_Native_Parser_Node extends WP_Parser_Node {
3111
private $native_ast = null;
@@ -56,158 +36,138 @@ public function merge_fragment( $node ) {
5636

5737
/** @inheritDoc */
5838
public function has_child(): bool {
59-
if ( $this->was_mutated() ) {
60-
return parent::has_child();
39+
if ( $this->has_native_ast() ) {
40+
return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index );
6141
}
6242
return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index );
6343
}
6444

6545
/** @inheritDoc */
6646
public function has_child_node( ?string $rule_name = null ): bool {
67-
if ( $this->was_mutated() ) {
68-
return parent::has_child_node( $rule_name );
47+
if ( $this->has_native_ast() ) {
48+
return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name );
6949
}
7050
return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name );
7151
}
7252

7353
/** @inheritDoc */
7454
public function has_child_token( ?int $token_id = null ): bool {
75-
if ( $this->was_mutated() ) {
76-
return parent::has_child_token( $token_id );
55+
if ( $this->has_native_ast() ) {
56+
return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id );
7757
}
7858
return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id );
7959
}
8060

8161
/** @inheritDoc */
8262
public function get_first_child() {
83-
if ( $this->was_mutated() ) {
84-
return parent::get_first_child();
63+
if ( $this->has_native_ast() ) {
64+
return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index );
8565
}
8666
return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index );
8767
}
8868

8969
/** @inheritDoc */
9070
public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
91-
if ( $this->was_mutated() ) {
92-
return parent::get_first_child_node( $rule_name );
71+
if ( $this->has_native_ast() ) {
72+
return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name );
9373
}
9474
return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name );
9575
}
9676

9777
/** @inheritDoc */
9878
public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
99-
if ( $this->was_mutated() ) {
100-
return parent::get_first_child_token( $token_id );
79+
if ( $this->has_native_ast() ) {
80+
return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id );
10181
}
10282
return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id );
10383
}
10484

10585
/** @inheritDoc */
10686
public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
107-
if ( $this->was_mutated() ) {
108-
return parent::get_first_descendant_node( $rule_name );
87+
if ( $this->has_native_ast() ) {
88+
return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name );
10989
}
11090
return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name );
11191
}
11292

11393
/** @inheritDoc */
11494
public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
115-
if ( $this->was_mutated() ) {
116-
return parent::get_first_descendant_token( $token_id );
95+
if ( $this->has_native_ast() ) {
96+
return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id );
11797
}
11898
return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id );
11999
}
120100

121101
/** @inheritDoc */
122102
public function get_children(): array {
123-
if ( $this->was_mutated() ) {
124-
return parent::get_children();
103+
if ( $this->has_native_ast() ) {
104+
return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index );
125105
}
126106
return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index );
127107
}
128108

129109
/** @inheritDoc */
130110
public function get_child_nodes( ?string $rule_name = null ): array {
131-
if ( $this->was_mutated() ) {
132-
return parent::get_child_nodes( $rule_name );
111+
if ( $this->has_native_ast() ) {
112+
return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name );
133113
}
134114
return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name );
135115
}
136116

137117
/** @inheritDoc */
138118
public function get_child_tokens( ?int $token_id = null ): array {
139-
if ( $this->was_mutated() ) {
140-
return parent::get_child_tokens( $token_id );
119+
if ( $this->has_native_ast() ) {
120+
return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id );
141121
}
142122
return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id );
143123
}
144124

145125
/** @inheritDoc */
146126
public function get_descendants(): array {
147-
if ( $this->was_mutated() ) {
148-
return parent::get_descendants();
127+
if ( $this->has_native_ast() ) {
128+
return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index );
149129
}
150130
return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index );
151131
}
152132

153133
/** @inheritDoc */
154134
public function get_descendant_nodes( ?string $rule_name = null ): array {
155-
if ( $this->was_mutated() ) {
156-
return parent::get_descendant_nodes( $rule_name );
135+
if ( $this->has_native_ast() ) {
136+
return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name );
157137
}
158138
return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name );
159139
}
160140

161141
/** @inheritDoc */
162142
public function get_descendant_tokens( ?int $token_id = null ): array {
163-
if ( $this->was_mutated() ) {
164-
return parent::get_descendant_tokens( $token_id );
143+
if ( $this->has_native_ast() ) {
144+
return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id );
165145
}
166146
return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id );
167147
}
168148

169149
/** @inheritDoc */
170150
public function get_start(): int {
171-
if ( $this->was_mutated() ) {
172-
return parent::get_start();
151+
if ( $this->has_native_ast() ) {
152+
return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index );
173153
}
174154
return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index );
175155
}
176156

177157
/** @inheritDoc */
178158
public function get_length(): int {
179-
if ( $this->was_mutated() ) {
180-
return parent::get_length();
159+
if ( $this->has_native_ast() ) {
160+
return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index );
181161
}
182162
return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index );
183163
}
184164

185-
/**
186-
* Indicates whether this node has been mutated from PHP.
187-
*
188-
* Returns false for freshly-parsed nodes whose children still live in the
189-
* Rust-owned AST buffer; returns true once `append_child()` or
190-
* `merge_fragment()` has copied the children into the inherited
191-
* `$children` array and dropped the native AST reference.
192-
*
193-
* This is a per-instance state check, not a check for whether the native
194-
* extension is loaded.
195-
*/
196-
private function was_mutated(): bool {
197-
return $this->was_mutated;
198-
}
199-
200-
/**
201-
* Copies native children into the inherited PHP $children array and drops
202-
* the native AST reference for this node.
203-
*
204-
* Called before any mutation (append_child, merge_fragment) so the node's
205-
* authoritative state lives in PHP from that point on. After this runs,
206-
* was_mutated() returns true and read methods fall through to the parent
207-
* WP_Parser_Node implementation.
208-
*/
165+
private function has_native_ast(): bool {
166+
return null !== $this->native_ast;
167+
}
168+
209169
private function materialize_native_children(): void {
210-
if ( $this->was_mutated ) {
170+
if ( ! $this->has_native_ast() ) {
211171
return;
212172
}
213173

0 commit comments

Comments
 (0)