Skip to content

Commit 6410c30

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 da0c5de commit 6410c30

1 file changed

Lines changed: 22 additions & 63 deletions

File tree

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

Lines changed: 22 additions & 63 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;
@@ -56,158 +35,138 @@ public function merge_fragment( $node ) {
5635

5736
/** @inheritDoc */
5837
public function has_child(): bool {
59-
if ( $this->has_unmaterialized_native_ast() ) {
38+
if ( $this->has_native_ast() ) {
6039
return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index );
6140
}
6241
return parent::has_child();
6342
}
6443

6544
/** @inheritDoc */
6645
public function has_child_node( ?string $rule_name = null ): bool {
67-
if ( $this->has_unmaterialized_native_ast() ) {
46+
if ( $this->has_native_ast() ) {
6847
return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name );
6948
}
7049
return parent::has_child_node( $rule_name );
7150
}
7251

7352
/** @inheritDoc */
7453
public function has_child_token( ?int $token_id = null ): bool {
75-
if ( $this->has_unmaterialized_native_ast() ) {
54+
if ( $this->has_native_ast() ) {
7655
return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id );
7756
}
7857
return parent::has_child_token( $token_id );
7958
}
8059

8160
/** @inheritDoc */
8261
public function get_first_child() {
83-
if ( $this->has_unmaterialized_native_ast() ) {
62+
if ( $this->has_native_ast() ) {
8463
return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index );
8564
}
8665
return parent::get_first_child();
8766
}
8867

8968
/** @inheritDoc */
9069
public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
91-
if ( $this->has_unmaterialized_native_ast() ) {
70+
if ( $this->has_native_ast() ) {
9271
return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name );
9372
}
9473
return parent::get_first_child_node( $rule_name );
9574
}
9675

9776
/** @inheritDoc */
9877
public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
99-
if ( $this->has_unmaterialized_native_ast() ) {
78+
if ( $this->has_native_ast() ) {
10079
return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id );
10180
}
10281
return parent::get_first_child_token( $token_id );
10382
}
10483

10584
/** @inheritDoc */
10685
public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
107-
if ( $this->has_unmaterialized_native_ast() ) {
86+
if ( $this->has_native_ast() ) {
10887
return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name );
10988
}
11089
return parent::get_first_descendant_node( $rule_name );
11190
}
11291

11392
/** @inheritDoc */
11493
public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
115-
if ( $this->has_unmaterialized_native_ast() ) {
94+
if ( $this->has_native_ast() ) {
11695
return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id );
11796
}
11897
return parent::get_first_descendant_token( $token_id );
11998
}
12099

121100
/** @inheritDoc */
122101
public function get_children(): array {
123-
if ( $this->has_unmaterialized_native_ast() ) {
102+
if ( $this->has_native_ast() ) {
124103
return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index );
125104
}
126105
return parent::get_children();
127106
}
128107

129108
/** @inheritDoc */
130109
public function get_child_nodes( ?string $rule_name = null ): array {
131-
if ( $this->has_unmaterialized_native_ast() ) {
110+
if ( $this->has_native_ast() ) {
132111
return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name );
133112
}
134113
return parent::get_child_nodes( $rule_name );
135114
}
136115

137116
/** @inheritDoc */
138117
public function get_child_tokens( ?int $token_id = null ): array {
139-
if ( $this->has_unmaterialized_native_ast() ) {
118+
if ( $this->has_native_ast() ) {
140119
return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id );
141120
}
142121
return parent::get_child_tokens( $token_id );
143122
}
144123

145124
/** @inheritDoc */
146125
public function get_descendants(): array {
147-
if ( $this->has_unmaterialized_native_ast() ) {
126+
if ( $this->has_native_ast() ) {
148127
return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index );
149128
}
150129
return parent::get_descendants();
151130
}
152131

153132
/** @inheritDoc */
154133
public function get_descendant_nodes( ?string $rule_name = null ): array {
155-
if ( $this->has_unmaterialized_native_ast() ) {
134+
if ( $this->has_native_ast() ) {
156135
return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name );
157136
}
158137
return parent::get_descendant_nodes( $rule_name );
159138
}
160139

161140
/** @inheritDoc */
162141
public function get_descendant_tokens( ?int $token_id = null ): array {
163-
if ( $this->has_unmaterialized_native_ast() ) {
142+
if ( $this->has_native_ast() ) {
164143
return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id );
165144
}
166145
return parent::get_descendant_tokens( $token_id );
167146
}
168147

169148
/** @inheritDoc */
170149
public function get_start(): int {
171-
if ( $this->has_unmaterialized_native_ast() ) {
150+
if ( $this->has_native_ast() ) {
172151
return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index );
173152
}
174153
return parent::get_start();
175154
}
176155

177156
/** @inheritDoc */
178157
public function get_length(): int {
179-
if ( $this->has_unmaterialized_native_ast() ) {
158+
if ( $this->has_native_ast() ) {
180159
return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index );
181160
}
182161
return parent::get_length();
183162
}
184163

185-
/**
186-
* Indicates whether this node still has an unmaterialized native AST.
187-
*
188-
* Returns true for freshly-parsed nodes whose children live in the
189-
* Rust-owned AST buffer; returns false once the node has been mutated and
190-
* its children copied into the inherited `$children` array (see
191-
* self::materialize_native_children()).
192-
*
193-
* This is a per-instance state check, not a check for whether the native
194-
* extension is loaded.
195-
*/
196-
private function has_unmaterialized_native_ast(): bool {
164+
private function has_native_ast(): bool {
197165
return null !== $this->native_ast;
198166
}
199167

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-
* has_unmaterialized_native_ast() returns false and read methods fall
207-
* through to the parent WP_Parser_Node implementation.
208-
*/
209168
private function materialize_native_children(): void {
210-
if ( ! $this->has_unmaterialized_native_ast() ) {
169+
if ( ! $this->has_native_ast() ) {
211170
return;
212171
}
213172

0 commit comments

Comments
 (0)