Skip to content

Commit 6cfd4c3

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 1b16d09 commit 6cfd4c3

1 file changed

Lines changed: 24 additions & 76 deletions

File tree

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

Lines changed: 24 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,11 @@
11
<?php
22

33
/**
4-
* Parser node backed by a native (Rust) AST.
4+
* 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->has_unmaterialized_native_ast() )`)
15-
* is NOT a runtime check for whether the native extension is loaded — if this
16-
* class is in use, the extension is loaded by definition. It checks whether
17-
* THIS specific node still has an authoritative native AST behind it. A node
18-
* loses its native backing the first time it is mutated from PHP via
19-
* `append_child()` or `merge_fragment()`: those overrides call
20-
* `materialize_native_children()`, which copies the native children into the
21-
* inherited `$children` array and then drops the native AST reference. From
22-
* that point on, the node is a plain PHP-backed `WP_Parser_Node` and the read
23-
* methods fall through to the parent implementation.
24-
*
25-
* Mutation from PHP is real and intentional — query rewriters in
26-
* `WP_PDO_MySQL_On_SQLite` (e.g. building synthetic `count(*)` expressions)
27-
* call `append_child()` on parsed nodes. The lazy-then-materialize design
28-
* keeps the fast path (read-only traversal) cheap while still allowing
29-
* 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.
309
*/
3110
class WP_MySQL_Native_Parser_Node extends WP_Parser_Node {
3211
private $native_ast = null;
@@ -39,24 +18,13 @@ public function __construct( $rule_id, $rule_name, $native_ast = null, $native_n
3918
$this->native_node_index = $native_node_index;
4019
}
4120

42-
/**
43-
* Materializes any native children before mutating, then appends.
44-
*
45-
* Once a node is mutated, its native AST is no longer authoritative, so we
46-
* copy the native children into PHP storage first and drop the native
47-
* reference. Subsequent reads use the parent's PHP implementation.
48-
*/
21+
/** @inheritDoc */
4922
public function append_child( $node ) {
5023
$this->materialize_native_children();
5124
parent::append_child( $node );
5225
}
5326

54-
/**
55-
* Materializes any native children on both nodes before merging.
56-
*
57-
* @see self::append_child() for why materialization is required before
58-
* mutation.
59-
*/
27+
/** @inheritDoc */
6028
public function merge_fragment( $node ) {
6129
$this->materialize_native_children();
6230
if ( $node instanceof self ) {
@@ -67,158 +35,138 @@ public function merge_fragment( $node ) {
6735

6836
/** @inheritDoc */
6937
public function has_child(): bool {
70-
if ( $this->has_unmaterialized_native_ast() ) {
38+
if ( $this->has_native_ast() ) {
7139
return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index );
7240
}
7341
return parent::has_child();
7442
}
7543

7644
/** @inheritDoc */
7745
public function has_child_node( ?string $rule_name = null ): bool {
78-
if ( $this->has_unmaterialized_native_ast() ) {
46+
if ( $this->has_native_ast() ) {
7947
return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name );
8048
}
8149
return parent::has_child_node( $rule_name );
8250
}
8351

8452
/** @inheritDoc */
8553
public function has_child_token( ?int $token_id = null ): bool {
86-
if ( $this->has_unmaterialized_native_ast() ) {
54+
if ( $this->has_native_ast() ) {
8755
return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id );
8856
}
8957
return parent::has_child_token( $token_id );
9058
}
9159

9260
/** @inheritDoc */
9361
public function get_first_child() {
94-
if ( $this->has_unmaterialized_native_ast() ) {
62+
if ( $this->has_native_ast() ) {
9563
return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index );
9664
}
9765
return parent::get_first_child();
9866
}
9967

10068
/** @inheritDoc */
10169
public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
102-
if ( $this->has_unmaterialized_native_ast() ) {
70+
if ( $this->has_native_ast() ) {
10371
return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name );
10472
}
10573
return parent::get_first_child_node( $rule_name );
10674
}
10775

10876
/** @inheritDoc */
10977
public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
110-
if ( $this->has_unmaterialized_native_ast() ) {
78+
if ( $this->has_native_ast() ) {
11179
return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id );
11280
}
11381
return parent::get_first_child_token( $token_id );
11482
}
11583

11684
/** @inheritDoc */
11785
public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
118-
if ( $this->has_unmaterialized_native_ast() ) {
86+
if ( $this->has_native_ast() ) {
11987
return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name );
12088
}
12189
return parent::get_first_descendant_node( $rule_name );
12290
}
12391

12492
/** @inheritDoc */
12593
public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
126-
if ( $this->has_unmaterialized_native_ast() ) {
94+
if ( $this->has_native_ast() ) {
12795
return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id );
12896
}
12997
return parent::get_first_descendant_token( $token_id );
13098
}
13199

