Skip to content

Commit 6bf696c

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 f333a57 commit 6bf696c

1 file changed

Lines changed: 108 additions & 24 deletions

File tree

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

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

33
/**
4-
* Native-backed parser node.
4+
* Parser node backed by a native (Rust) AST.
55
*
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.
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.
930
*/
1031
class WP_MySQL_Native_Parser_Node extends WP_Parser_Node {
1132
private $native_ast = null;
@@ -18,13 +39,24 @@ public function __construct( $rule_id, $rule_name, $native_ast = null, $native_n
1839
$this->native_node_index = $native_node_index;
1940
}
2041

21-
/** @inheritDoc */
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+
*/
2249
public function append_child( $node ) {
2350
$this->materialize_native_children();
2451
parent::append_child( $node );
2552
}
2653

27-
/** @inheritDoc */
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+
*/
2860
public function merge_fragment( $node ) {
2961
$this->materialize_native_children();
3062
if ( $node instanceof self ) {
@@ -35,106 +67,158 @@ public function merge_fragment( $node ) {
3567

3668
/** @inheritDoc */
3769
public function has_child(): bool {
38-
$this->materialize_native_children();
70+
if ( $this->has_unmaterialized_native_ast() ) {
71+
return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index );
72+
}
3973
return parent::has_child();
4074
}
4175

4276
/** @inheritDoc */
4377
public function has_child_node( ?string $rule_name = null ): bool {
44-
$this->materialize_native_children();
78+
if ( $this->has_unmaterialized_native_ast() ) {
79+
return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name );
80+
}
4581
return parent::has_child_node( $rule_name );
4682
}
4783

4884
/** @inheritDoc */
4985
public function has_child_token( ?int $token_id = null ): bool {
50-
$this->materialize_native_children();
86+
if ( $this->has_unmaterialized_native_ast() ) {
87+
return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id );
88+
}
5189
return parent::has_child_token( $token_id );
5290
}
5391

5492
/** @inheritDoc */
5593
public function get_first_child() {
56-
$this->materialize_native_children();
94+
if ( $this->has_unmaterialized_native_ast() ) {
95+
return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index );
96+
}
5797
return parent::get_first_child();
5898
}
5999

60100
/** @inheritDoc */
61101
public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
62-
$this->materialize_native_children();
102+
if ( $this->has_unmaterialized_native_ast() ) {
103+
return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name );
104+
}
63105
return parent::get_first_child_node( $rule_name );
64106
}
65107

66108
/** @inheritDoc */
67109
public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
68-
$this->materialize_native_children();
110+
if ( $this->has_unmaterialized_native_ast() ) {
111+
return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id );
112+
}
69113
return parent::get_first_child_token( $token_id );
70114
}
71115

72116
/** @inheritDoc */
73117
public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
74-
$this->materialize_native_children();
118+
if ( $this->has_unmaterialized_native_ast() ) {
119+
return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name );
120+
}
75121
return parent::get_first_descendant_node( $rule_name );
76122
}
77123

78124
/** @inheritDoc */
79125
public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
80-
$this->materialize_native_children();
126+
if ( $this->has_unmaterialized_native_ast() ) {
127+
return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id );
128+
}
81129
return parent::get_first_descendant_token( $token_id );
82130
}
83131

84132
/** @inheritDoc */
85133
public function get_children(): array {
86-
$this->materialize_native_children();
134+
if ( $this->has_unmaterialized_native_ast() ) {
135+
return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index );
136+
}
87137
return parent::get_children();
88138
}
89139

90140
/** @inheritDoc */
91141
public function get_child_nodes( ?string $rule_name = null ): array {
92-
$this->materialize_native_children();
142+
if ( $this->has_unmaterialized_native_ast() ) {
143+
return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name );
144+
}
93145
return parent::get_child_nodes( $rule_name );
94146
}
95147

96148
/** @inheritDoc */
97149
public function get_child_tokens( ?int $token_id = null ): array {
98-
$this->materialize_native_children();
150+
if ( $this->has_unmaterialized_native_ast() ) {
151+
return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id );
152+
}
99153
return parent::get_child_tokens( $token_id );
100154
}
101155

102156
/** @inheritDoc */
103157
public function get_descendants(): array {
104-
$this->materialize_native_children();
158+
if ( $this->has_unmaterialized_native_ast() ) {
159+
return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index );
160+
}
105161
return parent::get_descendants();
106162
}
107163

108164
/** @inheritDoc */
109165
public function get_descendant_nodes( ?string $rule_name = null ): array {
110-
$this->materialize_native_children();
166+
if ( $this->has_unmaterialized_native_ast() ) {
167+
return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name );
168+
}
111169
return parent::get_descendant_nodes( $rule_name );
112170
}
113171

114172
/** @inheritDoc */
115173
public function get_descendant_tokens( ?int $token_id = null ): array {
116-
$this->materialize_native_children();
174+
if ( $this->has_unmaterialized_native_ast() ) {
175+
return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id );
176+
}
117177
return parent::get_descendant_tokens( $token_id );
118178
}
119179

120180
/** @inheritDoc */
121181
public function get_start(): int {
122-
$this->materialize_native_children();
182+
if ( $this->has_unmaterialized_native_ast() ) {
183+
return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index );
184+
}
123185
return parent::get_start();
124186
}
125187

126188
/** @inheritDoc */
127189
public function get_length(): int {
128-
$this->materialize_native_children();
190+
if ( $this->has_unmaterialized_native_ast() ) {
191+
return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index );
192+
}
129193
return parent::get_length();
130194
}
131195

132-
private function has_native_ast(): bool {
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 {
133208
return null !== $this->native_ast;
134209
}
135210

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+
*/
136220
private function materialize_native_children(): void {
137-
if ( ! $this->has_native_ast() ) {
221+
if ( ! $this->has_unmaterialized_native_ast() ) {
138222
return;
139223
}
140224

0 commit comments

Comments
 (0)