33/**
44 * Parser node backed by a native (Rust) AST.
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+ * Constructed by the native MySQL parser extension. Read methods delegate
7+ * into the Rust-owned AST so children are never copied into PHP unless a
8+ * caller actually walks the tree. On the first mutation (append_child or
9+ * merge_fragment), the node materializes its children into the inherited
10+ * `$children` array and behaves like a plain WP_Parser_Node from then on.
3011 */
3112class WP_MySQL_Native_Parser_Node extends WP_Parser_Node {
3213 private $ native_ast = null ;
3314 private $ native_node_index = null ;
15+ private $ was_mutated = false ;
3416
3517 public function __construct ( $ rule_id , $ rule_name , $ native_ast = null , $ native_node_index = null ) {
3618 parent ::__construct ( $ rule_id , $ rule_name );
@@ -39,24 +21,13 @@ public function __construct( $rule_id, $rule_name, $native_ast = null, $native_n
3921 $ this ->native_node_index = $ native_node_index ;
4022 }
4123
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- */
24+ /** @inheritDoc */
4925 public function append_child ( $ node ) {
5026 $ this ->materialize_native_children ();
5127 parent ::append_child ( $ node );
5228 }
5329
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- */
30+ /** @inheritDoc */
6031 public function merge_fragment ( $ node ) {
6132 $ this ->materialize_native_children ();
6233 if ( $ node instanceof self ) {
@@ -67,131 +38,144 @@ public function merge_fragment( $node ) {
6738
6839 /** @inheritDoc */
6940 public function has_child (): bool {
70- $ this ->materialize_native_children ();
71- return parent ::has_child ();
41+ if ( $ this ->was_mutated () ) {
42+ return parent ::has_child ();
43+ }
44+ return wp_sqlite_mysql_native_ast_has_child ( $ this ->native_ast , $ this ->native_node_index );
7245 }
7346
7447 /** @inheritDoc */
7548 public function has_child_node ( ?string $ rule_name = null ): bool {
76- $ this ->materialize_native_children ();
77- return parent ::has_child_node ( $ rule_name );
49+ if ( $ this ->was_mutated () ) {
50+ return parent ::has_child_node ( $ rule_name );
51+ }
52+ return wp_sqlite_mysql_native_ast_has_child_node ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
7853 }
7954
8055 /** @inheritDoc */
8156 public function has_child_token ( ?int $ token_id = null ): bool {
82- $ this ->materialize_native_children ();
83- return parent ::has_child_token ( $ token_id );
57+ if ( $ this ->was_mutated () ) {
58+ return parent ::has_child_token ( $ token_id );
59+ }
60+ return wp_sqlite_mysql_native_ast_has_child_token ( $ this ->native_ast , $ this ->native_node_index , $ token_id );
8461 }
8562
8663 /** @inheritDoc */
8764 public function get_first_child () {
88- $ this ->materialize_native_children ();
89- return parent ::get_first_child ();
65+ if ( $ this ->was_mutated () ) {
66+ return parent ::get_first_child ();
67+ }
68+ return wp_sqlite_mysql_native_ast_get_first_child ( $ this ->native_ast , $ this ->native_node_index );
9069 }
9170
9271 /** @inheritDoc */
9372 public function get_first_child_node ( ?string $ rule_name = null ): ?WP_Parser_Node {
94- $ this ->materialize_native_children ();
95- return parent ::get_first_child_node ( $ rule_name );
73+ if ( $ this ->was_mutated () ) {
74+ return parent ::get_first_child_node ( $ rule_name );
75+ }
76+ return wp_sqlite_mysql_native_ast_get_first_child_node ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
9677 }
9778
9879 /** @inheritDoc */
9980 public function get_first_child_token ( ?int $ token_id = null ): ?WP_Parser_Token {
100- $ this ->materialize_native_children ();
101- return parent ::get_first_child_token ( $ token_id );
81+ if ( $ this ->was_mutated () ) {
82+ return parent ::get_first_child_token ( $ token_id );
83+ }
84+ return wp_sqlite_mysql_native_ast_get_first_child_token ( $ this ->native_ast , $ this ->native_node_index , $ token_id );
10285 }
10386
10487 /** @inheritDoc */
10588 public function get_first_descendant_node ( ?string $ rule_name = null ): ?WP_Parser_Node {
106- $ this ->materialize_native_children ();
107- return parent ::get_first_descendant_node ( $ rule_name );
89+ if ( $ this ->was_mutated () ) {
90+ return parent ::get_first_descendant_node ( $ rule_name );
91+ }
92+ return wp_sqlite_mysql_native_ast_get_first_descendant_node ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
10893 }
10994
11095 /** @inheritDoc */
11196 public function get_first_descendant_token ( ?int $ token_id = null ): ?WP_Parser_Token {
112- $ this ->materialize_native_children ();
113- return parent ::get_first_descendant_token ( $ token_id );
97+ if ( $ this ->was_mutated () ) {
98+ return parent ::get_first_descendant_token ( $ token_id );
99+ }
100+ return wp_sqlite_mysql_native_ast_get_first_descendant_token ( $ this ->native_ast , $ this ->native_node_index , $ token_id );
114101 }
115102
116103 /** @inheritDoc */
117104 public function get_children (): array {
118- $ this ->materialize_native_children ();
119- return parent ::get_children ();
105+ if ( $ this ->was_mutated () ) {
106+ return parent ::get_children ();
107+ }
108+ return wp_sqlite_mysql_native_ast_get_children ( $ this ->native_ast , $ this ->native_node_index );
120109 }
121110
122111 /** @inheritDoc */
123112 public function get_child_nodes ( ?string $ rule_name = null ): array {
124- $ this ->materialize_native_children ();
125- return parent ::get_child_nodes ( $ rule_name );
113+ if ( $ this ->was_mutated () ) {
114+ return parent ::get_child_nodes ( $ rule_name );
115+ }
116+ return wp_sqlite_mysql_native_ast_get_child_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
126117 }
127118
128119 /** @inheritDoc */
129120 public function get_child_tokens ( ?int $ token_id = null ): array {
130- $ this ->materialize_native_children ();
131- return parent ::get_child_tokens ( $ token_id );
121+ if ( $ this ->was_mutated () ) {
122+ return parent ::get_child_tokens ( $ token_id );
123+ }
124+ return wp_sqlite_mysql_native_ast_get_child_tokens ( $ this ->native_ast , $ this ->native_node_index , $ token_id );
132125 }
133126
134127 /** @inheritDoc */
135128 public function get_descendants (): array {
136- $ this ->materialize_native_children ();
137- return parent ::get_descendants ();
129+ if ( $ this ->was_mutated () ) {
130+ return parent ::get_descendants ();
131+ }
132+ return wp_sqlite_mysql_native_ast_get_descendants ( $ this ->native_ast , $ this ->native_node_index );
138133 }
139134
140135 /** @inheritDoc */
141136 public function get_descendant_nodes ( ?string $ rule_name = null ): array {
142- $ this ->materialize_native_children ();
143- return parent ::get_descendant_nodes ( $ rule_name );
137+ if ( $ this ->was_mutated () ) {
138+ return parent ::get_descendant_nodes ( $ rule_name );
139+ }
140+ return wp_sqlite_mysql_native_ast_get_descendant_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name );
144141 }
145142
146143 /** @inheritDoc */
147144 public function get_descendant_tokens ( ?int $ token_id = null ): array {
148- $ this ->materialize_native_children ();
149- return parent ::get_descendant_tokens ( $ token_id );
145+ if ( $ this ->was_mutated () ) {
146+ return parent ::get_descendant_tokens ( $ token_id );
147+ }
148+ return wp_sqlite_mysql_native_ast_get_descendant_tokens ( $ this ->native_ast , $ this ->native_node_index , $ token_id );
150149 }
151150
152151 /** @inheritDoc */
153152 public function get_start (): int {
154- $ this ->materialize_native_children ();
155- return parent ::get_start ();
153+ if ( $ this ->was_mutated () ) {
154+ return parent ::get_start ();
155+ }
156+ return wp_sqlite_mysql_native_ast_get_start ( $ this ->native_ast , $ this ->native_node_index );
156157 }
157158
158159 /** @inheritDoc */
159160 public function get_length (): int {
160- $ this ->materialize_native_children ();
161- return parent ::get_length ();
162- }
163-
164- /**
165- * Indicates whether this node still has an unmaterialized native AST.
166- *
167- * Returns true for freshly-parsed nodes whose children live in the
168- * Rust-owned AST buffer; returns false once the node has been mutated and
169- * its children copied into the inherited `$children` array (see
170- * self::materialize_native_children()).
171- *
172- * This is a per-instance state check, not a check for whether the native
173- * extension is loaded.
174- */
175- private function has_unmaterialized_native_ast (): bool {
176- return null !== $ this ->native_ast ;
177- }
178-
179- /**
180- * Copies native children into the inherited PHP $children array and drops
181- * the native AST reference for this node.
182- *
183- * Called before any mutation (append_child, merge_fragment) so the node's
184- * authoritative state lives in PHP from that point on. After this runs,
185- * has_unmaterialized_native_ast() returns false and read methods fall
186- * through to the parent WP_Parser_Node implementation.
187- */
161+ if ( $ this ->was_mutated () ) {
162+ return parent ::get_length ();
163+ }
164+ return wp_sqlite_mysql_native_ast_get_length ( $ this ->native_ast , $ this ->native_node_index );
165+ }
166+
167+ private function was_mutated (): bool {
168+ return $ this ->was_mutated ;
169+ }
170+
188171 private function materialize_native_children (): void {
189- if ( ! $ this ->has_unmaterialized_native_ast () ) {
172+ if ( $ this ->was_mutated ) {
190173 return ;
191174 }
192175
193176 $ this ->children = wp_sqlite_mysql_native_ast_get_children ( $ this ->native_ast , $ this ->native_node_index );
194177 $ this ->native_ast = null ;
195178 $ this ->native_node_index = null ;
179+ $ this ->was_mutated = true ;
196180 }
197181}
0 commit comments