132100
/** @inheritDoc */
133101
public function get_children(): array {
134-
if ( $this->has_unmaterialized_native_ast() ) {
102+
if ( $this->has_native_ast() ) {
135103
return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index );
136104
}
137105
return parent::get_children();
138106
}
139107

140108
/** @inheritDoc */
141109
public function get_child_nodes( ?string $rule_name = null ): array {
142-
if ( $this->has_unmaterialized_native_ast() ) {
110+
if ( $this->has_native_ast() ) {
143111
return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name );
144112
}
145113
return parent::get_child_nodes( $rule_name );
146114
}
147115

148116
/** @inheritDoc */
149117
public function get_child_tokens( ?int $token_id = null ): array {
150-
if ( $this->has_unmaterialized_native_ast() ) {
118+
if ( $this->has_native_ast() ) {
151119
return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id );
152120
}
153121
return parent::get_child_tokens( $token_id );
154122
}
155123

156124
/** @inheritDoc */
157125
public function get_descendants(): array {
158-
if ( $this->has_unmaterialized_native_ast() ) {
126+
if ( $this->has_native_ast() ) {
159127
return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index );
160128
}
161129
return parent::get_descendants();
162130
}
163131

164132
/** @inheritDoc */
165133
public function get_descendant_nodes( ?string $rule_name = null ): array {
166-
if ( $this->has_unmaterialized_native_ast() ) {
134+
if ( $this->has_native_ast() ) {
167135
return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name );
168136
}
169137
return parent::get_descendant_nodes( $rule_name );
170138
}
171139

172140
/** @inheritDoc */
173141
public function get_descendant_tokens( ?int $token_id = null ): array {
174-
if ( $this->has_unmaterialized_native_ast() ) {
142+
if ( $this->has_native_ast() ) {
175143
return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id );
176144
}
177145
return parent::get_descendant_tokens( $token_id );
178146
}
179147

180148
/** @inheritDoc */
181149
public function get_start(): int {
182-
if ( $this->has_unmaterialized_native_ast() ) {
150+
if ( $this->has_native_ast() ) {
183151
return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index );
184152
}
185153
return parent::get_start();
186154
}
187155

188156
/** @inheritDoc */
189157
public function get_length(): int {
190-
if ( $this->has_unmaterialized_native_ast() ) {
158+
if ( $this->has_native_ast() ) {
191159
return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index );
192160
}
193161
return parent::get_length();
194162
}
195163

196-
/**
197-
* Indicates whether this node still has an unmaterialized native AST.
198-
*
199-
* Returns true for freshly-parsed nodes whose children live in the
200-
* Rust-owned AST buffer; returns false once the node has been mutated and
201-
* its children copied into the inherited `$children` array (see
202-
* self::materialize_native_children()).
203-
*
204-
* This is a per-instance state check, not a check for whether the native
205-
* extension is loaded.
206-
*/
207-
private function has_unmaterialized_native_ast(): bool {
164+
private function has_native_ast(): bool {
208165
return null !== $this->native_ast;
209166
}
210167

211-
/**
212-
* Copies native children into the inherited PHP $children array and drops
213-
* the native AST reference for this node.
214-
*
215-
* Called before any mutation (append_child, merge_fragment) so the node's
216-
* authoritative state lives in PHP from that point on. After this runs,
217-
* has_unmaterialized_native_ast() returns false and read methods fall
218-
* through to the parent WP_Parser_Node implementation.
219-
*/
220168
private function materialize_native_children(): void {
221-
if ( ! $this->has_unmaterialized_native_ast() ) {
169+
if ( ! $this->has_native_ast() ) {
222170
return;
223171
}
224172

0 commit comments

Comments
 (0)