From 83001bc3ae5301dcb82e56bab670681e3c261da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 13:51:43 +0200 Subject: [PATCH 01/40] 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. --- .../class-wp-mysql-native-parser-node.php | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index e796bf68..071a60e7 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -3,11 +3,29 @@ /** * Parser node backed by a native (Rust) AST. * - * Constructed by the native MySQL parser extension. Read methods delegate - * into the Rust-owned AST so children are never copied into PHP unless a - * caller actually walks the tree. On the first mutation (append_child or - * merge_fragment), the node materializes its children into the inherited - * `$children` array and behaves like a plain WP_Parser_Node from then on. + * Instances of this class are constructed exclusively by the native MySQL + * parser extension: when the extension parses a query, it produces a tree of + * `WP_MySQL_Native_Parser_Node` objects whose `$native_ast` and + * `$native_node_index` fields point into a Rust-owned AST buffer. Read methods + * (`get_start`, `has_child`, `get_children`, ...) delegate to the extension so + * children are never materialized into PHP arrays unless something actually + * asks for them. + * + * The hedge in those methods (`if ( $this->was_mutated() )`) is NOT a runtime + * check for whether the native extension is loaded — if this class is in use, + * the extension is loaded by definition. It checks whether THIS specific node + * has been mutated from PHP. A node loses its native backing the first time + * `append_child()` or `merge_fragment()` is called on it: those overrides + * invoke `materialize_native_children()`, which copies the native children + * into the inherited `$children` array and drops the native AST reference. + * From that point on, the node is a plain PHP-backed `WP_Parser_Node` and the + * read methods fall through to the parent implementation. + * + * Mutation from PHP is real and intentional — query rewriters in + * `WP_PDO_MySQL_On_SQLite` (e.g. building synthetic `count(*)` expressions) + * call `append_child()` on parsed nodes. The lazy-then-materialize design + * keeps the fast path (read-only traversal) cheap while still allowing + * mutation when callers need it. */ class WP_MySQL_Native_Parser_Node extends WP_Parser_Node { private $native_ast = null; @@ -21,13 +39,24 @@ public function __construct( $rule_id, $rule_name, $native_ast = null, $native_n $this->native_node_index = $native_node_index; } - /** @inheritDoc */ + /** + * Materializes any native children before mutating, then appends. + * + * Once a node is mutated, its native AST is no longer authoritative, so we + * copy the native children into PHP storage first and drop the native + * reference. Subsequent reads use the parent's PHP implementation. + */ public function append_child( $node ) { $this->materialize_native_children(); parent::append_child( $node ); } - /** @inheritDoc */ + /** + * Materializes any native children on both nodes before merging. + * + * @see self::append_child() for why materialization is required before + * mutation. + */ public function merge_fragment( $node ) { $this->materialize_native_children(); if ( $node instanceof self ) { @@ -164,10 +193,30 @@ public function get_length(): int { return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index ); } + /** + * Indicates whether this node has been mutated from PHP. + * + * Returns false for freshly-parsed nodes whose children still live in the + * Rust-owned AST buffer; returns true once `append_child()` or + * `merge_fragment()` has copied the children into the inherited + * `$children` array and dropped the native AST reference. + * + * This is a per-instance state check, not a check for whether the native + * extension is loaded. + */ private function was_mutated(): bool { return $this->was_mutated; } + /** + * Copies native children into the inherited PHP $children array and drops + * the native AST reference for this node. + * + * Called before any mutation (append_child, merge_fragment) so the node's + * authoritative state lives in PHP from that point on. After this runs, + * was_mutated() returns true and read methods fall through to the parent + * WP_Parser_Node implementation. + */ private function materialize_native_children(): void { if ( $this->was_mutated ) { return; From c9e2eb64522ae3b05fe0ca4a8338d07f3692bc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 13:51:43 +0200 Subject: [PATCH 02/40] 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. --- .../class-wp-mysql-native-parser-node.php | 137 +++++++++--------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index 071a60e7..29a68cb5 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -11,15 +11,16 @@ * children are never materialized into PHP arrays unless something actually * asks for them. * - * The hedge in those methods (`if ( $this->was_mutated() )`) is NOT a runtime - * check for whether the native extension is loaded — if this class is in use, - * the extension is loaded by definition. It checks whether THIS specific node - * has been mutated from PHP. A node loses its native backing the first time - * `append_child()` or `merge_fragment()` is called on it: those overrides - * invoke `materialize_native_children()`, which copies the native children - * into the inherited `$children` array and drops the native AST reference. - * From that point on, the node is a plain PHP-backed `WP_Parser_Node` and the - * read methods fall through to the parent implementation. + * The hedge in those methods (`if ( $this->has_unmaterialized_native_ast() )`) + * is NOT a runtime check for whether the native extension is loaded — if this + * class is in use, the extension is loaded by definition. It checks whether + * THIS specific node still has an authoritative native AST behind it. A node + * loses its native backing the first time it is mutated from PHP via + * `append_child()` or `merge_fragment()`: those overrides call + * `materialize_native_children()`, which copies the native children into the + * inherited `$children` array and then drops the native AST reference. From + * that point on, the node is a plain PHP-backed `WP_Parser_Node` and the read + * methods fall through to the parent implementation. * * Mutation from PHP is real and intentional — query rewriters in * `WP_PDO_MySQL_On_SQLite` (e.g. building synthetic `count(*)` expressions) @@ -30,7 +31,6 @@ class WP_MySQL_Native_Parser_Node extends WP_Parser_Node { private $native_ast = null; private $native_node_index = null; - private $was_mutated = false; public function __construct( $rule_id, $rule_name, $native_ast = null, $native_node_index = null ) { parent::__construct( $rule_id, $rule_name ); @@ -67,145 +67,145 @@ public function merge_fragment( $node ) { /** @inheritDoc */ public function has_child(): bool { - if ( $this->was_mutated() ) { - return parent::has_child(); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index ); } - return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index ); + return parent::has_child(); } /** @inheritDoc */ public function has_child_node( ?string $rule_name = null ): bool { - if ( $this->was_mutated() ) { - return parent::has_child_node( $rule_name ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name ); } - return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name ); + return parent::has_child_node( $rule_name ); } /** @inheritDoc */ public function has_child_token( ?int $token_id = null ): bool { - if ( $this->was_mutated() ) { - return parent::has_child_token( $token_id ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id ); } - return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id ); + return parent::has_child_token( $token_id ); } /** @inheritDoc */ public function get_first_child() { - if ( $this->was_mutated() ) { - return parent::get_first_child(); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ); } - return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ); + return parent::get_first_child(); } /** @inheritDoc */ public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node { - if ( $this->was_mutated() ) { - return parent::get_first_child_node( $rule_name ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ); } - return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ); + return parent::get_first_child_node( $rule_name ); } /** @inheritDoc */ public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token { - if ( $this->was_mutated() ) { - return parent::get_first_child_token( $token_id ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id ); } - return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id ); + return parent::get_first_child_token( $token_id ); } /** @inheritDoc */ public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node { - if ( $this->was_mutated() ) { - return parent::get_first_descendant_node( $rule_name ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ); } - return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ); + return parent::get_first_descendant_node( $rule_name ); } /** @inheritDoc */ public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token { - if ( $this->was_mutated() ) { - return parent::get_first_descendant_token( $token_id ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id ); } - return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id ); + return parent::get_first_descendant_token( $token_id ); } /** @inheritDoc */ public function get_children(): array { - if ( $this->was_mutated() ) { - return parent::get_children(); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); } - return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); + return parent::get_children(); } /** @inheritDoc */ public function get_child_nodes( ?string $rule_name = null ): array { - if ( $this->was_mutated() ) { - return parent::get_child_nodes( $rule_name ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ); } - return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ); + return parent::get_child_nodes( $rule_name ); } /** @inheritDoc */ public function get_child_tokens( ?int $token_id = null ): array { - if ( $this->was_mutated() ) { - return parent::get_child_tokens( $token_id ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id ); } - return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id ); + return parent::get_child_tokens( $token_id ); } /** @inheritDoc */ public function get_descendants(): array { - if ( $this->was_mutated() ) { - return parent::get_descendants(); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ); } - return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ); + return parent::get_descendants(); } /** @inheritDoc */ public function get_descendant_nodes( ?string $rule_name = null ): array { - if ( $this->was_mutated() ) { - return parent::get_descendant_nodes( $rule_name ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ); } - return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ); + return parent::get_descendant_nodes( $rule_name ); } /** @inheritDoc */ public function get_descendant_tokens( ?int $token_id = null ): array { - if ( $this->was_mutated() ) { - return parent::get_descendant_tokens( $token_id ); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id ); } - return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id ); + return parent::get_descendant_tokens( $token_id ); } /** @inheritDoc */ public function get_start(): int { - if ( $this->was_mutated() ) { - return parent::get_start(); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index ); } - return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index ); + return parent::get_start(); } /** @inheritDoc */ public function get_length(): int { - if ( $this->was_mutated() ) { - return parent::get_length(); + if ( $this->has_unmaterialized_native_ast() ) { + return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index ); } - return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index ); + return parent::get_length(); } /** - * Indicates whether this node has been mutated from PHP. + * Indicates whether this node still has an unmaterialized native AST. * - * Returns false for freshly-parsed nodes whose children still live in the - * Rust-owned AST buffer; returns true once `append_child()` or - * `merge_fragment()` has copied the children into the inherited - * `$children` array and dropped the native AST reference. + * Returns true for freshly-parsed nodes whose children live in the + * Rust-owned AST buffer; returns false once the node has been mutated and + * its children copied into the inherited `$children` array (see + * self::materialize_native_children()). * * This is a per-instance state check, not a check for whether the native * extension is loaded. */ - private function was_mutated(): bool { - return $this->was_mutated; + private function has_unmaterialized_native_ast(): bool { + return null !== $this->native_ast; } /** @@ -214,17 +214,16 @@ private function was_mutated(): bool { * * Called before any mutation (append_child, merge_fragment) so the node's * authoritative state lives in PHP from that point on. After this runs, - * was_mutated() returns true and read methods fall through to the parent - * WP_Parser_Node implementation. + * has_unmaterialized_native_ast() returns false and read methods fall + * through to the parent WP_Parser_Node implementation. */ private function materialize_native_children(): void { - if ( $this->was_mutated ) { + if ( ! $this->has_unmaterialized_native_ast() ) { return; } $this->children = wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); $this->native_ast = null; $this->native_node_index = null; - $this->was_mutated = true; } } From dacfd7895c821812a315e4075e83341fc813792c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 01:49:25 +0200 Subject: [PATCH 03/40] Add Rust MySQL parser extension --- .../mysql-parser-extension-tests.yml | 69 + .gitignore | 2 + .../ext/wp-mysql-parser/.gitignore | 1 + .../ext/wp-mysql-parser/Cargo.lock | 1689 ++++++ .../ext/wp-mysql-parser/Cargo.toml | 17 + .../ext/wp-mysql-parser/README.md | 32 + .../wp-mysql-parser/src/lexer_constants.rs | 5274 +++++++++++++++++ .../ext/wp-mysql-parser/src/lib.rs | 1208 ++++ .../tools/generate-lexer-constants.php | 229 + packages/mysql-on-sqlite/phpunit.xml.dist | 3 + 10 files changed, 8524 insertions(+) create mode 100644 .github/workflows/mysql-parser-extension-tests.yml create mode 100644 packages/mysql-on-sqlite/ext/wp-mysql-parser/.gitignore create mode 100644 packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.lock create mode 100644 packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.toml create mode 100644 packages/mysql-on-sqlite/ext/wp-mysql-parser/README.md create mode 100644 packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lexer_constants.rs create mode 100644 packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs create mode 100644 packages/mysql-on-sqlite/ext/wp-mysql-parser/tools/generate-lexer-constants.php diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml new file mode 100644 index 00000000..32fb6638 --- /dev/null +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -0,0 +1,69 @@ +name: MySQL Parser Extension Tests + +on: + push: + paths: + - '.github/workflows/mysql-parser-extension-tests.yml' + - 'packages/mysql-on-sqlite/**' + pull_request: + paths: + - '.github/workflows/mysql-parser-extension-tests.yml' + - 'packages/mysql-on-sqlite/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + extension-tests: + name: PHP 8.2 / Rust extension / ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + tools: phpunit-polyfills + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install native build dependencies + run: | + sudo apt-get update + sudo apt-get install -y libclang-dev + echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV" + LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)" + echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV" + + - name: Install Composer dependencies (root) + uses: ramsey/composer-install@v3 + with: + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Install Composer dependencies (mysql-on-sqlite) + uses: ramsey/composer-install@v3 + with: + working-directory: packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Check Rust formatting + run: cargo fmt --check + working-directory: packages/mysql-on-sqlite/ext/wp-mysql-parser + + - name: Build parser extension + run: cargo build + working-directory: packages/mysql-on-sqlite/ext/wp-mysql-parser + + - name: Run PHPUnit tests with parser extension + run: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist + working-directory: packages/mysql-on-sqlite diff --git a/.gitignore b/.gitignore index 23c504c7..2bd131a8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ composer.lock /build /wordpress /.claude/settings.local.json +/.adversarial-loop/ +/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/ diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/.gitignore b/packages/mysql-on-sqlite/ext/wp-mysql-parser/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.lock b/packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.lock new file mode 100644 index 00000000..baf9d849 --- /dev/null +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.lock @@ -0,0 +1,1689 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures 0.3.0", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", + "zeroize", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cipher" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +dependencies = [ + "crypto-common 0.2.1", + "inout", +] + +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpubits" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", + "const-oid", + "crypto-common 0.2.1", + "ctutils", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "ext-php-rs" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0525834a3e26fdf0a60a8e7c27ec9521a35808eba8e41a15e135c22f670bc937" +dependencies = [ + "anyhow", + "bitflags", + "cc", + "cfg-if", + "ext-php-rs-bindgen", + "ext-php-rs-build", + "ext-php-rs-derive", + "inventory", + "native-tls", + "once_cell", + "parking_lot", + "skeptic", + "ureq", + "zip", +] + +[[package]] +name = "ext-php-rs-bindgen" +version = "0.72.1-extphprs.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4795dd0976bd7d7d321c49e88e836f8e5b5b2b481e089067e303f2945617458a" +dependencies = [ + "bitflags", + "cexpr", + "ext-php-rs-clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "ext-php-rs-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561bce8a6312a6182c078cd987d8d9cb6bf9292a35817cfd009ea0bffa794f5f" +dependencies = [ + "anyhow", +] + +[[package]] +name = "ext-php-rs-clang-sys" +version = "1.8.1-extphprs.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa1ad6e482017d457d57d73691f8bed148a8a6198babe90830310c3308480a61" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "ext-php-rs-derive" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6cb94d5f4c1e9758b6b936ad306dea36a034c125334d9a438fe966a5d596a85" +dependencies = [ + "convert_case", + "darling", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +dependencies = [ + "typenum", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lzma-rust2" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +dependencies = [ + "sha2", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.2", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stacker" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "js-sys", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "der", + "flate2", + "log", + "native-tls", + "percent-encoding", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-root-certs", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "wp_mysql_parser" +version = "0.1.0" +dependencies = [ + "ext-php-rs", + "libc", + "stacker", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.4.2", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.toml b/packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.toml new file mode 100644 index 00000000..91b46159 --- /dev/null +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wp_mysql_parser" +version = "0.1.0" +edition = "2021" +license = "GPL-2.0-or-later" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +ext-php-rs = "0.15.12" +libc = "0.2" +stacker = "0.1" + +[profile.release] +lto = "thin" +codegen-units = 1 diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/README.md b/packages/mysql-on-sqlite/ext/wp-mysql-parser/README.md new file mode 100644 index 00000000..a65c1f0d --- /dev/null +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/README.md @@ -0,0 +1,32 @@ +# WP MySQL Parser PHP Extension + +This crate builds an optional PHP extension named `wp_mysql_parser`. + +When the extension is loaded before `packages/mysql-on-sqlite/src/load.php`, it +registers native base classes used by the public `WP_MySQL_Lexer` and +`WP_MySQL_Parser` wrappers. Without the extension, those public wrappers extend +the existing PHP polyfills instead. + +## Build + +The build requires Rust, PHP development headers, `php-config`, and libclang. +Depending on the environment, `LIBCLANG_PATH` may need to point at the directory +containing `libclang.so`. + +```bash +PHP_CONFIG=/path/to/php-config \ +LIBCLANG_PATH=/path/to/libclang/lib \ +cargo build --release +``` + +The resulting shared object is written to: + +```text +target/release/libwp_mysql_parser.so +``` + +Load it for local test runs with: + +```bash +php -d extension=/path/to/libwp_mysql_parser.so vendor/bin/phpunit +``` diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lexer_constants.rs b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lexer_constants.rs new file mode 100644 index 00000000..5a3d3a40 --- /dev/null +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lexer_constants.rs @@ -0,0 +1,5274 @@ +#![allow(dead_code)] + +use std::mem; +use std::ptr; + +use ext_php_rs::boxed::ZBox; +use ext_php_rs::builders::ClassBuilder; +use ext_php_rs::ffi::{zval, HashTable}; +use ext_php_rs::types::ZendHashTable; + +type DtorFunc = Option; +const GC_IMMUTABLE: u32 = 1 << 6; + +extern "C" { + fn _zend_hash_init(ht: *mut HashTable, nSize: u32, pDestructor: DtorFunc, persistent: bool); +} + +fn persistent_array(capacity: usize) -> ZBox { + unsafe { + let pointer = libc::malloc(mem::size_of::()) as *mut ZendHashTable; + if pointer.is_null() { + panic!("Failed to allocate persistent Zend array"); + } + ptr::write_bytes(pointer, 0, 1); + _zend_hash_init(pointer, capacity as u32, None, true); + ZBox::from_raw(pointer) + } +} + +fn freeze_array(array: &mut ZendHashTable) { + unsafe { + array.gc.u.type_info |= GC_IMMUTABLE; + } +} +fn array_tokens() -> ZBox { + let mut array = persistent_array(800); + array.insert("ACCESSIBLE", 1i64).unwrap(); + array.insert("ACCOUNT", 2i64).unwrap(); + array.insert("ACTION", 3i64).unwrap(); + array.insert("ADD", 4i64).unwrap(); + array.insert("ADDDATE", 5i64).unwrap(); + array.insert("AFTER", 6i64).unwrap(); + array.insert("AGAINST", 7i64).unwrap(); + array.insert("AGGREGATE", 8i64).unwrap(); + array.insert("ALGORITHM", 9i64).unwrap(); + array.insert("ALL", 10i64).unwrap(); + array.insert("ALTER", 11i64).unwrap(); + array.insert("ALWAYS", 12i64).unwrap(); + array.insert("ANALYSE", 13i64).unwrap(); + array.insert("ANALYZE", 14i64).unwrap(); + array.insert("AND", 15i64).unwrap(); + array.insert("ANY", 16i64).unwrap(); + array.insert("AS", 17i64).unwrap(); + array.insert("ASC", 18i64).unwrap(); + array.insert("ASCII", 19i64).unwrap(); + array.insert("ASENSITIVE", 20i64).unwrap(); + array.insert("AT", 21i64).unwrap(); + array.insert("ATTRIBUTE", 812i64).unwrap(); + array.insert("AUTHORS", 22i64).unwrap(); + array.insert("AUTO_INCREMENT", 24i64).unwrap(); + array.insert("AUTOEXTEND_SIZE", 23i64).unwrap(); + array.insert("AVG", 26i64).unwrap(); + array.insert("AVG_ROW_LENGTH", 25i64).unwrap(); + array.insert("BACKUP", 27i64).unwrap(); + array.insert("BEFORE", 28i64).unwrap(); + array.insert("BEGIN", 29i64).unwrap(); + array.insert("BETWEEN", 30i64).unwrap(); + array.insert("BIGINT", 31i64).unwrap(); + array.insert("BIN_NUM", 34i64).unwrap(); + array.insert("BINARY", 32i64).unwrap(); + array.insert("BINLOG", 33i64).unwrap(); + array.insert("BIT", 37i64).unwrap(); + array.insert("BIT_AND", 35i64).unwrap(); + array.insert("BIT_OR", 36i64).unwrap(); + array.insert("BIT_XOR", 38i64).unwrap(); + array.insert("BLOB", 39i64).unwrap(); + array.insert("BLOCK", 40i64).unwrap(); + array.insert("BOOL", 42i64).unwrap(); + array.insert("BOOLEAN", 41i64).unwrap(); + array.insert("BOTH", 43i64).unwrap(); + array.insert("BTREE", 44i64).unwrap(); + array.insert("BY", 45i64).unwrap(); + array.insert("BYTE", 46i64).unwrap(); + array.insert("CACHE", 47i64).unwrap(); + array.insert("CALL", 48i64).unwrap(); + array.insert("CASCADE", 49i64).unwrap(); + array.insert("CASCADED", 50i64).unwrap(); + array.insert("CASE", 51i64).unwrap(); + array.insert("CAST", 52i64).unwrap(); + array.insert("CATALOG_NAME", 53i64).unwrap(); + array.insert("CHAIN", 54i64).unwrap(); + array.insert("CHANGE", 55i64).unwrap(); + array.insert("CHANGED", 56i64).unwrap(); + array.insert("CHANNEL", 57i64).unwrap(); + array.insert("CHAR", 60i64).unwrap(); + array.insert("CHARACTER", 59i64).unwrap(); + array.insert("CHARSET", 58i64).unwrap(); + array.insert("CHECK", 62i64).unwrap(); + array.insert("CHECKSUM", 61i64).unwrap(); + array.insert("CIPHER", 63i64).unwrap(); + array.insert("CLASS_ORIGIN", 64i64).unwrap(); + array.insert("CLIENT", 65i64).unwrap(); + array.insert("CLOSE", 66i64).unwrap(); + array.insert("COALESCE", 67i64).unwrap(); + array.insert("CODE", 68i64).unwrap(); + array.insert("COLLATE", 69i64).unwrap(); + array.insert("COLLATION", 70i64).unwrap(); + array.insert("COLUMN", 72i64).unwrap(); + array.insert("COLUMN_FORMAT", 74i64).unwrap(); + array.insert("COLUMN_NAME", 73i64).unwrap(); + array.insert("COLUMNS", 71i64).unwrap(); + array.insert("COMMENT", 75i64).unwrap(); + array.insert("COMMIT", 77i64).unwrap(); + array.insert("COMMITTED", 76i64).unwrap(); + array.insert("COMPACT", 78i64).unwrap(); + array.insert("COMPLETION", 79i64).unwrap(); + array.insert("COMPRESSED", 80i64).unwrap(); + array.insert("COMPRESSION", 81i64).unwrap(); + array.insert("CONCURRENT", 82i64).unwrap(); + array.insert("CONDITION", 83i64).unwrap(); + array.insert("CONNECTION", 84i64).unwrap(); + array.insert("CONSISTENT", 85i64).unwrap(); + array.insert("CONSTRAINT", 86i64).unwrap(); + array.insert("CONSTRAINT_CATALOG", 87i64).unwrap(); + array.insert("CONSTRAINT_NAME", 88i64).unwrap(); + array.insert("CONSTRAINT_SCHEMA", 89i64).unwrap(); + array.insert("CONTAINS", 90i64).unwrap(); + array.insert("CONTEXT", 91i64).unwrap(); + array.insert("CONTINUE", 92i64).unwrap(); + array.insert("CONTRIBUTORS", 93i64).unwrap(); + array.insert("CONVERT", 94i64).unwrap(); + array.insert("COUNT", 95i64).unwrap(); + array.insert("CPU", 96i64).unwrap(); + array.insert("CREATE", 97i64).unwrap(); + array.insert("CROSS", 98i64).unwrap(); + array.insert("CUBE", 99i64).unwrap(); + array.insert("CURDATE", 100i64).unwrap(); + array.insert("CURRENT", 101i64).unwrap(); + array.insert("CURRENT_DATE", 102i64).unwrap(); + array.insert("CURRENT_TIME", 103i64).unwrap(); + array.insert("CURRENT_TIMESTAMP", 104i64).unwrap(); + array.insert("CURRENT_USER", 105i64).unwrap(); + array.insert("CURSOR", 106i64).unwrap(); + array.insert("CURSOR_NAME", 107i64).unwrap(); + array.insert("CURTIME", 108i64).unwrap(); + array.insert("DATA", 112i64).unwrap(); + array.insert("DATABASE", 109i64).unwrap(); + array.insert("DATABASES", 110i64).unwrap(); + array.insert("DATAFILE", 111i64).unwrap(); + array.insert("DATE", 116i64).unwrap(); + array.insert("DATE_ADD", 114i64).unwrap(); + array.insert("DATE_SUB", 115i64).unwrap(); + array.insert("DATETIME", 113i64).unwrap(); + array.insert("DAY", 122i64).unwrap(); + array.insert("DAY_HOUR", 118i64).unwrap(); + array.insert("DAY_MICROSECOND", 119i64).unwrap(); + array.insert("DAY_MINUTE", 120i64).unwrap(); + array.insert("DAY_SECOND", 121i64).unwrap(); + array.insert("DAYOFMONTH", 117i64).unwrap(); + array.insert("DEALLOCATE", 123i64).unwrap(); + array.insert("DEC", 124i64).unwrap(); + array.insert("DECIMAL", 126i64).unwrap(); + array.insert("DECIMAL_NUM", 125i64).unwrap(); + array.insert("DECLARE", 127i64).unwrap(); + array.insert("DEFAULT", 128i64).unwrap(); + array.insert("DEFAULT_AUTH", 129i64).unwrap(); + array.insert("DEFINER", 130i64).unwrap(); + array.insert("DELAY_KEY_WRITE", 132i64).unwrap(); + array.insert("DELAYED", 131i64).unwrap(); + array.insert("DELETE", 133i64).unwrap(); + array.insert("DES_KEY_FILE", 136i64).unwrap(); + array.insert("DESC", 134i64).unwrap(); + array.insert("DESCRIBE", 135i64).unwrap(); + array.insert("DETERMINISTIC", 137i64).unwrap(); + array.insert("DIAGNOSTICS", 138i64).unwrap(); + array.insert("DIRECTORY", 139i64).unwrap(); + array.insert("DISABLE", 140i64).unwrap(); + array.insert("DISCARD", 141i64).unwrap(); + array.insert("DISK", 142i64).unwrap(); + array.insert("DISTINCT", 143i64).unwrap(); + array.insert("DISTINCTROW", 144i64).unwrap(); + array.insert("DIV", 145i64).unwrap(); + array.insert("DO", 147i64).unwrap(); + array.insert("DOUBLE", 146i64).unwrap(); + array.insert("DROP", 148i64).unwrap(); + array.insert("DUAL", 149i64).unwrap(); + array.insert("DUMPFILE", 150i64).unwrap(); + array.insert("DUPLICATE", 151i64).unwrap(); + array.insert("DYNAMIC", 152i64).unwrap(); + array.insert("EACH", 153i64).unwrap(); + array.insert("ELSE", 154i64).unwrap(); + array.insert("ELSEIF", 155i64).unwrap(); + array.insert("ENABLE", 156i64).unwrap(); + array.insert("ENCLOSED", 157i64).unwrap(); + array.insert("ENCRYPTION", 158i64).unwrap(); + array.insert("END", 159i64).unwrap(); + array.insert("END_OF_INPUT", -1i64).unwrap(); + array.insert("ENDS", 160i64).unwrap(); + array.insert("ENGINE", 163i64).unwrap(); + array.insert("ENGINES", 162i64).unwrap(); + array.insert("ENUM", 164i64).unwrap(); + array.insert("ERROR", 165i64).unwrap(); + array.insert("ERRORS", 166i64).unwrap(); + array.insert("ESCAPE", 168i64).unwrap(); + array.insert("ESCAPED", 167i64).unwrap(); + array.insert("EVENT", 170i64).unwrap(); + array.insert("EVENTS", 169i64).unwrap(); + array.insert("EVERY", 171i64).unwrap(); + array.insert("EXCHANGE", 172i64).unwrap(); + array.insert("EXECUTE", 173i64).unwrap(); + array.insert("EXISTS", 174i64).unwrap(); + array.insert("EXIT", 175i64).unwrap(); + array.insert("EXPANSION", 176i64).unwrap(); + array.insert("EXPIRE", 177i64).unwrap(); + array.insert("EXPLAIN", 178i64).unwrap(); + array.insert("EXPORT", 179i64).unwrap(); + array.insert("EXTENDED", 180i64).unwrap(); + array.insert("EXTENT_SIZE", 181i64).unwrap(); + array.insert("EXTRACT", 182i64).unwrap(); + array.insert("FALSE", 183i64).unwrap(); + array.insert("FAST", 184i64).unwrap(); + array.insert("FAULTS", 185i64).unwrap(); + array.insert("FETCH", 186i64).unwrap(); + array.insert("FIELDS", 187i64).unwrap(); + array.insert("FILE", 188i64).unwrap(); + array.insert("FILE_BLOCK_SIZE", 189i64).unwrap(); + array.insert("FILTER", 190i64).unwrap(); + array.insert("FIRST", 191i64).unwrap(); + array.insert("FIXED", 192i64).unwrap(); + array.insert("FLOAT", 195i64).unwrap(); + array.insert("FLOAT4", 193i64).unwrap(); + array.insert("FLOAT8", 194i64).unwrap(); + array.insert("FLUSH", 196i64).unwrap(); + array.insert("FOLLOWS", 197i64).unwrap(); + array.insert("FOR", 200i64).unwrap(); + array.insert("FORCE", 198i64).unwrap(); + array.insert("FOREIGN", 199i64).unwrap(); + array.insert("FORMAT", 201i64).unwrap(); + array.insert("FOUND", 202i64).unwrap(); + array.insert("FROM", 203i64).unwrap(); + array.insert("FULL", 204i64).unwrap(); + array.insert("FULLTEXT", 205i64).unwrap(); + array.insert("FUNCTION", 206i64).unwrap(); + array.insert("GENERAL", 208i64).unwrap(); + array.insert("GENERATED", 209i64).unwrap(); + array.insert("GEOMCOLLECTION", 852i64).unwrap(); + array.insert("GEOMETRY", 212i64).unwrap(); + array.insert("GEOMETRYCOLLECTION", 211i64).unwrap(); + array.insert("GET", 207i64).unwrap(); + array.insert("GET_FORMAT", 213i64).unwrap(); + array.insert("GLOBAL", 214i64).unwrap(); + array.insert("GRANT", 215i64).unwrap(); + array.insert("GRANTS", 216i64).unwrap(); + array.insert("GROUP", 217i64).unwrap(); + array.insert("GROUP_CONCAT", 218i64).unwrap(); + array.insert("GROUP_REPLICATION", 210i64).unwrap(); + array.insert("HANDLER", 219i64).unwrap(); + array.insert("HASH", 220i64).unwrap(); + array.insert("HAVING", 221i64).unwrap(); + array.insert("HELP", 222i64).unwrap(); + array.insert("HIGH_PRIORITY", 223i64).unwrap(); + array.insert("HOST", 224i64).unwrap(); + array.insert("HOSTS", 225i64).unwrap(); + array.insert("HOUR", 229i64).unwrap(); + array.insert("HOUR_MICROSECOND", 226i64).unwrap(); + array.insert("HOUR_MINUTE", 227i64).unwrap(); + array.insert("HOUR_SECOND", 228i64).unwrap(); + array.insert("IDENTIFIED", 230i64).unwrap(); + array.insert("IF", 231i64).unwrap(); + array.insert("IGNORE", 232i64).unwrap(); + array.insert("IGNORE_SERVER_IDS", 233i64).unwrap(); + array.insert("IMPORT", 234i64).unwrap(); + array.insert("IN", 251i64).unwrap(); + array.insert("INDEX", 236i64).unwrap(); + array.insert("INDEXES", 235i64).unwrap(); + array.insert("INFILE", 237i64).unwrap(); + array.insert("INITIAL_SIZE", 238i64).unwrap(); + array.insert("INNER", 239i64).unwrap(); + array.insert("INNODB", 844i64).unwrap(); + array.insert("INOUT", 240i64).unwrap(); + array.insert("INSENSITIVE", 241i64).unwrap(); + array.insert("INSERT", 242i64).unwrap(); + array.insert("INSERT_METHOD", 243i64).unwrap(); + array.insert("INSTALL", 245i64).unwrap(); + array.insert("INSTANCE", 244i64).unwrap(); + array.insert("INT", 249i64).unwrap(); + array.insert("INT1", 795i64).unwrap(); + array.insert("INT2", 796i64).unwrap(); + array.insert("INT3", 797i64).unwrap(); + array.insert("INT4", 798i64).unwrap(); + array.insert("INT8", 799i64).unwrap(); + array.insert("INTEGER", 246i64).unwrap(); + array.insert("INTERVAL", 247i64).unwrap(); + array.insert("INTO", 248i64).unwrap(); + array.insert("INVOKER", 250i64).unwrap(); + array.insert("IO", 255i64).unwrap(); + array.insert("IO_AFTER_GTIDS", 252i64).unwrap(); + array.insert("IO_BEFORE_GTIDS", 253i64).unwrap(); + array.insert("IO_THREAD", 254i64).unwrap(); + array.insert("IPC", 256i64).unwrap(); + array.insert("IS", 257i64).unwrap(); + array.insert("ISOLATION", 258i64).unwrap(); + array.insert("ISSUER", 259i64).unwrap(); + array.insert("ITERATE", 260i64).unwrap(); + array.insert("JOIN", 261i64).unwrap(); + array.insert("JSON", 262i64).unwrap(); + array.insert("KEY", 265i64).unwrap(); + array.insert("KEY_BLOCK_SIZE", 264i64).unwrap(); + array.insert("KEYS", 263i64).unwrap(); + array.insert("KILL", 266i64).unwrap(); + array.insert("LANGUAGE", 267i64).unwrap(); + array.insert("LAST", 268i64).unwrap(); + array.insert("LEADING", 269i64).unwrap(); + array.insert("LEAVE", 271i64).unwrap(); + array.insert("LEAVES", 270i64).unwrap(); + array.insert("LEFT", 272i64).unwrap(); + array.insert("LESS", 273i64).unwrap(); + array.insert("LEVEL", 274i64).unwrap(); + array.insert("LIKE", 275i64).unwrap(); + array.insert("LIMIT", 276i64).unwrap(); + array.insert("LINEAR", 277i64).unwrap(); + array.insert("LINES", 278i64).unwrap(); + array.insert("LINESTRING", 279i64).unwrap(); + array.insert("LIST", 280i64).unwrap(); + array.insert("LOAD", 281i64).unwrap(); + array.insert("LOCAL", 284i64).unwrap(); + array.insert("LOCALTIME", 282i64).unwrap(); + array.insert("LOCALTIMESTAMP", 283i64).unwrap(); + array.insert("LOCATOR", 285i64).unwrap(); + array.insert("LOCK", 287i64).unwrap(); + array.insert("LOCKS", 286i64).unwrap(); + array.insert("LOGFILE", 288i64).unwrap(); + array.insert("LOGS", 289i64).unwrap(); + array.insert("LONG", 293i64).unwrap(); + array.insert("LONG_NUM", 292i64).unwrap(); + array.insert("LONGBLOB", 290i64).unwrap(); + array.insert("LONGTEXT", 291i64).unwrap(); + array.insert("LOOP", 294i64).unwrap(); + array.insert("LOW_PRIORITY", 295i64).unwrap(); + array.insert("MASTER", 316i64).unwrap(); + array.insert("MASTER_AUTO_POSITION", 296i64).unwrap(); + array.insert("MASTER_BIND", 297i64).unwrap(); + array.insert("MASTER_CONNECT_RETRY", 298i64).unwrap(); + array.insert("MASTER_DELAY", 299i64).unwrap(); + array.insert("MASTER_HEARTBEAT_PERIOD", 319i64).unwrap(); + array.insert("MASTER_HOST", 300i64).unwrap(); + array.insert("MASTER_LOG_FILE", 301i64).unwrap(); + array.insert("MASTER_LOG_POS", 302i64).unwrap(); + array.insert("MASTER_PASSWORD", 303i64).unwrap(); + array.insert("MASTER_PORT", 304i64).unwrap(); + array.insert("MASTER_RETRY_COUNT", 305i64).unwrap(); + array.insert("MASTER_SERVER_ID", 306i64).unwrap(); + array.insert("MASTER_SSL", 314i64).unwrap(); + array.insert("MASTER_SSL_CA", 308i64).unwrap(); + array.insert("MASTER_SSL_CAPATH", 307i64).unwrap(); + array.insert("MASTER_SSL_CERT", 309i64).unwrap(); + array.insert("MASTER_SSL_CIPHER", 310i64).unwrap(); + array.insert("MASTER_SSL_CRL", 311i64).unwrap(); + array.insert("MASTER_SSL_CRLPATH", 312i64).unwrap(); + array.insert("MASTER_SSL_KEY", 313i64).unwrap(); + array + .insert("MASTER_SSL_VERIFY_SERVER_CERT", 315i64) + .unwrap(); + array.insert("MASTER_TLS_VERSION", 317i64).unwrap(); + array.insert("MASTER_USER", 318i64).unwrap(); + array.insert("MATCH", 320i64).unwrap(); + array.insert("MAX", 326i64).unwrap(); + array.insert("MAX_CONNECTIONS_PER_HOUR", 321i64).unwrap(); + array.insert("MAX_QUERIES_PER_HOUR", 322i64).unwrap(); + array.insert("MAX_ROWS", 323i64).unwrap(); + array.insert("MAX_SIZE", 324i64).unwrap(); + array.insert("MAX_STATEMENT_TIME", 325i64).unwrap(); + array.insert("MAX_UPDATES_PER_HOUR", 327i64).unwrap(); + array.insert("MAX_USER_CONNECTIONS", 328i64).unwrap(); + array.insert("MAXVALUE", 329i64).unwrap(); + array.insert("MEDIUM", 333i64).unwrap(); + array.insert("MEDIUMBLOB", 330i64).unwrap(); + array.insert("MEDIUMINT", 331i64).unwrap(); + array.insert("MEDIUMTEXT", 332i64).unwrap(); + array.insert("MEMORY", 334i64).unwrap(); + array.insert("MERGE", 335i64).unwrap(); + array.insert("MESSAGE_TEXT", 336i64).unwrap(); + array.insert("MICROSECOND", 337i64).unwrap(); + array.insert("MID", 338i64).unwrap(); + array.insert("MIDDLEINT", 339i64).unwrap(); + array.insert("MIGRATE", 340i64).unwrap(); + array.insert("MIN", 345i64).unwrap(); + array.insert("MIN_ROWS", 344i64).unwrap(); + array.insert("MINUTE", 343i64).unwrap(); + array.insert("MINUTE_MICROSECOND", 341i64).unwrap(); + array.insert("MINUTE_SECOND", 342i64).unwrap(); + array.insert("MOD", 349i64).unwrap(); + array.insert("MODE", 346i64).unwrap(); + array.insert("MODIFIES", 347i64).unwrap(); + array.insert("MODIFY", 348i64).unwrap(); + array.insert("MONTH", 350i64).unwrap(); + array.insert("MULTILINESTRING", 351i64).unwrap(); + array.insert("MULTIPOINT", 352i64).unwrap(); + array.insert("MULTIPOLYGON", 353i64).unwrap(); + array.insert("MUTEX", 354i64).unwrap(); + array.insert("MYSQL_ERRNO", 355i64).unwrap(); + array.insert("NAME", 357i64).unwrap(); + array.insert("NAMES", 356i64).unwrap(); + array.insert("NATIONAL", 358i64).unwrap(); + array.insert("NATURAL", 359i64).unwrap(); + array.insert("NCHAR", 361i64).unwrap(); + array.insert("NCHAR_STRING", 360i64).unwrap(); + array.insert("NDB", 362i64).unwrap(); + array.insert("NDBCLUSTER", 363i64).unwrap(); + array.insert("NEG", 364i64).unwrap(); + array.insert("NEVER", 365i64).unwrap(); + array.insert("NEW", 366i64).unwrap(); + array.insert("NEXT", 367i64).unwrap(); + array.insert("NO", 373i64).unwrap(); + array.insert("NO_WAIT", 374i64).unwrap(); + array.insert("NO_WRITE_TO_BINLOG", 375i64).unwrap(); + array.insert("NODEGROUP", 368i64).unwrap(); + array.insert("NONBLOCKING", 370i64).unwrap(); + array.insert("NONE", 369i64).unwrap(); + array.insert("NOT", 371i64).unwrap(); + array.insert("NOW", 372i64).unwrap(); + array.insert("NULL", 376i64).unwrap(); + array.insert("NUMBER", 377i64).unwrap(); + array.insert("NUMERIC", 378i64).unwrap(); + array.insert("NVARCHAR", 379i64).unwrap(); + array.insert("OFFLINE", 380i64).unwrap(); + array.insert("OFFSET", 381i64).unwrap(); + array.insert("OLD_PASSWORD", 382i64).unwrap(); + array.insert("ON", 383i64).unwrap(); + array.insert("ONE", 384i64).unwrap(); + array.insert("ONLINE", 385i64).unwrap(); + array.insert("ONLY", 386i64).unwrap(); + array.insert("OPEN", 387i64).unwrap(); + array.insert("OPTIMIZE", 388i64).unwrap(); + array.insert("OPTIMIZER_COSTS", 389i64).unwrap(); + array.insert("OPTION", 391i64).unwrap(); + array.insert("OPTIONALLY", 392i64).unwrap(); + array.insert("OPTIONS", 390i64).unwrap(); + array.insert("OR", 394i64).unwrap(); + array.insert("ORDER", 393i64).unwrap(); + array.insert("OUT", 397i64).unwrap(); + array.insert("OUTER", 395i64).unwrap(); + array.insert("OUTFILE", 396i64).unwrap(); + array.insert("OWNER", 398i64).unwrap(); + array.insert("PACK_KEYS", 399i64).unwrap(); + array.insert("PAGE", 400i64).unwrap(); + array.insert("PARSER", 401i64).unwrap(); + array.insert("PARTIAL", 402i64).unwrap(); + array.insert("PARTITION", 405i64).unwrap(); + array.insert("PARTITIONING", 403i64).unwrap(); + array.insert("PARTITIONS", 404i64).unwrap(); + array.insert("PASSWORD", 406i64).unwrap(); + array.insert("PHASE", 407i64).unwrap(); + array.insert("PLUGIN", 410i64).unwrap(); + array.insert("PLUGIN_DIR", 409i64).unwrap(); + array.insert("PLUGINS", 408i64).unwrap(); + array.insert("POINT", 411i64).unwrap(); + array.insert("POLYGON", 412i64).unwrap(); + array.insert("PORT", 413i64).unwrap(); + array.insert("POSITION", 414i64).unwrap(); + array.insert("PRECEDES", 415i64).unwrap(); + array.insert("PRECISION", 416i64).unwrap(); + array.insert("PREPARE", 417i64).unwrap(); + array.insert("PRESERVE", 418i64).unwrap(); + array.insert("PREV", 419i64).unwrap(); + array.insert("PRIMARY", 420i64).unwrap(); + array.insert("PRIVILEGES", 421i64).unwrap(); + array.insert("PROCEDURE", 422i64).unwrap(); + array.insert("PROCESS", 423i64).unwrap(); + array.insert("PROCESSLIST", 424i64).unwrap(); + array.insert("PROFILE", 425i64).unwrap(); + array.insert("PROFILES", 426i64).unwrap(); + array.insert("PROXY", 427i64).unwrap(); + array.insert("PURGE", 428i64).unwrap(); + array.insert("QUARTER", 429i64).unwrap(); + array.insert("QUERY", 430i64).unwrap(); + array.insert("QUICK", 431i64).unwrap(); + array.insert("RANGE", 432i64).unwrap(); + array.insert("READ", 435i64).unwrap(); + array.insert("READ_ONLY", 434i64).unwrap(); + array.insert("READ_WRITE", 436i64).unwrap(); + array.insert("READS", 433i64).unwrap(); + array.insert("REAL", 437i64).unwrap(); + array.insert("REBUILD", 438i64).unwrap(); + array.insert("RECOVER", 439i64).unwrap(); + array.insert("REDO_BUFFER_SIZE", 441i64).unwrap(); + array.insert("REDOFILE", 440i64).unwrap(); + array.insert("REDUNDANT", 442i64).unwrap(); + array.insert("REFERENCES", 443i64).unwrap(); + array.insert("REGEXP", 444i64).unwrap(); + array.insert("RELAY", 445i64).unwrap(); + array.insert("RELAY_LOG_FILE", 447i64).unwrap(); + array.insert("RELAY_LOG_POS", 448i64).unwrap(); + array.insert("RELAY_THREAD", 449i64).unwrap(); + array.insert("RELAYLOG", 446i64).unwrap(); + array.insert("RELEASE", 450i64).unwrap(); + array.insert("RELOAD", 451i64).unwrap(); + array.insert("REMOVE", 452i64).unwrap(); + array.insert("RENAME", 453i64).unwrap(); + array.insert("REORGANIZE", 454i64).unwrap(); + array.insert("REPAIR", 455i64).unwrap(); + array.insert("REPEAT", 457i64).unwrap(); + array.insert("REPEATABLE", 456i64).unwrap(); + array.insert("REPLACE", 458i64).unwrap(); + array.insert("REPLICATE_DO_DB", 460i64).unwrap(); + array.insert("REPLICATE_DO_TABLE", 462i64).unwrap(); + array.insert("REPLICATE_IGNORE_DB", 461i64).unwrap(); + array.insert("REPLICATE_IGNORE_TABLE", 463i64).unwrap(); + array.insert("REPLICATE_REWRITE_DB", 466i64).unwrap(); + array.insert("REPLICATE_WILD_DO_TABLE", 464i64).unwrap(); + array.insert("REPLICATE_WILD_IGNORE_TABLE", 465i64).unwrap(); + array.insert("REPLICATION", 459i64).unwrap(); + array.insert("REQUIRE", 467i64).unwrap(); + array.insert("RESET", 468i64).unwrap(); + array.insert("RESIGNAL", 469i64).unwrap(); + array.insert("RESTORE", 470i64).unwrap(); + array.insert("RESTRICT", 471i64).unwrap(); + array.insert("RESUME", 472i64).unwrap(); + array.insert("RETURN", 475i64).unwrap(); + array.insert("RETURNED_SQLSTATE", 473i64).unwrap(); + array.insert("RETURNS", 474i64).unwrap(); + array.insert("REVERSE", 476i64).unwrap(); + array.insert("REVOKE", 477i64).unwrap(); + array.insert("RIGHT", 478i64).unwrap(); + array.insert("RLIKE", 479i64).unwrap(); + array.insert("ROLLBACK", 480i64).unwrap(); + array.insert("ROLLUP", 481i64).unwrap(); + array.insert("ROTATE", 482i64).unwrap(); + array.insert("ROUTINE", 483i64).unwrap(); + array.insert("ROW", 487i64).unwrap(); + array.insert("ROW_COUNT", 485i64).unwrap(); + array.insert("ROW_FORMAT", 486i64).unwrap(); + array.insert("ROWS", 484i64).unwrap(); + array.insert("RTREE", 488i64).unwrap(); + array.insert("SAVEPOINT", 489i64).unwrap(); + array.insert("SCHEDULE", 490i64).unwrap(); + array.insert("SCHEMA", 491i64).unwrap(); + array.insert("SCHEMA_NAME", 492i64).unwrap(); + array.insert("SCHEMAS", 493i64).unwrap(); + array.insert("SECOND", 495i64).unwrap(); + array.insert("SECOND_MICROSECOND", 494i64).unwrap(); + array.insert("SECURITY", 496i64).unwrap(); + array.insert("SELECT", 497i64).unwrap(); + array.insert("SENSITIVE", 498i64).unwrap(); + array.insert("SEPARATOR", 499i64).unwrap(); + array.insert("SERIAL", 501i64).unwrap(); + array.insert("SERIALIZABLE", 500i64).unwrap(); + array.insert("SERVER", 503i64).unwrap(); + array.insert("SERVER_OPTIONS", 504i64).unwrap(); + array.insert("SESSION", 502i64).unwrap(); + array.insert("SESSION_USER", 505i64).unwrap(); + array.insert("SET", 506i64).unwrap(); + array.insert("SET_VAR", 507i64).unwrap(); + array.insert("SHARE", 508i64).unwrap(); + array.insert("SHOW", 509i64).unwrap(); + array.insert("SHUTDOWN", 510i64).unwrap(); + array.insert("SIGNAL", 511i64).unwrap(); + array.insert("SIGNED", 512i64).unwrap(); + array.insert("SIMPLE", 513i64).unwrap(); + array.insert("SLAVE", 514i64).unwrap(); + array.insert("SLOW", 515i64).unwrap(); + array.insert("SMALLINT", 516i64).unwrap(); + array.insert("SNAPSHOT", 517i64).unwrap(); + array.insert("SOCKET", 519i64).unwrap(); + array.insert("SOME", 518i64).unwrap(); + array.insert("SONAME", 520i64).unwrap(); + array.insert("SOUNDS", 521i64).unwrap(); + array.insert("SOURCE", 522i64).unwrap(); + array.insert("SPATIAL", 523i64).unwrap(); + array.insert("SPECIFIC", 524i64).unwrap(); + array.insert("SQL", 537i64).unwrap(); + array.insert("SQL_AFTER_GTIDS", 528i64).unwrap(); + array.insert("SQL_AFTER_MTS_GAPS", 529i64).unwrap(); + array.insert("SQL_BEFORE_GTIDS", 530i64).unwrap(); + array.insert("SQL_BIG_RESULT", 531i64).unwrap(); + array.insert("SQL_BUFFER_RESULT", 532i64).unwrap(); + array.insert("SQL_CACHE", 533i64).unwrap(); + array.insert("SQL_CALC_FOUND_ROWS", 534i64).unwrap(); + array.insert("SQL_NO_CACHE", 535i64).unwrap(); + array.insert("SQL_SMALL_RESULT", 536i64).unwrap(); + array.insert("SQL_THREAD", 538i64).unwrap(); + array.insert("SQL_TSI_DAY", 802i64).unwrap(); + array.insert("SQL_TSI_HOUR", 803i64).unwrap(); + array.insert("SQL_TSI_MICROSECOND", 804i64).unwrap(); + array.insert("SQL_TSI_MINUTE", 805i64).unwrap(); + array.insert("SQL_TSI_MONTH", 806i64).unwrap(); + array.insert("SQL_TSI_QUARTER", 807i64).unwrap(); + array.insert("SQL_TSI_SECOND", 808i64).unwrap(); + array.insert("SQL_TSI_WEEK", 809i64).unwrap(); + array.insert("SQL_TSI_YEAR", 810i64).unwrap(); + array.insert("SQLEXCEPTION", 525i64).unwrap(); + array.insert("SQLSTATE", 526i64).unwrap(); + array.insert("SQLWARNING", 527i64).unwrap(); + array.insert("SSL", 539i64).unwrap(); + array.insert("STACKED", 540i64).unwrap(); + array.insert("START", 543i64).unwrap(); + array.insert("STARTING", 541i64).unwrap(); + array.insert("STARTS", 542i64).unwrap(); + array.insert("STATS_AUTO_RECALC", 544i64).unwrap(); + array.insert("STATS_PERSISTENT", 545i64).unwrap(); + array.insert("STATS_SAMPLE_PAGES", 546i64).unwrap(); + array.insert("STATUS", 547i64).unwrap(); + array.insert("STD", 551i64).unwrap(); + array.insert("STDDEV", 549i64).unwrap(); + array.insert("STDDEV_POP", 550i64).unwrap(); + array.insert("STDDEV_SAMP", 548i64).unwrap(); + array.insert("STOP", 552i64).unwrap(); + array.insert("STORAGE", 553i64).unwrap(); + array.insert("STORED", 554i64).unwrap(); + array.insert("STRAIGHT_JOIN", 555i64).unwrap(); + array.insert("STRING", 556i64).unwrap(); + array.insert("SUBCLASS_ORIGIN", 557i64).unwrap(); + array.insert("SUBDATE", 558i64).unwrap(); + array.insert("SUBJECT", 559i64).unwrap(); + array.insert("SUBPARTITION", 561i64).unwrap(); + array.insert("SUBPARTITIONS", 560i64).unwrap(); + array.insert("SUBSTR", 562i64).unwrap(); + array.insert("SUBSTRING", 563i64).unwrap(); + array.insert("SUM", 564i64).unwrap(); + array.insert("SUPER", 565i64).unwrap(); + array.insert("SUSPEND", 566i64).unwrap(); + array.insert("SWAPS", 567i64).unwrap(); + array.insert("SWITCHES", 568i64).unwrap(); + array.insert("SYSDATE", 569i64).unwrap(); + array.insert("SYSTEM_USER", 570i64).unwrap(); + array.insert("TABLE", 574i64).unwrap(); + array.insert("TABLE_CHECKSUM", 575i64).unwrap(); + array.insert("TABLE_NAME", 576i64).unwrap(); + array.insert("TABLE_REF_PRIORITY", 573i64).unwrap(); + array.insert("TABLES", 571i64).unwrap(); + array.insert("TABLESPACE", 572i64).unwrap(); + array.insert("TEMPORARY", 577i64).unwrap(); + array.insert("TEMPTABLE", 578i64).unwrap(); + array.insert("TERMINATED", 579i64).unwrap(); + array.insert("TEXT", 580i64).unwrap(); + array.insert("THAN", 581i64).unwrap(); + array.insert("THEN", 582i64).unwrap(); + array.insert("TIME", 586i64).unwrap(); + array.insert("TIMESTAMP", 583i64).unwrap(); + array.insert("TIMESTAMP_ADD", 584i64).unwrap(); + array.insert("TIMESTAMP_DIFF", 585i64).unwrap(); + array.insert("TINYBLOB", 587i64).unwrap(); + array.insert("TINYINT", 588i64).unwrap(); + array.insert("TINYTEXT", 589i64).unwrap(); + array.insert("TO", 590i64).unwrap(); + array.insert("TRAILING", 591i64).unwrap(); + array.insert("TRANSACTION", 592i64).unwrap(); + array.insert("TRIGGER", 594i64).unwrap(); + array.insert("TRIGGERS", 593i64).unwrap(); + array.insert("TRIM", 595i64).unwrap(); + array.insert("TRUE", 596i64).unwrap(); + array.insert("TRUNCATE", 597i64).unwrap(); + array.insert("TYPE", 599i64).unwrap(); + array.insert("TYPES", 598i64).unwrap(); + array.insert("UDF_RETURNS", 600i64).unwrap(); + array.insert("UNCOMMITTED", 601i64).unwrap(); + array.insert("UNDEFINED", 602i64).unwrap(); + array.insert("UNDO", 605i64).unwrap(); + array.insert("UNDO_BUFFER_SIZE", 604i64).unwrap(); + array.insert("UNDOFILE", 603i64).unwrap(); + array.insert("UNICODE", 606i64).unwrap(); + array.insert("UNINSTALL", 607i64).unwrap(); + array.insert("UNION", 608i64).unwrap(); + array.insert("UNIQUE", 609i64).unwrap(); + array.insert("UNKNOWN", 610i64).unwrap(); + array.insert("UNLOCK", 611i64).unwrap(); + array.insert("UNSIGNED", 612i64).unwrap(); + array.insert("UNTIL", 613i64).unwrap(); + array.insert("UPDATE", 614i64).unwrap(); + array.insert("UPGRADE", 615i64).unwrap(); + array.insert("USAGE", 616i64).unwrap(); + array.insert("USE", 620i64).unwrap(); + array.insert("USE_FRM", 619i64).unwrap(); + array.insert("USER", 618i64).unwrap(); + array.insert("USER_RESOURCES", 617i64).unwrap(); + array.insert("USING", 621i64).unwrap(); + array.insert("UTC_DATE", 622i64).unwrap(); + array.insert("UTC_TIME", 624i64).unwrap(); + array.insert("UTC_TIMESTAMP", 623i64).unwrap(); + array.insert("VALIDATION", 625i64).unwrap(); + array.insert("VALUE", 627i64).unwrap(); + array.insert("VALUES", 626i64).unwrap(); + array.insert("VAR_POP", 634i64).unwrap(); + array.insert("VAR_SAMP", 635i64).unwrap(); + array.insert("VARBINARY", 628i64).unwrap(); + array.insert("VARCHAR", 629i64).unwrap(); + array.insert("VARCHARACTER", 630i64).unwrap(); + array.insert("VARIABLES", 631i64).unwrap(); + array.insert("VARIANCE", 632i64).unwrap(); + array.insert("VARYING", 633i64).unwrap(); + array.insert("VIEW", 636i64).unwrap(); + array.insert("VIRTUAL", 637i64).unwrap(); + array.insert("WAIT", 638i64).unwrap(); + array.insert("WARNINGS", 639i64).unwrap(); + array.insert("WEEK", 640i64).unwrap(); + array.insert("WEIGHT_STRING", 641i64).unwrap(); + array.insert("WHEN", 642i64).unwrap(); + array.insert("WHERE", 643i64).unwrap(); + array.insert("WHILE", 644i64).unwrap(); + array.insert("WITH", 645i64).unwrap(); + array.insert("WITHOUT", 646i64).unwrap(); + array.insert("WORK", 647i64).unwrap(); + array.insert("WRAPPER", 648i64).unwrap(); + array.insert("WRITE", 649i64).unwrap(); + array.insert("X509", 650i64).unwrap(); + array.insert("XA", 651i64).unwrap(); + array.insert("XID", 652i64).unwrap(); + array.insert("XML", 653i64).unwrap(); + array.insert("XOR", 654i64).unwrap(); + array.insert("YEAR", 656i64).unwrap(); + array.insert("YEAR_MONTH", 655i64).unwrap(); + array.insert("ZEROFILL", 657i64).unwrap(); + array.insert("ACTIVE", 724i64).unwrap(); + array.insert("ADMIN", 660i64).unwrap(); + array.insert("ARRAY", 731i64).unwrap(); + array + .insert("ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS", 842i64) + .unwrap(); + array.insert("BUCKETS", 675i64).unwrap(); + array.insert("CLONE", 677i64).unwrap(); + array.insert("COMPONENT", 664i64).unwrap(); + array.insert("CUME_DIST", 678i64).unwrap(); + array.insert("DEFINITION", 715i64).unwrap(); + array.insert("DENSE_RANK", 679i64).unwrap(); + array.insert("DESCRIPTION", 716i64).unwrap(); + array.insert("EMPTY", 700i64).unwrap(); + array.insert("ENFORCED", 730i64).unwrap(); + array.insert("ENGINE_ATTRIBUTE", 848i64).unwrap(); + array.insert("EXCEPT", 663i64).unwrap(); + array.insert("EXCLUDE", 680i64).unwrap(); + array.insert("FAILED_LOGIN_ATTEMPTS", 741i64).unwrap(); + array.insert("FIRST_VALUE", 681i64).unwrap(); + array.insert("FOLLOWING", 682i64).unwrap(); + array.insert("GET_MASTER_PUBLIC_KEY_SYM", 713i64).unwrap(); + array.insert("GET_SOURCE_PUBLIC_KEY", 840i64).unwrap(); + array.insert("GROUPING", 672i64).unwrap(); + array.insert("GROUPS", 683i64).unwrap(); + array.insert("GTID_ONLY", 841i64).unwrap(); + array.insert("HISTOGRAM", 674i64).unwrap(); + array.insert("HISTORY", 705i64).unwrap(); + array.insert("INACTIVE", 725i64).unwrap(); + array.insert("INTERSECT", 811i64).unwrap(); + array.insert("INVISIBLE", 661i64).unwrap(); + array.insert("JSON_ARRAYAGG", 667i64).unwrap(); + array.insert("JSON_OBJECTAGG", 666i64).unwrap(); + array.insert("JSON_TABLE", 701i64).unwrap(); + array.insert("JSON_VALUE", 850i64).unwrap(); + array.insert("KEYRING", 847i64).unwrap(); + array.insert("LAG", 684i64).unwrap(); + array.insert("LAST_VALUE", 685i64).unwrap(); + array.insert("LATERAL", 726i64).unwrap(); + array.insert("LEAD", 686i64).unwrap(); + array.insert("LOCKED", 670i64).unwrap(); + array + .insert("MASTER_COMPRESSION_ALGORITHM", 735i64) + .unwrap(); + array.insert("MASTER_PUBLIC_KEY_PATH", 712i64).unwrap(); + array.insert("MASTER_TLS_CIPHERSUITES", 738i64).unwrap(); + array + .insert("MASTER_ZSTD_COMPRESSION_LEVEL", 736i64) + .unwrap(); + array.insert("MEMBER", 733i64).unwrap(); + array.insert("NESTED", 702i64).unwrap(); + array.insert("NETWORK_NAMESPACE", 729i64).unwrap(); + array.insert("NOWAIT", 671i64).unwrap(); + array.insert("NTH_VALUE", 687i64).unwrap(); + array.insert("NTILE", 688i64).unwrap(); + array.insert("NULLS", 689i64).unwrap(); + array.insert("OF", 668i64).unwrap(); + array.insert("OFF", 744i64).unwrap(); + array.insert("OJ", 732i64).unwrap(); + array.insert("OLD", 728i64).unwrap(); + array.insert("OPTIONAL", 719i64).unwrap(); + array.insert("ORDINALITY", 703i64).unwrap(); + array.insert("ORGANIZATION", 717i64).unwrap(); + array.insert("OTHERS", 690i64).unwrap(); + array.insert("OVER", 691i64).unwrap(); + array.insert("PASSWORD_LOCK_TIME", 740i64).unwrap(); + array.insert("PATH", 704i64).unwrap(); + array.insert("PERCENT_RANK", 692i64).unwrap(); + array.insert("PERSIST", 658i64).unwrap(); + array.insert("PERSIST_ONLY", 673i64).unwrap(); + array.insert("PRECEDING", 693i64).unwrap(); + array.insert("PRIVILEGE_CHECKS_USER", 737i64).unwrap(); + array.insert("RANDOM", 734i64).unwrap(); + array.insert("RANK", 694i64).unwrap(); + array.insert("RECURSIVE", 665i64).unwrap(); + array.insert("REDO_LOG", 846i64).unwrap(); + array.insert("REFERENCE", 718i64).unwrap(); + array.insert("REMOTE", 676i64).unwrap(); + array.insert("REQUIRE_ROW_FORMAT", 739i64).unwrap(); + array + .insert("REQUIRE_TABLE_PRIMARY_KEY_CHECK", 742i64) + .unwrap(); + array.insert("RESOURCE", 709i64).unwrap(); + array.insert("RESPECT", 695i64).unwrap(); + array.insert("RESTART", 714i64).unwrap(); + array.insert("RETAIN", 727i64).unwrap(); + array.insert("RETURNING", 851i64).unwrap(); + array.insert("REUSE", 706i64).unwrap(); + array.insert("ROLE", 659i64).unwrap(); + array.insert("ROW_NUMBER", 696i64).unwrap(); + array.insert("SECONDARY", 720i64).unwrap(); + array.insert("SECONDARY_ENGINE", 721i64).unwrap(); + array.insert("SECONDARY_ENGINE_ATTRIBUTE", 849i64).unwrap(); + array.insert("SECONDARY_LOAD", 722i64).unwrap(); + array.insert("SECONDARY_UNLOAD", 723i64).unwrap(); + array.insert("SKIP", 669i64).unwrap(); + array.insert("SOURCE_AUTO_POSITION", 813i64).unwrap(); + array.insert("SOURCE_BIND", 814i64).unwrap(); + array + .insert("SOURCE_COMPRESSION_ALGORITHM", 815i64) + .unwrap(); + array.insert("SOURCE_CONNECT_RETRY", 816i64).unwrap(); + array + .insert("SOURCE_CONNECTION_AUTO_FAILOVER", 817i64) + .unwrap(); + array.insert("SOURCE_DELAY", 818i64).unwrap(); + array.insert("SOURCE_HEARTBEAT_PERIOD", 819i64).unwrap(); + array.insert("SOURCE_HOST", 820i64).unwrap(); + array.insert("SOURCE_LOG_FILE", 821i64).unwrap(); + array.insert("SOURCE_LOG_POS", 822i64).unwrap(); + array.insert("SOURCE_PASSWORD", 823i64).unwrap(); + array.insert("SOURCE_PORT", 824i64).unwrap(); + array.insert("SOURCE_PUBLIC_KEY_PATH", 825i64).unwrap(); + array.insert("SOURCE_RETRY_COUNT", 826i64).unwrap(); + array.insert("SOURCE_SSL", 827i64).unwrap(); + array.insert("SOURCE_SSL_CA", 828i64).unwrap(); + array.insert("SOURCE_SSL_CAPATH", 829i64).unwrap(); + array.insert("SOURCE_SSL_CERT", 830i64).unwrap(); + array.insert("SOURCE_SSL_CIPHER", 831i64).unwrap(); + array.insert("SOURCE_SSL_CRL", 832i64).unwrap(); + array.insert("SOURCE_SSL_CRLPATH", 833i64).unwrap(); + array.insert("SOURCE_SSL_KEY", 834i64).unwrap(); + array + .insert("SOURCE_SSL_VERIFY_SERVER_CERT", 835i64) + .unwrap(); + array.insert("SOURCE_TLS_CIPHERSUITES", 836i64).unwrap(); + array.insert("SOURCE_TLS_VERSION", 837i64).unwrap(); + array.insert("SOURCE_USER", 838i64).unwrap(); + array + .insert("SOURCE_ZSTD_COMPRESSION_LEVEL", 839i64) + .unwrap(); + array.insert("SRID", 707i64).unwrap(); + array.insert("STREAM", 743i64).unwrap(); + array.insert("SYSTEM", 710i64).unwrap(); + array.insert("THREAD_PRIORITY", 708i64).unwrap(); + array.insert("TIES", 697i64).unwrap(); + array.insert("TLS", 845i64).unwrap(); + array.insert("UNBOUNDED", 698i64).unwrap(); + array.insert("VCPU", 711i64).unwrap(); + array.insert("VISIBLE", 662i64).unwrap(); + array.insert("WINDOW", 699i64).unwrap(); + array.insert("ZONE", 843i64).unwrap(); + freeze_array(&mut array); + array +} + +fn array_functions() -> ZBox { + let mut array = persistent_array(34); + array.insert_at_index(5i64, true).unwrap(); + array.insert_at_index(35i64, true).unwrap(); + array.insert_at_index(36i64, true).unwrap(); + array.insert_at_index(38i64, true).unwrap(); + array.insert_at_index(52i64, true).unwrap(); + array.insert_at_index(95i64, true).unwrap(); + array.insert_at_index(100i64, true).unwrap(); + array.insert_at_index(102i64, true).unwrap(); + array.insert_at_index(103i64, true).unwrap(); + array.insert_at_index(108i64, true).unwrap(); + array.insert_at_index(114i64, true).unwrap(); + array.insert_at_index(115i64, true).unwrap(); + array.insert_at_index(182i64, true).unwrap(); + array.insert_at_index(218i64, true).unwrap(); + array.insert_at_index(326i64, true).unwrap(); + array.insert_at_index(338i64, true).unwrap(); + array.insert_at_index(345i64, true).unwrap(); + array.insert_at_index(372i64, true).unwrap(); + array.insert_at_index(414i64, true).unwrap(); + array.insert_at_index(505i64, true).unwrap(); + array.insert_at_index(551i64, true).unwrap(); + array.insert_at_index(550i64, true).unwrap(); + array.insert_at_index(548i64, true).unwrap(); + array.insert_at_index(549i64, true).unwrap(); + array.insert_at_index(558i64, true).unwrap(); + array.insert_at_index(562i64, true).unwrap(); + array.insert_at_index(563i64, true).unwrap(); + array.insert_at_index(564i64, true).unwrap(); + array.insert_at_index(569i64, true).unwrap(); + array.insert_at_index(570i64, true).unwrap(); + array.insert_at_index(595i64, true).unwrap(); + array.insert_at_index(634i64, true).unwrap(); + array.insert_at_index(635i64, true).unwrap(); + array.insert_at_index(632i64, true).unwrap(); + freeze_array(&mut array); + array +} + +fn array_synonyms() -> ZBox { + let mut array = persistent_array(43); + array.insert_at_index(59i64, 60i64).unwrap(); + array.insert_at_index(102i64, 100i64).unwrap(); + array.insert_at_index(103i64, 108i64).unwrap(); + array.insert_at_index(104i64, 372i64).unwrap(); + array.insert_at_index(117i64, 122i64).unwrap(); + array.insert_at_index(124i64, 126i64).unwrap(); + array.insert_at_index(144i64, 143i64).unwrap(); + array.insert_at_index(187i64, 71i64).unwrap(); + array.insert_at_index(193i64, 195i64).unwrap(); + array.insert_at_index(194i64, 146i64).unwrap(); + array.insert_at_index(852i64, 211i64).unwrap(); + array.insert_at_index(795i64, 588i64).unwrap(); + array.insert_at_index(796i64, 516i64).unwrap(); + array.insert_at_index(797i64, 331i64).unwrap(); + array.insert_at_index(798i64, 249i64).unwrap(); + array.insert_at_index(799i64, 31i64).unwrap(); + array.insert_at_index(246i64, 249i64).unwrap(); + array.insert_at_index(254i64, 449i64).unwrap(); + array.insert_at_index(282i64, 372i64).unwrap(); + array.insert_at_index(283i64, 372i64).unwrap(); + array.insert_at_index(338i64, 563i64).unwrap(); + array.insert_at_index(339i64, 331i64).unwrap(); + array.insert_at_index(362i64, 363i64).unwrap(); + array.insert_at_index(479i64, 444i64).unwrap(); + array.insert_at_index(491i64, 109i64).unwrap(); + array.insert_at_index(493i64, 110i64).unwrap(); + array.insert_at_index(505i64, 618i64).unwrap(); + array.insert_at_index(518i64, 16i64).unwrap(); + array.insert_at_index(802i64, 122i64).unwrap(); + array.insert_at_index(803i64, 229i64).unwrap(); + array.insert_at_index(804i64, 337i64).unwrap(); + array.insert_at_index(805i64, 343i64).unwrap(); + array.insert_at_index(806i64, 350i64).unwrap(); + array.insert_at_index(807i64, 429i64).unwrap(); + array.insert_at_index(808i64, 495i64).unwrap(); + array.insert_at_index(809i64, 640i64).unwrap(); + array.insert_at_index(810i64, 656i64).unwrap(); + array.insert_at_index(550i64, 551i64).unwrap(); + array.insert_at_index(549i64, 551i64).unwrap(); + array.insert_at_index(562i64, 563i64).unwrap(); + array.insert_at_index(570i64, 618i64).unwrap(); + array.insert_at_index(634i64, 632i64).unwrap(); + array.insert_at_index(630i64, 629i64).unwrap(); + freeze_array(&mut array); + array +} + +fn array_versions() -> ZBox { + let mut array = persistent_array(179); + array.insert_at_index(2i64, 50707i64).unwrap(); + array.insert_at_index(12i64, 50707i64).unwrap(); + array.insert_at_index(13i64, -80000i64).unwrap(); + array.insert_at_index(22i64, -50700i64).unwrap(); + array.insert_at_index(57i64, 50706i64).unwrap(); + array.insert_at_index(81i64, 50707i64).unwrap(); + array.insert_at_index(93i64, -50700i64).unwrap(); + array.insert_at_index(101i64, 50604i64).unwrap(); + array.insert_at_index(129i64, 50604i64).unwrap(); + array.insert_at_index(136i64, -80003i64).unwrap(); + array.insert_at_index(158i64, 50711i64).unwrap(); + array.insert_at_index(177i64, 50606i64).unwrap(); + array.insert_at_index(179i64, 50606i64).unwrap(); + array.insert_at_index(189i64, 50707i64).unwrap(); + array.insert_at_index(190i64, 50700i64).unwrap(); + array.insert_at_index(197i64, 50700i64).unwrap(); + array.insert_at_index(209i64, 50707i64).unwrap(); + array.insert_at_index(207i64, 50604i64).unwrap(); + array.insert_at_index(210i64, 50707i64).unwrap(); + array.insert_at_index(844i64, 50711i64).unwrap(); + array.insert_at_index(244i64, 50713i64).unwrap(); + array.insert_at_index(262i64, 50708i64).unwrap(); + array.insert_at_index(296i64, 50605i64).unwrap(); + array.insert_at_index(297i64, 50602i64).unwrap(); + array.insert_at_index(305i64, 50601i64).unwrap(); + array.insert_at_index(311i64, 50603i64).unwrap(); + array.insert_at_index(312i64, 50603i64).unwrap(); + array.insert_at_index(317i64, 50713i64).unwrap(); + array.insert_at_index(365i64, 50704i64).unwrap(); + array.insert_at_index(377i64, 50606i64).unwrap(); + array.insert_at_index(382i64, -50706i64).unwrap(); + array.insert_at_index(386i64, 50605i64).unwrap(); + array.insert_at_index(389i64, 50706i64).unwrap(); + array.insert_at_index(409i64, 50604i64).unwrap(); + array.insert_at_index(415i64, 50700i64).unwrap(); + array.insert_at_index(440i64, -80000i64).unwrap(); + array.insert_at_index(460i64, 50700i64).unwrap(); + array.insert_at_index(462i64, 50700i64).unwrap(); + array.insert_at_index(461i64, 50700i64).unwrap(); + array.insert_at_index(463i64, 50700i64).unwrap(); + array.insert_at_index(466i64, 50700i64).unwrap(); + array.insert_at_index(464i64, 50700i64).unwrap(); + array.insert_at_index(465i64, 50700i64).unwrap(); + array.insert_at_index(482i64, 50713i64).unwrap(); + array.insert_at_index(529i64, 50606i64).unwrap(); + array.insert_at_index(533i64, -80000i64).unwrap(); + array.insert_at_index(540i64, 50700i64).unwrap(); + array.insert_at_index(554i64, 50707i64).unwrap(); + array.insert_at_index(573i64, -80000i64).unwrap(); + array.insert_at_index(625i64, 50706i64).unwrap(); + array.insert_at_index(637i64, 50707i64).unwrap(); + array.insert_at_index(652i64, 50704i64).unwrap(); + array.insert_at_index(724i64, 80014i64).unwrap(); + array.insert_at_index(660i64, 80000i64).unwrap(); + array.insert_at_index(731i64, 80017i64).unwrap(); + array.insert_at_index(842i64, 80000i64).unwrap(); + array.insert_at_index(812i64, 80021i64).unwrap(); + array.insert_at_index(675i64, 80000i64).unwrap(); + array.insert_at_index(677i64, 80000i64).unwrap(); + array.insert_at_index(664i64, 80000i64).unwrap(); + array.insert_at_index(678i64, 80000i64).unwrap(); + array.insert_at_index(715i64, 80011i64).unwrap(); + array.insert_at_index(679i64, 80000i64).unwrap(); + array.insert_at_index(716i64, 80011i64).unwrap(); + array.insert_at_index(700i64, 80000i64).unwrap(); + array.insert_at_index(730i64, 80017i64).unwrap(); + array.insert_at_index(848i64, 80021i64).unwrap(); + array.insert_at_index(663i64, 80000i64).unwrap(); + array.insert_at_index(680i64, 80000i64).unwrap(); + array.insert_at_index(741i64, 80019i64).unwrap(); + array.insert_at_index(681i64, 80000i64).unwrap(); + array.insert_at_index(682i64, 80000i64).unwrap(); + array.insert_at_index(852i64, 80000i64).unwrap(); + array.insert_at_index(713i64, 80000i64).unwrap(); + array.insert_at_index(840i64, 80000i64).unwrap(); + array.insert_at_index(672i64, 80000i64).unwrap(); + array.insert_at_index(683i64, 80000i64).unwrap(); + array.insert_at_index(841i64, 80000i64).unwrap(); + array.insert_at_index(674i64, 80000i64).unwrap(); + array.insert_at_index(705i64, 80000i64).unwrap(); + array.insert_at_index(725i64, 80014i64).unwrap(); + array.insert_at_index(811i64, 80031i64).unwrap(); + array.insert_at_index(661i64, 80000i64).unwrap(); + array.insert_at_index(667i64, 80000i64).unwrap(); + array.insert_at_index(666i64, 80000i64).unwrap(); + array.insert_at_index(701i64, 80000i64).unwrap(); + array.insert_at_index(850i64, 80021i64).unwrap(); + array.insert_at_index(847i64, 80024i64).unwrap(); + array.insert_at_index(684i64, 80000i64).unwrap(); + array.insert_at_index(685i64, 80000i64).unwrap(); + array.insert_at_index(726i64, 80014i64).unwrap(); + array.insert_at_index(686i64, 80000i64).unwrap(); + array.insert_at_index(670i64, 80000i64).unwrap(); + array.insert_at_index(735i64, 80018i64).unwrap(); + array.insert_at_index(712i64, 80000i64).unwrap(); + array.insert_at_index(738i64, 80018i64).unwrap(); + array.insert_at_index(736i64, 80018i64).unwrap(); + array.insert_at_index(733i64, 80017i64).unwrap(); + array.insert_at_index(702i64, 80000i64).unwrap(); + array.insert_at_index(729i64, 80017i64).unwrap(); + array.insert_at_index(671i64, 80000i64).unwrap(); + array.insert_at_index(687i64, 80000i64).unwrap(); + array.insert_at_index(688i64, 80000i64).unwrap(); + array.insert_at_index(689i64, 80000i64).unwrap(); + array.insert_at_index(668i64, 80000i64).unwrap(); + array.insert_at_index(744i64, 80019i64).unwrap(); + array.insert_at_index(732i64, 80017i64).unwrap(); + array.insert_at_index(728i64, 80014i64).unwrap(); + array.insert_at_index(719i64, 80013i64).unwrap(); + array.insert_at_index(703i64, 80000i64).unwrap(); + array.insert_at_index(717i64, 80011i64).unwrap(); + array.insert_at_index(690i64, 80000i64).unwrap(); + array.insert_at_index(691i64, 80000i64).unwrap(); + array.insert_at_index(740i64, 80019i64).unwrap(); + array.insert_at_index(704i64, 80000i64).unwrap(); + array.insert_at_index(692i64, 80000i64).unwrap(); + array.insert_at_index(673i64, 80000i64).unwrap(); + array.insert_at_index(658i64, 80000i64).unwrap(); + array.insert_at_index(693i64, 80000i64).unwrap(); + array.insert_at_index(737i64, 80018i64).unwrap(); + array.insert_at_index(734i64, 80018i64).unwrap(); + array.insert_at_index(694i64, 80000i64).unwrap(); + array.insert_at_index(665i64, 80000i64).unwrap(); + array.insert_at_index(846i64, 80021i64).unwrap(); + array.insert_at_index(718i64, 80011i64).unwrap(); + array.insert_at_index(739i64, 80019i64).unwrap(); + array.insert_at_index(742i64, 80019i64).unwrap(); + array.insert_at_index(709i64, 80000i64).unwrap(); + array.insert_at_index(695i64, 80000i64).unwrap(); + array.insert_at_index(714i64, 80011i64).unwrap(); + array.insert_at_index(727i64, 80014i64).unwrap(); + array.insert_at_index(706i64, 80000i64).unwrap(); + array.insert_at_index(851i64, 80021i64).unwrap(); + array.insert_at_index(659i64, 80000i64).unwrap(); + array.insert_at_index(696i64, 80000i64).unwrap(); + array.insert_at_index(849i64, 80021i64).unwrap(); + array.insert_at_index(721i64, 80013i64).unwrap(); + array.insert_at_index(722i64, 80013i64).unwrap(); + array.insert_at_index(720i64, 80013i64).unwrap(); + array.insert_at_index(723i64, 80013i64).unwrap(); + array.insert_at_index(669i64, 80000i64).unwrap(); + array.insert_at_index(813i64, 80000i64).unwrap(); + array.insert_at_index(814i64, 80000i64).unwrap(); + array.insert_at_index(815i64, 80000i64).unwrap(); + array.insert_at_index(816i64, 80000i64).unwrap(); + array.insert_at_index(817i64, 80000i64).unwrap(); + array.insert_at_index(818i64, 80000i64).unwrap(); + array.insert_at_index(819i64, 80000i64).unwrap(); + array.insert_at_index(820i64, 80000i64).unwrap(); + array.insert_at_index(821i64, 80000i64).unwrap(); + array.insert_at_index(822i64, 80000i64).unwrap(); + array.insert_at_index(823i64, 80000i64).unwrap(); + array.insert_at_index(824i64, 80000i64).unwrap(); + array.insert_at_index(825i64, 80000i64).unwrap(); + array.insert_at_index(826i64, 80000i64).unwrap(); + array.insert_at_index(828i64, 80000i64).unwrap(); + array.insert_at_index(829i64, 80000i64).unwrap(); + array.insert_at_index(830i64, 80000i64).unwrap(); + array.insert_at_index(831i64, 80000i64).unwrap(); + array.insert_at_index(832i64, 80000i64).unwrap(); + array.insert_at_index(833i64, 80000i64).unwrap(); + array.insert_at_index(834i64, 80000i64).unwrap(); + array.insert_at_index(827i64, 80000i64).unwrap(); + array.insert_at_index(835i64, 80000i64).unwrap(); + array.insert_at_index(836i64, 80000i64).unwrap(); + array.insert_at_index(837i64, 80000i64).unwrap(); + array.insert_at_index(838i64, 80000i64).unwrap(); + array.insert_at_index(839i64, 80000i64).unwrap(); + array.insert_at_index(707i64, 80000i64).unwrap(); + array.insert_at_index(743i64, 80019i64).unwrap(); + array.insert_at_index(710i64, 80000i64).unwrap(); + array.insert_at_index(708i64, 80000i64).unwrap(); + array.insert_at_index(697i64, 80000i64).unwrap(); + array.insert_at_index(845i64, 80016i64).unwrap(); + array.insert_at_index(698i64, 80000i64).unwrap(); + array.insert_at_index(711i64, 80000i64).unwrap(); + array.insert_at_index(662i64, 80000i64).unwrap(); + array.insert_at_index(699i64, 80000i64).unwrap(); + array.insert_at_index(843i64, 80022i64).unwrap(); + freeze_array(&mut array); + array +} + +fn array_underscore_charsets() -> ZBox { + let mut array = persistent_array(42); + array.insert("_armscii8", true).unwrap(); + array.insert("_ascii", true).unwrap(); + array.insert("_big5", true).unwrap(); + array.insert("_binary", true).unwrap(); + array.insert("_cp1250", true).unwrap(); + array.insert("_cp1251", true).unwrap(); + array.insert("_cp1256", true).unwrap(); + array.insert("_cp1257", true).unwrap(); + array.insert("_cp850", true).unwrap(); + array.insert("_cp852", true).unwrap(); + array.insert("_cp866", true).unwrap(); + array.insert("_cp932", true).unwrap(); + array.insert("_dec8", true).unwrap(); + array.insert("_eucjpms", true).unwrap(); + array.insert("_euckr", true).unwrap(); + array.insert("_gb18030", true).unwrap(); + array.insert("_gb2312", true).unwrap(); + array.insert("_gbk", true).unwrap(); + array.insert("_geostd8", true).unwrap(); + array.insert("_greek", true).unwrap(); + array.insert("_hebrew", true).unwrap(); + array.insert("_hp8", true).unwrap(); + array.insert("_keybcs2", true).unwrap(); + array.insert("_koi8r", true).unwrap(); + array.insert("_koi8u", true).unwrap(); + array.insert("_latin1", true).unwrap(); + array.insert("_latin2", true).unwrap(); + array.insert("_latin5", true).unwrap(); + array.insert("_latin7", true).unwrap(); + array.insert("_macce", true).unwrap(); + array.insert("_macroman", true).unwrap(); + array.insert("_sjis", true).unwrap(); + array.insert("_swe7", true).unwrap(); + array.insert("_tis620", true).unwrap(); + array.insert("_ucs2", true).unwrap(); + array.insert("_ujis", true).unwrap(); + array.insert("_utf16", true).unwrap(); + array.insert("_utf16le", true).unwrap(); + array.insert("_utf32", true).unwrap(); + array.insert("_utf8", true).unwrap(); + array.insert("_utf8mb3", true).unwrap(); + array.insert("_utf8mb4", true).unwrap(); + freeze_array(&mut array); + array +} + +pub const SCALAR_INT_CONSTANTS: &[(&str, i64)] = &[ + ("SQL_MODE_HIGH_NOT_PRECEDENCE", 1i64), + ("SQL_MODE_PIPES_AS_CONCAT", 2i64), + ("SQL_MODE_IGNORE_SPACE", 4i64), + ("SQL_MODE_NO_BACKSLASH_ESCAPES", 8i64), + ("ACCESSIBLE_SYMBOL", 1i64), + ("ACCOUNT_SYMBOL", 2i64), + ("ACTION_SYMBOL", 3i64), + ("ADD_SYMBOL", 4i64), + ("ADDDATE_SYMBOL", 5i64), + ("AFTER_SYMBOL", 6i64), + ("AGAINST_SYMBOL", 7i64), + ("AGGREGATE_SYMBOL", 8i64), + ("ALGORITHM_SYMBOL", 9i64), + ("ALL_SYMBOL", 10i64), + ("ALTER_SYMBOL", 11i64), + ("ALWAYS_SYMBOL", 12i64), + ("ANALYSE_SYMBOL", 13i64), + ("ANALYZE_SYMBOL", 14i64), + ("AND_SYMBOL", 15i64), + ("ANY_SYMBOL", 16i64), + ("AS_SYMBOL", 17i64), + ("ASC_SYMBOL", 18i64), + ("ASCII_SYMBOL", 19i64), + ("ASENSITIVE_SYMBOL", 20i64), + ("AT_SYMBOL", 21i64), + ("AUTHORS_SYMBOL", 22i64), + ("AUTOEXTEND_SIZE_SYMBOL", 23i64), + ("AUTO_INCREMENT_SYMBOL", 24i64), + ("AVG_ROW_LENGTH_SYMBOL", 25i64), + ("AVG_SYMBOL", 26i64), + ("BACKUP_SYMBOL", 27i64), + ("BEFORE_SYMBOL", 28i64), + ("BEGIN_SYMBOL", 29i64), + ("BETWEEN_SYMBOL", 30i64), + ("BIGINT_SYMBOL", 31i64), + ("BINARY_SYMBOL", 32i64), + ("BINLOG_SYMBOL", 33i64), + ("BIN_NUM_SYMBOL", 34i64), + ("BIT_AND_SYMBOL", 35i64), + ("BIT_OR_SYMBOL", 36i64), + ("BIT_SYMBOL", 37i64), + ("BIT_XOR_SYMBOL", 38i64), + ("BLOB_SYMBOL", 39i64), + ("BLOCK_SYMBOL", 40i64), + ("BOOLEAN_SYMBOL", 41i64), + ("BOOL_SYMBOL", 42i64), + ("BOTH_SYMBOL", 43i64), + ("BTREE_SYMBOL", 44i64), + ("BY_SYMBOL", 45i64), + ("BYTE_SYMBOL", 46i64), + ("CACHE_SYMBOL", 47i64), + ("CALL_SYMBOL", 48i64), + ("CASCADE_SYMBOL", 49i64), + ("CASCADED_SYMBOL", 50i64), + ("CASE_SYMBOL", 51i64), + ("CAST_SYMBOL", 52i64), + ("CATALOG_NAME_SYMBOL", 53i64), + ("CHAIN_SYMBOL", 54i64), + ("CHANGE_SYMBOL", 55i64), + ("CHANGED_SYMBOL", 56i64), + ("CHANNEL_SYMBOL", 57i64), + ("CHARSET_SYMBOL", 58i64), + ("CHARACTER_SYMBOL", 59i64), + ("CHAR_SYMBOL", 60i64), + ("CHECKSUM_SYMBOL", 61i64), + ("CHECK_SYMBOL", 62i64), + ("CIPHER_SYMBOL", 63i64), + ("CLASS_ORIGIN_SYMBOL", 64i64), + ("CLIENT_SYMBOL", 65i64), + ("CLOSE_SYMBOL", 66i64), + ("COALESCE_SYMBOL", 67i64), + ("CODE_SYMBOL", 68i64), + ("COLLATE_SYMBOL", 69i64), + ("COLLATION_SYMBOL", 70i64), + ("COLUMNS_SYMBOL", 71i64), + ("COLUMN_SYMBOL", 72i64), + ("COLUMN_NAME_SYMBOL", 73i64), + ("COLUMN_FORMAT_SYMBOL", 74i64), + ("COMMENT_SYMBOL", 75i64), + ("COMMITTED_SYMBOL", 76i64), + ("COMMIT_SYMBOL", 77i64), + ("COMPACT_SYMBOL", 78i64), + ("COMPLETION_SYMBOL", 79i64), + ("COMPRESSED_SYMBOL", 80i64), + ("COMPRESSION_SYMBOL", 81i64), + ("CONCURRENT_SYMBOL", 82i64), + ("CONDITION_SYMBOL", 83i64), + ("CONNECTION_SYMBOL", 84i64), + ("CONSISTENT_SYMBOL", 85i64), + ("CONSTRAINT_SYMBOL", 86i64), + ("CONSTRAINT_CATALOG_SYMBOL", 87i64), + ("CONSTRAINT_NAME_SYMBOL", 88i64), + ("CONSTRAINT_SCHEMA_SYMBOL", 89i64), + ("CONTAINS_SYMBOL", 90i64), + ("CONTEXT_SYMBOL", 91i64), + ("CONTINUE_SYMBOL", 92i64), + ("CONTRIBUTORS_SYMBOL", 93i64), + ("CONVERT_SYMBOL", 94i64), + ("COUNT_SYMBOL", 95i64), + ("CPU_SYMBOL", 96i64), + ("CREATE_SYMBOL", 97i64), + ("CROSS_SYMBOL", 98i64), + ("CUBE_SYMBOL", 99i64), + ("CURDATE_SYMBOL", 100i64), + ("CURRENT_SYMBOL", 101i64), + ("CURRENT_DATE_SYMBOL", 102i64), + ("CURRENT_TIME_SYMBOL", 103i64), + ("CURRENT_TIMESTAMP_SYMBOL", 104i64), + ("CURRENT_USER_SYMBOL", 105i64), + ("CURSOR_SYMBOL", 106i64), + ("CURSOR_NAME_SYMBOL", 107i64), + ("CURTIME_SYMBOL", 108i64), + ("DATABASE_SYMBOL", 109i64), + ("DATABASES_SYMBOL", 110i64), + ("DATAFILE_SYMBOL", 111i64), + ("DATA_SYMBOL", 112i64), + ("DATETIME_SYMBOL", 113i64), + ("DATE_ADD_SYMBOL", 114i64), + ("DATE_SUB_SYMBOL", 115i64), + ("DATE_SYMBOL", 116i64), + ("DAYOFMONTH_SYMBOL", 117i64), + ("DAY_HOUR_SYMBOL", 118i64), + ("DAY_MICROSECOND_SYMBOL", 119i64), + ("DAY_MINUTE_SYMBOL", 120i64), + ("DAY_SECOND_SYMBOL", 121i64), + ("DAY_SYMBOL", 122i64), + ("DEALLOCATE_SYMBOL", 123i64), + ("DEC_SYMBOL", 124i64), + ("DECIMAL_NUM_SYMBOL", 125i64), + ("DECIMAL_SYMBOL", 126i64), + ("DECLARE_SYMBOL", 127i64), + ("DEFAULT_SYMBOL", 128i64), + ("DEFAULT_AUTH_SYMBOL", 129i64), + ("DEFINER_SYMBOL", 130i64), + ("DELAYED_SYMBOL", 131i64), + ("DELAY_KEY_WRITE_SYMBOL", 132i64), + ("DELETE_SYMBOL", 133i64), + ("DESC_SYMBOL", 134i64), + ("DESCRIBE_SYMBOL", 135i64), + ("DES_KEY_FILE_SYMBOL", 136i64), + ("DETERMINISTIC_SYMBOL", 137i64), + ("DIAGNOSTICS_SYMBOL", 138i64), + ("DIRECTORY_SYMBOL", 139i64), + ("DISABLE_SYMBOL", 140i64), + ("DISCARD_SYMBOL", 141i64), + ("DISK_SYMBOL", 142i64), + ("DISTINCT_SYMBOL", 143i64), + ("DISTINCTROW_SYMBOL", 144i64), + ("DIV_SYMBOL", 145i64), + ("DOUBLE_SYMBOL", 146i64), + ("DO_SYMBOL", 147i64), + ("DROP_SYMBOL", 148i64), + ("DUAL_SYMBOL", 149i64), + ("DUMPFILE_SYMBOL", 150i64), + ("DUPLICATE_SYMBOL", 151i64), + ("DYNAMIC_SYMBOL", 152i64), + ("EACH_SYMBOL", 153i64), + ("ELSE_SYMBOL", 154i64), + ("ELSEIF_SYMBOL", 155i64), + ("ENABLE_SYMBOL", 156i64), + ("ENCLOSED_SYMBOL", 157i64), + ("ENCRYPTION_SYMBOL", 158i64), + ("END_SYMBOL", 159i64), + ("ENDS_SYMBOL", 160i64), + ("END_OF_INPUT_SYMBOL", 161i64), + ("ENGINES_SYMBOL", 162i64), + ("ENGINE_SYMBOL", 163i64), + ("ENUM_SYMBOL", 164i64), + ("ERROR_SYMBOL", 165i64), + ("ERRORS_SYMBOL", 166i64), + ("ESCAPED_SYMBOL", 167i64), + ("ESCAPE_SYMBOL", 168i64), + ("EVENTS_SYMBOL", 169i64), + ("EVENT_SYMBOL", 170i64), + ("EVERY_SYMBOL", 171i64), + ("EXCHANGE_SYMBOL", 172i64), + ("EXECUTE_SYMBOL", 173i64), + ("EXISTS_SYMBOL", 174i64), + ("EXIT_SYMBOL", 175i64), + ("EXPANSION_SYMBOL", 176i64), + ("EXPIRE_SYMBOL", 177i64), + ("EXPLAIN_SYMBOL", 178i64), + ("EXPORT_SYMBOL", 179i64), + ("EXTENDED_SYMBOL", 180i64), + ("EXTENT_SIZE_SYMBOL", 181i64), + ("EXTRACT_SYMBOL", 182i64), + ("FALSE_SYMBOL", 183i64), + ("FAST_SYMBOL", 184i64), + ("FAULTS_SYMBOL", 185i64), + ("FETCH_SYMBOL", 186i64), + ("FIELDS_SYMBOL", 187i64), + ("FILE_SYMBOL", 188i64), + ("FILE_BLOCK_SIZE_SYMBOL", 189i64), + ("FILTER_SYMBOL", 190i64), + ("FIRST_SYMBOL", 191i64), + ("FIXED_SYMBOL", 192i64), + ("FLOAT4_SYMBOL", 193i64), + ("FLOAT8_SYMBOL", 194i64), + ("FLOAT_SYMBOL", 195i64), + ("FLUSH_SYMBOL", 196i64), + ("FOLLOWS_SYMBOL", 197i64), + ("FORCE_SYMBOL", 198i64), + ("FOREIGN_SYMBOL", 199i64), + ("FOR_SYMBOL", 200i64), + ("FORMAT_SYMBOL", 201i64), + ("FOUND_SYMBOL", 202i64), + ("FROM_SYMBOL", 203i64), + ("FULL_SYMBOL", 204i64), + ("FULLTEXT_SYMBOL", 205i64), + ("FUNCTION_SYMBOL", 206i64), + ("GET_SYMBOL", 207i64), + ("GENERAL_SYMBOL", 208i64), + ("GENERATED_SYMBOL", 209i64), + ("GROUP_REPLICATION_SYMBOL", 210i64), + ("GEOMETRYCOLLECTION_SYMBOL", 211i64), + ("GEOMETRY_SYMBOL", 212i64), + ("GET_FORMAT_SYMBOL", 213i64), + ("GLOBAL_SYMBOL", 214i64), + ("GRANT_SYMBOL", 215i64), + ("GRANTS_SYMBOL", 216i64), + ("GROUP_SYMBOL", 217i64), + ("GROUP_CONCAT_SYMBOL", 218i64), + ("HANDLER_SYMBOL", 219i64), + ("HASH_SYMBOL", 220i64), + ("HAVING_SYMBOL", 221i64), + ("HELP_SYMBOL", 222i64), + ("HIGH_PRIORITY_SYMBOL", 223i64), + ("HOST_SYMBOL", 224i64), + ("HOSTS_SYMBOL", 225i64), + ("HOUR_MICROSECOND_SYMBOL", 226i64), + ("HOUR_MINUTE_SYMBOL", 227i64), + ("HOUR_SECOND_SYMBOL", 228i64), + ("HOUR_SYMBOL", 229i64), + ("IDENTIFIED_SYMBOL", 230i64), + ("IF_SYMBOL", 231i64), + ("IGNORE_SYMBOL", 232i64), + ("IGNORE_SERVER_IDS_SYMBOL", 233i64), + ("IMPORT_SYMBOL", 234i64), + ("INDEXES_SYMBOL", 235i64), + ("INDEX_SYMBOL", 236i64), + ("INFILE_SYMBOL", 237i64), + ("INITIAL_SIZE_SYMBOL", 238i64), + ("INNER_SYMBOL", 239i64), + ("INOUT_SYMBOL", 240i64), + ("INSENSITIVE_SYMBOL", 241i64), + ("INSERT_SYMBOL", 242i64), + ("INSERT_METHOD_SYMBOL", 243i64), + ("INSTANCE_SYMBOL", 244i64), + ("INSTALL_SYMBOL", 245i64), + ("INTEGER_SYMBOL", 246i64), + ("INTERVAL_SYMBOL", 247i64), + ("INTO_SYMBOL", 248i64), + ("INT_SYMBOL", 249i64), + ("INVOKER_SYMBOL", 250i64), + ("IN_SYMBOL", 251i64), + ("IO_AFTER_GTIDS_SYMBOL", 252i64), + ("IO_BEFORE_GTIDS_SYMBOL", 253i64), + ("IO_THREAD_SYMBOL", 254i64), + ("IO_SYMBOL", 255i64), + ("IPC_SYMBOL", 256i64), + ("IS_SYMBOL", 257i64), + ("ISOLATION_SYMBOL", 258i64), + ("ISSUER_SYMBOL", 259i64), + ("ITERATE_SYMBOL", 260i64), + ("JOIN_SYMBOL", 261i64), + ("JSON_SYMBOL", 262i64), + ("KEYS_SYMBOL", 263i64), + ("KEY_BLOCK_SIZE_SYMBOL", 264i64), + ("KEY_SYMBOL", 265i64), + ("KILL_SYMBOL", 266i64), + ("LANGUAGE_SYMBOL", 267i64), + ("LAST_SYMBOL", 268i64), + ("LEADING_SYMBOL", 269i64), + ("LEAVES_SYMBOL", 270i64), + ("LEAVE_SYMBOL", 271i64), + ("LEFT_SYMBOL", 272i64), + ("LESS_SYMBOL", 273i64), + ("LEVEL_SYMBOL", 274i64), + ("LIKE_SYMBOL", 275i64), + ("LIMIT_SYMBOL", 276i64), + ("LINEAR_SYMBOL", 277i64), + ("LINES_SYMBOL", 278i64), + ("LINESTRING_SYMBOL", 279i64), + ("LIST_SYMBOL", 280i64), + ("LOAD_SYMBOL", 281i64), + ("LOCALTIME_SYMBOL", 282i64), + ("LOCALTIMESTAMP_SYMBOL", 283i64), + ("LOCAL_SYMBOL", 284i64), + ("LOCATOR_SYMBOL", 285i64), + ("LOCKS_SYMBOL", 286i64), + ("LOCK_SYMBOL", 287i64), + ("LOGFILE_SYMBOL", 288i64), + ("LOGS_SYMBOL", 289i64), + ("LONGBLOB_SYMBOL", 290i64), + ("LONGTEXT_SYMBOL", 291i64), + ("LONG_NUM_SYMBOL", 292i64), + ("LONG_SYMBOL", 293i64), + ("LOOP_SYMBOL", 294i64), + ("LOW_PRIORITY_SYMBOL", 295i64), + ("MASTER_AUTO_POSITION_SYMBOL", 296i64), + ("MASTER_BIND_SYMBOL", 297i64), + ("MASTER_CONNECT_RETRY_SYMBOL", 298i64), + ("MASTER_DELAY_SYMBOL", 299i64), + ("MASTER_HOST_SYMBOL", 300i64), + ("MASTER_LOG_FILE_SYMBOL", 301i64), + ("MASTER_LOG_POS_SYMBOL", 302i64), + ("MASTER_PASSWORD_SYMBOL", 303i64), + ("MASTER_PORT_SYMBOL", 304i64), + ("MASTER_RETRY_COUNT_SYMBOL", 305i64), + ("MASTER_SERVER_ID_SYMBOL", 306i64), + ("MASTER_SSL_CAPATH_SYMBOL", 307i64), + ("MASTER_SSL_CA_SYMBOL", 308i64), + ("MASTER_SSL_CERT_SYMBOL", 309i64), + ("MASTER_SSL_CIPHER_SYMBOL", 310i64), + ("MASTER_SSL_CRL_SYMBOL", 311i64), + ("MASTER_SSL_CRLPATH_SYMBOL", 312i64), + ("MASTER_SSL_KEY_SYMBOL", 313i64), + ("MASTER_SSL_SYMBOL", 314i64), + ("MASTER_SSL_VERIFY_SERVER_CERT_SYMBOL", 315i64), + ("MASTER_SYMBOL", 316i64), + ("MASTER_TLS_VERSION_SYMBOL", 317i64), + ("MASTER_USER_SYMBOL", 318i64), + ("MASTER_HEARTBEAT_PERIOD_SYMBOL", 319i64), + ("MATCH_SYMBOL", 320i64), + ("MAX_CONNECTIONS_PER_HOUR_SYMBOL", 321i64), + ("MAX_QUERIES_PER_HOUR_SYMBOL", 322i64), + ("MAX_ROWS_SYMBOL", 323i64), + ("MAX_SIZE_SYMBOL", 324i64), + ("MAX_STATEMENT_TIME_SYMBOL", 325i64), + ("MAX_SYMBOL", 326i64), + ("MAX_UPDATES_PER_HOUR_SYMBOL", 327i64), + ("MAX_USER_CONNECTIONS_SYMBOL", 328i64), + ("MAXVALUE_SYMBOL", 329i64), + ("MEDIUMBLOB_SYMBOL", 330i64), + ("MEDIUMINT_SYMBOL", 331i64), + ("MEDIUMTEXT_SYMBOL", 332i64), + ("MEDIUM_SYMBOL", 333i64), + ("MEMORY_SYMBOL", 334i64), + ("MERGE_SYMBOL", 335i64), + ("MESSAGE_TEXT_SYMBOL", 336i64), + ("MICROSECOND_SYMBOL", 337i64), + ("MID_SYMBOL", 338i64), + ("MIDDLEINT_SYMBOL", 339i64), + ("MIGRATE_SYMBOL", 340i64), + ("MINUTE_MICROSECOND_SYMBOL", 341i64), + ("MINUTE_SECOND_SYMBOL", 342i64), + ("MINUTE_SYMBOL", 343i64), + ("MIN_ROWS_SYMBOL", 344i64), + ("MIN_SYMBOL", 345i64), + ("MODE_SYMBOL", 346i64), + ("MODIFIES_SYMBOL", 347i64), + ("MODIFY_SYMBOL", 348i64), + ("MOD_SYMBOL", 349i64), + ("MONTH_SYMBOL", 350i64), + ("MULTILINESTRING_SYMBOL", 351i64), + ("MULTIPOINT_SYMBOL", 352i64), + ("MULTIPOLYGON_SYMBOL", 353i64), + ("MUTEX_SYMBOL", 354i64), + ("MYSQL_ERRNO_SYMBOL", 355i64), + ("NAMES_SYMBOL", 356i64), + ("NAME_SYMBOL", 357i64), + ("NATIONAL_SYMBOL", 358i64), + ("NATURAL_SYMBOL", 359i64), + ("NCHAR_STRING_SYMBOL", 360i64), + ("NCHAR_SYMBOL", 361i64), + ("NDB_SYMBOL", 362i64), + ("NDBCLUSTER_SYMBOL", 363i64), + ("NEG_SYMBOL", 364i64), + ("NEVER_SYMBOL", 365i64), + ("NEW_SYMBOL", 366i64), + ("NEXT_SYMBOL", 367i64), + ("NODEGROUP_SYMBOL", 368i64), + ("NONE_SYMBOL", 369i64), + ("NONBLOCKING_SYMBOL", 370i64), + ("NOT_SYMBOL", 371i64), + ("NOW_SYMBOL", 372i64), + ("NO_SYMBOL", 373i64), + ("NO_WAIT_SYMBOL", 374i64), + ("NO_WRITE_TO_BINLOG_SYMBOL", 375i64), + ("NULL_SYMBOL", 376i64), + ("NUMBER_SYMBOL", 377i64), + ("NUMERIC_SYMBOL", 378i64), + ("NVARCHAR_SYMBOL", 379i64), + ("OFFLINE_SYMBOL", 380i64), + ("OFFSET_SYMBOL", 381i64), + ("OLD_PASSWORD_SYMBOL", 382i64), + ("ON_SYMBOL", 383i64), + ("ONE_SYMBOL", 384i64), + ("ONLINE_SYMBOL", 385i64), + ("ONLY_SYMBOL", 386i64), + ("OPEN_SYMBOL", 387i64), + ("OPTIMIZE_SYMBOL", 388i64), + ("OPTIMIZER_COSTS_SYMBOL", 389i64), + ("OPTIONS_SYMBOL", 390i64), + ("OPTION_SYMBOL", 391i64), + ("OPTIONALLY_SYMBOL", 392i64), + ("ORDER_SYMBOL", 393i64), + ("OR_SYMBOL", 394i64), + ("OUTER_SYMBOL", 395i64), + ("OUTFILE_SYMBOL", 396i64), + ("OUT_SYMBOL", 397i64), + ("OWNER_SYMBOL", 398i64), + ("PACK_KEYS_SYMBOL", 399i64), + ("PAGE_SYMBOL", 400i64), + ("PARSER_SYMBOL", 401i64), + ("PARTIAL_SYMBOL", 402i64), + ("PARTITIONING_SYMBOL", 403i64), + ("PARTITIONS_SYMBOL", 404i64), + ("PARTITION_SYMBOL", 405i64), + ("PASSWORD_SYMBOL", 406i64), + ("PHASE_SYMBOL", 407i64), + ("PLUGINS_SYMBOL", 408i64), + ("PLUGIN_DIR_SYMBOL", 409i64), + ("PLUGIN_SYMBOL", 410i64), + ("POINT_SYMBOL", 411i64), + ("POLYGON_SYMBOL", 412i64), + ("PORT_SYMBOL", 413i64), + ("POSITION_SYMBOL", 414i64), + ("PRECEDES_SYMBOL", 415i64), + ("PRECISION_SYMBOL", 416i64), + ("PREPARE_SYMBOL", 417i64), + ("PRESERVE_SYMBOL", 418i64), + ("PREV_SYMBOL", 419i64), + ("PRIMARY_SYMBOL", 420i64), + ("PRIVILEGES_SYMBOL", 421i64), + ("PROCEDURE_SYMBOL", 422i64), + ("PROCESS_SYMBOL", 423i64), + ("PROCESSLIST_SYMBOL", 424i64), + ("PROFILE_SYMBOL", 425i64), + ("PROFILES_SYMBOL", 426i64), + ("PROXY_SYMBOL", 427i64), + ("PURGE_SYMBOL", 428i64), + ("QUARTER_SYMBOL", 429i64), + ("QUERY_SYMBOL", 430i64), + ("QUICK_SYMBOL", 431i64), + ("RANGE_SYMBOL", 432i64), + ("READS_SYMBOL", 433i64), + ("READ_ONLY_SYMBOL", 434i64), + ("READ_SYMBOL", 435i64), + ("READ_WRITE_SYMBOL", 436i64), + ("REAL_SYMBOL", 437i64), + ("REBUILD_SYMBOL", 438i64), + ("RECOVER_SYMBOL", 439i64), + ("REDOFILE_SYMBOL", 440i64), + ("REDO_BUFFER_SIZE_SYMBOL", 441i64), + ("REDUNDANT_SYMBOL", 442i64), + ("REFERENCES_SYMBOL", 443i64), + ("REGEXP_SYMBOL", 444i64), + ("RELAY_SYMBOL", 445i64), + ("RELAYLOG_SYMBOL", 446i64), + ("RELAY_LOG_FILE_SYMBOL", 447i64), + ("RELAY_LOG_POS_SYMBOL", 448i64), + ("RELAY_THREAD_SYMBOL", 449i64), + ("RELEASE_SYMBOL", 450i64), + ("RELOAD_SYMBOL", 451i64), + ("REMOVE_SYMBOL", 452i64), + ("RENAME_SYMBOL", 453i64), + ("REORGANIZE_SYMBOL", 454i64), + ("REPAIR_SYMBOL", 455i64), + ("REPEATABLE_SYMBOL", 456i64), + ("REPEAT_SYMBOL", 457i64), + ("REPLACE_SYMBOL", 458i64), + ("REPLICATION_SYMBOL", 459i64), + ("REPLICATE_DO_DB_SYMBOL", 460i64), + ("REPLICATE_IGNORE_DB_SYMBOL", 461i64), + ("REPLICATE_DO_TABLE_SYMBOL", 462i64), + ("REPLICATE_IGNORE_TABLE_SYMBOL", 463i64), + ("REPLICATE_WILD_DO_TABLE_SYMBOL", 464i64), + ("REPLICATE_WILD_IGNORE_TABLE_SYMBOL", 465i64), + ("REPLICATE_REWRITE_DB_SYMBOL", 466i64), + ("REQUIRE_SYMBOL", 467i64), + ("RESET_SYMBOL", 468i64), + ("RESIGNAL_SYMBOL", 469i64), + ("RESTORE_SYMBOL", 470i64), + ("RESTRICT_SYMBOL", 471i64), + ("RESUME_SYMBOL", 472i64), + ("RETURNED_SQLSTATE_SYMBOL", 473i64), + ("RETURNS_SYMBOL", 474i64), + ("RETURN_SYMBOL", 475i64), + ("REVERSE_SYMBOL", 476i64), + ("REVOKE_SYMBOL", 477i64), + ("RIGHT_SYMBOL", 478i64), + ("RLIKE_SYMBOL", 479i64), + ("ROLLBACK_SYMBOL", 480i64), + ("ROLLUP_SYMBOL", 481i64), + ("ROTATE_SYMBOL", 482i64), + ("ROUTINE_SYMBOL", 483i64), + ("ROWS_SYMBOL", 484i64), + ("ROW_COUNT_SYMBOL", 485i64), + ("ROW_FORMAT_SYMBOL", 486i64), + ("ROW_SYMBOL", 487i64), + ("RTREE_SYMBOL", 488i64), + ("SAVEPOINT_SYMBOL", 489i64), + ("SCHEDULE_SYMBOL", 490i64), + ("SCHEMA_SYMBOL", 491i64), + ("SCHEMA_NAME_SYMBOL", 492i64), + ("SCHEMAS_SYMBOL", 493i64), + ("SECOND_MICROSECOND_SYMBOL", 494i64), + ("SECOND_SYMBOL", 495i64), + ("SECURITY_SYMBOL", 496i64), + ("SELECT_SYMBOL", 497i64), + ("SENSITIVE_SYMBOL", 498i64), + ("SEPARATOR_SYMBOL", 499i64), + ("SERIALIZABLE_SYMBOL", 500i64), + ("SERIAL_SYMBOL", 501i64), + ("SESSION_SYMBOL", 502i64), + ("SERVER_SYMBOL", 503i64), + ("SERVER_OPTIONS_SYMBOL", 504i64), + ("SESSION_USER_SYMBOL", 505i64), + ("SET_SYMBOL", 506i64), + ("SET_VAR_SYMBOL", 507i64), + ("SHARE_SYMBOL", 508i64), + ("SHOW_SYMBOL", 509i64), + ("SHUTDOWN_SYMBOL", 510i64), + ("SIGNAL_SYMBOL", 511i64), + ("SIGNED_SYMBOL", 512i64), + ("SIMPLE_SYMBOL", 513i64), + ("SLAVE_SYMBOL", 514i64), + ("SLOW_SYMBOL", 515i64), + ("SMALLINT_SYMBOL", 516i64), + ("SNAPSHOT_SYMBOL", 517i64), + ("SOME_SYMBOL", 518i64), + ("SOCKET_SYMBOL", 519i64), + ("SONAME_SYMBOL", 520i64), + ("SOUNDS_SYMBOL", 521i64), + ("SOURCE_SYMBOL", 522i64), + ("SPATIAL_SYMBOL", 523i64), + ("SPECIFIC_SYMBOL", 524i64), + ("SQLEXCEPTION_SYMBOL", 525i64), + ("SQLSTATE_SYMBOL", 526i64), + ("SQLWARNING_SYMBOL", 527i64), + ("SQL_AFTER_GTIDS_SYMBOL", 528i64), + ("SQL_AFTER_MTS_GAPS_SYMBOL", 529i64), + ("SQL_BEFORE_GTIDS_SYMBOL", 530i64), + ("SQL_BIG_RESULT_SYMBOL", 531i64), + ("SQL_BUFFER_RESULT_SYMBOL", 532i64), + ("SQL_CACHE_SYMBOL", 533i64), + ("SQL_CALC_FOUND_ROWS_SYMBOL", 534i64), + ("SQL_NO_CACHE_SYMBOL", 535i64), + ("SQL_SMALL_RESULT_SYMBOL", 536i64), + ("SQL_SYMBOL", 537i64), + ("SQL_THREAD_SYMBOL", 538i64), + ("SSL_SYMBOL", 539i64), + ("STACKED_SYMBOL", 540i64), + ("STARTING_SYMBOL", 541i64), + ("STARTS_SYMBOL", 542i64), + ("START_SYMBOL", 543i64), + ("STATS_AUTO_RECALC_SYMBOL", 544i64), + ("STATS_PERSISTENT_SYMBOL", 545i64), + ("STATS_SAMPLE_PAGES_SYMBOL", 546i64), + ("STATUS_SYMBOL", 547i64), + ("STDDEV_SAMP_SYMBOL", 548i64), + ("STDDEV_SYMBOL", 549i64), + ("STDDEV_POP_SYMBOL", 550i64), + ("STD_SYMBOL", 551i64), + ("STOP_SYMBOL", 552i64), + ("STORAGE_SYMBOL", 553i64), + ("STORED_SYMBOL", 554i64), + ("STRAIGHT_JOIN_SYMBOL", 555i64), + ("STRING_SYMBOL", 556i64), + ("SUBCLASS_ORIGIN_SYMBOL", 557i64), + ("SUBDATE_SYMBOL", 558i64), + ("SUBJECT_SYMBOL", 559i64), + ("SUBPARTITIONS_SYMBOL", 560i64), + ("SUBPARTITION_SYMBOL", 561i64), + ("SUBSTR_SYMBOL", 562i64), + ("SUBSTRING_SYMBOL", 563i64), + ("SUM_SYMBOL", 564i64), + ("SUPER_SYMBOL", 565i64), + ("SUSPEND_SYMBOL", 566i64), + ("SWAPS_SYMBOL", 567i64), + ("SWITCHES_SYMBOL", 568i64), + ("SYSDATE_SYMBOL", 569i64), + ("SYSTEM_USER_SYMBOL", 570i64), + ("TABLES_SYMBOL", 571i64), + ("TABLESPACE_SYMBOL", 572i64), + ("TABLE_REF_PRIORITY_SYMBOL", 573i64), + ("TABLE_SYMBOL", 574i64), + ("TABLE_CHECKSUM_SYMBOL", 575i64), + ("TABLE_NAME_SYMBOL", 576i64), + ("TEMPORARY_SYMBOL", 577i64), + ("TEMPTABLE_SYMBOL", 578i64), + ("TERMINATED_SYMBOL", 579i64), + ("TEXT_SYMBOL", 580i64), + ("THAN_SYMBOL", 581i64), + ("THEN_SYMBOL", 582i64), + ("TIMESTAMP_SYMBOL", 583i64), + ("TIMESTAMP_ADD_SYMBOL", 584i64), + ("TIMESTAMP_DIFF_SYMBOL", 585i64), + ("TIME_SYMBOL", 586i64), + ("TINYBLOB_SYMBOL", 587i64), + ("TINYINT_SYMBOL", 588i64), + ("TINYTEXT_SYMBOL", 589i64), + ("TO_SYMBOL", 590i64), + ("TRAILING_SYMBOL", 591i64), + ("TRANSACTION_SYMBOL", 592i64), + ("TRIGGERS_SYMBOL", 593i64), + ("TRIGGER_SYMBOL", 594i64), + ("TRIM_SYMBOL", 595i64), + ("TRUE_SYMBOL", 596i64), + ("TRUNCATE_SYMBOL", 597i64), + ("TYPES_SYMBOL", 598i64), + ("TYPE_SYMBOL", 599i64), + ("UDF_RETURNS_SYMBOL", 600i64), + ("UNCOMMITTED_SYMBOL", 601i64), + ("UNDEFINED_SYMBOL", 602i64), + ("UNDOFILE_SYMBOL", 603i64), + ("UNDO_BUFFER_SIZE_SYMBOL", 604i64), + ("UNDO_SYMBOL", 605i64), + ("UNICODE_SYMBOL", 606i64), + ("UNINSTALL_SYMBOL", 607i64), + ("UNION_SYMBOL", 608i64), + ("UNIQUE_SYMBOL", 609i64), + ("UNKNOWN_SYMBOL", 610i64), + ("UNLOCK_SYMBOL", 611i64), + ("UNSIGNED_SYMBOL", 612i64), + ("UNTIL_SYMBOL", 613i64), + ("UPDATE_SYMBOL", 614i64), + ("UPGRADE_SYMBOL", 615i64), + ("USAGE_SYMBOL", 616i64), + ("USER_RESOURCES_SYMBOL", 617i64), + ("USER_SYMBOL", 618i64), + ("USE_FRM_SYMBOL", 619i64), + ("USE_SYMBOL", 620i64), + ("USING_SYMBOL", 621i64), + ("UTC_DATE_SYMBOL", 622i64), + ("UTC_TIMESTAMP_SYMBOL", 623i64), + ("UTC_TIME_SYMBOL", 624i64), + ("VALIDATION_SYMBOL", 625i64), + ("VALUES_SYMBOL", 626i64), + ("VALUE_SYMBOL", 627i64), + ("VARBINARY_SYMBOL", 628i64), + ("VARCHAR_SYMBOL", 629i64), + ("VARCHARACTER_SYMBOL", 630i64), + ("VARIABLES_SYMBOL", 631i64), + ("VARIANCE_SYMBOL", 632i64), + ("VARYING_SYMBOL", 633i64), + ("VAR_POP_SYMBOL", 634i64), + ("VAR_SAMP_SYMBOL", 635i64), + ("VIEW_SYMBOL", 636i64), + ("VIRTUAL_SYMBOL", 637i64), + ("WAIT_SYMBOL", 638i64), + ("WARNINGS_SYMBOL", 639i64), + ("WEEK_SYMBOL", 640i64), + ("WEIGHT_STRING_SYMBOL", 641i64), + ("WHEN_SYMBOL", 642i64), + ("WHERE_SYMBOL", 643i64), + ("WHILE_SYMBOL", 644i64), + ("WITH_SYMBOL", 645i64), + ("WITHOUT_SYMBOL", 646i64), + ("WORK_SYMBOL", 647i64), + ("WRAPPER_SYMBOL", 648i64), + ("WRITE_SYMBOL", 649i64), + ("X509_SYMBOL", 650i64), + ("XA_SYMBOL", 651i64), + ("XID_SYMBOL", 652i64), + ("XML_SYMBOL", 653i64), + ("XOR_SYMBOL", 654i64), + ("YEAR_MONTH_SYMBOL", 655i64), + ("YEAR_SYMBOL", 656i64), + ("ZEROFILL_SYMBOL", 657i64), + ("PERSIST_SYMBOL", 658i64), + ("ROLE_SYMBOL", 659i64), + ("ADMIN_SYMBOL", 660i64), + ("INVISIBLE_SYMBOL", 661i64), + ("VISIBLE_SYMBOL", 662i64), + ("EXCEPT_SYMBOL", 663i64), + ("COMPONENT_SYMBOL", 664i64), + ("RECURSIVE_SYMBOL", 665i64), + ("JSON_OBJECTAGG_SYMBOL", 666i64), + ("JSON_ARRAYAGG_SYMBOL", 667i64), + ("OF_SYMBOL", 668i64), + ("SKIP_SYMBOL", 669i64), + ("LOCKED_SYMBOL", 670i64), + ("NOWAIT_SYMBOL", 671i64), + ("GROUPING_SYMBOL", 672i64), + ("PERSIST_ONLY_SYMBOL", 673i64), + ("HISTOGRAM_SYMBOL", 674i64), + ("BUCKETS_SYMBOL", 675i64), + ("REMOTE_SYMBOL", 676i64), + ("CLONE_SYMBOL", 677i64), + ("CUME_DIST_SYMBOL", 678i64), + ("DENSE_RANK_SYMBOL", 679i64), + ("EXCLUDE_SYMBOL", 680i64), + ("FIRST_VALUE_SYMBOL", 681i64), + ("FOLLOWING_SYMBOL", 682i64), + ("GROUPS_SYMBOL", 683i64), + ("LAG_SYMBOL", 684i64), + ("LAST_VALUE_SYMBOL", 685i64), + ("LEAD_SYMBOL", 686i64), + ("NTH_VALUE_SYMBOL", 687i64), + ("NTILE_SYMBOL", 688i64), + ("NULLS_SYMBOL", 689i64), + ("OTHERS_SYMBOL", 690i64), + ("OVER_SYMBOL", 691i64), + ("PERCENT_RANK_SYMBOL", 692i64), + ("PRECEDING_SYMBOL", 693i64), + ("RANK_SYMBOL", 694i64), + ("RESPECT_SYMBOL", 695i64), + ("ROW_NUMBER_SYMBOL", 696i64), + ("TIES_SYMBOL", 697i64), + ("UNBOUNDED_SYMBOL", 698i64), + ("WINDOW_SYMBOL", 699i64), + ("EMPTY_SYMBOL", 700i64), + ("JSON_TABLE_SYMBOL", 701i64), + ("NESTED_SYMBOL", 702i64), + ("ORDINALITY_SYMBOL", 703i64), + ("PATH_SYMBOL", 704i64), + ("HISTORY_SYMBOL", 705i64), + ("REUSE_SYMBOL", 706i64), + ("SRID_SYMBOL", 707i64), + ("THREAD_PRIORITY_SYMBOL", 708i64), + ("RESOURCE_SYMBOL", 709i64), + ("SYSTEM_SYMBOL", 710i64), + ("VCPU_SYMBOL", 711i64), + ("MASTER_PUBLIC_KEY_PATH_SYMBOL", 712i64), + ("GET_MASTER_PUBLIC_KEY_SYMBOL", 713i64), + ("RESTART_SYMBOL", 714i64), + ("DEFINITION_SYMBOL", 715i64), + ("DESCRIPTION_SYMBOL", 716i64), + ("ORGANIZATION_SYMBOL", 717i64), + ("REFERENCE_SYMBOL", 718i64), + ("OPTIONAL_SYMBOL", 719i64), + ("SECONDARY_SYMBOL", 720i64), + ("SECONDARY_ENGINE_SYMBOL", 721i64), + ("SECONDARY_LOAD_SYMBOL", 722i64), + ("SECONDARY_UNLOAD_SYMBOL", 723i64), + ("ACTIVE_SYMBOL", 724i64), + ("INACTIVE_SYMBOL", 725i64), + ("LATERAL_SYMBOL", 726i64), + ("RETAIN_SYMBOL", 727i64), + ("OLD_SYMBOL", 728i64), + ("NETWORK_NAMESPACE_SYMBOL", 729i64), + ("ENFORCED_SYMBOL", 730i64), + ("ARRAY_SYMBOL", 731i64), + ("OJ_SYMBOL", 732i64), + ("MEMBER_SYMBOL", 733i64), + ("RANDOM_SYMBOL", 734i64), + ("MASTER_COMPRESSION_ALGORITHM_SYMBOL", 735i64), + ("MASTER_ZSTD_COMPRESSION_LEVEL_SYMBOL", 736i64), + ("PRIVILEGE_CHECKS_USER_SYMBOL", 737i64), + ("MASTER_TLS_CIPHERSUITES_SYMBOL", 738i64), + ("REQUIRE_ROW_FORMAT_SYMBOL", 739i64), + ("PASSWORD_LOCK_TIME_SYMBOL", 740i64), + ("FAILED_LOGIN_ATTEMPTS_SYMBOL", 741i64), + ("REQUIRE_TABLE_PRIMARY_KEY_CHECK_SYMBOL", 742i64), + ("STREAM_SYMBOL", 743i64), + ("OFF_SYMBOL", 744i64), + ("AT_AT_SIGN_SYMBOL", 745i64), + ("AT_SIGN_SYMBOL", 746i64), + ("CLOSE_CURLY_SYMBOL", 747i64), + ("CLOSE_PAR_SYMBOL", 748i64), + ("COLON_SYMBOL", 749i64), + ("COMMA_SYMBOL", 750i64), + ("DOT_SYMBOL", 751i64), + ("OPEN_CURLY_SYMBOL", 752i64), + ("OPEN_PAR_SYMBOL", 753i64), + ("PARAM_MARKER", 754i64), + ("SEMICOLON_SYMBOL", 755i64), + ("ASSIGN_OPERATOR", 756i64), + ("BITWISE_AND_OPERATOR", 757i64), + ("BITWISE_NOT_OPERATOR", 758i64), + ("BITWISE_OR_OPERATOR", 759i64), + ("BITWISE_XOR_OPERATOR", 760i64), + ("CONCAT_PIPES_SYMBOL", 761i64), + ("DIV_OPERATOR", 762i64), + ("EQUAL_OPERATOR", 763i64), + ("GREATER_OR_EQUAL_OPERATOR", 764i64), + ("GREATER_THAN_OPERATOR", 765i64), + ("JSON_SEPARATOR_SYMBOL", 766i64), + ("JSON_UNQUOTED_SEPARATOR_SYMBOL", 767i64), + ("LESS_OR_EQUAL_OPERATOR", 768i64), + ("LESS_THAN_OPERATOR", 769i64), + ("LOGICAL_AND_OPERATOR", 770i64), + ("LOGICAL_NOT_OPERATOR", 771i64), + ("LOGICAL_OR_OPERATOR", 772i64), + ("MINUS_OPERATOR", 773i64), + ("MOD_OPERATOR", 774i64), + ("MULT_OPERATOR", 775i64), + ("NOT_EQUAL_OPERATOR", 776i64), + ("NULL_SAFE_EQUAL_OPERATOR", 777i64), + ("PLUS_OPERATOR", 778i64), + ("SHIFT_LEFT_OPERATOR", 779i64), + ("SHIFT_RIGHT_OPERATOR", 780i64), + ("BACK_TICK_QUOTED_ID", 781i64), + ("BIN_NUMBER", 782i64), + ("DECIMAL_NUMBER", 783i64), + ("DOUBLE_QUOTED_TEXT", 784i64), + ("FLOAT_NUMBER", 785i64), + ("HEX_NUMBER", 786i64), + ("INT_NUMBER", 787i64), + ("LONG_NUMBER", 788i64), + ("NCHAR_TEXT", 789i64), + ("SINGLE_QUOTED_TEXT", 790i64), + ("ULONGLONG_NUMBER", 791i64), + ("AT_TEXT_SUFFIX", 792i64), + ("IDENTIFIER", 793i64), + ("UNDERSCORE_CHARSET", 794i64), + ("INT1_SYMBOL", 795i64), + ("INT2_SYMBOL", 796i64), + ("INT3_SYMBOL", 797i64), + ("INT4_SYMBOL", 798i64), + ("INT8_SYMBOL", 799i64), + ("NOT2_SYMBOL", 800i64), + ("NULL2_SYMBOL", 801i64), + ("SQL_TSI_DAY_SYMBOL", 802i64), + ("SQL_TSI_HOUR_SYMBOL", 803i64), + ("SQL_TSI_MICROSECOND_SYMBOL", 804i64), + ("SQL_TSI_MINUTE_SYMBOL", 805i64), + ("SQL_TSI_MONTH_SYMBOL", 806i64), + ("SQL_TSI_QUARTER_SYMBOL", 807i64), + ("SQL_TSI_SECOND_SYMBOL", 808i64), + ("SQL_TSI_WEEK_SYMBOL", 809i64), + ("SQL_TSI_YEAR_SYMBOL", 810i64), + ("INTERSECT_SYMBOL", 811i64), + ("ATTRIBUTE_SYMBOL", 812i64), + ("SOURCE_AUTO_POSITION_SYMBOL", 813i64), + ("SOURCE_BIND_SYMBOL", 814i64), + ("SOURCE_COMPRESSION_ALGORITHM_SYMBOL", 815i64), + ("SOURCE_CONNECT_RETRY_SYMBOL", 816i64), + ("SOURCE_CONNECTION_AUTO_FAILOVER_SYMBOL", 817i64), + ("SOURCE_DELAY_SYMBOL", 818i64), + ("SOURCE_HEARTBEAT_PERIOD_SYMBOL", 819i64), + ("SOURCE_HOST_SYMBOL", 820i64), + ("SOURCE_LOG_FILE_SYMBOL", 821i64), + ("SOURCE_LOG_POS_SYMBOL", 822i64), + ("SOURCE_PASSWORD_SYMBOL", 823i64), + ("SOURCE_PORT_SYMBOL", 824i64), + ("SOURCE_PUBLIC_KEY_PATH_SYMBOL", 825i64), + ("SOURCE_RETRY_COUNT_SYMBOL", 826i64), + ("SOURCE_SSL_SYMBOL", 827i64), + ("SOURCE_SSL_CA_SYMBOL", 828i64), + ("SOURCE_SSL_CAPATH_SYMBOL", 829i64), + ("SOURCE_SSL_CERT_SYMBOL", 830i64), + ("SOURCE_SSL_CIPHER_SYMBOL", 831i64), + ("SOURCE_SSL_CRL_SYMBOL", 832i64), + ("SOURCE_SSL_CRLPATH_SYMBOL", 833i64), + ("SOURCE_SSL_KEY_SYMBOL", 834i64), + ("SOURCE_SSL_VERIFY_SERVER_CERT_SYMBOL", 835i64), + ("SOURCE_TLS_CIPHERSUITES_SYMBOL", 836i64), + ("SOURCE_TLS_VERSION_SYMBOL", 837i64), + ("SOURCE_USER_SYMBOL", 838i64), + ("SOURCE_ZSTD_COMPRESSION_LEVEL_SYMBOL", 839i64), + ("GET_SOURCE_PUBLIC_KEY_SYMBOL", 840i64), + ("GTID_ONLY_SYMBOL", 841i64), + ("ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_SYMBOL", 842i64), + ("ZONE_SYMBOL", 843i64), + ("INNODB_SYMBOL", 844i64), + ("TLS_SYMBOL", 845i64), + ("REDO_LOG_SYMBOL", 846i64), + ("KEYRING_SYMBOL", 847i64), + ("ENGINE_ATTRIBUTE_SYMBOL", 848i64), + ("SECONDARY_ENGINE_ATTRIBUTE_SYMBOL", 849i64), + ("JSON_VALUE_SYMBOL", 850i64), + ("RETURNING_SYMBOL", 851i64), + ("GEOMCOLLECTION_SYMBOL", 852i64), + ("COMMENT", 900i64), + ("MYSQL_COMMENT_START", 901i64), + ("MYSQL_COMMENT_END", 902i64), + ("WHITESPACE", 0i64), + ("EOF", -1i64), +]; + +pub const SQL_MODE_HIGH_NOT_PRECEDENCE: i64 = 1i64; +pub const SQL_MODE_PIPES_AS_CONCAT: i64 = 2i64; +pub const SQL_MODE_IGNORE_SPACE: i64 = 4i64; +pub const SQL_MODE_NO_BACKSLASH_ESCAPES: i64 = 8i64; +pub const WHITESPACE_MASK: &str = " \t\n\r\x0c"; +pub const DIGIT_MASK: &str = "0123456789"; +pub const HEX_DIGIT_MASK: &str = "0123456789abcdefABCDEF"; +pub const ACCESSIBLE_SYMBOL: i64 = 1i64; +pub const ACCOUNT_SYMBOL: i64 = 2i64; +pub const ACTION_SYMBOL: i64 = 3i64; +pub const ADD_SYMBOL: i64 = 4i64; +pub const ADDDATE_SYMBOL: i64 = 5i64; +pub const AFTER_SYMBOL: i64 = 6i64; +pub const AGAINST_SYMBOL: i64 = 7i64; +pub const AGGREGATE_SYMBOL: i64 = 8i64; +pub const ALGORITHM_SYMBOL: i64 = 9i64; +pub const ALL_SYMBOL: i64 = 10i64; +pub const ALTER_SYMBOL: i64 = 11i64; +pub const ALWAYS_SYMBOL: i64 = 12i64; +pub const ANALYSE_SYMBOL: i64 = 13i64; +pub const ANALYZE_SYMBOL: i64 = 14i64; +pub const AND_SYMBOL: i64 = 15i64; +pub const ANY_SYMBOL: i64 = 16i64; +pub const AS_SYMBOL: i64 = 17i64; +pub const ASC_SYMBOL: i64 = 18i64; +pub const ASCII_SYMBOL: i64 = 19i64; +pub const ASENSITIVE_SYMBOL: i64 = 20i64; +pub const AT_SYMBOL: i64 = 21i64; +pub const AUTHORS_SYMBOL: i64 = 22i64; +pub const AUTOEXTEND_SIZE_SYMBOL: i64 = 23i64; +pub const AUTO_INCREMENT_SYMBOL: i64 = 24i64; +pub const AVG_ROW_LENGTH_SYMBOL: i64 = 25i64; +pub const AVG_SYMBOL: i64 = 26i64; +pub const BACKUP_SYMBOL: i64 = 27i64; +pub const BEFORE_SYMBOL: i64 = 28i64; +pub const BEGIN_SYMBOL: i64 = 29i64; +pub const BETWEEN_SYMBOL: i64 = 30i64; +pub const BIGINT_SYMBOL: i64 = 31i64; +pub const BINARY_SYMBOL: i64 = 32i64; +pub const BINLOG_SYMBOL: i64 = 33i64; +pub const BIN_NUM_SYMBOL: i64 = 34i64; +pub const BIT_AND_SYMBOL: i64 = 35i64; +pub const BIT_OR_SYMBOL: i64 = 36i64; +pub const BIT_SYMBOL: i64 = 37i64; +pub const BIT_XOR_SYMBOL: i64 = 38i64; +pub const BLOB_SYMBOL: i64 = 39i64; +pub const BLOCK_SYMBOL: i64 = 40i64; +pub const BOOLEAN_SYMBOL: i64 = 41i64; +pub const BOOL_SYMBOL: i64 = 42i64; +pub const BOTH_SYMBOL: i64 = 43i64; +pub const BTREE_SYMBOL: i64 = 44i64; +pub const BY_SYMBOL: i64 = 45i64; +pub const BYTE_SYMBOL: i64 = 46i64; +pub const CACHE_SYMBOL: i64 = 47i64; +pub const CALL_SYMBOL: i64 = 48i64; +pub const CASCADE_SYMBOL: i64 = 49i64; +pub const CASCADED_SYMBOL: i64 = 50i64; +pub const CASE_SYMBOL: i64 = 51i64; +pub const CAST_SYMBOL: i64 = 52i64; +pub const CATALOG_NAME_SYMBOL: i64 = 53i64; +pub const CHAIN_SYMBOL: i64 = 54i64; +pub const CHANGE_SYMBOL: i64 = 55i64; +pub const CHANGED_SYMBOL: i64 = 56i64; +pub const CHANNEL_SYMBOL: i64 = 57i64; +pub const CHARSET_SYMBOL: i64 = 58i64; +pub const CHARACTER_SYMBOL: i64 = 59i64; +pub const CHAR_SYMBOL: i64 = 60i64; +pub const CHECKSUM_SYMBOL: i64 = 61i64; +pub const CHECK_SYMBOL: i64 = 62i64; +pub const CIPHER_SYMBOL: i64 = 63i64; +pub const CLASS_ORIGIN_SYMBOL: i64 = 64i64; +pub const CLIENT_SYMBOL: i64 = 65i64; +pub const CLOSE_SYMBOL: i64 = 66i64; +pub const COALESCE_SYMBOL: i64 = 67i64; +pub const CODE_SYMBOL: i64 = 68i64; +pub const COLLATE_SYMBOL: i64 = 69i64; +pub const COLLATION_SYMBOL: i64 = 70i64; +pub const COLUMNS_SYMBOL: i64 = 71i64; +pub const COLUMN_SYMBOL: i64 = 72i64; +pub const COLUMN_NAME_SYMBOL: i64 = 73i64; +pub const COLUMN_FORMAT_SYMBOL: i64 = 74i64; +pub const COMMENT_SYMBOL: i64 = 75i64; +pub const COMMITTED_SYMBOL: i64 = 76i64; +pub const COMMIT_SYMBOL: i64 = 77i64; +pub const COMPACT_SYMBOL: i64 = 78i64; +pub const COMPLETION_SYMBOL: i64 = 79i64; +pub const COMPRESSED_SYMBOL: i64 = 80i64; +pub const COMPRESSION_SYMBOL: i64 = 81i64; +pub const CONCURRENT_SYMBOL: i64 = 82i64; +pub const CONDITION_SYMBOL: i64 = 83i64; +pub const CONNECTION_SYMBOL: i64 = 84i64; +pub const CONSISTENT_SYMBOL: i64 = 85i64; +pub const CONSTRAINT_SYMBOL: i64 = 86i64; +pub const CONSTRAINT_CATALOG_SYMBOL: i64 = 87i64; +pub const CONSTRAINT_NAME_SYMBOL: i64 = 88i64; +pub const CONSTRAINT_SCHEMA_SYMBOL: i64 = 89i64; +pub const CONTAINS_SYMBOL: i64 = 90i64; +pub const CONTEXT_SYMBOL: i64 = 91i64; +pub const CONTINUE_SYMBOL: i64 = 92i64; +pub const CONTRIBUTORS_SYMBOL: i64 = 93i64; +pub const CONVERT_SYMBOL: i64 = 94i64; +pub const COUNT_SYMBOL: i64 = 95i64; +pub const CPU_SYMBOL: i64 = 96i64; +pub const CREATE_SYMBOL: i64 = 97i64; +pub const CROSS_SYMBOL: i64 = 98i64; +pub const CUBE_SYMBOL: i64 = 99i64; +pub const CURDATE_SYMBOL: i64 = 100i64; +pub const CURRENT_SYMBOL: i64 = 101i64; +pub const CURRENT_DATE_SYMBOL: i64 = 102i64; +pub const CURRENT_TIME_SYMBOL: i64 = 103i64; +pub const CURRENT_TIMESTAMP_SYMBOL: i64 = 104i64; +pub const CURRENT_USER_SYMBOL: i64 = 105i64; +pub const CURSOR_SYMBOL: i64 = 106i64; +pub const CURSOR_NAME_SYMBOL: i64 = 107i64; +pub const CURTIME_SYMBOL: i64 = 108i64; +pub const DATABASE_SYMBOL: i64 = 109i64; +pub const DATABASES_SYMBOL: i64 = 110i64; +pub const DATAFILE_SYMBOL: i64 = 111i64; +pub const DATA_SYMBOL: i64 = 112i64; +pub const DATETIME_SYMBOL: i64 = 113i64; +pub const DATE_ADD_SYMBOL: i64 = 114i64; +pub const DATE_SUB_SYMBOL: i64 = 115i64; +pub const DATE_SYMBOL: i64 = 116i64; +pub const DAYOFMONTH_SYMBOL: i64 = 117i64; +pub const DAY_HOUR_SYMBOL: i64 = 118i64; +pub const DAY_MICROSECOND_SYMBOL: i64 = 119i64; +pub const DAY_MINUTE_SYMBOL: i64 = 120i64; +pub const DAY_SECOND_SYMBOL: i64 = 121i64; +pub const DAY_SYMBOL: i64 = 122i64; +pub const DEALLOCATE_SYMBOL: i64 = 123i64; +pub const DEC_SYMBOL: i64 = 124i64; +pub const DECIMAL_NUM_SYMBOL: i64 = 125i64; +pub const DECIMAL_SYMBOL: i64 = 126i64; +pub const DECLARE_SYMBOL: i64 = 127i64; +pub const DEFAULT_SYMBOL: i64 = 128i64; +pub const DEFAULT_AUTH_SYMBOL: i64 = 129i64; +pub const DEFINER_SYMBOL: i64 = 130i64; +pub const DELAYED_SYMBOL: i64 = 131i64; +pub const DELAY_KEY_WRITE_SYMBOL: i64 = 132i64; +pub const DELETE_SYMBOL: i64 = 133i64; +pub const DESC_SYMBOL: i64 = 134i64; +pub const DESCRIBE_SYMBOL: i64 = 135i64; +pub const DES_KEY_FILE_SYMBOL: i64 = 136i64; +pub const DETERMINISTIC_SYMBOL: i64 = 137i64; +pub const DIAGNOSTICS_SYMBOL: i64 = 138i64; +pub const DIRECTORY_SYMBOL: i64 = 139i64; +pub const DISABLE_SYMBOL: i64 = 140i64; +pub const DISCARD_SYMBOL: i64 = 141i64; +pub const DISK_SYMBOL: i64 = 142i64; +pub const DISTINCT_SYMBOL: i64 = 143i64; +pub const DISTINCTROW_SYMBOL: i64 = 144i64; +pub const DIV_SYMBOL: i64 = 145i64; +pub const DOUBLE_SYMBOL: i64 = 146i64; +pub const DO_SYMBOL: i64 = 147i64; +pub const DROP_SYMBOL: i64 = 148i64; +pub const DUAL_SYMBOL: i64 = 149i64; +pub const DUMPFILE_SYMBOL: i64 = 150i64; +pub const DUPLICATE_SYMBOL: i64 = 151i64; +pub const DYNAMIC_SYMBOL: i64 = 152i64; +pub const EACH_SYMBOL: i64 = 153i64; +pub const ELSE_SYMBOL: i64 = 154i64; +pub const ELSEIF_SYMBOL: i64 = 155i64; +pub const ENABLE_SYMBOL: i64 = 156i64; +pub const ENCLOSED_SYMBOL: i64 = 157i64; +pub const ENCRYPTION_SYMBOL: i64 = 158i64; +pub const END_SYMBOL: i64 = 159i64; +pub const ENDS_SYMBOL: i64 = 160i64; +pub const END_OF_INPUT_SYMBOL: i64 = 161i64; +pub const ENGINES_SYMBOL: i64 = 162i64; +pub const ENGINE_SYMBOL: i64 = 163i64; +pub const ENUM_SYMBOL: i64 = 164i64; +pub const ERROR_SYMBOL: i64 = 165i64; +pub const ERRORS_SYMBOL: i64 = 166i64; +pub const ESCAPED_SYMBOL: i64 = 167i64; +pub const ESCAPE_SYMBOL: i64 = 168i64; +pub const EVENTS_SYMBOL: i64 = 169i64; +pub const EVENT_SYMBOL: i64 = 170i64; +pub const EVERY_SYMBOL: i64 = 171i64; +pub const EXCHANGE_SYMBOL: i64 = 172i64; +pub const EXECUTE_SYMBOL: i64 = 173i64; +pub const EXISTS_SYMBOL: i64 = 174i64; +pub const EXIT_SYMBOL: i64 = 175i64; +pub const EXPANSION_SYMBOL: i64 = 176i64; +pub const EXPIRE_SYMBOL: i64 = 177i64; +pub const EXPLAIN_SYMBOL: i64 = 178i64; +pub const EXPORT_SYMBOL: i64 = 179i64; +pub const EXTENDED_SYMBOL: i64 = 180i64; +pub const EXTENT_SIZE_SYMBOL: i64 = 181i64; +pub const EXTRACT_SYMBOL: i64 = 182i64; +pub const FALSE_SYMBOL: i64 = 183i64; +pub const FAST_SYMBOL: i64 = 184i64; +pub const FAULTS_SYMBOL: i64 = 185i64; +pub const FETCH_SYMBOL: i64 = 186i64; +pub const FIELDS_SYMBOL: i64 = 187i64; +pub const FILE_SYMBOL: i64 = 188i64; +pub const FILE_BLOCK_SIZE_SYMBOL: i64 = 189i64; +pub const FILTER_SYMBOL: i64 = 190i64; +pub const FIRST_SYMBOL: i64 = 191i64; +pub const FIXED_SYMBOL: i64 = 192i64; +pub const FLOAT4_SYMBOL: i64 = 193i64; +pub const FLOAT8_SYMBOL: i64 = 194i64; +pub const FLOAT_SYMBOL: i64 = 195i64; +pub const FLUSH_SYMBOL: i64 = 196i64; +pub const FOLLOWS_SYMBOL: i64 = 197i64; +pub const FORCE_SYMBOL: i64 = 198i64; +pub const FOREIGN_SYMBOL: i64 = 199i64; +pub const FOR_SYMBOL: i64 = 200i64; +pub const FORMAT_SYMBOL: i64 = 201i64; +pub const FOUND_SYMBOL: i64 = 202i64; +pub const FROM_SYMBOL: i64 = 203i64; +pub const FULL_SYMBOL: i64 = 204i64; +pub const FULLTEXT_SYMBOL: i64 = 205i64; +pub const FUNCTION_SYMBOL: i64 = 206i64; +pub const GET_SYMBOL: i64 = 207i64; +pub const GENERAL_SYMBOL: i64 = 208i64; +pub const GENERATED_SYMBOL: i64 = 209i64; +pub const GROUP_REPLICATION_SYMBOL: i64 = 210i64; +pub const GEOMETRYCOLLECTION_SYMBOL: i64 = 211i64; +pub const GEOMETRY_SYMBOL: i64 = 212i64; +pub const GET_FORMAT_SYMBOL: i64 = 213i64; +pub const GLOBAL_SYMBOL: i64 = 214i64; +pub const GRANT_SYMBOL: i64 = 215i64; +pub const GRANTS_SYMBOL: i64 = 216i64; +pub const GROUP_SYMBOL: i64 = 217i64; +pub const GROUP_CONCAT_SYMBOL: i64 = 218i64; +pub const HANDLER_SYMBOL: i64 = 219i64; +pub const HASH_SYMBOL: i64 = 220i64; +pub const HAVING_SYMBOL: i64 = 221i64; +pub const HELP_SYMBOL: i64 = 222i64; +pub const HIGH_PRIORITY_SYMBOL: i64 = 223i64; +pub const HOST_SYMBOL: i64 = 224i64; +pub const HOSTS_SYMBOL: i64 = 225i64; +pub const HOUR_MICROSECOND_SYMBOL: i64 = 226i64; +pub const HOUR_MINUTE_SYMBOL: i64 = 227i64; +pub const HOUR_SECOND_SYMBOL: i64 = 228i64; +pub const HOUR_SYMBOL: i64 = 229i64; +pub const IDENTIFIED_SYMBOL: i64 = 230i64; +pub const IF_SYMBOL: i64 = 231i64; +pub const IGNORE_SYMBOL: i64 = 232i64; +pub const IGNORE_SERVER_IDS_SYMBOL: i64 = 233i64; +pub const IMPORT_SYMBOL: i64 = 234i64; +pub const INDEXES_SYMBOL: i64 = 235i64; +pub const INDEX_SYMBOL: i64 = 236i64; +pub const INFILE_SYMBOL: i64 = 237i64; +pub const INITIAL_SIZE_SYMBOL: i64 = 238i64; +pub const INNER_SYMBOL: i64 = 239i64; +pub const INOUT_SYMBOL: i64 = 240i64; +pub const INSENSITIVE_SYMBOL: i64 = 241i64; +pub const INSERT_SYMBOL: i64 = 242i64; +pub const INSERT_METHOD_SYMBOL: i64 = 243i64; +pub const INSTANCE_SYMBOL: i64 = 244i64; +pub const INSTALL_SYMBOL: i64 = 245i64; +pub const INTEGER_SYMBOL: i64 = 246i64; +pub const INTERVAL_SYMBOL: i64 = 247i64; +pub const INTO_SYMBOL: i64 = 248i64; +pub const INT_SYMBOL: i64 = 249i64; +pub const INVOKER_SYMBOL: i64 = 250i64; +pub const IN_SYMBOL: i64 = 251i64; +pub const IO_AFTER_GTIDS_SYMBOL: i64 = 252i64; +pub const IO_BEFORE_GTIDS_SYMBOL: i64 = 253i64; +pub const IO_THREAD_SYMBOL: i64 = 254i64; +pub const IO_SYMBOL: i64 = 255i64; +pub const IPC_SYMBOL: i64 = 256i64; +pub const IS_SYMBOL: i64 = 257i64; +pub const ISOLATION_SYMBOL: i64 = 258i64; +pub const ISSUER_SYMBOL: i64 = 259i64; +pub const ITERATE_SYMBOL: i64 = 260i64; +pub const JOIN_SYMBOL: i64 = 261i64; +pub const JSON_SYMBOL: i64 = 262i64; +pub const KEYS_SYMBOL: i64 = 263i64; +pub const KEY_BLOCK_SIZE_SYMBOL: i64 = 264i64; +pub const KEY_SYMBOL: i64 = 265i64; +pub const KILL_SYMBOL: i64 = 266i64; +pub const LANGUAGE_SYMBOL: i64 = 267i64; +pub const LAST_SYMBOL: i64 = 268i64; +pub const LEADING_SYMBOL: i64 = 269i64; +pub const LEAVES_SYMBOL: i64 = 270i64; +pub const LEAVE_SYMBOL: i64 = 271i64; +pub const LEFT_SYMBOL: i64 = 272i64; +pub const LESS_SYMBOL: i64 = 273i64; +pub const LEVEL_SYMBOL: i64 = 274i64; +pub const LIKE_SYMBOL: i64 = 275i64; +pub const LIMIT_SYMBOL: i64 = 276i64; +pub const LINEAR_SYMBOL: i64 = 277i64; +pub const LINES_SYMBOL: i64 = 278i64; +pub const LINESTRING_SYMBOL: i64 = 279i64; +pub const LIST_SYMBOL: i64 = 280i64; +pub const LOAD_SYMBOL: i64 = 281i64; +pub const LOCALTIME_SYMBOL: i64 = 282i64; +pub const LOCALTIMESTAMP_SYMBOL: i64 = 283i64; +pub const LOCAL_SYMBOL: i64 = 284i64; +pub const LOCATOR_SYMBOL: i64 = 285i64; +pub const LOCKS_SYMBOL: i64 = 286i64; +pub const LOCK_SYMBOL: i64 = 287i64; +pub const LOGFILE_SYMBOL: i64 = 288i64; +pub const LOGS_SYMBOL: i64 = 289i64; +pub const LONGBLOB_SYMBOL: i64 = 290i64; +pub const LONGTEXT_SYMBOL: i64 = 291i64; +pub const LONG_NUM_SYMBOL: i64 = 292i64; +pub const LONG_SYMBOL: i64 = 293i64; +pub const LOOP_SYMBOL: i64 = 294i64; +pub const LOW_PRIORITY_SYMBOL: i64 = 295i64; +pub const MASTER_AUTO_POSITION_SYMBOL: i64 = 296i64; +pub const MASTER_BIND_SYMBOL: i64 = 297i64; +pub const MASTER_CONNECT_RETRY_SYMBOL: i64 = 298i64; +pub const MASTER_DELAY_SYMBOL: i64 = 299i64; +pub const MASTER_HOST_SYMBOL: i64 = 300i64; +pub const MASTER_LOG_FILE_SYMBOL: i64 = 301i64; +pub const MASTER_LOG_POS_SYMBOL: i64 = 302i64; +pub const MASTER_PASSWORD_SYMBOL: i64 = 303i64; +pub const MASTER_PORT_SYMBOL: i64 = 304i64; +pub const MASTER_RETRY_COUNT_SYMBOL: i64 = 305i64; +pub const MASTER_SERVER_ID_SYMBOL: i64 = 306i64; +pub const MASTER_SSL_CAPATH_SYMBOL: i64 = 307i64; +pub const MASTER_SSL_CA_SYMBOL: i64 = 308i64; +pub const MASTER_SSL_CERT_SYMBOL: i64 = 309i64; +pub const MASTER_SSL_CIPHER_SYMBOL: i64 = 310i64; +pub const MASTER_SSL_CRL_SYMBOL: i64 = 311i64; +pub const MASTER_SSL_CRLPATH_SYMBOL: i64 = 312i64; +pub const MASTER_SSL_KEY_SYMBOL: i64 = 313i64; +pub const MASTER_SSL_SYMBOL: i64 = 314i64; +pub const MASTER_SSL_VERIFY_SERVER_CERT_SYMBOL: i64 = 315i64; +pub const MASTER_SYMBOL: i64 = 316i64; +pub const MASTER_TLS_VERSION_SYMBOL: i64 = 317i64; +pub const MASTER_USER_SYMBOL: i64 = 318i64; +pub const MASTER_HEARTBEAT_PERIOD_SYMBOL: i64 = 319i64; +pub const MATCH_SYMBOL: i64 = 320i64; +pub const MAX_CONNECTIONS_PER_HOUR_SYMBOL: i64 = 321i64; +pub const MAX_QUERIES_PER_HOUR_SYMBOL: i64 = 322i64; +pub const MAX_ROWS_SYMBOL: i64 = 323i64; +pub const MAX_SIZE_SYMBOL: i64 = 324i64; +pub const MAX_STATEMENT_TIME_SYMBOL: i64 = 325i64; +pub const MAX_SYMBOL: i64 = 326i64; +pub const MAX_UPDATES_PER_HOUR_SYMBOL: i64 = 327i64; +pub const MAX_USER_CONNECTIONS_SYMBOL: i64 = 328i64; +pub const MAXVALUE_SYMBOL: i64 = 329i64; +pub const MEDIUMBLOB_SYMBOL: i64 = 330i64; +pub const MEDIUMINT_SYMBOL: i64 = 331i64; +pub const MEDIUMTEXT_SYMBOL: i64 = 332i64; +pub const MEDIUM_SYMBOL: i64 = 333i64; +pub const MEMORY_SYMBOL: i64 = 334i64; +pub const MERGE_SYMBOL: i64 = 335i64; +pub const MESSAGE_TEXT_SYMBOL: i64 = 336i64; +pub const MICROSECOND_SYMBOL: i64 = 337i64; +pub const MID_SYMBOL: i64 = 338i64; +pub const MIDDLEINT_SYMBOL: i64 = 339i64; +pub const MIGRATE_SYMBOL: i64 = 340i64; +pub const MINUTE_MICROSECOND_SYMBOL: i64 = 341i64; +pub const MINUTE_SECOND_SYMBOL: i64 = 342i64; +pub const MINUTE_SYMBOL: i64 = 343i64; +pub const MIN_ROWS_SYMBOL: i64 = 344i64; +pub const MIN_SYMBOL: i64 = 345i64; +pub const MODE_SYMBOL: i64 = 346i64; +pub const MODIFIES_SYMBOL: i64 = 347i64; +pub const MODIFY_SYMBOL: i64 = 348i64; +pub const MOD_SYMBOL: i64 = 349i64; +pub const MONTH_SYMBOL: i64 = 350i64; +pub const MULTILINESTRING_SYMBOL: i64 = 351i64; +pub const MULTIPOINT_SYMBOL: i64 = 352i64; +pub const MULTIPOLYGON_SYMBOL: i64 = 353i64; +pub const MUTEX_SYMBOL: i64 = 354i64; +pub const MYSQL_ERRNO_SYMBOL: i64 = 355i64; +pub const NAMES_SYMBOL: i64 = 356i64; +pub const NAME_SYMBOL: i64 = 357i64; +pub const NATIONAL_SYMBOL: i64 = 358i64; +pub const NATURAL_SYMBOL: i64 = 359i64; +pub const NCHAR_STRING_SYMBOL: i64 = 360i64; +pub const NCHAR_SYMBOL: i64 = 361i64; +pub const NDB_SYMBOL: i64 = 362i64; +pub const NDBCLUSTER_SYMBOL: i64 = 363i64; +pub const NEG_SYMBOL: i64 = 364i64; +pub const NEVER_SYMBOL: i64 = 365i64; +pub const NEW_SYMBOL: i64 = 366i64; +pub const NEXT_SYMBOL: i64 = 367i64; +pub const NODEGROUP_SYMBOL: i64 = 368i64; +pub const NONE_SYMBOL: i64 = 369i64; +pub const NONBLOCKING_SYMBOL: i64 = 370i64; +pub const NOT_SYMBOL: i64 = 371i64; +pub const NOW_SYMBOL: i64 = 372i64; +pub const NO_SYMBOL: i64 = 373i64; +pub const NO_WAIT_SYMBOL: i64 = 374i64; +pub const NO_WRITE_TO_BINLOG_SYMBOL: i64 = 375i64; +pub const NULL_SYMBOL: i64 = 376i64; +pub const NUMBER_SYMBOL: i64 = 377i64; +pub const NUMERIC_SYMBOL: i64 = 378i64; +pub const NVARCHAR_SYMBOL: i64 = 379i64; +pub const OFFLINE_SYMBOL: i64 = 380i64; +pub const OFFSET_SYMBOL: i64 = 381i64; +pub const OLD_PASSWORD_SYMBOL: i64 = 382i64; +pub const ON_SYMBOL: i64 = 383i64; +pub const ONE_SYMBOL: i64 = 384i64; +pub const ONLINE_SYMBOL: i64 = 385i64; +pub const ONLY_SYMBOL: i64 = 386i64; +pub const OPEN_SYMBOL: i64 = 387i64; +pub const OPTIMIZE_SYMBOL: i64 = 388i64; +pub const OPTIMIZER_COSTS_SYMBOL: i64 = 389i64; +pub const OPTIONS_SYMBOL: i64 = 390i64; +pub const OPTION_SYMBOL: i64 = 391i64; +pub const OPTIONALLY_SYMBOL: i64 = 392i64; +pub const ORDER_SYMBOL: i64 = 393i64; +pub const OR_SYMBOL: i64 = 394i64; +pub const OUTER_SYMBOL: i64 = 395i64; +pub const OUTFILE_SYMBOL: i64 = 396i64; +pub const OUT_SYMBOL: i64 = 397i64; +pub const OWNER_SYMBOL: i64 = 398i64; +pub const PACK_KEYS_SYMBOL: i64 = 399i64; +pub const PAGE_SYMBOL: i64 = 400i64; +pub const PARSER_SYMBOL: i64 = 401i64; +pub const PARTIAL_SYMBOL: i64 = 402i64; +pub const PARTITIONING_SYMBOL: i64 = 403i64; +pub const PARTITIONS_SYMBOL: i64 = 404i64; +pub const PARTITION_SYMBOL: i64 = 405i64; +pub const PASSWORD_SYMBOL: i64 = 406i64; +pub const PHASE_SYMBOL: i64 = 407i64; +pub const PLUGINS_SYMBOL: i64 = 408i64; +pub const PLUGIN_DIR_SYMBOL: i64 = 409i64; +pub const PLUGIN_SYMBOL: i64 = 410i64; +pub const POINT_SYMBOL: i64 = 411i64; +pub const POLYGON_SYMBOL: i64 = 412i64; +pub const PORT_SYMBOL: i64 = 413i64; +pub const POSITION_SYMBOL: i64 = 414i64; +pub const PRECEDES_SYMBOL: i64 = 415i64; +pub const PRECISION_SYMBOL: i64 = 416i64; +pub const PREPARE_SYMBOL: i64 = 417i64; +pub const PRESERVE_SYMBOL: i64 = 418i64; +pub const PREV_SYMBOL: i64 = 419i64; +pub const PRIMARY_SYMBOL: i64 = 420i64; +pub const PRIVILEGES_SYMBOL: i64 = 421i64; +pub const PROCEDURE_SYMBOL: i64 = 422i64; +pub const PROCESS_SYMBOL: i64 = 423i64; +pub const PROCESSLIST_SYMBOL: i64 = 424i64; +pub const PROFILE_SYMBOL: i64 = 425i64; +pub const PROFILES_SYMBOL: i64 = 426i64; +pub const PROXY_SYMBOL: i64 = 427i64; +pub const PURGE_SYMBOL: i64 = 428i64; +pub const QUARTER_SYMBOL: i64 = 429i64; +pub const QUERY_SYMBOL: i64 = 430i64; +pub const QUICK_SYMBOL: i64 = 431i64; +pub const RANGE_SYMBOL: i64 = 432i64; +pub const READS_SYMBOL: i64 = 433i64; +pub const READ_ONLY_SYMBOL: i64 = 434i64; +pub const READ_SYMBOL: i64 = 435i64; +pub const READ_WRITE_SYMBOL: i64 = 436i64; +pub const REAL_SYMBOL: i64 = 437i64; +pub const REBUILD_SYMBOL: i64 = 438i64; +pub const RECOVER_SYMBOL: i64 = 439i64; +pub const REDOFILE_SYMBOL: i64 = 440i64; +pub const REDO_BUFFER_SIZE_SYMBOL: i64 = 441i64; +pub const REDUNDANT_SYMBOL: i64 = 442i64; +pub const REFERENCES_SYMBOL: i64 = 443i64; +pub const REGEXP_SYMBOL: i64 = 444i64; +pub const RELAY_SYMBOL: i64 = 445i64; +pub const RELAYLOG_SYMBOL: i64 = 446i64; +pub const RELAY_LOG_FILE_SYMBOL: i64 = 447i64; +pub const RELAY_LOG_POS_SYMBOL: i64 = 448i64; +pub const RELAY_THREAD_SYMBOL: i64 = 449i64; +pub const RELEASE_SYMBOL: i64 = 450i64; +pub const RELOAD_SYMBOL: i64 = 451i64; +pub const REMOVE_SYMBOL: i64 = 452i64; +pub const RENAME_SYMBOL: i64 = 453i64; +pub const REORGANIZE_SYMBOL: i64 = 454i64; +pub const REPAIR_SYMBOL: i64 = 455i64; +pub const REPEATABLE_SYMBOL: i64 = 456i64; +pub const REPEAT_SYMBOL: i64 = 457i64; +pub const REPLACE_SYMBOL: i64 = 458i64; +pub const REPLICATION_SYMBOL: i64 = 459i64; +pub const REPLICATE_DO_DB_SYMBOL: i64 = 460i64; +pub const REPLICATE_IGNORE_DB_SYMBOL: i64 = 461i64; +pub const REPLICATE_DO_TABLE_SYMBOL: i64 = 462i64; +pub const REPLICATE_IGNORE_TABLE_SYMBOL: i64 = 463i64; +pub const REPLICATE_WILD_DO_TABLE_SYMBOL: i64 = 464i64; +pub const REPLICATE_WILD_IGNORE_TABLE_SYMBOL: i64 = 465i64; +pub const REPLICATE_REWRITE_DB_SYMBOL: i64 = 466i64; +pub const REQUIRE_SYMBOL: i64 = 467i64; +pub const RESET_SYMBOL: i64 = 468i64; +pub const RESIGNAL_SYMBOL: i64 = 469i64; +pub const RESTORE_SYMBOL: i64 = 470i64; +pub const RESTRICT_SYMBOL: i64 = 471i64; +pub const RESUME_SYMBOL: i64 = 472i64; +pub const RETURNED_SQLSTATE_SYMBOL: i64 = 473i64; +pub const RETURNS_SYMBOL: i64 = 474i64; +pub const RETURN_SYMBOL: i64 = 475i64; +pub const REVERSE_SYMBOL: i64 = 476i64; +pub const REVOKE_SYMBOL: i64 = 477i64; +pub const RIGHT_SYMBOL: i64 = 478i64; +pub const RLIKE_SYMBOL: i64 = 479i64; +pub const ROLLBACK_SYMBOL: i64 = 480i64; +pub const ROLLUP_SYMBOL: i64 = 481i64; +pub const ROTATE_SYMBOL: i64 = 482i64; +pub const ROUTINE_SYMBOL: i64 = 483i64; +pub const ROWS_SYMBOL: i64 = 484i64; +pub const ROW_COUNT_SYMBOL: i64 = 485i64; +pub const ROW_FORMAT_SYMBOL: i64 = 486i64; +pub const ROW_SYMBOL: i64 = 487i64; +pub const RTREE_SYMBOL: i64 = 488i64; +pub const SAVEPOINT_SYMBOL: i64 = 489i64; +pub const SCHEDULE_SYMBOL: i64 = 490i64; +pub const SCHEMA_SYMBOL: i64 = 491i64; +pub const SCHEMA_NAME_SYMBOL: i64 = 492i64; +pub const SCHEMAS_SYMBOL: i64 = 493i64; +pub const SECOND_MICROSECOND_SYMBOL: i64 = 494i64; +pub const SECOND_SYMBOL: i64 = 495i64; +pub const SECURITY_SYMBOL: i64 = 496i64; +pub const SELECT_SYMBOL: i64 = 497i64; +pub const SENSITIVE_SYMBOL: i64 = 498i64; +pub const SEPARATOR_SYMBOL: i64 = 499i64; +pub const SERIALIZABLE_SYMBOL: i64 = 500i64; +pub const SERIAL_SYMBOL: i64 = 501i64; +pub const SESSION_SYMBOL: i64 = 502i64; +pub const SERVER_SYMBOL: i64 = 503i64; +pub const SERVER_OPTIONS_SYMBOL: i64 = 504i64; +pub const SESSION_USER_SYMBOL: i64 = 505i64; +pub const SET_SYMBOL: i64 = 506i64; +pub const SET_VAR_SYMBOL: i64 = 507i64; +pub const SHARE_SYMBOL: i64 = 508i64; +pub const SHOW_SYMBOL: i64 = 509i64; +pub const SHUTDOWN_SYMBOL: i64 = 510i64; +pub const SIGNAL_SYMBOL: i64 = 511i64; +pub const SIGNED_SYMBOL: i64 = 512i64; +pub const SIMPLE_SYMBOL: i64 = 513i64; +pub const SLAVE_SYMBOL: i64 = 514i64; +pub const SLOW_SYMBOL: i64 = 515i64; +pub const SMALLINT_SYMBOL: i64 = 516i64; +pub const SNAPSHOT_SYMBOL: i64 = 517i64; +pub const SOME_SYMBOL: i64 = 518i64; +pub const SOCKET_SYMBOL: i64 = 519i64; +pub const SONAME_SYMBOL: i64 = 520i64; +pub const SOUNDS_SYMBOL: i64 = 521i64; +pub const SOURCE_SYMBOL: i64 = 522i64; +pub const SPATIAL_SYMBOL: i64 = 523i64; +pub const SPECIFIC_SYMBOL: i64 = 524i64; +pub const SQLEXCEPTION_SYMBOL: i64 = 525i64; +pub const SQLSTATE_SYMBOL: i64 = 526i64; +pub const SQLWARNING_SYMBOL: i64 = 527i64; +pub const SQL_AFTER_GTIDS_SYMBOL: i64 = 528i64; +pub const SQL_AFTER_MTS_GAPS_SYMBOL: i64 = 529i64; +pub const SQL_BEFORE_GTIDS_SYMBOL: i64 = 530i64; +pub const SQL_BIG_RESULT_SYMBOL: i64 = 531i64; +pub const SQL_BUFFER_RESULT_SYMBOL: i64 = 532i64; +pub const SQL_CACHE_SYMBOL: i64 = 533i64; +pub const SQL_CALC_FOUND_ROWS_SYMBOL: i64 = 534i64; +pub const SQL_NO_CACHE_SYMBOL: i64 = 535i64; +pub const SQL_SMALL_RESULT_SYMBOL: i64 = 536i64; +pub const SQL_SYMBOL: i64 = 537i64; +pub const SQL_THREAD_SYMBOL: i64 = 538i64; +pub const SSL_SYMBOL: i64 = 539i64; +pub const STACKED_SYMBOL: i64 = 540i64; +pub const STARTING_SYMBOL: i64 = 541i64; +pub const STARTS_SYMBOL: i64 = 542i64; +pub const START_SYMBOL: i64 = 543i64; +pub const STATS_AUTO_RECALC_SYMBOL: i64 = 544i64; +pub const STATS_PERSISTENT_SYMBOL: i64 = 545i64; +pub const STATS_SAMPLE_PAGES_SYMBOL: i64 = 546i64; +pub const STATUS_SYMBOL: i64 = 547i64; +pub const STDDEV_SAMP_SYMBOL: i64 = 548i64; +pub const STDDEV_SYMBOL: i64 = 549i64; +pub const STDDEV_POP_SYMBOL: i64 = 550i64; +pub const STD_SYMBOL: i64 = 551i64; +pub const STOP_SYMBOL: i64 = 552i64; +pub const STORAGE_SYMBOL: i64 = 553i64; +pub const STORED_SYMBOL: i64 = 554i64; +pub const STRAIGHT_JOIN_SYMBOL: i64 = 555i64; +pub const STRING_SYMBOL: i64 = 556i64; +pub const SUBCLASS_ORIGIN_SYMBOL: i64 = 557i64; +pub const SUBDATE_SYMBOL: i64 = 558i64; +pub const SUBJECT_SYMBOL: i64 = 559i64; +pub const SUBPARTITIONS_SYMBOL: i64 = 560i64; +pub const SUBPARTITION_SYMBOL: i64 = 561i64; +pub const SUBSTR_SYMBOL: i64 = 562i64; +pub const SUBSTRING_SYMBOL: i64 = 563i64; +pub const SUM_SYMBOL: i64 = 564i64; +pub const SUPER_SYMBOL: i64 = 565i64; +pub const SUSPEND_SYMBOL: i64 = 566i64; +pub const SWAPS_SYMBOL: i64 = 567i64; +pub const SWITCHES_SYMBOL: i64 = 568i64; +pub const SYSDATE_SYMBOL: i64 = 569i64; +pub const SYSTEM_USER_SYMBOL: i64 = 570i64; +pub const TABLES_SYMBOL: i64 = 571i64; +pub const TABLESPACE_SYMBOL: i64 = 572i64; +pub const TABLE_REF_PRIORITY_SYMBOL: i64 = 573i64; +pub const TABLE_SYMBOL: i64 = 574i64; +pub const TABLE_CHECKSUM_SYMBOL: i64 = 575i64; +pub const TABLE_NAME_SYMBOL: i64 = 576i64; +pub const TEMPORARY_SYMBOL: i64 = 577i64; +pub const TEMPTABLE_SYMBOL: i64 = 578i64; +pub const TERMINATED_SYMBOL: i64 = 579i64; +pub const TEXT_SYMBOL: i64 = 580i64; +pub const THAN_SYMBOL: i64 = 581i64; +pub const THEN_SYMBOL: i64 = 582i64; +pub const TIMESTAMP_SYMBOL: i64 = 583i64; +pub const TIMESTAMP_ADD_SYMBOL: i64 = 584i64; +pub const TIMESTAMP_DIFF_SYMBOL: i64 = 585i64; +pub const TIME_SYMBOL: i64 = 586i64; +pub const TINYBLOB_SYMBOL: i64 = 587i64; +pub const TINYINT_SYMBOL: i64 = 588i64; +pub const TINYTEXT_SYMBOL: i64 = 589i64; +pub const TO_SYMBOL: i64 = 590i64; +pub const TRAILING_SYMBOL: i64 = 591i64; +pub const TRANSACTION_SYMBOL: i64 = 592i64; +pub const TRIGGERS_SYMBOL: i64 = 593i64; +pub const TRIGGER_SYMBOL: i64 = 594i64; +pub const TRIM_SYMBOL: i64 = 595i64; +pub const TRUE_SYMBOL: i64 = 596i64; +pub const TRUNCATE_SYMBOL: i64 = 597i64; +pub const TYPES_SYMBOL: i64 = 598i64; +pub const TYPE_SYMBOL: i64 = 599i64; +pub const UDF_RETURNS_SYMBOL: i64 = 600i64; +pub const UNCOMMITTED_SYMBOL: i64 = 601i64; +pub const UNDEFINED_SYMBOL: i64 = 602i64; +pub const UNDOFILE_SYMBOL: i64 = 603i64; +pub const UNDO_BUFFER_SIZE_SYMBOL: i64 = 604i64; +pub const UNDO_SYMBOL: i64 = 605i64; +pub const UNICODE_SYMBOL: i64 = 606i64; +pub const UNINSTALL_SYMBOL: i64 = 607i64; +pub const UNION_SYMBOL: i64 = 608i64; +pub const UNIQUE_SYMBOL: i64 = 609i64; +pub const UNKNOWN_SYMBOL: i64 = 610i64; +pub const UNLOCK_SYMBOL: i64 = 611i64; +pub const UNSIGNED_SYMBOL: i64 = 612i64; +pub const UNTIL_SYMBOL: i64 = 613i64; +pub const UPDATE_SYMBOL: i64 = 614i64; +pub const UPGRADE_SYMBOL: i64 = 615i64; +pub const USAGE_SYMBOL: i64 = 616i64; +pub const USER_RESOURCES_SYMBOL: i64 = 617i64; +pub const USER_SYMBOL: i64 = 618i64; +pub const USE_FRM_SYMBOL: i64 = 619i64; +pub const USE_SYMBOL: i64 = 620i64; +pub const USING_SYMBOL: i64 = 621i64; +pub const UTC_DATE_SYMBOL: i64 = 622i64; +pub const UTC_TIMESTAMP_SYMBOL: i64 = 623i64; +pub const UTC_TIME_SYMBOL: i64 = 624i64; +pub const VALIDATION_SYMBOL: i64 = 625i64; +pub const VALUES_SYMBOL: i64 = 626i64; +pub const VALUE_SYMBOL: i64 = 627i64; +pub const VARBINARY_SYMBOL: i64 = 628i64; +pub const VARCHAR_SYMBOL: i64 = 629i64; +pub const VARCHARACTER_SYMBOL: i64 = 630i64; +pub const VARIABLES_SYMBOL: i64 = 631i64; +pub const VARIANCE_SYMBOL: i64 = 632i64; +pub const VARYING_SYMBOL: i64 = 633i64; +pub const VAR_POP_SYMBOL: i64 = 634i64; +pub const VAR_SAMP_SYMBOL: i64 = 635i64; +pub const VIEW_SYMBOL: i64 = 636i64; +pub const VIRTUAL_SYMBOL: i64 = 637i64; +pub const WAIT_SYMBOL: i64 = 638i64; +pub const WARNINGS_SYMBOL: i64 = 639i64; +pub const WEEK_SYMBOL: i64 = 640i64; +pub const WEIGHT_STRING_SYMBOL: i64 = 641i64; +pub const WHEN_SYMBOL: i64 = 642i64; +pub const WHERE_SYMBOL: i64 = 643i64; +pub const WHILE_SYMBOL: i64 = 644i64; +pub const WITH_SYMBOL: i64 = 645i64; +pub const WITHOUT_SYMBOL: i64 = 646i64; +pub const WORK_SYMBOL: i64 = 647i64; +pub const WRAPPER_SYMBOL: i64 = 648i64; +pub const WRITE_SYMBOL: i64 = 649i64; +pub const X509_SYMBOL: i64 = 650i64; +pub const XA_SYMBOL: i64 = 651i64; +pub const XID_SYMBOL: i64 = 652i64; +pub const XML_SYMBOL: i64 = 653i64; +pub const XOR_SYMBOL: i64 = 654i64; +pub const YEAR_MONTH_SYMBOL: i64 = 655i64; +pub const YEAR_SYMBOL: i64 = 656i64; +pub const ZEROFILL_SYMBOL: i64 = 657i64; +pub const PERSIST_SYMBOL: i64 = 658i64; +pub const ROLE_SYMBOL: i64 = 659i64; +pub const ADMIN_SYMBOL: i64 = 660i64; +pub const INVISIBLE_SYMBOL: i64 = 661i64; +pub const VISIBLE_SYMBOL: i64 = 662i64; +pub const EXCEPT_SYMBOL: i64 = 663i64; +pub const COMPONENT_SYMBOL: i64 = 664i64; +pub const RECURSIVE_SYMBOL: i64 = 665i64; +pub const JSON_OBJECTAGG_SYMBOL: i64 = 666i64; +pub const JSON_ARRAYAGG_SYMBOL: i64 = 667i64; +pub const OF_SYMBOL: i64 = 668i64; +pub const SKIP_SYMBOL: i64 = 669i64; +pub const LOCKED_SYMBOL: i64 = 670i64; +pub const NOWAIT_SYMBOL: i64 = 671i64; +pub const GROUPING_SYMBOL: i64 = 672i64; +pub const PERSIST_ONLY_SYMBOL: i64 = 673i64; +pub const HISTOGRAM_SYMBOL: i64 = 674i64; +pub const BUCKETS_SYMBOL: i64 = 675i64; +pub const REMOTE_SYMBOL: i64 = 676i64; +pub const CLONE_SYMBOL: i64 = 677i64; +pub const CUME_DIST_SYMBOL: i64 = 678i64; +pub const DENSE_RANK_SYMBOL: i64 = 679i64; +pub const EXCLUDE_SYMBOL: i64 = 680i64; +pub const FIRST_VALUE_SYMBOL: i64 = 681i64; +pub const FOLLOWING_SYMBOL: i64 = 682i64; +pub const GROUPS_SYMBOL: i64 = 683i64; +pub const LAG_SYMBOL: i64 = 684i64; +pub const LAST_VALUE_SYMBOL: i64 = 685i64; +pub const LEAD_SYMBOL: i64 = 686i64; +pub const NTH_VALUE_SYMBOL: i64 = 687i64; +pub const NTILE_SYMBOL: i64 = 688i64; +pub const NULLS_SYMBOL: i64 = 689i64; +pub const OTHERS_SYMBOL: i64 = 690i64; +pub const OVER_SYMBOL: i64 = 691i64; +pub const PERCENT_RANK_SYMBOL: i64 = 692i64; +pub const PRECEDING_SYMBOL: i64 = 693i64; +pub const RANK_SYMBOL: i64 = 694i64; +pub const RESPECT_SYMBOL: i64 = 695i64; +pub const ROW_NUMBER_SYMBOL: i64 = 696i64; +pub const TIES_SYMBOL: i64 = 697i64; +pub const UNBOUNDED_SYMBOL: i64 = 698i64; +pub const WINDOW_SYMBOL: i64 = 699i64; +pub const EMPTY_SYMBOL: i64 = 700i64; +pub const JSON_TABLE_SYMBOL: i64 = 701i64; +pub const NESTED_SYMBOL: i64 = 702i64; +pub const ORDINALITY_SYMBOL: i64 = 703i64; +pub const PATH_SYMBOL: i64 = 704i64; +pub const HISTORY_SYMBOL: i64 = 705i64; +pub const REUSE_SYMBOL: i64 = 706i64; +pub const SRID_SYMBOL: i64 = 707i64; +pub const THREAD_PRIORITY_SYMBOL: i64 = 708i64; +pub const RESOURCE_SYMBOL: i64 = 709i64; +pub const SYSTEM_SYMBOL: i64 = 710i64; +pub const VCPU_SYMBOL: i64 = 711i64; +pub const MASTER_PUBLIC_KEY_PATH_SYMBOL: i64 = 712i64; +pub const GET_MASTER_PUBLIC_KEY_SYMBOL: i64 = 713i64; +pub const RESTART_SYMBOL: i64 = 714i64; +pub const DEFINITION_SYMBOL: i64 = 715i64; +pub const DESCRIPTION_SYMBOL: i64 = 716i64; +pub const ORGANIZATION_SYMBOL: i64 = 717i64; +pub const REFERENCE_SYMBOL: i64 = 718i64; +pub const OPTIONAL_SYMBOL: i64 = 719i64; +pub const SECONDARY_SYMBOL: i64 = 720i64; +pub const SECONDARY_ENGINE_SYMBOL: i64 = 721i64; +pub const SECONDARY_LOAD_SYMBOL: i64 = 722i64; +pub const SECONDARY_UNLOAD_SYMBOL: i64 = 723i64; +pub const ACTIVE_SYMBOL: i64 = 724i64; +pub const INACTIVE_SYMBOL: i64 = 725i64; +pub const LATERAL_SYMBOL: i64 = 726i64; +pub const RETAIN_SYMBOL: i64 = 727i64; +pub const OLD_SYMBOL: i64 = 728i64; +pub const NETWORK_NAMESPACE_SYMBOL: i64 = 729i64; +pub const ENFORCED_SYMBOL: i64 = 730i64; +pub const ARRAY_SYMBOL: i64 = 731i64; +pub const OJ_SYMBOL: i64 = 732i64; +pub const MEMBER_SYMBOL: i64 = 733i64; +pub const RANDOM_SYMBOL: i64 = 734i64; +pub const MASTER_COMPRESSION_ALGORITHM_SYMBOL: i64 = 735i64; +pub const MASTER_ZSTD_COMPRESSION_LEVEL_SYMBOL: i64 = 736i64; +pub const PRIVILEGE_CHECKS_USER_SYMBOL: i64 = 737i64; +pub const MASTER_TLS_CIPHERSUITES_SYMBOL: i64 = 738i64; +pub const REQUIRE_ROW_FORMAT_SYMBOL: i64 = 739i64; +pub const PASSWORD_LOCK_TIME_SYMBOL: i64 = 740i64; +pub const FAILED_LOGIN_ATTEMPTS_SYMBOL: i64 = 741i64; +pub const REQUIRE_TABLE_PRIMARY_KEY_CHECK_SYMBOL: i64 = 742i64; +pub const STREAM_SYMBOL: i64 = 743i64; +pub const OFF_SYMBOL: i64 = 744i64; +pub const AT_AT_SIGN_SYMBOL: i64 = 745i64; +pub const AT_SIGN_SYMBOL: i64 = 746i64; +pub const CLOSE_CURLY_SYMBOL: i64 = 747i64; +pub const CLOSE_PAR_SYMBOL: i64 = 748i64; +pub const COLON_SYMBOL: i64 = 749i64; +pub const COMMA_SYMBOL: i64 = 750i64; +pub const DOT_SYMBOL: i64 = 751i64; +pub const OPEN_CURLY_SYMBOL: i64 = 752i64; +pub const OPEN_PAR_SYMBOL: i64 = 753i64; +pub const PARAM_MARKER: i64 = 754i64; +pub const SEMICOLON_SYMBOL: i64 = 755i64; +pub const ASSIGN_OPERATOR: i64 = 756i64; +pub const BITWISE_AND_OPERATOR: i64 = 757i64; +pub const BITWISE_NOT_OPERATOR: i64 = 758i64; +pub const BITWISE_OR_OPERATOR: i64 = 759i64; +pub const BITWISE_XOR_OPERATOR: i64 = 760i64; +pub const CONCAT_PIPES_SYMBOL: i64 = 761i64; +pub const DIV_OPERATOR: i64 = 762i64; +pub const EQUAL_OPERATOR: i64 = 763i64; +pub const GREATER_OR_EQUAL_OPERATOR: i64 = 764i64; +pub const GREATER_THAN_OPERATOR: i64 = 765i64; +pub const JSON_SEPARATOR_SYMBOL: i64 = 766i64; +pub const JSON_UNQUOTED_SEPARATOR_SYMBOL: i64 = 767i64; +pub const LESS_OR_EQUAL_OPERATOR: i64 = 768i64; +pub const LESS_THAN_OPERATOR: i64 = 769i64; +pub const LOGICAL_AND_OPERATOR: i64 = 770i64; +pub const LOGICAL_NOT_OPERATOR: i64 = 771i64; +pub const LOGICAL_OR_OPERATOR: i64 = 772i64; +pub const MINUS_OPERATOR: i64 = 773i64; +pub const MOD_OPERATOR: i64 = 774i64; +pub const MULT_OPERATOR: i64 = 775i64; +pub const NOT_EQUAL_OPERATOR: i64 = 776i64; +pub const NULL_SAFE_EQUAL_OPERATOR: i64 = 777i64; +pub const PLUS_OPERATOR: i64 = 778i64; +pub const SHIFT_LEFT_OPERATOR: i64 = 779i64; +pub const SHIFT_RIGHT_OPERATOR: i64 = 780i64; +pub const BACK_TICK_QUOTED_ID: i64 = 781i64; +pub const BIN_NUMBER: i64 = 782i64; +pub const DECIMAL_NUMBER: i64 = 783i64; +pub const DOUBLE_QUOTED_TEXT: i64 = 784i64; +pub const FLOAT_NUMBER: i64 = 785i64; +pub const HEX_NUMBER: i64 = 786i64; +pub const INT_NUMBER: i64 = 787i64; +pub const LONG_NUMBER: i64 = 788i64; +pub const NCHAR_TEXT: i64 = 789i64; +pub const SINGLE_QUOTED_TEXT: i64 = 790i64; +pub const ULONGLONG_NUMBER: i64 = 791i64; +pub const AT_TEXT_SUFFIX: i64 = 792i64; +pub const IDENTIFIER: i64 = 793i64; +pub const UNDERSCORE_CHARSET: i64 = 794i64; +pub const INT1_SYMBOL: i64 = 795i64; +pub const INT2_SYMBOL: i64 = 796i64; +pub const INT3_SYMBOL: i64 = 797i64; +pub const INT4_SYMBOL: i64 = 798i64; +pub const INT8_SYMBOL: i64 = 799i64; +pub const NOT2_SYMBOL: i64 = 800i64; +pub const NULL2_SYMBOL: i64 = 801i64; +pub const SQL_TSI_DAY_SYMBOL: i64 = 802i64; +pub const SQL_TSI_HOUR_SYMBOL: i64 = 803i64; +pub const SQL_TSI_MICROSECOND_SYMBOL: i64 = 804i64; +pub const SQL_TSI_MINUTE_SYMBOL: i64 = 805i64; +pub const SQL_TSI_MONTH_SYMBOL: i64 = 806i64; +pub const SQL_TSI_QUARTER_SYMBOL: i64 = 807i64; +pub const SQL_TSI_SECOND_SYMBOL: i64 = 808i64; +pub const SQL_TSI_WEEK_SYMBOL: i64 = 809i64; +pub const SQL_TSI_YEAR_SYMBOL: i64 = 810i64; +pub const INTERSECT_SYMBOL: i64 = 811i64; +pub const ATTRIBUTE_SYMBOL: i64 = 812i64; +pub const SOURCE_AUTO_POSITION_SYMBOL: i64 = 813i64; +pub const SOURCE_BIND_SYMBOL: i64 = 814i64; +pub const SOURCE_COMPRESSION_ALGORITHM_SYMBOL: i64 = 815i64; +pub const SOURCE_CONNECT_RETRY_SYMBOL: i64 = 816i64; +pub const SOURCE_CONNECTION_AUTO_FAILOVER_SYMBOL: i64 = 817i64; +pub const SOURCE_DELAY_SYMBOL: i64 = 818i64; +pub const SOURCE_HEARTBEAT_PERIOD_SYMBOL: i64 = 819i64; +pub const SOURCE_HOST_SYMBOL: i64 = 820i64; +pub const SOURCE_LOG_FILE_SYMBOL: i64 = 821i64; +pub const SOURCE_LOG_POS_SYMBOL: i64 = 822i64; +pub const SOURCE_PASSWORD_SYMBOL: i64 = 823i64; +pub const SOURCE_PORT_SYMBOL: i64 = 824i64; +pub const SOURCE_PUBLIC_KEY_PATH_SYMBOL: i64 = 825i64; +pub const SOURCE_RETRY_COUNT_SYMBOL: i64 = 826i64; +pub const SOURCE_SSL_SYMBOL: i64 = 827i64; +pub const SOURCE_SSL_CA_SYMBOL: i64 = 828i64; +pub const SOURCE_SSL_CAPATH_SYMBOL: i64 = 829i64; +pub const SOURCE_SSL_CERT_SYMBOL: i64 = 830i64; +pub const SOURCE_SSL_CIPHER_SYMBOL: i64 = 831i64; +pub const SOURCE_SSL_CRL_SYMBOL: i64 = 832i64; +pub const SOURCE_SSL_CRLPATH_SYMBOL: i64 = 833i64; +pub const SOURCE_SSL_KEY_SYMBOL: i64 = 834i64; +pub const SOURCE_SSL_VERIFY_SERVER_CERT_SYMBOL: i64 = 835i64; +pub const SOURCE_TLS_CIPHERSUITES_SYMBOL: i64 = 836i64; +pub const SOURCE_TLS_VERSION_SYMBOL: i64 = 837i64; +pub const SOURCE_USER_SYMBOL: i64 = 838i64; +pub const SOURCE_ZSTD_COMPRESSION_LEVEL_SYMBOL: i64 = 839i64; +pub const GET_SOURCE_PUBLIC_KEY_SYMBOL: i64 = 840i64; +pub const GTID_ONLY_SYMBOL: i64 = 841i64; +pub const ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_SYMBOL: i64 = 842i64; +pub const ZONE_SYMBOL: i64 = 843i64; +pub const INNODB_SYMBOL: i64 = 844i64; +pub const TLS_SYMBOL: i64 = 845i64; +pub const REDO_LOG_SYMBOL: i64 = 846i64; +pub const KEYRING_SYMBOL: i64 = 847i64; +pub const ENGINE_ATTRIBUTE_SYMBOL: i64 = 848i64; +pub const SECONDARY_ENGINE_ATTRIBUTE_SYMBOL: i64 = 849i64; +pub const JSON_VALUE_SYMBOL: i64 = 850i64; +pub const RETURNING_SYMBOL: i64 = 851i64; +pub const GEOMCOLLECTION_SYMBOL: i64 = 852i64; +pub const COMMENT: i64 = 900i64; +pub const MYSQL_COMMENT_START: i64 = 901i64; +pub const MYSQL_COMMENT_END: i64 = 902i64; +pub const WHITESPACE: i64 = 0i64; +pub const EOF: i64 = -1i64; + +pub const KEYWORD_TOKENS: &[(&str, i64)] = &[ + ("ACCESSIBLE", 1i64), + ("ACCOUNT", 2i64), + ("ACTION", 3i64), + ("ADD", 4i64), + ("ADDDATE", 5i64), + ("AFTER", 6i64), + ("AGAINST", 7i64), + ("AGGREGATE", 8i64), + ("ALGORITHM", 9i64), + ("ALL", 10i64), + ("ALTER", 11i64), + ("ALWAYS", 12i64), + ("ANALYSE", 13i64), + ("ANALYZE", 14i64), + ("AND", 15i64), + ("ANY", 16i64), + ("AS", 17i64), + ("ASC", 18i64), + ("ASCII", 19i64), + ("ASENSITIVE", 20i64), + ("AT", 21i64), + ("ATTRIBUTE", 812i64), + ("AUTHORS", 22i64), + ("AUTO_INCREMENT", 24i64), + ("AUTOEXTEND_SIZE", 23i64), + ("AVG", 26i64), + ("AVG_ROW_LENGTH", 25i64), + ("BACKUP", 27i64), + ("BEFORE", 28i64), + ("BEGIN", 29i64), + ("BETWEEN", 30i64), + ("BIGINT", 31i64), + ("BIN_NUM", 34i64), + ("BINARY", 32i64), + ("BINLOG", 33i64), + ("BIT", 37i64), + ("BIT_AND", 35i64), + ("BIT_OR", 36i64), + ("BIT_XOR", 38i64), + ("BLOB", 39i64), + ("BLOCK", 40i64), + ("BOOL", 42i64), + ("BOOLEAN", 41i64), + ("BOTH", 43i64), + ("BTREE", 44i64), + ("BY", 45i64), + ("BYTE", 46i64), + ("CACHE", 47i64), + ("CALL", 48i64), + ("CASCADE", 49i64), + ("CASCADED", 50i64), + ("CASE", 51i64), + ("CAST", 52i64), + ("CATALOG_NAME", 53i64), + ("CHAIN", 54i64), + ("CHANGE", 55i64), + ("CHANGED", 56i64), + ("CHANNEL", 57i64), + ("CHAR", 60i64), + ("CHARACTER", 59i64), + ("CHARSET", 58i64), + ("CHECK", 62i64), + ("CHECKSUM", 61i64), + ("CIPHER", 63i64), + ("CLASS_ORIGIN", 64i64), + ("CLIENT", 65i64), + ("CLOSE", 66i64), + ("COALESCE", 67i64), + ("CODE", 68i64), + ("COLLATE", 69i64), + ("COLLATION", 70i64), + ("COLUMN", 72i64), + ("COLUMN_FORMAT", 74i64), + ("COLUMN_NAME", 73i64), + ("COLUMNS", 71i64), + ("COMMENT", 75i64), + ("COMMIT", 77i64), + ("COMMITTED", 76i64), + ("COMPACT", 78i64), + ("COMPLETION", 79i64), + ("COMPRESSED", 80i64), + ("COMPRESSION", 81i64), + ("CONCURRENT", 82i64), + ("CONDITION", 83i64), + ("CONNECTION", 84i64), + ("CONSISTENT", 85i64), + ("CONSTRAINT", 86i64), + ("CONSTRAINT_CATALOG", 87i64), + ("CONSTRAINT_NAME", 88i64), + ("CONSTRAINT_SCHEMA", 89i64), + ("CONTAINS", 90i64), + ("CONTEXT", 91i64), + ("CONTINUE", 92i64), + ("CONTRIBUTORS", 93i64), + ("CONVERT", 94i64), + ("COUNT", 95i64), + ("CPU", 96i64), + ("CREATE", 97i64), + ("CROSS", 98i64), + ("CUBE", 99i64), + ("CURDATE", 100i64), + ("CURRENT", 101i64), + ("CURRENT_DATE", 102i64), + ("CURRENT_TIME", 103i64), + ("CURRENT_TIMESTAMP", 104i64), + ("CURRENT_USER", 105i64), + ("CURSOR", 106i64), + ("CURSOR_NAME", 107i64), + ("CURTIME", 108i64), + ("DATA", 112i64), + ("DATABASE", 109i64), + ("DATABASES", 110i64), + ("DATAFILE", 111i64), + ("DATE", 116i64), + ("DATE_ADD", 114i64), + ("DATE_SUB", 115i64), + ("DATETIME", 113i64), + ("DAY", 122i64), + ("DAY_HOUR", 118i64), + ("DAY_MICROSECOND", 119i64), + ("DAY_MINUTE", 120i64), + ("DAY_SECOND", 121i64), + ("DAYOFMONTH", 117i64), + ("DEALLOCATE", 123i64), + ("DEC", 124i64), + ("DECIMAL", 126i64), + ("DECIMAL_NUM", 125i64), + ("DECLARE", 127i64), + ("DEFAULT", 128i64), + ("DEFAULT_AUTH", 129i64), + ("DEFINER", 130i64), + ("DELAY_KEY_WRITE", 132i64), + ("DELAYED", 131i64), + ("DELETE", 133i64), + ("DES_KEY_FILE", 136i64), + ("DESC", 134i64), + ("DESCRIBE", 135i64), + ("DETERMINISTIC", 137i64), + ("DIAGNOSTICS", 138i64), + ("DIRECTORY", 139i64), + ("DISABLE", 140i64), + ("DISCARD", 141i64), + ("DISK", 142i64), + ("DISTINCT", 143i64), + ("DISTINCTROW", 144i64), + ("DIV", 145i64), + ("DO", 147i64), + ("DOUBLE", 146i64), + ("DROP", 148i64), + ("DUAL", 149i64), + ("DUMPFILE", 150i64), + ("DUPLICATE", 151i64), + ("DYNAMIC", 152i64), + ("EACH", 153i64), + ("ELSE", 154i64), + ("ELSEIF", 155i64), + ("ENABLE", 156i64), + ("ENCLOSED", 157i64), + ("ENCRYPTION", 158i64), + ("END", 159i64), + ("END_OF_INPUT", -1i64), + ("ENDS", 160i64), + ("ENGINE", 163i64), + ("ENGINES", 162i64), + ("ENUM", 164i64), + ("ERROR", 165i64), + ("ERRORS", 166i64), + ("ESCAPE", 168i64), + ("ESCAPED", 167i64), + ("EVENT", 170i64), + ("EVENTS", 169i64), + ("EVERY", 171i64), + ("EXCHANGE", 172i64), + ("EXECUTE", 173i64), + ("EXISTS", 174i64), + ("EXIT", 175i64), + ("EXPANSION", 176i64), + ("EXPIRE", 177i64), + ("EXPLAIN", 178i64), + ("EXPORT", 179i64), + ("EXTENDED", 180i64), + ("EXTENT_SIZE", 181i64), + ("EXTRACT", 182i64), + ("FALSE", 183i64), + ("FAST", 184i64), + ("FAULTS", 185i64), + ("FETCH", 186i64), + ("FIELDS", 187i64), + ("FILE", 188i64), + ("FILE_BLOCK_SIZE", 189i64), + ("FILTER", 190i64), + ("FIRST", 191i64), + ("FIXED", 192i64), + ("FLOAT", 195i64), + ("FLOAT4", 193i64), + ("FLOAT8", 194i64), + ("FLUSH", 196i64), + ("FOLLOWS", 197i64), + ("FOR", 200i64), + ("FORCE", 198i64), + ("FOREIGN", 199i64), + ("FORMAT", 201i64), + ("FOUND", 202i64), + ("FROM", 203i64), + ("FULL", 204i64), + ("FULLTEXT", 205i64), + ("FUNCTION", 206i64), + ("GENERAL", 208i64), + ("GENERATED", 209i64), + ("GEOMCOLLECTION", 852i64), + ("GEOMETRY", 212i64), + ("GEOMETRYCOLLECTION", 211i64), + ("GET", 207i64), + ("GET_FORMAT", 213i64), + ("GLOBAL", 214i64), + ("GRANT", 215i64), + ("GRANTS", 216i64), + ("GROUP", 217i64), + ("GROUP_CONCAT", 218i64), + ("GROUP_REPLICATION", 210i64), + ("HANDLER", 219i64), + ("HASH", 220i64), + ("HAVING", 221i64), + ("HELP", 222i64), + ("HIGH_PRIORITY", 223i64), + ("HOST", 224i64), + ("HOSTS", 225i64), + ("HOUR", 229i64), + ("HOUR_MICROSECOND", 226i64), + ("HOUR_MINUTE", 227i64), + ("HOUR_SECOND", 228i64), + ("IDENTIFIED", 230i64), + ("IF", 231i64), + ("IGNORE", 232i64), + ("IGNORE_SERVER_IDS", 233i64), + ("IMPORT", 234i64), + ("IN", 251i64), + ("INDEX", 236i64), + ("INDEXES", 235i64), + ("INFILE", 237i64), + ("INITIAL_SIZE", 238i64), + ("INNER", 239i64), + ("INNODB", 844i64), + ("INOUT", 240i64), + ("INSENSITIVE", 241i64), + ("INSERT", 242i64), + ("INSERT_METHOD", 243i64), + ("INSTALL", 245i64), + ("INSTANCE", 244i64), + ("INT", 249i64), + ("INT1", 795i64), + ("INT2", 796i64), + ("INT3", 797i64), + ("INT4", 798i64), + ("INT8", 799i64), + ("INTEGER", 246i64), + ("INTERVAL", 247i64), + ("INTO", 248i64), + ("INVOKER", 250i64), + ("IO", 255i64), + ("IO_AFTER_GTIDS", 252i64), + ("IO_BEFORE_GTIDS", 253i64), + ("IO_THREAD", 254i64), + ("IPC", 256i64), + ("IS", 257i64), + ("ISOLATION", 258i64), + ("ISSUER", 259i64), + ("ITERATE", 260i64), + ("JOIN", 261i64), + ("JSON", 262i64), + ("KEY", 265i64), + ("KEY_BLOCK_SIZE", 264i64), + ("KEYS", 263i64), + ("KILL", 266i64), + ("LANGUAGE", 267i64), + ("LAST", 268i64), + ("LEADING", 269i64), + ("LEAVE", 271i64), + ("LEAVES", 270i64), + ("LEFT", 272i64), + ("LESS", 273i64), + ("LEVEL", 274i64), + ("LIKE", 275i64), + ("LIMIT", 276i64), + ("LINEAR", 277i64), + ("LINES", 278i64), + ("LINESTRING", 279i64), + ("LIST", 280i64), + ("LOAD", 281i64), + ("LOCAL", 284i64), + ("LOCALTIME", 282i64), + ("LOCALTIMESTAMP", 283i64), + ("LOCATOR", 285i64), + ("LOCK", 287i64), + ("LOCKS", 286i64), + ("LOGFILE", 288i64), + ("LOGS", 289i64), + ("LONG", 293i64), + ("LONG_NUM", 292i64), + ("LONGBLOB", 290i64), + ("LONGTEXT", 291i64), + ("LOOP", 294i64), + ("LOW_PRIORITY", 295i64), + ("MASTER", 316i64), + ("MASTER_AUTO_POSITION", 296i64), + ("MASTER_BIND", 297i64), + ("MASTER_CONNECT_RETRY", 298i64), + ("MASTER_DELAY", 299i64), + ("MASTER_HEARTBEAT_PERIOD", 319i64), + ("MASTER_HOST", 300i64), + ("MASTER_LOG_FILE", 301i64), + ("MASTER_LOG_POS", 302i64), + ("MASTER_PASSWORD", 303i64), + ("MASTER_PORT", 304i64), + ("MASTER_RETRY_COUNT", 305i64), + ("MASTER_SERVER_ID", 306i64), + ("MASTER_SSL", 314i64), + ("MASTER_SSL_CA", 308i64), + ("MASTER_SSL_CAPATH", 307i64), + ("MASTER_SSL_CERT", 309i64), + ("MASTER_SSL_CIPHER", 310i64), + ("MASTER_SSL_CRL", 311i64), + ("MASTER_SSL_CRLPATH", 312i64), + ("MASTER_SSL_KEY", 313i64), + ("MASTER_SSL_VERIFY_SERVER_CERT", 315i64), + ("MASTER_TLS_VERSION", 317i64), + ("MASTER_USER", 318i64), + ("MATCH", 320i64), + ("MAX", 326i64), + ("MAX_CONNECTIONS_PER_HOUR", 321i64), + ("MAX_QUERIES_PER_HOUR", 322i64), + ("MAX_ROWS", 323i64), + ("MAX_SIZE", 324i64), + ("MAX_STATEMENT_TIME", 325i64), + ("MAX_UPDATES_PER_HOUR", 327i64), + ("MAX_USER_CONNECTIONS", 328i64), + ("MAXVALUE", 329i64), + ("MEDIUM", 333i64), + ("MEDIUMBLOB", 330i64), + ("MEDIUMINT", 331i64), + ("MEDIUMTEXT", 332i64), + ("MEMORY", 334i64), + ("MERGE", 335i64), + ("MESSAGE_TEXT", 336i64), + ("MICROSECOND", 337i64), + ("MID", 338i64), + ("MIDDLEINT", 339i64), + ("MIGRATE", 340i64), + ("MIN", 345i64), + ("MIN_ROWS", 344i64), + ("MINUTE", 343i64), + ("MINUTE_MICROSECOND", 341i64), + ("MINUTE_SECOND", 342i64), + ("MOD", 349i64), + ("MODE", 346i64), + ("MODIFIES", 347i64), + ("MODIFY", 348i64), + ("MONTH", 350i64), + ("MULTILINESTRING", 351i64), + ("MULTIPOINT", 352i64), + ("MULTIPOLYGON", 353i64), + ("MUTEX", 354i64), + ("MYSQL_ERRNO", 355i64), + ("NAME", 357i64), + ("NAMES", 356i64), + ("NATIONAL", 358i64), + ("NATURAL", 359i64), + ("NCHAR", 361i64), + ("NCHAR_STRING", 360i64), + ("NDB", 362i64), + ("NDBCLUSTER", 363i64), + ("NEG", 364i64), + ("NEVER", 365i64), + ("NEW", 366i64), + ("NEXT", 367i64), + ("NO", 373i64), + ("NO_WAIT", 374i64), + ("NO_WRITE_TO_BINLOG", 375i64), + ("NODEGROUP", 368i64), + ("NONBLOCKING", 370i64), + ("NONE", 369i64), + ("NOT", 371i64), + ("NOW", 372i64), + ("NULL", 376i64), + ("NUMBER", 377i64), + ("NUMERIC", 378i64), + ("NVARCHAR", 379i64), + ("OFFLINE", 380i64), + ("OFFSET", 381i64), + ("OLD_PASSWORD", 382i64), + ("ON", 383i64), + ("ONE", 384i64), + ("ONLINE", 385i64), + ("ONLY", 386i64), + ("OPEN", 387i64), + ("OPTIMIZE", 388i64), + ("OPTIMIZER_COSTS", 389i64), + ("OPTION", 391i64), + ("OPTIONALLY", 392i64), + ("OPTIONS", 390i64), + ("OR", 394i64), + ("ORDER", 393i64), + ("OUT", 397i64), + ("OUTER", 395i64), + ("OUTFILE", 396i64), + ("OWNER", 398i64), + ("PACK_KEYS", 399i64), + ("PAGE", 400i64), + ("PARSER", 401i64), + ("PARTIAL", 402i64), + ("PARTITION", 405i64), + ("PARTITIONING", 403i64), + ("PARTITIONS", 404i64), + ("PASSWORD", 406i64), + ("PHASE", 407i64), + ("PLUGIN", 410i64), + ("PLUGIN_DIR", 409i64), + ("PLUGINS", 408i64), + ("POINT", 411i64), + ("POLYGON", 412i64), + ("PORT", 413i64), + ("POSITION", 414i64), + ("PRECEDES", 415i64), + ("PRECISION", 416i64), + ("PREPARE", 417i64), + ("PRESERVE", 418i64), + ("PREV", 419i64), + ("PRIMARY", 420i64), + ("PRIVILEGES", 421i64), + ("PROCEDURE", 422i64), + ("PROCESS", 423i64), + ("PROCESSLIST", 424i64), + ("PROFILE", 425i64), + ("PROFILES", 426i64), + ("PROXY", 427i64), + ("PURGE", 428i64), + ("QUARTER", 429i64), + ("QUERY", 430i64), + ("QUICK", 431i64), + ("RANGE", 432i64), + ("READ", 435i64), + ("READ_ONLY", 434i64), + ("READ_WRITE", 436i64), + ("READS", 433i64), + ("REAL", 437i64), + ("REBUILD", 438i64), + ("RECOVER", 439i64), + ("REDO_BUFFER_SIZE", 441i64), + ("REDOFILE", 440i64), + ("REDUNDANT", 442i64), + ("REFERENCES", 443i64), + ("REGEXP", 444i64), + ("RELAY", 445i64), + ("RELAY_LOG_FILE", 447i64), + ("RELAY_LOG_POS", 448i64), + ("RELAY_THREAD", 449i64), + ("RELAYLOG", 446i64), + ("RELEASE", 450i64), + ("RELOAD", 451i64), + ("REMOVE", 452i64), + ("RENAME", 453i64), + ("REORGANIZE", 454i64), + ("REPAIR", 455i64), + ("REPEAT", 457i64), + ("REPEATABLE", 456i64), + ("REPLACE", 458i64), + ("REPLICATE_DO_DB", 460i64), + ("REPLICATE_DO_TABLE", 462i64), + ("REPLICATE_IGNORE_DB", 461i64), + ("REPLICATE_IGNORE_TABLE", 463i64), + ("REPLICATE_REWRITE_DB", 466i64), + ("REPLICATE_WILD_DO_TABLE", 464i64), + ("REPLICATE_WILD_IGNORE_TABLE", 465i64), + ("REPLICATION", 459i64), + ("REQUIRE", 467i64), + ("RESET", 468i64), + ("RESIGNAL", 469i64), + ("RESTORE", 470i64), + ("RESTRICT", 471i64), + ("RESUME", 472i64), + ("RETURN", 475i64), + ("RETURNED_SQLSTATE", 473i64), + ("RETURNS", 474i64), + ("REVERSE", 476i64), + ("REVOKE", 477i64), + ("RIGHT", 478i64), + ("RLIKE", 479i64), + ("ROLLBACK", 480i64), + ("ROLLUP", 481i64), + ("ROTATE", 482i64), + ("ROUTINE", 483i64), + ("ROW", 487i64), + ("ROW_COUNT", 485i64), + ("ROW_FORMAT", 486i64), + ("ROWS", 484i64), + ("RTREE", 488i64), + ("SAVEPOINT", 489i64), + ("SCHEDULE", 490i64), + ("SCHEMA", 491i64), + ("SCHEMA_NAME", 492i64), + ("SCHEMAS", 493i64), + ("SECOND", 495i64), + ("SECOND_MICROSECOND", 494i64), + ("SECURITY", 496i64), + ("SELECT", 497i64), + ("SENSITIVE", 498i64), + ("SEPARATOR", 499i64), + ("SERIAL", 501i64), + ("SERIALIZABLE", 500i64), + ("SERVER", 503i64), + ("SERVER_OPTIONS", 504i64), + ("SESSION", 502i64), + ("SESSION_USER", 505i64), + ("SET", 506i64), + ("SET_VAR", 507i64), + ("SHARE", 508i64), + ("SHOW", 509i64), + ("SHUTDOWN", 510i64), + ("SIGNAL", 511i64), + ("SIGNED", 512i64), + ("SIMPLE", 513i64), + ("SLAVE", 514i64), + ("SLOW", 515i64), + ("SMALLINT", 516i64), + ("SNAPSHOT", 517i64), + ("SOCKET", 519i64), + ("SOME", 518i64), + ("SONAME", 520i64), + ("SOUNDS", 521i64), + ("SOURCE", 522i64), + ("SPATIAL", 523i64), + ("SPECIFIC", 524i64), + ("SQL", 537i64), + ("SQL_AFTER_GTIDS", 528i64), + ("SQL_AFTER_MTS_GAPS", 529i64), + ("SQL_BEFORE_GTIDS", 530i64), + ("SQL_BIG_RESULT", 531i64), + ("SQL_BUFFER_RESULT", 532i64), + ("SQL_CACHE", 533i64), + ("SQL_CALC_FOUND_ROWS", 534i64), + ("SQL_NO_CACHE", 535i64), + ("SQL_SMALL_RESULT", 536i64), + ("SQL_THREAD", 538i64), + ("SQL_TSI_DAY", 802i64), + ("SQL_TSI_HOUR", 803i64), + ("SQL_TSI_MICROSECOND", 804i64), + ("SQL_TSI_MINUTE", 805i64), + ("SQL_TSI_MONTH", 806i64), + ("SQL_TSI_QUARTER", 807i64), + ("SQL_TSI_SECOND", 808i64), + ("SQL_TSI_WEEK", 809i64), + ("SQL_TSI_YEAR", 810i64), + ("SQLEXCEPTION", 525i64), + ("SQLSTATE", 526i64), + ("SQLWARNING", 527i64), + ("SSL", 539i64), + ("STACKED", 540i64), + ("START", 543i64), + ("STARTING", 541i64), + ("STARTS", 542i64), + ("STATS_AUTO_RECALC", 544i64), + ("STATS_PERSISTENT", 545i64), + ("STATS_SAMPLE_PAGES", 546i64), + ("STATUS", 547i64), + ("STD", 551i64), + ("STDDEV", 549i64), + ("STDDEV_POP", 550i64), + ("STDDEV_SAMP", 548i64), + ("STOP", 552i64), + ("STORAGE", 553i64), + ("STORED", 554i64), + ("STRAIGHT_JOIN", 555i64), + ("STRING", 556i64), + ("SUBCLASS_ORIGIN", 557i64), + ("SUBDATE", 558i64), + ("SUBJECT", 559i64), + ("SUBPARTITION", 561i64), + ("SUBPARTITIONS", 560i64), + ("SUBSTR", 562i64), + ("SUBSTRING", 563i64), + ("SUM", 564i64), + ("SUPER", 565i64), + ("SUSPEND", 566i64), + ("SWAPS", 567i64), + ("SWITCHES", 568i64), + ("SYSDATE", 569i64), + ("SYSTEM_USER", 570i64), + ("TABLE", 574i64), + ("TABLE_CHECKSUM", 575i64), + ("TABLE_NAME", 576i64), + ("TABLE_REF_PRIORITY", 573i64), + ("TABLES", 571i64), + ("TABLESPACE", 572i64), + ("TEMPORARY", 577i64), + ("TEMPTABLE", 578i64), + ("TERMINATED", 579i64), + ("TEXT", 580i64), + ("THAN", 581i64), + ("THEN", 582i64), + ("TIME", 586i64), + ("TIMESTAMP", 583i64), + ("TIMESTAMP_ADD", 584i64), + ("TIMESTAMP_DIFF", 585i64), + ("TINYBLOB", 587i64), + ("TINYINT", 588i64), + ("TINYTEXT", 589i64), + ("TO", 590i64), + ("TRAILING", 591i64), + ("TRANSACTION", 592i64), + ("TRIGGER", 594i64), + ("TRIGGERS", 593i64), + ("TRIM", 595i64), + ("TRUE", 596i64), + ("TRUNCATE", 597i64), + ("TYPE", 599i64), + ("TYPES", 598i64), + ("UDF_RETURNS", 600i64), + ("UNCOMMITTED", 601i64), + ("UNDEFINED", 602i64), + ("UNDO", 605i64), + ("UNDO_BUFFER_SIZE", 604i64), + ("UNDOFILE", 603i64), + ("UNICODE", 606i64), + ("UNINSTALL", 607i64), + ("UNION", 608i64), + ("UNIQUE", 609i64), + ("UNKNOWN", 610i64), + ("UNLOCK", 611i64), + ("UNSIGNED", 612i64), + ("UNTIL", 613i64), + ("UPDATE", 614i64), + ("UPGRADE", 615i64), + ("USAGE", 616i64), + ("USE", 620i64), + ("USE_FRM", 619i64), + ("USER", 618i64), + ("USER_RESOURCES", 617i64), + ("USING", 621i64), + ("UTC_DATE", 622i64), + ("UTC_TIME", 624i64), + ("UTC_TIMESTAMP", 623i64), + ("VALIDATION", 625i64), + ("VALUE", 627i64), + ("VALUES", 626i64), + ("VAR_POP", 634i64), + ("VAR_SAMP", 635i64), + ("VARBINARY", 628i64), + ("VARCHAR", 629i64), + ("VARCHARACTER", 630i64), + ("VARIABLES", 631i64), + ("VARIANCE", 632i64), + ("VARYING", 633i64), + ("VIEW", 636i64), + ("VIRTUAL", 637i64), + ("WAIT", 638i64), + ("WARNINGS", 639i64), + ("WEEK", 640i64), + ("WEIGHT_STRING", 641i64), + ("WHEN", 642i64), + ("WHERE", 643i64), + ("WHILE", 644i64), + ("WITH", 645i64), + ("WITHOUT", 646i64), + ("WORK", 647i64), + ("WRAPPER", 648i64), + ("WRITE", 649i64), + ("X509", 650i64), + ("XA", 651i64), + ("XID", 652i64), + ("XML", 653i64), + ("XOR", 654i64), + ("YEAR", 656i64), + ("YEAR_MONTH", 655i64), + ("ZEROFILL", 657i64), + ("ACTIVE", 724i64), + ("ADMIN", 660i64), + ("ARRAY", 731i64), + ("ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS", 842i64), + ("BUCKETS", 675i64), + ("CLONE", 677i64), + ("COMPONENT", 664i64), + ("CUME_DIST", 678i64), + ("DEFINITION", 715i64), + ("DENSE_RANK", 679i64), + ("DESCRIPTION", 716i64), + ("EMPTY", 700i64), + ("ENFORCED", 730i64), + ("ENGINE_ATTRIBUTE", 848i64), + ("EXCEPT", 663i64), + ("EXCLUDE", 680i64), + ("FAILED_LOGIN_ATTEMPTS", 741i64), + ("FIRST_VALUE", 681i64), + ("FOLLOWING", 682i64), + ("GET_MASTER_PUBLIC_KEY_SYM", 713i64), + ("GET_SOURCE_PUBLIC_KEY", 840i64), + ("GROUPING", 672i64), + ("GROUPS", 683i64), + ("GTID_ONLY", 841i64), + ("HISTOGRAM", 674i64), + ("HISTORY", 705i64), + ("INACTIVE", 725i64), + ("INTERSECT", 811i64), + ("INVISIBLE", 661i64), + ("JSON_ARRAYAGG", 667i64), + ("JSON_OBJECTAGG", 666i64), + ("JSON_TABLE", 701i64), + ("JSON_VALUE", 850i64), + ("KEYRING", 847i64), + ("LAG", 684i64), + ("LAST_VALUE", 685i64), + ("LATERAL", 726i64), + ("LEAD", 686i64), + ("LOCKED", 670i64), + ("MASTER_COMPRESSION_ALGORITHM", 735i64), + ("MASTER_PUBLIC_KEY_PATH", 712i64), + ("MASTER_TLS_CIPHERSUITES", 738i64), + ("MASTER_ZSTD_COMPRESSION_LEVEL", 736i64), + ("MEMBER", 733i64), + ("NESTED", 702i64), + ("NETWORK_NAMESPACE", 729i64), + ("NOWAIT", 671i64), + ("NTH_VALUE", 687i64), + ("NTILE", 688i64), + ("NULLS", 689i64), + ("OF", 668i64), + ("OFF", 744i64), + ("OJ", 732i64), + ("OLD", 728i64), + ("OPTIONAL", 719i64), + ("ORDINALITY", 703i64), + ("ORGANIZATION", 717i64), + ("OTHERS", 690i64), + ("OVER", 691i64), + ("PASSWORD_LOCK_TIME", 740i64), + ("PATH", 704i64), + ("PERCENT_RANK", 692i64), + ("PERSIST", 658i64), + ("PERSIST_ONLY", 673i64), + ("PRECEDING", 693i64), + ("PRIVILEGE_CHECKS_USER", 737i64), + ("RANDOM", 734i64), + ("RANK", 694i64), + ("RECURSIVE", 665i64), + ("REDO_LOG", 846i64), + ("REFERENCE", 718i64), + ("REMOTE", 676i64), + ("REQUIRE_ROW_FORMAT", 739i64), + ("REQUIRE_TABLE_PRIMARY_KEY_CHECK", 742i64), + ("RESOURCE", 709i64), + ("RESPECT", 695i64), + ("RESTART", 714i64), + ("RETAIN", 727i64), + ("RETURNING", 851i64), + ("REUSE", 706i64), + ("ROLE", 659i64), + ("ROW_NUMBER", 696i64), + ("SECONDARY", 720i64), + ("SECONDARY_ENGINE", 721i64), + ("SECONDARY_ENGINE_ATTRIBUTE", 849i64), + ("SECONDARY_LOAD", 722i64), + ("SECONDARY_UNLOAD", 723i64), + ("SKIP", 669i64), + ("SOURCE_AUTO_POSITION", 813i64), + ("SOURCE_BIND", 814i64), + ("SOURCE_COMPRESSION_ALGORITHM", 815i64), + ("SOURCE_CONNECT_RETRY", 816i64), + ("SOURCE_CONNECTION_AUTO_FAILOVER", 817i64), + ("SOURCE_DELAY", 818i64), + ("SOURCE_HEARTBEAT_PERIOD", 819i64), + ("SOURCE_HOST", 820i64), + ("SOURCE_LOG_FILE", 821i64), + ("SOURCE_LOG_POS", 822i64), + ("SOURCE_PASSWORD", 823i64), + ("SOURCE_PORT", 824i64), + ("SOURCE_PUBLIC_KEY_PATH", 825i64), + ("SOURCE_RETRY_COUNT", 826i64), + ("SOURCE_SSL", 827i64), + ("SOURCE_SSL_CA", 828i64), + ("SOURCE_SSL_CAPATH", 829i64), + ("SOURCE_SSL_CERT", 830i64), + ("SOURCE_SSL_CIPHER", 831i64), + ("SOURCE_SSL_CRL", 832i64), + ("SOURCE_SSL_CRLPATH", 833i64), + ("SOURCE_SSL_KEY", 834i64), + ("SOURCE_SSL_VERIFY_SERVER_CERT", 835i64), + ("SOURCE_TLS_CIPHERSUITES", 836i64), + ("SOURCE_TLS_VERSION", 837i64), + ("SOURCE_USER", 838i64), + ("SOURCE_ZSTD_COMPRESSION_LEVEL", 839i64), + ("SRID", 707i64), + ("STREAM", 743i64), + ("SYSTEM", 710i64), + ("THREAD_PRIORITY", 708i64), + ("TIES", 697i64), + ("TLS", 845i64), + ("UNBOUNDED", 698i64), + ("VCPU", 711i64), + ("VISIBLE", 662i64), + ("WINDOW", 699i64), + ("ZONE", 843i64), +]; + +pub const VERSION_RULES: &[(i64, i64)] = &[ + (2i64, 50707i64), + (12i64, 50707i64), + (13i64, -80000i64), + (22i64, -50700i64), + (57i64, 50706i64), + (81i64, 50707i64), + (93i64, -50700i64), + (101i64, 50604i64), + (129i64, 50604i64), + (136i64, -80003i64), + (158i64, 50711i64), + (177i64, 50606i64), + (179i64, 50606i64), + (189i64, 50707i64), + (190i64, 50700i64), + (197i64, 50700i64), + (209i64, 50707i64), + (207i64, 50604i64), + (210i64, 50707i64), + (844i64, 50711i64), + (244i64, 50713i64), + (262i64, 50708i64), + (296i64, 50605i64), + (297i64, 50602i64), + (305i64, 50601i64), + (311i64, 50603i64), + (312i64, 50603i64), + (317i64, 50713i64), + (365i64, 50704i64), + (377i64, 50606i64), + (382i64, -50706i64), + (386i64, 50605i64), + (389i64, 50706i64), + (409i64, 50604i64), + (415i64, 50700i64), + (440i64, -80000i64), + (460i64, 50700i64), + (462i64, 50700i64), + (461i64, 50700i64), + (463i64, 50700i64), + (466i64, 50700i64), + (464i64, 50700i64), + (465i64, 50700i64), + (482i64, 50713i64), + (529i64, 50606i64), + (533i64, -80000i64), + (540i64, 50700i64), + (554i64, 50707i64), + (573i64, -80000i64), + (625i64, 50706i64), + (637i64, 50707i64), + (652i64, 50704i64), + (724i64, 80014i64), + (660i64, 80000i64), + (731i64, 80017i64), + (842i64, 80000i64), + (812i64, 80021i64), + (675i64, 80000i64), + (677i64, 80000i64), + (664i64, 80000i64), + (678i64, 80000i64), + (715i64, 80011i64), + (679i64, 80000i64), + (716i64, 80011i64), + (700i64, 80000i64), + (730i64, 80017i64), + (848i64, 80021i64), + (663i64, 80000i64), + (680i64, 80000i64), + (741i64, 80019i64), + (681i64, 80000i64), + (682i64, 80000i64), + (852i64, 80000i64), + (713i64, 80000i64), + (840i64, 80000i64), + (672i64, 80000i64), + (683i64, 80000i64), + (841i64, 80000i64), + (674i64, 80000i64), + (705i64, 80000i64), + (725i64, 80014i64), + (811i64, 80031i64), + (661i64, 80000i64), + (667i64, 80000i64), + (666i64, 80000i64), + (701i64, 80000i64), + (850i64, 80021i64), + (847i64, 80024i64), + (684i64, 80000i64), + (685i64, 80000i64), + (726i64, 80014i64), + (686i64, 80000i64), + (670i64, 80000i64), + (735i64, 80018i64), + (712i64, 80000i64), + (738i64, 80018i64), + (736i64, 80018i64), + (733i64, 80017i64), + (702i64, 80000i64), + (729i64, 80017i64), + (671i64, 80000i64), + (687i64, 80000i64), + (688i64, 80000i64), + (689i64, 80000i64), + (668i64, 80000i64), + (744i64, 80019i64), + (732i64, 80017i64), + (728i64, 80014i64), + (719i64, 80013i64), + (703i64, 80000i64), + (717i64, 80011i64), + (690i64, 80000i64), + (691i64, 80000i64), + (740i64, 80019i64), + (704i64, 80000i64), + (692i64, 80000i64), + (673i64, 80000i64), + (658i64, 80000i64), + (693i64, 80000i64), + (737i64, 80018i64), + (734i64, 80018i64), + (694i64, 80000i64), + (665i64, 80000i64), + (846i64, 80021i64), + (718i64, 80011i64), + (739i64, 80019i64), + (742i64, 80019i64), + (709i64, 80000i64), + (695i64, 80000i64), + (714i64, 80011i64), + (727i64, 80014i64), + (706i64, 80000i64), + (851i64, 80021i64), + (659i64, 80000i64), + (696i64, 80000i64), + (849i64, 80021i64), + (721i64, 80013i64), + (722i64, 80013i64), + (720i64, 80013i64), + (723i64, 80013i64), + (669i64, 80000i64), + (813i64, 80000i64), + (814i64, 80000i64), + (815i64, 80000i64), + (816i64, 80000i64), + (817i64, 80000i64), + (818i64, 80000i64), + (819i64, 80000i64), + (820i64, 80000i64), + (821i64, 80000i64), + (822i64, 80000i64), + (823i64, 80000i64), + (824i64, 80000i64), + (825i64, 80000i64), + (826i64, 80000i64), + (828i64, 80000i64), + (829i64, 80000i64), + (830i64, 80000i64), + (831i64, 80000i64), + (832i64, 80000i64), + (833i64, 80000i64), + (834i64, 80000i64), + (827i64, 80000i64), + (835i64, 80000i64), + (836i64, 80000i64), + (837i64, 80000i64), + (838i64, 80000i64), + (839i64, 80000i64), + (707i64, 80000i64), + (743i64, 80019i64), + (710i64, 80000i64), + (708i64, 80000i64), + (697i64, 80000i64), + (845i64, 80016i64), + (698i64, 80000i64), + (711i64, 80000i64), + (662i64, 80000i64), + (699i64, 80000i64), + (843i64, 80022i64), +]; + +pub const FUNCTION_TOKENS: &[i64] = &[ + 5i64, 35i64, 36i64, 38i64, 52i64, 95i64, 100i64, 102i64, 103i64, 108i64, 114i64, 115i64, + 182i64, 218i64, 326i64, 338i64, 345i64, 372i64, 414i64, 505i64, 551i64, 550i64, 548i64, 549i64, + 558i64, 562i64, 563i64, 564i64, 569i64, 570i64, 595i64, 634i64, 635i64, 632i64, +]; + +pub const TOKEN_SYNONYMS: &[(i64, i64)] = &[ + (59i64, 60i64), + (102i64, 100i64), + (103i64, 108i64), + (104i64, 372i64), + (117i64, 122i64), + (124i64, 126i64), + (144i64, 143i64), + (187i64, 71i64), + (193i64, 195i64), + (194i64, 146i64), + (852i64, 211i64), + (795i64, 588i64), + (796i64, 516i64), + (797i64, 331i64), + (798i64, 249i64), + (799i64, 31i64), + (246i64, 249i64), + (254i64, 449i64), + (282i64, 372i64), + (283i64, 372i64), + (338i64, 563i64), + (339i64, 331i64), + (362i64, 363i64), + (479i64, 444i64), + (491i64, 109i64), + (493i64, 110i64), + (505i64, 618i64), + (518i64, 16i64), + (802i64, 122i64), + (803i64, 229i64), + (804i64, 337i64), + (805i64, 343i64), + (806i64, 350i64), + (807i64, 429i64), + (808i64, 495i64), + (809i64, 640i64), + (810i64, 656i64), + (550i64, 551i64), + (549i64, 551i64), + (562i64, 563i64), + (570i64, 618i64), + (634i64, 632i64), + (630i64, 629i64), +]; + +pub const UNDERSCORE_CHARSET_NAMES: &[&str] = &[ + "_armscii8", + "_ascii", + "_big5", + "_binary", + "_cp1250", + "_cp1251", + "_cp1256", + "_cp1257", + "_cp850", + "_cp852", + "_cp866", + "_cp932", + "_dec8", + "_eucjpms", + "_euckr", + "_gb18030", + "_gb2312", + "_gbk", + "_geostd8", + "_greek", + "_hebrew", + "_hp8", + "_keybcs2", + "_koi8r", + "_koi8u", + "_latin1", + "_latin2", + "_latin5", + "_latin7", + "_macce", + "_macroman", + "_sjis", + "_swe7", + "_tis620", + "_ucs2", + "_ujis", + "_utf16", + "_utf16le", + "_utf32", + "_utf8", + "_utf8mb3", + "_utf8mb4", +]; + +pub fn token_id(name: &str) -> Option { + SCALAR_INT_CONSTANTS + .iter() + .find_map(|(constant_name, id)| (*constant_name == name).then_some(*id)) +} + +pub fn token_name(id: i64) -> Option<&'static str> { + SCALAR_INT_CONSTANTS + .iter() + .rev() + .find_map(|(constant_name, token_id)| (*token_id == id).then_some(*constant_name)) +} + +pub fn keyword_token(keyword: &str) -> Option { + KEYWORD_TOKENS + .iter() + .find_map(|(candidate, id)| (*candidate == keyword).then_some(*id)) +} + +pub fn version_rule(token_id: i64) -> Option { + VERSION_RULES + .iter() + .find_map(|(candidate, version)| (*candidate == token_id).then_some(*version)) +} + +pub fn is_function_token(token_id: i64) -> bool { + FUNCTION_TOKENS.contains(&token_id) +} + +pub fn token_synonym(token_id: i64) -> Option { + TOKEN_SYNONYMS + .iter() + .find_map(|(candidate, synonym)| (*candidate == token_id).then_some(*synonym)) +} + +pub fn is_underscore_charset(name: &str) -> bool { + UNDERSCORE_CHARSET_NAMES.contains(&name) +} + +pub fn register_lexer_constants(mut builder: ClassBuilder) -> ClassBuilder { + builder = builder + .constant("SQL_MODE_HIGH_NOT_PRECEDENCE", 1i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_MODE_PIPES_AS_CONCAT", 2i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_MODE_IGNORE_SPACE", 4i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_MODE_NO_BACKSLASH_ESCAPES", 8i64, &[]) + .unwrap(); + builder = builder + .constant("WHITESPACE_MASK", " \t\n\r\x0c", &[]) + .unwrap(); + builder = builder.constant("DIGIT_MASK", "0123456789", &[]).unwrap(); + builder = builder + .constant("HEX_DIGIT_MASK", "0123456789abcdefABCDEF", &[]) + .unwrap(); + builder = builder.constant("ACCESSIBLE_SYMBOL", 1i64, &[]).unwrap(); + builder = builder.constant("ACCOUNT_SYMBOL", 2i64, &[]).unwrap(); + builder = builder.constant("ACTION_SYMBOL", 3i64, &[]).unwrap(); + builder = builder.constant("ADD_SYMBOL", 4i64, &[]).unwrap(); + builder = builder.constant("ADDDATE_SYMBOL", 5i64, &[]).unwrap(); + builder = builder.constant("AFTER_SYMBOL", 6i64, &[]).unwrap(); + builder = builder.constant("AGAINST_SYMBOL", 7i64, &[]).unwrap(); + builder = builder.constant("AGGREGATE_SYMBOL", 8i64, &[]).unwrap(); + builder = builder.constant("ALGORITHM_SYMBOL", 9i64, &[]).unwrap(); + builder = builder.constant("ALL_SYMBOL", 10i64, &[]).unwrap(); + builder = builder.constant("ALTER_SYMBOL", 11i64, &[]).unwrap(); + builder = builder.constant("ALWAYS_SYMBOL", 12i64, &[]).unwrap(); + builder = builder.constant("ANALYSE_SYMBOL", 13i64, &[]).unwrap(); + builder = builder.constant("ANALYZE_SYMBOL", 14i64, &[]).unwrap(); + builder = builder.constant("AND_SYMBOL", 15i64, &[]).unwrap(); + builder = builder.constant("ANY_SYMBOL", 16i64, &[]).unwrap(); + builder = builder.constant("AS_SYMBOL", 17i64, &[]).unwrap(); + builder = builder.constant("ASC_SYMBOL", 18i64, &[]).unwrap(); + builder = builder.constant("ASCII_SYMBOL", 19i64, &[]).unwrap(); + builder = builder.constant("ASENSITIVE_SYMBOL", 20i64, &[]).unwrap(); + builder = builder.constant("AT_SYMBOL", 21i64, &[]).unwrap(); + builder = builder.constant("AUTHORS_SYMBOL", 22i64, &[]).unwrap(); + builder = builder + .constant("AUTOEXTEND_SIZE_SYMBOL", 23i64, &[]) + .unwrap(); + builder = builder + .constant("AUTO_INCREMENT_SYMBOL", 24i64, &[]) + .unwrap(); + builder = builder + .constant("AVG_ROW_LENGTH_SYMBOL", 25i64, &[]) + .unwrap(); + builder = builder.constant("AVG_SYMBOL", 26i64, &[]).unwrap(); + builder = builder.constant("BACKUP_SYMBOL", 27i64, &[]).unwrap(); + builder = builder.constant("BEFORE_SYMBOL", 28i64, &[]).unwrap(); + builder = builder.constant("BEGIN_SYMBOL", 29i64, &[]).unwrap(); + builder = builder.constant("BETWEEN_SYMBOL", 30i64, &[]).unwrap(); + builder = builder.constant("BIGINT_SYMBOL", 31i64, &[]).unwrap(); + builder = builder.constant("BINARY_SYMBOL", 32i64, &[]).unwrap(); + builder = builder.constant("BINLOG_SYMBOL", 33i64, &[]).unwrap(); + builder = builder.constant("BIN_NUM_SYMBOL", 34i64, &[]).unwrap(); + builder = builder.constant("BIT_AND_SYMBOL", 35i64, &[]).unwrap(); + builder = builder.constant("BIT_OR_SYMBOL", 36i64, &[]).unwrap(); + builder = builder.constant("BIT_SYMBOL", 37i64, &[]).unwrap(); + builder = builder.constant("BIT_XOR_SYMBOL", 38i64, &[]).unwrap(); + builder = builder.constant("BLOB_SYMBOL", 39i64, &[]).unwrap(); + builder = builder.constant("BLOCK_SYMBOL", 40i64, &[]).unwrap(); + builder = builder.constant("BOOLEAN_SYMBOL", 41i64, &[]).unwrap(); + builder = builder.constant("BOOL_SYMBOL", 42i64, &[]).unwrap(); + builder = builder.constant("BOTH_SYMBOL", 43i64, &[]).unwrap(); + builder = builder.constant("BTREE_SYMBOL", 44i64, &[]).unwrap(); + builder = builder.constant("BY_SYMBOL", 45i64, &[]).unwrap(); + builder = builder.constant("BYTE_SYMBOL", 46i64, &[]).unwrap(); + builder = builder.constant("CACHE_SYMBOL", 47i64, &[]).unwrap(); + builder = builder.constant("CALL_SYMBOL", 48i64, &[]).unwrap(); + builder = builder.constant("CASCADE_SYMBOL", 49i64, &[]).unwrap(); + builder = builder.constant("CASCADED_SYMBOL", 50i64, &[]).unwrap(); + builder = builder.constant("CASE_SYMBOL", 51i64, &[]).unwrap(); + builder = builder.constant("CAST_SYMBOL", 52i64, &[]).unwrap(); + builder = builder.constant("CATALOG_NAME_SYMBOL", 53i64, &[]).unwrap(); + builder = builder.constant("CHAIN_SYMBOL", 54i64, &[]).unwrap(); + builder = builder.constant("CHANGE_SYMBOL", 55i64, &[]).unwrap(); + builder = builder.constant("CHANGED_SYMBOL", 56i64, &[]).unwrap(); + builder = builder.constant("CHANNEL_SYMBOL", 57i64, &[]).unwrap(); + builder = builder.constant("CHARSET_SYMBOL", 58i64, &[]).unwrap(); + builder = builder.constant("CHARACTER_SYMBOL", 59i64, &[]).unwrap(); + builder = builder.constant("CHAR_SYMBOL", 60i64, &[]).unwrap(); + builder = builder.constant("CHECKSUM_SYMBOL", 61i64, &[]).unwrap(); + builder = builder.constant("CHECK_SYMBOL", 62i64, &[]).unwrap(); + builder = builder.constant("CIPHER_SYMBOL", 63i64, &[]).unwrap(); + builder = builder.constant("CLASS_ORIGIN_SYMBOL", 64i64, &[]).unwrap(); + builder = builder.constant("CLIENT_SYMBOL", 65i64, &[]).unwrap(); + builder = builder.constant("CLOSE_SYMBOL", 66i64, &[]).unwrap(); + builder = builder.constant("COALESCE_SYMBOL", 67i64, &[]).unwrap(); + builder = builder.constant("CODE_SYMBOL", 68i64, &[]).unwrap(); + builder = builder.constant("COLLATE_SYMBOL", 69i64, &[]).unwrap(); + builder = builder.constant("COLLATION_SYMBOL", 70i64, &[]).unwrap(); + builder = builder.constant("COLUMNS_SYMBOL", 71i64, &[]).unwrap(); + builder = builder.constant("COLUMN_SYMBOL", 72i64, &[]).unwrap(); + builder = builder.constant("COLUMN_NAME_SYMBOL", 73i64, &[]).unwrap(); + builder = builder + .constant("COLUMN_FORMAT_SYMBOL", 74i64, &[]) + .unwrap(); + builder = builder.constant("COMMENT_SYMBOL", 75i64, &[]).unwrap(); + builder = builder.constant("COMMITTED_SYMBOL", 76i64, &[]).unwrap(); + builder = builder.constant("COMMIT_SYMBOL", 77i64, &[]).unwrap(); + builder = builder.constant("COMPACT_SYMBOL", 78i64, &[]).unwrap(); + builder = builder.constant("COMPLETION_SYMBOL", 79i64, &[]).unwrap(); + builder = builder.constant("COMPRESSED_SYMBOL", 80i64, &[]).unwrap(); + builder = builder.constant("COMPRESSION_SYMBOL", 81i64, &[]).unwrap(); + builder = builder.constant("CONCURRENT_SYMBOL", 82i64, &[]).unwrap(); + builder = builder.constant("CONDITION_SYMBOL", 83i64, &[]).unwrap(); + builder = builder.constant("CONNECTION_SYMBOL", 84i64, &[]).unwrap(); + builder = builder.constant("CONSISTENT_SYMBOL", 85i64, &[]).unwrap(); + builder = builder.constant("CONSTRAINT_SYMBOL", 86i64, &[]).unwrap(); + builder = builder + .constant("CONSTRAINT_CATALOG_SYMBOL", 87i64, &[]) + .unwrap(); + builder = builder + .constant("CONSTRAINT_NAME_SYMBOL", 88i64, &[]) + .unwrap(); + builder = builder + .constant("CONSTRAINT_SCHEMA_SYMBOL", 89i64, &[]) + .unwrap(); + builder = builder.constant("CONTAINS_SYMBOL", 90i64, &[]).unwrap(); + builder = builder.constant("CONTEXT_SYMBOL", 91i64, &[]).unwrap(); + builder = builder.constant("CONTINUE_SYMBOL", 92i64, &[]).unwrap(); + builder = builder.constant("CONTRIBUTORS_SYMBOL", 93i64, &[]).unwrap(); + builder = builder.constant("CONVERT_SYMBOL", 94i64, &[]).unwrap(); + builder = builder.constant("COUNT_SYMBOL", 95i64, &[]).unwrap(); + builder = builder.constant("CPU_SYMBOL", 96i64, &[]).unwrap(); + builder = builder.constant("CREATE_SYMBOL", 97i64, &[]).unwrap(); + builder = builder.constant("CROSS_SYMBOL", 98i64, &[]).unwrap(); + builder = builder.constant("CUBE_SYMBOL", 99i64, &[]).unwrap(); + builder = builder.constant("CURDATE_SYMBOL", 100i64, &[]).unwrap(); + builder = builder.constant("CURRENT_SYMBOL", 101i64, &[]).unwrap(); + builder = builder + .constant("CURRENT_DATE_SYMBOL", 102i64, &[]) + .unwrap(); + builder = builder + .constant("CURRENT_TIME_SYMBOL", 103i64, &[]) + .unwrap(); + builder = builder + .constant("CURRENT_TIMESTAMP_SYMBOL", 104i64, &[]) + .unwrap(); + builder = builder + .constant("CURRENT_USER_SYMBOL", 105i64, &[]) + .unwrap(); + builder = builder.constant("CURSOR_SYMBOL", 106i64, &[]).unwrap(); + builder = builder.constant("CURSOR_NAME_SYMBOL", 107i64, &[]).unwrap(); + builder = builder.constant("CURTIME_SYMBOL", 108i64, &[]).unwrap(); + builder = builder.constant("DATABASE_SYMBOL", 109i64, &[]).unwrap(); + builder = builder.constant("DATABASES_SYMBOL", 110i64, &[]).unwrap(); + builder = builder.constant("DATAFILE_SYMBOL", 111i64, &[]).unwrap(); + builder = builder.constant("DATA_SYMBOL", 112i64, &[]).unwrap(); + builder = builder.constant("DATETIME_SYMBOL", 113i64, &[]).unwrap(); + builder = builder.constant("DATE_ADD_SYMBOL", 114i64, &[]).unwrap(); + builder = builder.constant("DATE_SUB_SYMBOL", 115i64, &[]).unwrap(); + builder = builder.constant("DATE_SYMBOL", 116i64, &[]).unwrap(); + builder = builder.constant("DAYOFMONTH_SYMBOL", 117i64, &[]).unwrap(); + builder = builder.constant("DAY_HOUR_SYMBOL", 118i64, &[]).unwrap(); + builder = builder + .constant("DAY_MICROSECOND_SYMBOL", 119i64, &[]) + .unwrap(); + builder = builder.constant("DAY_MINUTE_SYMBOL", 120i64, &[]).unwrap(); + builder = builder.constant("DAY_SECOND_SYMBOL", 121i64, &[]).unwrap(); + builder = builder.constant("DAY_SYMBOL", 122i64, &[]).unwrap(); + builder = builder.constant("DEALLOCATE_SYMBOL", 123i64, &[]).unwrap(); + builder = builder.constant("DEC_SYMBOL", 124i64, &[]).unwrap(); + builder = builder.constant("DECIMAL_NUM_SYMBOL", 125i64, &[]).unwrap(); + builder = builder.constant("DECIMAL_SYMBOL", 126i64, &[]).unwrap(); + builder = builder.constant("DECLARE_SYMBOL", 127i64, &[]).unwrap(); + builder = builder.constant("DEFAULT_SYMBOL", 128i64, &[]).unwrap(); + builder = builder + .constant("DEFAULT_AUTH_SYMBOL", 129i64, &[]) + .unwrap(); + builder = builder.constant("DEFINER_SYMBOL", 130i64, &[]).unwrap(); + builder = builder.constant("DELAYED_SYMBOL", 131i64, &[]).unwrap(); + builder = builder + .constant("DELAY_KEY_WRITE_SYMBOL", 132i64, &[]) + .unwrap(); + builder = builder.constant("DELETE_SYMBOL", 133i64, &[]).unwrap(); + builder = builder.constant("DESC_SYMBOL", 134i64, &[]).unwrap(); + builder = builder.constant("DESCRIBE_SYMBOL", 135i64, &[]).unwrap(); + builder = builder + .constant("DES_KEY_FILE_SYMBOL", 136i64, &[]) + .unwrap(); + builder = builder + .constant("DETERMINISTIC_SYMBOL", 137i64, &[]) + .unwrap(); + builder = builder.constant("DIAGNOSTICS_SYMBOL", 138i64, &[]).unwrap(); + builder = builder.constant("DIRECTORY_SYMBOL", 139i64, &[]).unwrap(); + builder = builder.constant("DISABLE_SYMBOL", 140i64, &[]).unwrap(); + builder = builder.constant("DISCARD_SYMBOL", 141i64, &[]).unwrap(); + builder = builder.constant("DISK_SYMBOL", 142i64, &[]).unwrap(); + builder = builder.constant("DISTINCT_SYMBOL", 143i64, &[]).unwrap(); + builder = builder.constant("DISTINCTROW_SYMBOL", 144i64, &[]).unwrap(); + builder = builder.constant("DIV_SYMBOL", 145i64, &[]).unwrap(); + builder = builder.constant("DOUBLE_SYMBOL", 146i64, &[]).unwrap(); + builder = builder.constant("DO_SYMBOL", 147i64, &[]).unwrap(); + builder = builder.constant("DROP_SYMBOL", 148i64, &[]).unwrap(); + builder = builder.constant("DUAL_SYMBOL", 149i64, &[]).unwrap(); + builder = builder.constant("DUMPFILE_SYMBOL", 150i64, &[]).unwrap(); + builder = builder.constant("DUPLICATE_SYMBOL", 151i64, &[]).unwrap(); + builder = builder.constant("DYNAMIC_SYMBOL", 152i64, &[]).unwrap(); + builder = builder.constant("EACH_SYMBOL", 153i64, &[]).unwrap(); + builder = builder.constant("ELSE_SYMBOL", 154i64, &[]).unwrap(); + builder = builder.constant("ELSEIF_SYMBOL", 155i64, &[]).unwrap(); + builder = builder.constant("ENABLE_SYMBOL", 156i64, &[]).unwrap(); + builder = builder.constant("ENCLOSED_SYMBOL", 157i64, &[]).unwrap(); + builder = builder.constant("ENCRYPTION_SYMBOL", 158i64, &[]).unwrap(); + builder = builder.constant("END_SYMBOL", 159i64, &[]).unwrap(); + builder = builder.constant("ENDS_SYMBOL", 160i64, &[]).unwrap(); + builder = builder + .constant("END_OF_INPUT_SYMBOL", 161i64, &[]) + .unwrap(); + builder = builder.constant("ENGINES_SYMBOL", 162i64, &[]).unwrap(); + builder = builder.constant("ENGINE_SYMBOL", 163i64, &[]).unwrap(); + builder = builder.constant("ENUM_SYMBOL", 164i64, &[]).unwrap(); + builder = builder.constant("ERROR_SYMBOL", 165i64, &[]).unwrap(); + builder = builder.constant("ERRORS_SYMBOL", 166i64, &[]).unwrap(); + builder = builder.constant("ESCAPED_SYMBOL", 167i64, &[]).unwrap(); + builder = builder.constant("ESCAPE_SYMBOL", 168i64, &[]).unwrap(); + builder = builder.constant("EVENTS_SYMBOL", 169i64, &[]).unwrap(); + builder = builder.constant("EVENT_SYMBOL", 170i64, &[]).unwrap(); + builder = builder.constant("EVERY_SYMBOL", 171i64, &[]).unwrap(); + builder = builder.constant("EXCHANGE_SYMBOL", 172i64, &[]).unwrap(); + builder = builder.constant("EXECUTE_SYMBOL", 173i64, &[]).unwrap(); + builder = builder.constant("EXISTS_SYMBOL", 174i64, &[]).unwrap(); + builder = builder.constant("EXIT_SYMBOL", 175i64, &[]).unwrap(); + builder = builder.constant("EXPANSION_SYMBOL", 176i64, &[]).unwrap(); + builder = builder.constant("EXPIRE_SYMBOL", 177i64, &[]).unwrap(); + builder = builder.constant("EXPLAIN_SYMBOL", 178i64, &[]).unwrap(); + builder = builder.constant("EXPORT_SYMBOL", 179i64, &[]).unwrap(); + builder = builder.constant("EXTENDED_SYMBOL", 180i64, &[]).unwrap(); + builder = builder.constant("EXTENT_SIZE_SYMBOL", 181i64, &[]).unwrap(); + builder = builder.constant("EXTRACT_SYMBOL", 182i64, &[]).unwrap(); + builder = builder.constant("FALSE_SYMBOL", 183i64, &[]).unwrap(); + builder = builder.constant("FAST_SYMBOL", 184i64, &[]).unwrap(); + builder = builder.constant("FAULTS_SYMBOL", 185i64, &[]).unwrap(); + builder = builder.constant("FETCH_SYMBOL", 186i64, &[]).unwrap(); + builder = builder.constant("FIELDS_SYMBOL", 187i64, &[]).unwrap(); + builder = builder.constant("FILE_SYMBOL", 188i64, &[]).unwrap(); + builder = builder + .constant("FILE_BLOCK_SIZE_SYMBOL", 189i64, &[]) + .unwrap(); + builder = builder.constant("FILTER_SYMBOL", 190i64, &[]).unwrap(); + builder = builder.constant("FIRST_SYMBOL", 191i64, &[]).unwrap(); + builder = builder.constant("FIXED_SYMBOL", 192i64, &[]).unwrap(); + builder = builder.constant("FLOAT4_SYMBOL", 193i64, &[]).unwrap(); + builder = builder.constant("FLOAT8_SYMBOL", 194i64, &[]).unwrap(); + builder = builder.constant("FLOAT_SYMBOL", 195i64, &[]).unwrap(); + builder = builder.constant("FLUSH_SYMBOL", 196i64, &[]).unwrap(); + builder = builder.constant("FOLLOWS_SYMBOL", 197i64, &[]).unwrap(); + builder = builder.constant("FORCE_SYMBOL", 198i64, &[]).unwrap(); + builder = builder.constant("FOREIGN_SYMBOL", 199i64, &[]).unwrap(); + builder = builder.constant("FOR_SYMBOL", 200i64, &[]).unwrap(); + builder = builder.constant("FORMAT_SYMBOL", 201i64, &[]).unwrap(); + builder = builder.constant("FOUND_SYMBOL", 202i64, &[]).unwrap(); + builder = builder.constant("FROM_SYMBOL", 203i64, &[]).unwrap(); + builder = builder.constant("FULL_SYMBOL", 204i64, &[]).unwrap(); + builder = builder.constant("FULLTEXT_SYMBOL", 205i64, &[]).unwrap(); + builder = builder.constant("FUNCTION_SYMBOL", 206i64, &[]).unwrap(); + builder = builder.constant("GET_SYMBOL", 207i64, &[]).unwrap(); + builder = builder.constant("GENERAL_SYMBOL", 208i64, &[]).unwrap(); + builder = builder.constant("GENERATED_SYMBOL", 209i64, &[]).unwrap(); + builder = builder + .constant("GROUP_REPLICATION_SYMBOL", 210i64, &[]) + .unwrap(); + builder = builder + .constant("GEOMETRYCOLLECTION_SYMBOL", 211i64, &[]) + .unwrap(); + builder = builder.constant("GEOMETRY_SYMBOL", 212i64, &[]).unwrap(); + builder = builder.constant("GET_FORMAT_SYMBOL", 213i64, &[]).unwrap(); + builder = builder.constant("GLOBAL_SYMBOL", 214i64, &[]).unwrap(); + builder = builder.constant("GRANT_SYMBOL", 215i64, &[]).unwrap(); + builder = builder.constant("GRANTS_SYMBOL", 216i64, &[]).unwrap(); + builder = builder.constant("GROUP_SYMBOL", 217i64, &[]).unwrap(); + builder = builder + .constant("GROUP_CONCAT_SYMBOL", 218i64, &[]) + .unwrap(); + builder = builder.constant("HANDLER_SYMBOL", 219i64, &[]).unwrap(); + builder = builder.constant("HASH_SYMBOL", 220i64, &[]).unwrap(); + builder = builder.constant("HAVING_SYMBOL", 221i64, &[]).unwrap(); + builder = builder.constant("HELP_SYMBOL", 222i64, &[]).unwrap(); + builder = builder + .constant("HIGH_PRIORITY_SYMBOL", 223i64, &[]) + .unwrap(); + builder = builder.constant("HOST_SYMBOL", 224i64, &[]).unwrap(); + builder = builder.constant("HOSTS_SYMBOL", 225i64, &[]).unwrap(); + builder = builder + .constant("HOUR_MICROSECOND_SYMBOL", 226i64, &[]) + .unwrap(); + builder = builder.constant("HOUR_MINUTE_SYMBOL", 227i64, &[]).unwrap(); + builder = builder.constant("HOUR_SECOND_SYMBOL", 228i64, &[]).unwrap(); + builder = builder.constant("HOUR_SYMBOL", 229i64, &[]).unwrap(); + builder = builder.constant("IDENTIFIED_SYMBOL", 230i64, &[]).unwrap(); + builder = builder.constant("IF_SYMBOL", 231i64, &[]).unwrap(); + builder = builder.constant("IGNORE_SYMBOL", 232i64, &[]).unwrap(); + builder = builder + .constant("IGNORE_SERVER_IDS_SYMBOL", 233i64, &[]) + .unwrap(); + builder = builder.constant("IMPORT_SYMBOL", 234i64, &[]).unwrap(); + builder = builder.constant("INDEXES_SYMBOL", 235i64, &[]).unwrap(); + builder = builder.constant("INDEX_SYMBOL", 236i64, &[]).unwrap(); + builder = builder.constant("INFILE_SYMBOL", 237i64, &[]).unwrap(); + builder = builder + .constant("INITIAL_SIZE_SYMBOL", 238i64, &[]) + .unwrap(); + builder = builder.constant("INNER_SYMBOL", 239i64, &[]).unwrap(); + builder = builder.constant("INOUT_SYMBOL", 240i64, &[]).unwrap(); + builder = builder.constant("INSENSITIVE_SYMBOL", 241i64, &[]).unwrap(); + builder = builder.constant("INSERT_SYMBOL", 242i64, &[]).unwrap(); + builder = builder + .constant("INSERT_METHOD_SYMBOL", 243i64, &[]) + .unwrap(); + builder = builder.constant("INSTANCE_SYMBOL", 244i64, &[]).unwrap(); + builder = builder.constant("INSTALL_SYMBOL", 245i64, &[]).unwrap(); + builder = builder.constant("INTEGER_SYMBOL", 246i64, &[]).unwrap(); + builder = builder.constant("INTERVAL_SYMBOL", 247i64, &[]).unwrap(); + builder = builder.constant("INTO_SYMBOL", 248i64, &[]).unwrap(); + builder = builder.constant("INT_SYMBOL", 249i64, &[]).unwrap(); + builder = builder.constant("INVOKER_SYMBOL", 250i64, &[]).unwrap(); + builder = builder.constant("IN_SYMBOL", 251i64, &[]).unwrap(); + builder = builder + .constant("IO_AFTER_GTIDS_SYMBOL", 252i64, &[]) + .unwrap(); + builder = builder + .constant("IO_BEFORE_GTIDS_SYMBOL", 253i64, &[]) + .unwrap(); + builder = builder.constant("IO_THREAD_SYMBOL", 254i64, &[]).unwrap(); + builder = builder.constant("IO_SYMBOL", 255i64, &[]).unwrap(); + builder = builder.constant("IPC_SYMBOL", 256i64, &[]).unwrap(); + builder = builder.constant("IS_SYMBOL", 257i64, &[]).unwrap(); + builder = builder.constant("ISOLATION_SYMBOL", 258i64, &[]).unwrap(); + builder = builder.constant("ISSUER_SYMBOL", 259i64, &[]).unwrap(); + builder = builder.constant("ITERATE_SYMBOL", 260i64, &[]).unwrap(); + builder = builder.constant("JOIN_SYMBOL", 261i64, &[]).unwrap(); + builder = builder.constant("JSON_SYMBOL", 262i64, &[]).unwrap(); + builder = builder.constant("KEYS_SYMBOL", 263i64, &[]).unwrap(); + builder = builder + .constant("KEY_BLOCK_SIZE_SYMBOL", 264i64, &[]) + .unwrap(); + builder = builder.constant("KEY_SYMBOL", 265i64, &[]).unwrap(); + builder = builder.constant("KILL_SYMBOL", 266i64, &[]).unwrap(); + builder = builder.constant("LANGUAGE_SYMBOL", 267i64, &[]).unwrap(); + builder = builder.constant("LAST_SYMBOL", 268i64, &[]).unwrap(); + builder = builder.constant("LEADING_SYMBOL", 269i64, &[]).unwrap(); + builder = builder.constant("LEAVES_SYMBOL", 270i64, &[]).unwrap(); + builder = builder.constant("LEAVE_SYMBOL", 271i64, &[]).unwrap(); + builder = builder.constant("LEFT_SYMBOL", 272i64, &[]).unwrap(); + builder = builder.constant("LESS_SYMBOL", 273i64, &[]).unwrap(); + builder = builder.constant("LEVEL_SYMBOL", 274i64, &[]).unwrap(); + builder = builder.constant("LIKE_SYMBOL", 275i64, &[]).unwrap(); + builder = builder.constant("LIMIT_SYMBOL", 276i64, &[]).unwrap(); + builder = builder.constant("LINEAR_SYMBOL", 277i64, &[]).unwrap(); + builder = builder.constant("LINES_SYMBOL", 278i64, &[]).unwrap(); + builder = builder.constant("LINESTRING_SYMBOL", 279i64, &[]).unwrap(); + builder = builder.constant("LIST_SYMBOL", 280i64, &[]).unwrap(); + builder = builder.constant("LOAD_SYMBOL", 281i64, &[]).unwrap(); + builder = builder.constant("LOCALTIME_SYMBOL", 282i64, &[]).unwrap(); + builder = builder + .constant("LOCALTIMESTAMP_SYMBOL", 283i64, &[]) + .unwrap(); + builder = builder.constant("LOCAL_SYMBOL", 284i64, &[]).unwrap(); + builder = builder.constant("LOCATOR_SYMBOL", 285i64, &[]).unwrap(); + builder = builder.constant("LOCKS_SYMBOL", 286i64, &[]).unwrap(); + builder = builder.constant("LOCK_SYMBOL", 287i64, &[]).unwrap(); + builder = builder.constant("LOGFILE_SYMBOL", 288i64, &[]).unwrap(); + builder = builder.constant("LOGS_SYMBOL", 289i64, &[]).unwrap(); + builder = builder.constant("LONGBLOB_SYMBOL", 290i64, &[]).unwrap(); + builder = builder.constant("LONGTEXT_SYMBOL", 291i64, &[]).unwrap(); + builder = builder.constant("LONG_NUM_SYMBOL", 292i64, &[]).unwrap(); + builder = builder.constant("LONG_SYMBOL", 293i64, &[]).unwrap(); + builder = builder.constant("LOOP_SYMBOL", 294i64, &[]).unwrap(); + builder = builder + .constant("LOW_PRIORITY_SYMBOL", 295i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_AUTO_POSITION_SYMBOL", 296i64, &[]) + .unwrap(); + builder = builder.constant("MASTER_BIND_SYMBOL", 297i64, &[]).unwrap(); + builder = builder + .constant("MASTER_CONNECT_RETRY_SYMBOL", 298i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_DELAY_SYMBOL", 299i64, &[]) + .unwrap(); + builder = builder.constant("MASTER_HOST_SYMBOL", 300i64, &[]).unwrap(); + builder = builder + .constant("MASTER_LOG_FILE_SYMBOL", 301i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_LOG_POS_SYMBOL", 302i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_PASSWORD_SYMBOL", 303i64, &[]) + .unwrap(); + builder = builder.constant("MASTER_PORT_SYMBOL", 304i64, &[]).unwrap(); + builder = builder + .constant("MASTER_RETRY_COUNT_SYMBOL", 305i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_SERVER_ID_SYMBOL", 306i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_SSL_CAPATH_SYMBOL", 307i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_SSL_CA_SYMBOL", 308i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_SSL_CERT_SYMBOL", 309i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_SSL_CIPHER_SYMBOL", 310i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_SSL_CRL_SYMBOL", 311i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_SSL_CRLPATH_SYMBOL", 312i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_SSL_KEY_SYMBOL", 313i64, &[]) + .unwrap(); + builder = builder.constant("MASTER_SSL_SYMBOL", 314i64, &[]).unwrap(); + builder = builder + .constant("MASTER_SSL_VERIFY_SERVER_CERT_SYMBOL", 315i64, &[]) + .unwrap(); + builder = builder.constant("MASTER_SYMBOL", 316i64, &[]).unwrap(); + builder = builder + .constant("MASTER_TLS_VERSION_SYMBOL", 317i64, &[]) + .unwrap(); + builder = builder.constant("MASTER_USER_SYMBOL", 318i64, &[]).unwrap(); + builder = builder + .constant("MASTER_HEARTBEAT_PERIOD_SYMBOL", 319i64, &[]) + .unwrap(); + builder = builder.constant("MATCH_SYMBOL", 320i64, &[]).unwrap(); + builder = builder + .constant("MAX_CONNECTIONS_PER_HOUR_SYMBOL", 321i64, &[]) + .unwrap(); + builder = builder + .constant("MAX_QUERIES_PER_HOUR_SYMBOL", 322i64, &[]) + .unwrap(); + builder = builder.constant("MAX_ROWS_SYMBOL", 323i64, &[]).unwrap(); + builder = builder.constant("MAX_SIZE_SYMBOL", 324i64, &[]).unwrap(); + builder = builder + .constant("MAX_STATEMENT_TIME_SYMBOL", 325i64, &[]) + .unwrap(); + builder = builder.constant("MAX_SYMBOL", 326i64, &[]).unwrap(); + builder = builder + .constant("MAX_UPDATES_PER_HOUR_SYMBOL", 327i64, &[]) + .unwrap(); + builder = builder + .constant("MAX_USER_CONNECTIONS_SYMBOL", 328i64, &[]) + .unwrap(); + builder = builder.constant("MAXVALUE_SYMBOL", 329i64, &[]).unwrap(); + builder = builder.constant("MEDIUMBLOB_SYMBOL", 330i64, &[]).unwrap(); + builder = builder.constant("MEDIUMINT_SYMBOL", 331i64, &[]).unwrap(); + builder = builder.constant("MEDIUMTEXT_SYMBOL", 332i64, &[]).unwrap(); + builder = builder.constant("MEDIUM_SYMBOL", 333i64, &[]).unwrap(); + builder = builder.constant("MEMORY_SYMBOL", 334i64, &[]).unwrap(); + builder = builder.constant("MERGE_SYMBOL", 335i64, &[]).unwrap(); + builder = builder + .constant("MESSAGE_TEXT_SYMBOL", 336i64, &[]) + .unwrap(); + builder = builder.constant("MICROSECOND_SYMBOL", 337i64, &[]).unwrap(); + builder = builder.constant("MID_SYMBOL", 338i64, &[]).unwrap(); + builder = builder.constant("MIDDLEINT_SYMBOL", 339i64, &[]).unwrap(); + builder = builder.constant("MIGRATE_SYMBOL", 340i64, &[]).unwrap(); + builder = builder + .constant("MINUTE_MICROSECOND_SYMBOL", 341i64, &[]) + .unwrap(); + builder = builder + .constant("MINUTE_SECOND_SYMBOL", 342i64, &[]) + .unwrap(); + builder = builder.constant("MINUTE_SYMBOL", 343i64, &[]).unwrap(); + builder = builder.constant("MIN_ROWS_SYMBOL", 344i64, &[]).unwrap(); + builder = builder.constant("MIN_SYMBOL", 345i64, &[]).unwrap(); + builder = builder.constant("MODE_SYMBOL", 346i64, &[]).unwrap(); + builder = builder.constant("MODIFIES_SYMBOL", 347i64, &[]).unwrap(); + builder = builder.constant("MODIFY_SYMBOL", 348i64, &[]).unwrap(); + builder = builder.constant("MOD_SYMBOL", 349i64, &[]).unwrap(); + builder = builder.constant("MONTH_SYMBOL", 350i64, &[]).unwrap(); + builder = builder + .constant("MULTILINESTRING_SYMBOL", 351i64, &[]) + .unwrap(); + builder = builder.constant("MULTIPOINT_SYMBOL", 352i64, &[]).unwrap(); + builder = builder + .constant("MULTIPOLYGON_SYMBOL", 353i64, &[]) + .unwrap(); + builder = builder.constant("MUTEX_SYMBOL", 354i64, &[]).unwrap(); + builder = builder.constant("MYSQL_ERRNO_SYMBOL", 355i64, &[]).unwrap(); + builder = builder.constant("NAMES_SYMBOL", 356i64, &[]).unwrap(); + builder = builder.constant("NAME_SYMBOL", 357i64, &[]).unwrap(); + builder = builder.constant("NATIONAL_SYMBOL", 358i64, &[]).unwrap(); + builder = builder.constant("NATURAL_SYMBOL", 359i64, &[]).unwrap(); + builder = builder + .constant("NCHAR_STRING_SYMBOL", 360i64, &[]) + .unwrap(); + builder = builder.constant("NCHAR_SYMBOL", 361i64, &[]).unwrap(); + builder = builder.constant("NDB_SYMBOL", 362i64, &[]).unwrap(); + builder = builder.constant("NDBCLUSTER_SYMBOL", 363i64, &[]).unwrap(); + builder = builder.constant("NEG_SYMBOL", 364i64, &[]).unwrap(); + builder = builder.constant("NEVER_SYMBOL", 365i64, &[]).unwrap(); + builder = builder.constant("NEW_SYMBOL", 366i64, &[]).unwrap(); + builder = builder.constant("NEXT_SYMBOL", 367i64, &[]).unwrap(); + builder = builder.constant("NODEGROUP_SYMBOL", 368i64, &[]).unwrap(); + builder = builder.constant("NONE_SYMBOL", 369i64, &[]).unwrap(); + builder = builder.constant("NONBLOCKING_SYMBOL", 370i64, &[]).unwrap(); + builder = builder.constant("NOT_SYMBOL", 371i64, &[]).unwrap(); + builder = builder.constant("NOW_SYMBOL", 372i64, &[]).unwrap(); + builder = builder.constant("NO_SYMBOL", 373i64, &[]).unwrap(); + builder = builder.constant("NO_WAIT_SYMBOL", 374i64, &[]).unwrap(); + builder = builder + .constant("NO_WRITE_TO_BINLOG_SYMBOL", 375i64, &[]) + .unwrap(); + builder = builder.constant("NULL_SYMBOL", 376i64, &[]).unwrap(); + builder = builder.constant("NUMBER_SYMBOL", 377i64, &[]).unwrap(); + builder = builder.constant("NUMERIC_SYMBOL", 378i64, &[]).unwrap(); + builder = builder.constant("NVARCHAR_SYMBOL", 379i64, &[]).unwrap(); + builder = builder.constant("OFFLINE_SYMBOL", 380i64, &[]).unwrap(); + builder = builder.constant("OFFSET_SYMBOL", 381i64, &[]).unwrap(); + builder = builder + .constant("OLD_PASSWORD_SYMBOL", 382i64, &[]) + .unwrap(); + builder = builder.constant("ON_SYMBOL", 383i64, &[]).unwrap(); + builder = builder.constant("ONE_SYMBOL", 384i64, &[]).unwrap(); + builder = builder.constant("ONLINE_SYMBOL", 385i64, &[]).unwrap(); + builder = builder.constant("ONLY_SYMBOL", 386i64, &[]).unwrap(); + builder = builder.constant("OPEN_SYMBOL", 387i64, &[]).unwrap(); + builder = builder.constant("OPTIMIZE_SYMBOL", 388i64, &[]).unwrap(); + builder = builder + .constant("OPTIMIZER_COSTS_SYMBOL", 389i64, &[]) + .unwrap(); + builder = builder.constant("OPTIONS_SYMBOL", 390i64, &[]).unwrap(); + builder = builder.constant("OPTION_SYMBOL", 391i64, &[]).unwrap(); + builder = builder.constant("OPTIONALLY_SYMBOL", 392i64, &[]).unwrap(); + builder = builder.constant("ORDER_SYMBOL", 393i64, &[]).unwrap(); + builder = builder.constant("OR_SYMBOL", 394i64, &[]).unwrap(); + builder = builder.constant("OUTER_SYMBOL", 395i64, &[]).unwrap(); + builder = builder.constant("OUTFILE_SYMBOL", 396i64, &[]).unwrap(); + builder = builder.constant("OUT_SYMBOL", 397i64, &[]).unwrap(); + builder = builder.constant("OWNER_SYMBOL", 398i64, &[]).unwrap(); + builder = builder.constant("PACK_KEYS_SYMBOL", 399i64, &[]).unwrap(); + builder = builder.constant("PAGE_SYMBOL", 400i64, &[]).unwrap(); + builder = builder.constant("PARSER_SYMBOL", 401i64, &[]).unwrap(); + builder = builder.constant("PARTIAL_SYMBOL", 402i64, &[]).unwrap(); + builder = builder + .constant("PARTITIONING_SYMBOL", 403i64, &[]) + .unwrap(); + builder = builder.constant("PARTITIONS_SYMBOL", 404i64, &[]).unwrap(); + builder = builder.constant("PARTITION_SYMBOL", 405i64, &[]).unwrap(); + builder = builder.constant("PASSWORD_SYMBOL", 406i64, &[]).unwrap(); + builder = builder.constant("PHASE_SYMBOL", 407i64, &[]).unwrap(); + builder = builder.constant("PLUGINS_SYMBOL", 408i64, &[]).unwrap(); + builder = builder.constant("PLUGIN_DIR_SYMBOL", 409i64, &[]).unwrap(); + builder = builder.constant("PLUGIN_SYMBOL", 410i64, &[]).unwrap(); + builder = builder.constant("POINT_SYMBOL", 411i64, &[]).unwrap(); + builder = builder.constant("POLYGON_SYMBOL", 412i64, &[]).unwrap(); + builder = builder.constant("PORT_SYMBOL", 413i64, &[]).unwrap(); + builder = builder.constant("POSITION_SYMBOL", 414i64, &[]).unwrap(); + builder = builder.constant("PRECEDES_SYMBOL", 415i64, &[]).unwrap(); + builder = builder.constant("PRECISION_SYMBOL", 416i64, &[]).unwrap(); + builder = builder.constant("PREPARE_SYMBOL", 417i64, &[]).unwrap(); + builder = builder.constant("PRESERVE_SYMBOL", 418i64, &[]).unwrap(); + builder = builder.constant("PREV_SYMBOL", 419i64, &[]).unwrap(); + builder = builder.constant("PRIMARY_SYMBOL", 420i64, &[]).unwrap(); + builder = builder.constant("PRIVILEGES_SYMBOL", 421i64, &[]).unwrap(); + builder = builder.constant("PROCEDURE_SYMBOL", 422i64, &[]).unwrap(); + builder = builder.constant("PROCESS_SYMBOL", 423i64, &[]).unwrap(); + builder = builder.constant("PROCESSLIST_SYMBOL", 424i64, &[]).unwrap(); + builder = builder.constant("PROFILE_SYMBOL", 425i64, &[]).unwrap(); + builder = builder.constant("PROFILES_SYMBOL", 426i64, &[]).unwrap(); + builder = builder.constant("PROXY_SYMBOL", 427i64, &[]).unwrap(); + builder = builder.constant("PURGE_SYMBOL", 428i64, &[]).unwrap(); + builder = builder.constant("QUARTER_SYMBOL", 429i64, &[]).unwrap(); + builder = builder.constant("QUERY_SYMBOL", 430i64, &[]).unwrap(); + builder = builder.constant("QUICK_SYMBOL", 431i64, &[]).unwrap(); + builder = builder.constant("RANGE_SYMBOL", 432i64, &[]).unwrap(); + builder = builder.constant("READS_SYMBOL", 433i64, &[]).unwrap(); + builder = builder.constant("READ_ONLY_SYMBOL", 434i64, &[]).unwrap(); + builder = builder.constant("READ_SYMBOL", 435i64, &[]).unwrap(); + builder = builder.constant("READ_WRITE_SYMBOL", 436i64, &[]).unwrap(); + builder = builder.constant("REAL_SYMBOL", 437i64, &[]).unwrap(); + builder = builder.constant("REBUILD_SYMBOL", 438i64, &[]).unwrap(); + builder = builder.constant("RECOVER_SYMBOL", 439i64, &[]).unwrap(); + builder = builder.constant("REDOFILE_SYMBOL", 440i64, &[]).unwrap(); + builder = builder + .constant("REDO_BUFFER_SIZE_SYMBOL", 441i64, &[]) + .unwrap(); + builder = builder.constant("REDUNDANT_SYMBOL", 442i64, &[]).unwrap(); + builder = builder.constant("REFERENCES_SYMBOL", 443i64, &[]).unwrap(); + builder = builder.constant("REGEXP_SYMBOL", 444i64, &[]).unwrap(); + builder = builder.constant("RELAY_SYMBOL", 445i64, &[]).unwrap(); + builder = builder.constant("RELAYLOG_SYMBOL", 446i64, &[]).unwrap(); + builder = builder + .constant("RELAY_LOG_FILE_SYMBOL", 447i64, &[]) + .unwrap(); + builder = builder + .constant("RELAY_LOG_POS_SYMBOL", 448i64, &[]) + .unwrap(); + builder = builder + .constant("RELAY_THREAD_SYMBOL", 449i64, &[]) + .unwrap(); + builder = builder.constant("RELEASE_SYMBOL", 450i64, &[]).unwrap(); + builder = builder.constant("RELOAD_SYMBOL", 451i64, &[]).unwrap(); + builder = builder.constant("REMOVE_SYMBOL", 452i64, &[]).unwrap(); + builder = builder.constant("RENAME_SYMBOL", 453i64, &[]).unwrap(); + builder = builder.constant("REORGANIZE_SYMBOL", 454i64, &[]).unwrap(); + builder = builder.constant("REPAIR_SYMBOL", 455i64, &[]).unwrap(); + builder = builder.constant("REPEATABLE_SYMBOL", 456i64, &[]).unwrap(); + builder = builder.constant("REPEAT_SYMBOL", 457i64, &[]).unwrap(); + builder = builder.constant("REPLACE_SYMBOL", 458i64, &[]).unwrap(); + builder = builder.constant("REPLICATION_SYMBOL", 459i64, &[]).unwrap(); + builder = builder + .constant("REPLICATE_DO_DB_SYMBOL", 460i64, &[]) + .unwrap(); + builder = builder + .constant("REPLICATE_IGNORE_DB_SYMBOL", 461i64, &[]) + .unwrap(); + builder = builder + .constant("REPLICATE_DO_TABLE_SYMBOL", 462i64, &[]) + .unwrap(); + builder = builder + .constant("REPLICATE_IGNORE_TABLE_SYMBOL", 463i64, &[]) + .unwrap(); + builder = builder + .constant("REPLICATE_WILD_DO_TABLE_SYMBOL", 464i64, &[]) + .unwrap(); + builder = builder + .constant("REPLICATE_WILD_IGNORE_TABLE_SYMBOL", 465i64, &[]) + .unwrap(); + builder = builder + .constant("REPLICATE_REWRITE_DB_SYMBOL", 466i64, &[]) + .unwrap(); + builder = builder.constant("REQUIRE_SYMBOL", 467i64, &[]).unwrap(); + builder = builder.constant("RESET_SYMBOL", 468i64, &[]).unwrap(); + builder = builder.constant("RESIGNAL_SYMBOL", 469i64, &[]).unwrap(); + builder = builder.constant("RESTORE_SYMBOL", 470i64, &[]).unwrap(); + builder = builder.constant("RESTRICT_SYMBOL", 471i64, &[]).unwrap(); + builder = builder.constant("RESUME_SYMBOL", 472i64, &[]).unwrap(); + builder = builder + .constant("RETURNED_SQLSTATE_SYMBOL", 473i64, &[]) + .unwrap(); + builder = builder.constant("RETURNS_SYMBOL", 474i64, &[]).unwrap(); + builder = builder.constant("RETURN_SYMBOL", 475i64, &[]).unwrap(); + builder = builder.constant("REVERSE_SYMBOL", 476i64, &[]).unwrap(); + builder = builder.constant("REVOKE_SYMBOL", 477i64, &[]).unwrap(); + builder = builder.constant("RIGHT_SYMBOL", 478i64, &[]).unwrap(); + builder = builder.constant("RLIKE_SYMBOL", 479i64, &[]).unwrap(); + builder = builder.constant("ROLLBACK_SYMBOL", 480i64, &[]).unwrap(); + builder = builder.constant("ROLLUP_SYMBOL", 481i64, &[]).unwrap(); + builder = builder.constant("ROTATE_SYMBOL", 482i64, &[]).unwrap(); + builder = builder.constant("ROUTINE_SYMBOL", 483i64, &[]).unwrap(); + builder = builder.constant("ROWS_SYMBOL", 484i64, &[]).unwrap(); + builder = builder.constant("ROW_COUNT_SYMBOL", 485i64, &[]).unwrap(); + builder = builder.constant("ROW_FORMAT_SYMBOL", 486i64, &[]).unwrap(); + builder = builder.constant("ROW_SYMBOL", 487i64, &[]).unwrap(); + builder = builder.constant("RTREE_SYMBOL", 488i64, &[]).unwrap(); + builder = builder.constant("SAVEPOINT_SYMBOL", 489i64, &[]).unwrap(); + builder = builder.constant("SCHEDULE_SYMBOL", 490i64, &[]).unwrap(); + builder = builder.constant("SCHEMA_SYMBOL", 491i64, &[]).unwrap(); + builder = builder.constant("SCHEMA_NAME_SYMBOL", 492i64, &[]).unwrap(); + builder = builder.constant("SCHEMAS_SYMBOL", 493i64, &[]).unwrap(); + builder = builder + .constant("SECOND_MICROSECOND_SYMBOL", 494i64, &[]) + .unwrap(); + builder = builder.constant("SECOND_SYMBOL", 495i64, &[]).unwrap(); + builder = builder.constant("SECURITY_SYMBOL", 496i64, &[]).unwrap(); + builder = builder.constant("SELECT_SYMBOL", 497i64, &[]).unwrap(); + builder = builder.constant("SENSITIVE_SYMBOL", 498i64, &[]).unwrap(); + builder = builder.constant("SEPARATOR_SYMBOL", 499i64, &[]).unwrap(); + builder = builder + .constant("SERIALIZABLE_SYMBOL", 500i64, &[]) + .unwrap(); + builder = builder.constant("SERIAL_SYMBOL", 501i64, &[]).unwrap(); + builder = builder.constant("SESSION_SYMBOL", 502i64, &[]).unwrap(); + builder = builder.constant("SERVER_SYMBOL", 503i64, &[]).unwrap(); + builder = builder + .constant("SERVER_OPTIONS_SYMBOL", 504i64, &[]) + .unwrap(); + builder = builder + .constant("SESSION_USER_SYMBOL", 505i64, &[]) + .unwrap(); + builder = builder.constant("SET_SYMBOL", 506i64, &[]).unwrap(); + builder = builder.constant("SET_VAR_SYMBOL", 507i64, &[]).unwrap(); + builder = builder.constant("SHARE_SYMBOL", 508i64, &[]).unwrap(); + builder = builder.constant("SHOW_SYMBOL", 509i64, &[]).unwrap(); + builder = builder.constant("SHUTDOWN_SYMBOL", 510i64, &[]).unwrap(); + builder = builder.constant("SIGNAL_SYMBOL", 511i64, &[]).unwrap(); + builder = builder.constant("SIGNED_SYMBOL", 512i64, &[]).unwrap(); + builder = builder.constant("SIMPLE_SYMBOL", 513i64, &[]).unwrap(); + builder = builder.constant("SLAVE_SYMBOL", 514i64, &[]).unwrap(); + builder = builder.constant("SLOW_SYMBOL", 515i64, &[]).unwrap(); + builder = builder.constant("SMALLINT_SYMBOL", 516i64, &[]).unwrap(); + builder = builder.constant("SNAPSHOT_SYMBOL", 517i64, &[]).unwrap(); + builder = builder.constant("SOME_SYMBOL", 518i64, &[]).unwrap(); + builder = builder.constant("SOCKET_SYMBOL", 519i64, &[]).unwrap(); + builder = builder.constant("SONAME_SYMBOL", 520i64, &[]).unwrap(); + builder = builder.constant("SOUNDS_SYMBOL", 521i64, &[]).unwrap(); + builder = builder.constant("SOURCE_SYMBOL", 522i64, &[]).unwrap(); + builder = builder.constant("SPATIAL_SYMBOL", 523i64, &[]).unwrap(); + builder = builder.constant("SPECIFIC_SYMBOL", 524i64, &[]).unwrap(); + builder = builder + .constant("SQLEXCEPTION_SYMBOL", 525i64, &[]) + .unwrap(); + builder = builder.constant("SQLSTATE_SYMBOL", 526i64, &[]).unwrap(); + builder = builder.constant("SQLWARNING_SYMBOL", 527i64, &[]).unwrap(); + builder = builder + .constant("SQL_AFTER_GTIDS_SYMBOL", 528i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_AFTER_MTS_GAPS_SYMBOL", 529i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_BEFORE_GTIDS_SYMBOL", 530i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_BIG_RESULT_SYMBOL", 531i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_BUFFER_RESULT_SYMBOL", 532i64, &[]) + .unwrap(); + builder = builder.constant("SQL_CACHE_SYMBOL", 533i64, &[]).unwrap(); + builder = builder + .constant("SQL_CALC_FOUND_ROWS_SYMBOL", 534i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_NO_CACHE_SYMBOL", 535i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_SMALL_RESULT_SYMBOL", 536i64, &[]) + .unwrap(); + builder = builder.constant("SQL_SYMBOL", 537i64, &[]).unwrap(); + builder = builder.constant("SQL_THREAD_SYMBOL", 538i64, &[]).unwrap(); + builder = builder.constant("SSL_SYMBOL", 539i64, &[]).unwrap(); + builder = builder.constant("STACKED_SYMBOL", 540i64, &[]).unwrap(); + builder = builder.constant("STARTING_SYMBOL", 541i64, &[]).unwrap(); + builder = builder.constant("STARTS_SYMBOL", 542i64, &[]).unwrap(); + builder = builder.constant("START_SYMBOL", 543i64, &[]).unwrap(); + builder = builder + .constant("STATS_AUTO_RECALC_SYMBOL", 544i64, &[]) + .unwrap(); + builder = builder + .constant("STATS_PERSISTENT_SYMBOL", 545i64, &[]) + .unwrap(); + builder = builder + .constant("STATS_SAMPLE_PAGES_SYMBOL", 546i64, &[]) + .unwrap(); + builder = builder.constant("STATUS_SYMBOL", 547i64, &[]).unwrap(); + builder = builder.constant("STDDEV_SAMP_SYMBOL", 548i64, &[]).unwrap(); + builder = builder.constant("STDDEV_SYMBOL", 549i64, &[]).unwrap(); + builder = builder.constant("STDDEV_POP_SYMBOL", 550i64, &[]).unwrap(); + builder = builder.constant("STD_SYMBOL", 551i64, &[]).unwrap(); + builder = builder.constant("STOP_SYMBOL", 552i64, &[]).unwrap(); + builder = builder.constant("STORAGE_SYMBOL", 553i64, &[]).unwrap(); + builder = builder.constant("STORED_SYMBOL", 554i64, &[]).unwrap(); + builder = builder + .constant("STRAIGHT_JOIN_SYMBOL", 555i64, &[]) + .unwrap(); + builder = builder.constant("STRING_SYMBOL", 556i64, &[]).unwrap(); + builder = builder + .constant("SUBCLASS_ORIGIN_SYMBOL", 557i64, &[]) + .unwrap(); + builder = builder.constant("SUBDATE_SYMBOL", 558i64, &[]).unwrap(); + builder = builder.constant("SUBJECT_SYMBOL", 559i64, &[]).unwrap(); + builder = builder + .constant("SUBPARTITIONS_SYMBOL", 560i64, &[]) + .unwrap(); + builder = builder + .constant("SUBPARTITION_SYMBOL", 561i64, &[]) + .unwrap(); + builder = builder.constant("SUBSTR_SYMBOL", 562i64, &[]).unwrap(); + builder = builder.constant("SUBSTRING_SYMBOL", 563i64, &[]).unwrap(); + builder = builder.constant("SUM_SYMBOL", 564i64, &[]).unwrap(); + builder = builder.constant("SUPER_SYMBOL", 565i64, &[]).unwrap(); + builder = builder.constant("SUSPEND_SYMBOL", 566i64, &[]).unwrap(); + builder = builder.constant("SWAPS_SYMBOL", 567i64, &[]).unwrap(); + builder = builder.constant("SWITCHES_SYMBOL", 568i64, &[]).unwrap(); + builder = builder.constant("SYSDATE_SYMBOL", 569i64, &[]).unwrap(); + builder = builder.constant("SYSTEM_USER_SYMBOL", 570i64, &[]).unwrap(); + builder = builder.constant("TABLES_SYMBOL", 571i64, &[]).unwrap(); + builder = builder.constant("TABLESPACE_SYMBOL", 572i64, &[]).unwrap(); + builder = builder + .constant("TABLE_REF_PRIORITY_SYMBOL", 573i64, &[]) + .unwrap(); + builder = builder.constant("TABLE_SYMBOL", 574i64, &[]).unwrap(); + builder = builder + .constant("TABLE_CHECKSUM_SYMBOL", 575i64, &[]) + .unwrap(); + builder = builder.constant("TABLE_NAME_SYMBOL", 576i64, &[]).unwrap(); + builder = builder.constant("TEMPORARY_SYMBOL", 577i64, &[]).unwrap(); + builder = builder.constant("TEMPTABLE_SYMBOL", 578i64, &[]).unwrap(); + builder = builder.constant("TERMINATED_SYMBOL", 579i64, &[]).unwrap(); + builder = builder.constant("TEXT_SYMBOL", 580i64, &[]).unwrap(); + builder = builder.constant("THAN_SYMBOL", 581i64, &[]).unwrap(); + builder = builder.constant("THEN_SYMBOL", 582i64, &[]).unwrap(); + builder = builder.constant("TIMESTAMP_SYMBOL", 583i64, &[]).unwrap(); + builder = builder + .constant("TIMESTAMP_ADD_SYMBOL", 584i64, &[]) + .unwrap(); + builder = builder + .constant("TIMESTAMP_DIFF_SYMBOL", 585i64, &[]) + .unwrap(); + builder = builder.constant("TIME_SYMBOL", 586i64, &[]).unwrap(); + builder = builder.constant("TINYBLOB_SYMBOL", 587i64, &[]).unwrap(); + builder = builder.constant("TINYINT_SYMBOL", 588i64, &[]).unwrap(); + builder = builder.constant("TINYTEXT_SYMBOL", 589i64, &[]).unwrap(); + builder = builder.constant("TO_SYMBOL", 590i64, &[]).unwrap(); + builder = builder.constant("TRAILING_SYMBOL", 591i64, &[]).unwrap(); + builder = builder.constant("TRANSACTION_SYMBOL", 592i64, &[]).unwrap(); + builder = builder.constant("TRIGGERS_SYMBOL", 593i64, &[]).unwrap(); + builder = builder.constant("TRIGGER_SYMBOL", 594i64, &[]).unwrap(); + builder = builder.constant("TRIM_SYMBOL", 595i64, &[]).unwrap(); + builder = builder.constant("TRUE_SYMBOL", 596i64, &[]).unwrap(); + builder = builder.constant("TRUNCATE_SYMBOL", 597i64, &[]).unwrap(); + builder = builder.constant("TYPES_SYMBOL", 598i64, &[]).unwrap(); + builder = builder.constant("TYPE_SYMBOL", 599i64, &[]).unwrap(); + builder = builder.constant("UDF_RETURNS_SYMBOL", 600i64, &[]).unwrap(); + builder = builder.constant("UNCOMMITTED_SYMBOL", 601i64, &[]).unwrap(); + builder = builder.constant("UNDEFINED_SYMBOL", 602i64, &[]).unwrap(); + builder = builder.constant("UNDOFILE_SYMBOL", 603i64, &[]).unwrap(); + builder = builder + .constant("UNDO_BUFFER_SIZE_SYMBOL", 604i64, &[]) + .unwrap(); + builder = builder.constant("UNDO_SYMBOL", 605i64, &[]).unwrap(); + builder = builder.constant("UNICODE_SYMBOL", 606i64, &[]).unwrap(); + builder = builder.constant("UNINSTALL_SYMBOL", 607i64, &[]).unwrap(); + builder = builder.constant("UNION_SYMBOL", 608i64, &[]).unwrap(); + builder = builder.constant("UNIQUE_SYMBOL", 609i64, &[]).unwrap(); + builder = builder.constant("UNKNOWN_SYMBOL", 610i64, &[]).unwrap(); + builder = builder.constant("UNLOCK_SYMBOL", 611i64, &[]).unwrap(); + builder = builder.constant("UNSIGNED_SYMBOL", 612i64, &[]).unwrap(); + builder = builder.constant("UNTIL_SYMBOL", 613i64, &[]).unwrap(); + builder = builder.constant("UPDATE_SYMBOL", 614i64, &[]).unwrap(); + builder = builder.constant("UPGRADE_SYMBOL", 615i64, &[]).unwrap(); + builder = builder.constant("USAGE_SYMBOL", 616i64, &[]).unwrap(); + builder = builder + .constant("USER_RESOURCES_SYMBOL", 617i64, &[]) + .unwrap(); + builder = builder.constant("USER_SYMBOL", 618i64, &[]).unwrap(); + builder = builder.constant("USE_FRM_SYMBOL", 619i64, &[]).unwrap(); + builder = builder.constant("USE_SYMBOL", 620i64, &[]).unwrap(); + builder = builder.constant("USING_SYMBOL", 621i64, &[]).unwrap(); + builder = builder.constant("UTC_DATE_SYMBOL", 622i64, &[]).unwrap(); + builder = builder + .constant("UTC_TIMESTAMP_SYMBOL", 623i64, &[]) + .unwrap(); + builder = builder.constant("UTC_TIME_SYMBOL", 624i64, &[]).unwrap(); + builder = builder.constant("VALIDATION_SYMBOL", 625i64, &[]).unwrap(); + builder = builder.constant("VALUES_SYMBOL", 626i64, &[]).unwrap(); + builder = builder.constant("VALUE_SYMBOL", 627i64, &[]).unwrap(); + builder = builder.constant("VARBINARY_SYMBOL", 628i64, &[]).unwrap(); + builder = builder.constant("VARCHAR_SYMBOL", 629i64, &[]).unwrap(); + builder = builder + .constant("VARCHARACTER_SYMBOL", 630i64, &[]) + .unwrap(); + builder = builder.constant("VARIABLES_SYMBOL", 631i64, &[]).unwrap(); + builder = builder.constant("VARIANCE_SYMBOL", 632i64, &[]).unwrap(); + builder = builder.constant("VARYING_SYMBOL", 633i64, &[]).unwrap(); + builder = builder.constant("VAR_POP_SYMBOL", 634i64, &[]).unwrap(); + builder = builder.constant("VAR_SAMP_SYMBOL", 635i64, &[]).unwrap(); + builder = builder.constant("VIEW_SYMBOL", 636i64, &[]).unwrap(); + builder = builder.constant("VIRTUAL_SYMBOL", 637i64, &[]).unwrap(); + builder = builder.constant("WAIT_SYMBOL", 638i64, &[]).unwrap(); + builder = builder.constant("WARNINGS_SYMBOL", 639i64, &[]).unwrap(); + builder = builder.constant("WEEK_SYMBOL", 640i64, &[]).unwrap(); + builder = builder + .constant("WEIGHT_STRING_SYMBOL", 641i64, &[]) + .unwrap(); + builder = builder.constant("WHEN_SYMBOL", 642i64, &[]).unwrap(); + builder = builder.constant("WHERE_SYMBOL", 643i64, &[]).unwrap(); + builder = builder.constant("WHILE_SYMBOL", 644i64, &[]).unwrap(); + builder = builder.constant("WITH_SYMBOL", 645i64, &[]).unwrap(); + builder = builder.constant("WITHOUT_SYMBOL", 646i64, &[]).unwrap(); + builder = builder.constant("WORK_SYMBOL", 647i64, &[]).unwrap(); + builder = builder.constant("WRAPPER_SYMBOL", 648i64, &[]).unwrap(); + builder = builder.constant("WRITE_SYMBOL", 649i64, &[]).unwrap(); + builder = builder.constant("X509_SYMBOL", 650i64, &[]).unwrap(); + builder = builder.constant("XA_SYMBOL", 651i64, &[]).unwrap(); + builder = builder.constant("XID_SYMBOL", 652i64, &[]).unwrap(); + builder = builder.constant("XML_SYMBOL", 653i64, &[]).unwrap(); + builder = builder.constant("XOR_SYMBOL", 654i64, &[]).unwrap(); + builder = builder.constant("YEAR_MONTH_SYMBOL", 655i64, &[]).unwrap(); + builder = builder.constant("YEAR_SYMBOL", 656i64, &[]).unwrap(); + builder = builder.constant("ZEROFILL_SYMBOL", 657i64, &[]).unwrap(); + builder = builder.constant("PERSIST_SYMBOL", 658i64, &[]).unwrap(); + builder = builder.constant("ROLE_SYMBOL", 659i64, &[]).unwrap(); + builder = builder.constant("ADMIN_SYMBOL", 660i64, &[]).unwrap(); + builder = builder.constant("INVISIBLE_SYMBOL", 661i64, &[]).unwrap(); + builder = builder.constant("VISIBLE_SYMBOL", 662i64, &[]).unwrap(); + builder = builder.constant("EXCEPT_SYMBOL", 663i64, &[]).unwrap(); + builder = builder.constant("COMPONENT_SYMBOL", 664i64, &[]).unwrap(); + builder = builder.constant("RECURSIVE_SYMBOL", 665i64, &[]).unwrap(); + builder = builder + .constant("JSON_OBJECTAGG_SYMBOL", 666i64, &[]) + .unwrap(); + builder = builder + .constant("JSON_ARRAYAGG_SYMBOL", 667i64, &[]) + .unwrap(); + builder = builder.constant("OF_SYMBOL", 668i64, &[]).unwrap(); + builder = builder.constant("SKIP_SYMBOL", 669i64, &[]).unwrap(); + builder = builder.constant("LOCKED_SYMBOL", 670i64, &[]).unwrap(); + builder = builder.constant("NOWAIT_SYMBOL", 671i64, &[]).unwrap(); + builder = builder.constant("GROUPING_SYMBOL", 672i64, &[]).unwrap(); + builder = builder + .constant("PERSIST_ONLY_SYMBOL", 673i64, &[]) + .unwrap(); + builder = builder.constant("HISTOGRAM_SYMBOL", 674i64, &[]).unwrap(); + builder = builder.constant("BUCKETS_SYMBOL", 675i64, &[]).unwrap(); + builder = builder.constant("REMOTE_SYMBOL", 676i64, &[]).unwrap(); + builder = builder.constant("CLONE_SYMBOL", 677i64, &[]).unwrap(); + builder = builder.constant("CUME_DIST_SYMBOL", 678i64, &[]).unwrap(); + builder = builder.constant("DENSE_RANK_SYMBOL", 679i64, &[]).unwrap(); + builder = builder.constant("EXCLUDE_SYMBOL", 680i64, &[]).unwrap(); + builder = builder.constant("FIRST_VALUE_SYMBOL", 681i64, &[]).unwrap(); + builder = builder.constant("FOLLOWING_SYMBOL", 682i64, &[]).unwrap(); + builder = builder.constant("GROUPS_SYMBOL", 683i64, &[]).unwrap(); + builder = builder.constant("LAG_SYMBOL", 684i64, &[]).unwrap(); + builder = builder.constant("LAST_VALUE_SYMBOL", 685i64, &[]).unwrap(); + builder = builder.constant("LEAD_SYMBOL", 686i64, &[]).unwrap(); + builder = builder.constant("NTH_VALUE_SYMBOL", 687i64, &[]).unwrap(); + builder = builder.constant("NTILE_SYMBOL", 688i64, &[]).unwrap(); + builder = builder.constant("NULLS_SYMBOL", 689i64, &[]).unwrap(); + builder = builder.constant("OTHERS_SYMBOL", 690i64, &[]).unwrap(); + builder = builder.constant("OVER_SYMBOL", 691i64, &[]).unwrap(); + builder = builder + .constant("PERCENT_RANK_SYMBOL", 692i64, &[]) + .unwrap(); + builder = builder.constant("PRECEDING_SYMBOL", 693i64, &[]).unwrap(); + builder = builder.constant("RANK_SYMBOL", 694i64, &[]).unwrap(); + builder = builder.constant("RESPECT_SYMBOL", 695i64, &[]).unwrap(); + builder = builder.constant("ROW_NUMBER_SYMBOL", 696i64, &[]).unwrap(); + builder = builder.constant("TIES_SYMBOL", 697i64, &[]).unwrap(); + builder = builder.constant("UNBOUNDED_SYMBOL", 698i64, &[]).unwrap(); + builder = builder.constant("WINDOW_SYMBOL", 699i64, &[]).unwrap(); + builder = builder.constant("EMPTY_SYMBOL", 700i64, &[]).unwrap(); + builder = builder.constant("JSON_TABLE_SYMBOL", 701i64, &[]).unwrap(); + builder = builder.constant("NESTED_SYMBOL", 702i64, &[]).unwrap(); + builder = builder.constant("ORDINALITY_SYMBOL", 703i64, &[]).unwrap(); + builder = builder.constant("PATH_SYMBOL", 704i64, &[]).unwrap(); + builder = builder.constant("HISTORY_SYMBOL", 705i64, &[]).unwrap(); + builder = builder.constant("REUSE_SYMBOL", 706i64, &[]).unwrap(); + builder = builder.constant("SRID_SYMBOL", 707i64, &[]).unwrap(); + builder = builder + .constant("THREAD_PRIORITY_SYMBOL", 708i64, &[]) + .unwrap(); + builder = builder.constant("RESOURCE_SYMBOL", 709i64, &[]).unwrap(); + builder = builder.constant("SYSTEM_SYMBOL", 710i64, &[]).unwrap(); + builder = builder.constant("VCPU_SYMBOL", 711i64, &[]).unwrap(); + builder = builder + .constant("MASTER_PUBLIC_KEY_PATH_SYMBOL", 712i64, &[]) + .unwrap(); + builder = builder + .constant("GET_MASTER_PUBLIC_KEY_SYMBOL", 713i64, &[]) + .unwrap(); + builder = builder.constant("RESTART_SYMBOL", 714i64, &[]).unwrap(); + builder = builder.constant("DEFINITION_SYMBOL", 715i64, &[]).unwrap(); + builder = builder.constant("DESCRIPTION_SYMBOL", 716i64, &[]).unwrap(); + builder = builder + .constant("ORGANIZATION_SYMBOL", 717i64, &[]) + .unwrap(); + builder = builder.constant("REFERENCE_SYMBOL", 718i64, &[]).unwrap(); + builder = builder.constant("OPTIONAL_SYMBOL", 719i64, &[]).unwrap(); + builder = builder.constant("SECONDARY_SYMBOL", 720i64, &[]).unwrap(); + builder = builder + .constant("SECONDARY_ENGINE_SYMBOL", 721i64, &[]) + .unwrap(); + builder = builder + .constant("SECONDARY_LOAD_SYMBOL", 722i64, &[]) + .unwrap(); + builder = builder + .constant("SECONDARY_UNLOAD_SYMBOL", 723i64, &[]) + .unwrap(); + builder = builder.constant("ACTIVE_SYMBOL", 724i64, &[]).unwrap(); + builder = builder.constant("INACTIVE_SYMBOL", 725i64, &[]).unwrap(); + builder = builder.constant("LATERAL_SYMBOL", 726i64, &[]).unwrap(); + builder = builder.constant("RETAIN_SYMBOL", 727i64, &[]).unwrap(); + builder = builder.constant("OLD_SYMBOL", 728i64, &[]).unwrap(); + builder = builder + .constant("NETWORK_NAMESPACE_SYMBOL", 729i64, &[]) + .unwrap(); + builder = builder.constant("ENFORCED_SYMBOL", 730i64, &[]).unwrap(); + builder = builder.constant("ARRAY_SYMBOL", 731i64, &[]).unwrap(); + builder = builder.constant("OJ_SYMBOL", 732i64, &[]).unwrap(); + builder = builder.constant("MEMBER_SYMBOL", 733i64, &[]).unwrap(); + builder = builder.constant("RANDOM_SYMBOL", 734i64, &[]).unwrap(); + builder = builder + .constant("MASTER_COMPRESSION_ALGORITHM_SYMBOL", 735i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_ZSTD_COMPRESSION_LEVEL_SYMBOL", 736i64, &[]) + .unwrap(); + builder = builder + .constant("PRIVILEGE_CHECKS_USER_SYMBOL", 737i64, &[]) + .unwrap(); + builder = builder + .constant("MASTER_TLS_CIPHERSUITES_SYMBOL", 738i64, &[]) + .unwrap(); + builder = builder + .constant("REQUIRE_ROW_FORMAT_SYMBOL", 739i64, &[]) + .unwrap(); + builder = builder + .constant("PASSWORD_LOCK_TIME_SYMBOL", 740i64, &[]) + .unwrap(); + builder = builder + .constant("FAILED_LOGIN_ATTEMPTS_SYMBOL", 741i64, &[]) + .unwrap(); + builder = builder + .constant("REQUIRE_TABLE_PRIMARY_KEY_CHECK_SYMBOL", 742i64, &[]) + .unwrap(); + builder = builder.constant("STREAM_SYMBOL", 743i64, &[]).unwrap(); + builder = builder.constant("OFF_SYMBOL", 744i64, &[]).unwrap(); + builder = builder.constant("AT_AT_SIGN_SYMBOL", 745i64, &[]).unwrap(); + builder = builder.constant("AT_SIGN_SYMBOL", 746i64, &[]).unwrap(); + builder = builder.constant("CLOSE_CURLY_SYMBOL", 747i64, &[]).unwrap(); + builder = builder.constant("CLOSE_PAR_SYMBOL", 748i64, &[]).unwrap(); + builder = builder.constant("COLON_SYMBOL", 749i64, &[]).unwrap(); + builder = builder.constant("COMMA_SYMBOL", 750i64, &[]).unwrap(); + builder = builder.constant("DOT_SYMBOL", 751i64, &[]).unwrap(); + builder = builder.constant("OPEN_CURLY_SYMBOL", 752i64, &[]).unwrap(); + builder = builder.constant("OPEN_PAR_SYMBOL", 753i64, &[]).unwrap(); + builder = builder.constant("PARAM_MARKER", 754i64, &[]).unwrap(); + builder = builder.constant("SEMICOLON_SYMBOL", 755i64, &[]).unwrap(); + builder = builder.constant("ASSIGN_OPERATOR", 756i64, &[]).unwrap(); + builder = builder + .constant("BITWISE_AND_OPERATOR", 757i64, &[]) + .unwrap(); + builder = builder + .constant("BITWISE_NOT_OPERATOR", 758i64, &[]) + .unwrap(); + builder = builder + .constant("BITWISE_OR_OPERATOR", 759i64, &[]) + .unwrap(); + builder = builder + .constant("BITWISE_XOR_OPERATOR", 760i64, &[]) + .unwrap(); + builder = builder + .constant("CONCAT_PIPES_SYMBOL", 761i64, &[]) + .unwrap(); + builder = builder.constant("DIV_OPERATOR", 762i64, &[]).unwrap(); + builder = builder.constant("EQUAL_OPERATOR", 763i64, &[]).unwrap(); + builder = builder + .constant("GREATER_OR_EQUAL_OPERATOR", 764i64, &[]) + .unwrap(); + builder = builder + .constant("GREATER_THAN_OPERATOR", 765i64, &[]) + .unwrap(); + builder = builder + .constant("JSON_SEPARATOR_SYMBOL", 766i64, &[]) + .unwrap(); + builder = builder + .constant("JSON_UNQUOTED_SEPARATOR_SYMBOL", 767i64, &[]) + .unwrap(); + builder = builder + .constant("LESS_OR_EQUAL_OPERATOR", 768i64, &[]) + .unwrap(); + builder = builder.constant("LESS_THAN_OPERATOR", 769i64, &[]).unwrap(); + builder = builder + .constant("LOGICAL_AND_OPERATOR", 770i64, &[]) + .unwrap(); + builder = builder + .constant("LOGICAL_NOT_OPERATOR", 771i64, &[]) + .unwrap(); + builder = builder + .constant("LOGICAL_OR_OPERATOR", 772i64, &[]) + .unwrap(); + builder = builder.constant("MINUS_OPERATOR", 773i64, &[]).unwrap(); + builder = builder.constant("MOD_OPERATOR", 774i64, &[]).unwrap(); + builder = builder.constant("MULT_OPERATOR", 775i64, &[]).unwrap(); + builder = builder.constant("NOT_EQUAL_OPERATOR", 776i64, &[]).unwrap(); + builder = builder + .constant("NULL_SAFE_EQUAL_OPERATOR", 777i64, &[]) + .unwrap(); + builder = builder.constant("PLUS_OPERATOR", 778i64, &[]).unwrap(); + builder = builder + .constant("SHIFT_LEFT_OPERATOR", 779i64, &[]) + .unwrap(); + builder = builder + .constant("SHIFT_RIGHT_OPERATOR", 780i64, &[]) + .unwrap(); + builder = builder + .constant("BACK_TICK_QUOTED_ID", 781i64, &[]) + .unwrap(); + builder = builder.constant("BIN_NUMBER", 782i64, &[]).unwrap(); + builder = builder.constant("DECIMAL_NUMBER", 783i64, &[]).unwrap(); + builder = builder.constant("DOUBLE_QUOTED_TEXT", 784i64, &[]).unwrap(); + builder = builder.constant("FLOAT_NUMBER", 785i64, &[]).unwrap(); + builder = builder.constant("HEX_NUMBER", 786i64, &[]).unwrap(); + builder = builder.constant("INT_NUMBER", 787i64, &[]).unwrap(); + builder = builder.constant("LONG_NUMBER", 788i64, &[]).unwrap(); + builder = builder.constant("NCHAR_TEXT", 789i64, &[]).unwrap(); + builder = builder.constant("SINGLE_QUOTED_TEXT", 790i64, &[]).unwrap(); + builder = builder.constant("ULONGLONG_NUMBER", 791i64, &[]).unwrap(); + builder = builder.constant("AT_TEXT_SUFFIX", 792i64, &[]).unwrap(); + builder = builder.constant("IDENTIFIER", 793i64, &[]).unwrap(); + builder = builder.constant("UNDERSCORE_CHARSET", 794i64, &[]).unwrap(); + builder = builder.constant("INT1_SYMBOL", 795i64, &[]).unwrap(); + builder = builder.constant("INT2_SYMBOL", 796i64, &[]).unwrap(); + builder = builder.constant("INT3_SYMBOL", 797i64, &[]).unwrap(); + builder = builder.constant("INT4_SYMBOL", 798i64, &[]).unwrap(); + builder = builder.constant("INT8_SYMBOL", 799i64, &[]).unwrap(); + builder = builder.constant("NOT2_SYMBOL", 800i64, &[]).unwrap(); + builder = builder.constant("NULL2_SYMBOL", 801i64, &[]).unwrap(); + builder = builder.constant("SQL_TSI_DAY_SYMBOL", 802i64, &[]).unwrap(); + builder = builder + .constant("SQL_TSI_HOUR_SYMBOL", 803i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_TSI_MICROSECOND_SYMBOL", 804i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_TSI_MINUTE_SYMBOL", 805i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_TSI_MONTH_SYMBOL", 806i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_TSI_QUARTER_SYMBOL", 807i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_TSI_SECOND_SYMBOL", 808i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_TSI_WEEK_SYMBOL", 809i64, &[]) + .unwrap(); + builder = builder + .constant("SQL_TSI_YEAR_SYMBOL", 810i64, &[]) + .unwrap(); + builder = builder.constant("INTERSECT_SYMBOL", 811i64, &[]).unwrap(); + builder = builder.constant("ATTRIBUTE_SYMBOL", 812i64, &[]).unwrap(); + builder = builder + .constant("SOURCE_AUTO_POSITION_SYMBOL", 813i64, &[]) + .unwrap(); + builder = builder.constant("SOURCE_BIND_SYMBOL", 814i64, &[]).unwrap(); + builder = builder + .constant("SOURCE_COMPRESSION_ALGORITHM_SYMBOL", 815i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_CONNECT_RETRY_SYMBOL", 816i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_CONNECTION_AUTO_FAILOVER_SYMBOL", 817i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_DELAY_SYMBOL", 818i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_HEARTBEAT_PERIOD_SYMBOL", 819i64, &[]) + .unwrap(); + builder = builder.constant("SOURCE_HOST_SYMBOL", 820i64, &[]).unwrap(); + builder = builder + .constant("SOURCE_LOG_FILE_SYMBOL", 821i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_LOG_POS_SYMBOL", 822i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_PASSWORD_SYMBOL", 823i64, &[]) + .unwrap(); + builder = builder.constant("SOURCE_PORT_SYMBOL", 824i64, &[]).unwrap(); + builder = builder + .constant("SOURCE_PUBLIC_KEY_PATH_SYMBOL", 825i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_RETRY_COUNT_SYMBOL", 826i64, &[]) + .unwrap(); + builder = builder.constant("SOURCE_SSL_SYMBOL", 827i64, &[]).unwrap(); + builder = builder + .constant("SOURCE_SSL_CA_SYMBOL", 828i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_SSL_CAPATH_SYMBOL", 829i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_SSL_CERT_SYMBOL", 830i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_SSL_CIPHER_SYMBOL", 831i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_SSL_CRL_SYMBOL", 832i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_SSL_CRLPATH_SYMBOL", 833i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_SSL_KEY_SYMBOL", 834i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_SSL_VERIFY_SERVER_CERT_SYMBOL", 835i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_TLS_CIPHERSUITES_SYMBOL", 836i64, &[]) + .unwrap(); + builder = builder + .constant("SOURCE_TLS_VERSION_SYMBOL", 837i64, &[]) + .unwrap(); + builder = builder.constant("SOURCE_USER_SYMBOL", 838i64, &[]).unwrap(); + builder = builder + .constant("SOURCE_ZSTD_COMPRESSION_LEVEL_SYMBOL", 839i64, &[]) + .unwrap(); + builder = builder + .constant("GET_SOURCE_PUBLIC_KEY_SYMBOL", 840i64, &[]) + .unwrap(); + builder = builder.constant("GTID_ONLY_SYMBOL", 841i64, &[]).unwrap(); + builder = builder + .constant("ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_SYMBOL", 842i64, &[]) + .unwrap(); + builder = builder.constant("ZONE_SYMBOL", 843i64, &[]).unwrap(); + builder = builder.constant("INNODB_SYMBOL", 844i64, &[]).unwrap(); + builder = builder.constant("TLS_SYMBOL", 845i64, &[]).unwrap(); + builder = builder.constant("REDO_LOG_SYMBOL", 846i64, &[]).unwrap(); + builder = builder.constant("KEYRING_SYMBOL", 847i64, &[]).unwrap(); + builder = builder + .constant("ENGINE_ATTRIBUTE_SYMBOL", 848i64, &[]) + .unwrap(); + builder = builder + .constant("SECONDARY_ENGINE_ATTRIBUTE_SYMBOL", 849i64, &[]) + .unwrap(); + builder = builder.constant("JSON_VALUE_SYMBOL", 850i64, &[]).unwrap(); + builder = builder.constant("RETURNING_SYMBOL", 851i64, &[]).unwrap(); + builder = builder + .constant("GEOMCOLLECTION_SYMBOL", 852i64, &[]) + .unwrap(); + builder = builder.constant("COMMENT", 900i64, &[]).unwrap(); + builder = builder + .constant("MYSQL_COMMENT_START", 901i64, &[]) + .unwrap(); + builder = builder.constant("MYSQL_COMMENT_END", 902i64, &[]).unwrap(); + builder = builder.constant("WHITESPACE", 0i64, &[]).unwrap(); + builder = builder.constant("EOF", -1i64, &[]).unwrap(); + builder = builder.constant("TOKENS", array_tokens(), &[]).unwrap(); + builder = builder + .constant("FUNCTIONS", array_functions(), &[]) + .unwrap(); + builder = builder.constant("SYNONYMS", array_synonyms(), &[]).unwrap(); + builder = builder.constant("VERSIONS", array_versions(), &[]).unwrap(); + builder = builder + .constant("UNDERSCORE_CHARSETS", array_underscore_charsets(), &[]) + .unwrap(); + builder +} diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs new file mode 100644 index 00000000..e622b585 --- /dev/null +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs @@ -0,0 +1,1208 @@ +#![cfg_attr(windows, feature(abi_vectorcall))] + +use std::collections::{HashMap, HashSet}; +use std::sync::{Arc, Mutex, OnceLock}; + +use ext_php_rs::convert::{IntoZval, IntoZvalDyn}; +use ext_php_rs::exception::{PhpException, PhpResult}; +use ext_php_rs::flags::DataType; +use ext_php_rs::prelude::*; +use ext_php_rs::types::{ArrayKey, ZendCallable, ZendHashTable, Zval}; +use ext_php_rs::zend::ModuleEntry; +use ext_php_rs::{info_table_end, info_table_row, info_table_start}; + +mod lexer_constants; +use lexer_constants as lex; +use lexer_constants::register_lexer_constants; + +const SQL_MODE_HIGH_NOT_PRECEDENCE: i64 = 1; +const SQL_MODE_PIPES_AS_CONCAT: i64 = 2; +const SQL_MODE_IGNORE_SPACE: i64 = 4; +const SQL_MODE_NO_BACKSLASH_ESCAPES: i64 = 8; +const STACK_RED_ZONE: usize = 128 * 1024; +const STACK_GROW_SIZE: usize = 8 * 1024 * 1024; + +#[derive(Clone)] +struct BinaryString(Vec); + +impl IntoZval for BinaryString { + const TYPE: DataType = DataType::String; + const NULLABLE: bool = false; + + fn set_zval(self, zv: &mut Zval, _persistent: bool) -> ext_php_rs::error::Result<()> { + zv.set_binary(self.0); + Ok(()) + } +} + +fn php_error(message: impl ToString) -> PhpException { + PhpException::default(message.to_string()) +} + +fn php_function(name: &str) -> PhpResult> { + ZendCallable::try_from_name(name).map_err(php_error) +} + +fn sql_modes_mask(sql_modes: &[String]) -> i64 { + let mut mask = 0; + for sql_mode in sql_modes { + match sql_mode.to_ascii_uppercase().as_str() { + "HIGH_NOT_PRECEDENCE" => mask |= SQL_MODE_HIGH_NOT_PRECEDENCE, + "PIPES_AS_CONCAT" => mask |= SQL_MODE_PIPES_AS_CONCAT, + "IGNORE_SPACE" => mask |= SQL_MODE_IGNORE_SPACE, + "NO_BACKSLASH_ESCAPES" => mask |= SQL_MODE_NO_BACKSLASH_ESCAPES, + _ => {} + } + } + mask +} + +fn zval_to_weak_string_bytes(zv: &Zval) -> PhpResult> { + if let Some(bytes) = zv.binary::() { + return Ok(bytes); + } + if zv.is_null() || zv.is_false() { + return Ok(Vec::new()); + } + if zv.is_true() { + return Ok(b"1".to_vec()); + } + if let Some(value) = zv.long() { + return Ok(value.to_string().into_bytes()); + } + if let Some(value) = zv.double() { + return Ok(value.to_string().into_bytes()); + } + Err(php_error("Invalid value given for argument `sql`.")) +} + +fn byte_in(byte: u8, mask: &[u8]) -> bool { + mask.contains(&byte) +} + +fn span_while(bytes: &[u8], mut pos: usize, predicate: impl Fn(u8) -> bool) -> usize { + while pos < bytes.len() && predicate(bytes[pos]) { + pos += 1; + } + pos +} + +fn span_until(bytes: &[u8], mut pos: usize, needles: &[u8]) -> usize { + while pos < bytes.len() && !needles.contains(&bytes[pos]) { + pos += 1; + } + pos +} + +fn bytes_ascii_upper(bytes: &[u8]) -> String { + bytes + .iter() + .map(|byte| byte.to_ascii_uppercase() as char) + .collect() +} + +fn bytes_ascii_lower(bytes: &[u8]) -> String { + bytes + .iter() + .map(|byte| byte.to_ascii_lowercase() as char) + .collect() +} + +#[derive(Clone, Copy)] +struct TokenInfo { + id: i64, + start: usize, + end: usize, +} + +#[php_class] +#[php(name = "WP_MySQL_Native_Lexer", modifier = "register_lexer_constants")] +pub struct WpMySqlNativeLexer { + sql: Vec, + sql_zval: Zval, + mysql_version: i64, + sql_modes: i64, + bytes_already_read: usize, + token_starts_at: usize, + token_type: Option, + in_mysql_comment: bool, +} + +#[php_impl] +#[php(change_method_case = "snake_case")] +impl WpMySqlNativeLexer { + pub fn __construct( + sql: &Zval, + mysql_version: Option, + sql_modes: Option>, + ) -> PhpResult { + let mysql_version = mysql_version.unwrap_or(80038); + let sql_modes = sql_modes.unwrap_or_default(); + let sql = zval_to_weak_string_bytes(sql)?; + let sql_zval = BinaryString(sql.clone()) + .into_zval(false) + .map_err(php_error)?; + + Ok(Self { + sql, + sql_zval, + mysql_version, + sql_modes: sql_modes_mask(&sql_modes), + bytes_already_read: 0, + token_starts_at: 0, + token_type: None, + in_mysql_comment: false, + }) + } + + pub fn next_token(&mut self) -> bool { + if self.token_type == Some(lex::EOF) + || (self.token_type.is_none() && self.bytes_already_read > 0) + { + self.token_type = None; + return false; + } + + loop { + self.token_starts_at = self.bytes_already_read; + self.token_type = self.read_next_token(); + if !matches!( + self.token_type, + Some( + lex::WHITESPACE + | lex::COMMENT + | lex::MYSQL_COMMENT_START + | lex::MYSQL_COMMENT_END + ) + ) { + break; + } + } + + self.token_type.is_some() + } + + pub fn get_token(&mut self) -> PhpResult { + match self.current_token_info() { + Some(token) => self.create_token(token), + None => Ok(Zval::null()), + } + } + + pub fn remaining_tokens(&mut self) -> PhpResult> { + let mut tokens = Vec::new(); + while self.next_token() { + if let Some(token) = self.current_token_info() { + tokens.push(self.create_token(token)?); + } + } + Ok(tokens) + } + + pub fn get_mysql_version(&self) -> i64 { + self.mysql_version + } + + pub fn is_sql_mode_active(&self, mode: i64) -> bool { + (self.sql_modes & mode) != 0 + } + + pub fn get_token_id(token_name: String) -> Option { + lex::token_id(&token_name) + } + + pub fn get_token_name(token_id: i64) -> Option { + lex::token_name(token_id).map(ToOwned::to_owned) + } +} + +impl WpMySqlNativeLexer { + fn current_token_info(&self) -> Option { + self.token_type.map(|id| TokenInfo { + id, + start: self.token_starts_at, + end: self.bytes_already_read, + }) + } + + fn create_token(&self, token: TokenInfo) -> PhpResult { + let id = token.id; + let start = i64::try_from(token.start).map_err(php_error)?; + let length = i64::try_from(token.end.saturating_sub(token.start)).map_err(php_error)?; + let no_backslash = self.is_sql_mode_active(SQL_MODE_NO_BACKSLASH_ESCAPES); + php_function("wp_sqlite_mysql_native_new_token")? + .try_call(vec![ + &id as &dyn IntoZvalDyn, + &start as &dyn IntoZvalDyn, + &length as &dyn IntoZvalDyn, + &self.sql_zval as &dyn IntoZvalDyn, + &no_backslash as &dyn IntoZvalDyn, + ]) + .map_err(php_error) + } + + fn read_next_token(&mut self) -> Option { + let byte = self.byte_at(self.bytes_already_read); + let next_byte = self.byte_at(self.bytes_already_read + 1); + + let token = match byte { + Some(b'\'' | b'"' | b'`') => self.read_quoted_text(), + Some(byte) if byte.is_ascii_digit() => self.read_number(), + Some(b'.') => { + if next_byte.is_some_and(|byte| byte.is_ascii_digit()) { + self.read_number() + } else { + self.bytes_already_read += 1; + Some(lex::DOT_SYMBOL) + } + } + Some(b'=') => { + self.bytes_already_read += 1; + Some(lex::EQUAL_OPERATOR) + } + Some(b':') => { + self.bytes_already_read += 1; + if next_byte == Some(b'=') { + self.bytes_already_read += 1; + Some(lex::ASSIGN_OPERATOR) + } else { + Some(lex::COLON_SYMBOL) + } + } + Some(b'<') => self.read_less_than(next_byte), + Some(b'>') => self.read_greater_than(next_byte), + Some(b'!') => { + self.bytes_already_read += 1; + if next_byte == Some(b'=') { + self.bytes_already_read += 1; + Some(lex::NOT_EQUAL_OPERATOR) + } else { + Some(lex::LOGICAL_NOT_OPERATOR) + } + } + Some(b'+') => { + self.bytes_already_read += 1; + Some(lex::PLUS_OPERATOR) + } + Some(b'-') => self.read_minus(next_byte), + Some(b'*') => self.read_star(next_byte), + Some(b'/') => self.read_slash(next_byte), + Some(b'%') => { + self.bytes_already_read += 1; + Some(lex::MOD_OPERATOR) + } + Some(b'&') => { + self.bytes_already_read += 1; + if next_byte == Some(b'&') { + self.bytes_already_read += 1; + Some(lex::LOGICAL_AND_OPERATOR) + } else { + Some(lex::BITWISE_AND_OPERATOR) + } + } + Some(b'^') => { + self.bytes_already_read += 1; + Some(lex::BITWISE_XOR_OPERATOR) + } + Some(b'|') => { + self.bytes_already_read += 1; + if next_byte == Some(b'|') { + self.bytes_already_read += 1; + if self.is_sql_mode_active(SQL_MODE_PIPES_AS_CONCAT) { + Some(lex::CONCAT_PIPES_SYMBOL) + } else { + Some(lex::LOGICAL_OR_OPERATOR) + } + } else { + Some(lex::BITWISE_OR_OPERATOR) + } + } + Some(b'~') => { + self.bytes_already_read += 1; + Some(lex::BITWISE_NOT_OPERATOR) + } + Some(b',') => { + self.bytes_already_read += 1; + Some(lex::COMMA_SYMBOL) + } + Some(b';') => { + self.bytes_already_read += 1; + Some(lex::SEMICOLON_SYMBOL) + } + Some(b'(') => { + self.bytes_already_read += 1; + Some(lex::OPEN_PAR_SYMBOL) + } + Some(b')') => { + self.bytes_already_read += 1; + Some(lex::CLOSE_PAR_SYMBOL) + } + Some(b'{') => { + self.bytes_already_read += 1; + Some(lex::OPEN_CURLY_SYMBOL) + } + Some(b'}') => { + self.bytes_already_read += 1; + Some(lex::CLOSE_CURLY_SYMBOL) + } + Some(b'@') => self.read_at(next_byte), + Some(b'?') => { + self.bytes_already_read += 1; + Some(lex::PARAM_MARKER) + } + Some(b'\\') => { + self.bytes_already_read += 1; + if next_byte == Some(b'N') { + self.bytes_already_read += 1; + Some(lex::NULL2_SYMBOL) + } else { + None + } + } + Some(b'#') => Some(self.read_line_comment()), + Some(byte) if byte_in(byte, lex::WHITESPACE_MASK.as_bytes()) => { + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + byte_in(byte, lex::WHITESPACE_MASK.as_bytes()) + }); + Some(lex::WHITESPACE) + } + Some(b'x' | b'X' | b'b' | b'B') if next_byte == Some(b'\'') => self.read_number(), + Some(b'n' | b'N') if next_byte == Some(b'\'') => { + self.bytes_already_read += 1; + let token = self.read_quoted_text(); + if token == Some(lex::SINGLE_QUOTED_TEXT) { + Some(lex::NCHAR_TEXT) + } else { + token + } + } + None => Some(lex::EOF), + Some(_) => { + let started_at = self.bytes_already_read; + let mut token = self.read_identifier(); + if token == Some(lex::IDENTIFIER) { + if started_at > 0 && self.byte_at(started_at - 1) == Some(b'.') { + token = Some(lex::IDENTIFIER); + } else if self.byte_at(started_at) == Some(b'_') + && lex::is_underscore_charset(&bytes_ascii_lower( + &self.sql[self.token_starts_at..self.bytes_already_read], + )) + { + token = Some(lex::UNDERSCORE_CHARSET); + } else { + let identifier = + self.sql[self.token_starts_at..self.bytes_already_read].to_vec(); + token = Some(self.determine_identifier_or_keyword_type(&identifier)); + } + } + token + } + }; + + token + } + + fn byte_at(&self, pos: usize) -> Option { + self.sql.get(pos).copied() + } + + fn read_less_than(&mut self, next_byte: Option) -> Option { + self.bytes_already_read += 1; + if next_byte == Some(b'=') { + self.bytes_already_read += 1; + if self.byte_at(self.bytes_already_read) == Some(b'>') { + self.bytes_already_read += 1; + Some(lex::NULL_SAFE_EQUAL_OPERATOR) + } else { + Some(lex::LESS_OR_EQUAL_OPERATOR) + } + } else if next_byte == Some(b'>') { + self.bytes_already_read += 1; + Some(lex::NOT_EQUAL_OPERATOR) + } else if next_byte == Some(b'<') { + self.bytes_already_read += 1; + Some(lex::SHIFT_LEFT_OPERATOR) + } else { + Some(lex::LESS_THAN_OPERATOR) + } + } + + fn read_greater_than(&mut self, next_byte: Option) -> Option { + self.bytes_already_read += 1; + if next_byte == Some(b'=') { + self.bytes_already_read += 1; + Some(lex::GREATER_OR_EQUAL_OPERATOR) + } else if next_byte == Some(b'>') { + self.bytes_already_read += 1; + Some(lex::SHIFT_RIGHT_OPERATOR) + } else { + Some(lex::GREATER_THAN_OPERATOR) + } + } + + fn read_minus(&mut self, next_byte: Option) -> Option { + if next_byte == Some(b'-') + && self.bytes_already_read + 2 < self.sql.len() + && byte_in( + self.sql[self.bytes_already_read + 2], + lex::WHITESPACE_MASK.as_bytes(), + ) + { + Some(self.read_line_comment()) + } else if next_byte == Some(b'>') { + self.bytes_already_read += 2; + if self.byte_at(self.bytes_already_read) == Some(b'>') { + self.bytes_already_read += 1; + if self.mysql_version >= 50713 { + Some(lex::JSON_UNQUOTED_SEPARATOR_SYMBOL) + } else { + None + } + } else if self.mysql_version >= 50708 { + Some(lex::JSON_SEPARATOR_SYMBOL) + } else { + None + } + } else { + self.bytes_already_read += 1; + Some(lex::MINUS_OPERATOR) + } + } + + fn read_star(&mut self, next_byte: Option) -> Option { + self.bytes_already_read += 1; + if next_byte == Some(b'/') && self.in_mysql_comment { + self.bytes_already_read += 1; + self.in_mysql_comment = false; + Some(lex::MYSQL_COMMENT_END) + } else { + Some(lex::MULT_OPERATOR) + } + } + + fn read_slash(&mut self, next_byte: Option) -> Option { + if next_byte == Some(b'*') { + if self.byte_at(self.bytes_already_read + 2) == Some(b'!') { + Some(self.read_mysql_comment()) + } else { + self.bytes_already_read += 2; + self.read_comment_content(); + Some(lex::COMMENT) + } + } else { + self.bytes_already_read += 1; + Some(lex::DIV_OPERATOR) + } + } + + fn read_at(&mut self, next_byte: Option) -> Option { + self.bytes_already_read += 1; + if next_byte == Some(b'@') { + self.bytes_already_read += 1; + return Some(lex::AT_AT_SIGN_SYMBOL); + } + + let start = self.bytes_already_read; + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'.' | b'$') + }); + if self.bytes_already_read > start { + Some(lex::AT_TEXT_SUFFIX) + } else { + Some(lex::AT_SIGN_SYMBOL) + } + } + + fn read_identifier(&mut self) -> Option { + let started_at = self.bytes_already_read; + loop { + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'$') + }); + + let byte_1 = self.byte_at(self.bytes_already_read).unwrap_or(0); + if !(0xC2..=0xEF).contains(&byte_1) { + break; + } + + let byte_2 = self.byte_at(self.bytes_already_read + 1).unwrap_or(0); + if byte_1 <= 0xDF && (0x80..=0xBF).contains(&byte_2) { + self.bytes_already_read += 2; + continue; + } + + let byte_3 = self.byte_at(self.bytes_already_read + 2).unwrap_or(0); + if byte_1 <= 0xEF + && (0x80..=0xBF).contains(&byte_2) + && (0x80..=0xBF).contains(&byte_3) + && !(byte_1 == 0xED && byte_2 >= 0xA0) + && !(byte_1 == 0xE0 && byte_2 < 0xA0) + { + self.bytes_already_read += 3; + continue; + } + + break; + } + + (self.bytes_already_read > started_at).then_some(lex::IDENTIFIER) + } + + fn read_number(&mut self) -> Option { + let byte = self.byte_at(self.bytes_already_read); + let next_byte = self.byte_at(self.bytes_already_read + 1); + let third_byte = self.byte_at(self.bytes_already_read + 2); + let mut token_type; + + if (byte == Some(b'0') + && next_byte == Some(b'x') + && third_byte.is_some_and(|byte| byte_in(byte, lex::HEX_DIGIT_MASK.as_bytes()))) + || (matches!(byte, Some(b'x' | b'X')) && next_byte == Some(b'\'')) + { + let is_quoted = next_byte == Some(b'\''); + self.bytes_already_read += 2; + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + byte_in(byte, lex::HEX_DIGIT_MASK.as_bytes()) + }); + if is_quoted { + if self.byte_at(self.bytes_already_read) != Some(b'\'') { + return None; + } + self.bytes_already_read += 1; + } + token_type = lex::HEX_NUMBER; + } else if (byte == Some(b'0') + && next_byte == Some(b'b') + && matches!(third_byte, Some(b'0' | b'1'))) + || (matches!(byte, Some(b'b' | b'B')) && next_byte == Some(b'\'')) + { + let is_quoted = next_byte == Some(b'\''); + self.bytes_already_read += 2; + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + matches!(byte, b'0' | b'1') + }); + if is_quoted { + if self.byte_at(self.bytes_already_read) != Some(b'\'') { + return None; + } + self.bytes_already_read += 1; + } + token_type = lex::BIN_NUMBER; + } else { + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + byte.is_ascii_digit() + }); + token_type = lex::INT_NUMBER; + + if self.byte_at(self.bytes_already_read) == Some(b'.') { + self.bytes_already_read += 1; + token_type = lex::DECIMAL_NUMBER; + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + byte.is_ascii_digit() + }); + } + + let exponent = self.byte_at(self.bytes_already_read); + let next = self.byte_at(self.bytes_already_read + 1); + let has_exponent = matches!(exponent, Some(b'e' | b'E')) + && (next.is_some_and(|byte| byte.is_ascii_digit()) + || (matches!(next, Some(b'+' | b'-')) + && self + .byte_at(self.bytes_already_read + 2) + .is_some_and(|byte| byte.is_ascii_digit()))); + if has_exponent { + self.bytes_already_read += 2; + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + byte.is_ascii_digit() + }); + token_type = lex::FLOAT_NUMBER; + } + } + + let token_bytes = &self.sql[self.token_starts_at..self.bytes_already_read]; + let possible_identifier_prefix = token_type == lex::INT_NUMBER + || (token_bytes.first() == Some(&b'0') + && matches!(token_bytes.get(1), Some(b'b' | b'x'))); + + if possible_identifier_prefix && self.read_identifier() == Some(lex::IDENTIFIER) { + token_type = lex::IDENTIFIER; + } + + if token_type == lex::INT_NUMBER { + let mut bytes = &self.sql[self.token_starts_at..self.bytes_already_read]; + if bytes.len() < 10 { + return Some(lex::INT_NUMBER); + } + while bytes.first() == Some(&b'0') { + bytes = &bytes[1..]; + } + let len = bytes.len(); + return Some(if len < 10 { + lex::INT_NUMBER + } else if len == 10 { + if bytes > b"2147483647" { + lex::LONG_NUMBER + } else { + lex::INT_NUMBER + } + } else if len < 19 { + lex::LONG_NUMBER + } else if len == 19 { + if bytes > b"9223372036854775807" { + lex::ULONGLONG_NUMBER + } else { + lex::LONG_NUMBER + } + } else if len == 20 { + if bytes > b"18446744073709551615" { + lex::DECIMAL_NUMBER + } else { + lex::ULONGLONG_NUMBER + } + } else { + lex::DECIMAL_NUMBER + }); + } + + Some(token_type) + } + + fn read_quoted_text(&mut self) -> Option { + let quote = self.byte_at(self.bytes_already_read)?; + self.bytes_already_read += 1; + let no_backslash_escapes = self.is_sql_mode_active(SQL_MODE_NO_BACKSLASH_ESCAPES); + let mut at = self.bytes_already_read; + + loop { + at = span_until(&self.sql, at, &[quote]); + + if !no_backslash_escapes { + let mut i = 0usize; + while at > i && self.byte_at(at - i - 1) == Some(b'\\') { + i += 1; + } + if i % 2 == 1 { + at += 1; + if at > self.sql.len() { + return None; + } + continue; + } + } + + if self.byte_at(at) != Some(quote) { + return None; + } + + if self.byte_at(at + 1) == Some(quote) { + at += 2; + continue; + } + break; + } + + self.bytes_already_read = at + 1; + Some(match quote { + b'`' => lex::BACK_TICK_QUOTED_ID, + b'"' => lex::DOUBLE_QUOTED_TEXT, + _ => lex::SINGLE_QUOTED_TEXT, + }) + } + + fn read_line_comment(&mut self) -> i64 { + self.bytes_already_read = span_until(&self.sql, self.bytes_already_read, b"\r\n"); + lex::COMMENT + } + + fn read_mysql_comment(&mut self) -> i64 { + self.bytes_already_read += 3; + let digit_start = self.bytes_already_read; + let digit_end = + span_while(&self.sql, digit_start, |byte| byte.is_ascii_digit()).min(digit_start + 5); + let digit_count = digit_end - digit_start; + let is_version_comment = digit_count == 5; + let version = if is_version_comment { + std::str::from_utf8(&self.sql[digit_start..digit_end]) + .ok() + .and_then(|value| value.parse::().ok()) + .unwrap_or(0) + } else { + 0 + }; + + if self.mysql_version < version { + self.read_comment_content(); + lex::COMMENT + } else { + self.bytes_already_read += digit_count; + self.in_mysql_comment = true; + lex::MYSQL_COMMENT_START + } + } + + fn read_comment_content(&mut self) { + loop { + self.bytes_already_read = span_until(&self.sql, self.bytes_already_read, b"*"); + self.bytes_already_read += 1; + match self.byte_at(self.bytes_already_read) { + None => break, + Some(b'/') => { + self.bytes_already_read += 1; + break; + } + _ => {} + } + } + } + + fn determine_identifier_or_keyword_type(&mut self, value: &[u8]) -> i64 { + let upper = bytes_ascii_upper(value); + let mut token_type = match lex::keyword_token(&upper) { + Some(token_type) => token_type, + None => return lex::IDENTIFIER, + }; + + if let Some(version) = lex::version_rule(token_type) { + if self.mysql_version < version || -version >= self.mysql_version { + return lex::IDENTIFIER; + } + } + + if token_type == lex::MAX_STATEMENT_TIME_SYMBOL + && !(self.mysql_version >= 50704 && self.mysql_version < 50708) + { + return lex::IDENTIFIER; + } + if token_type == lex::NONBLOCKING_SYMBOL + && !(self.mysql_version >= 50700 && self.mysql_version < 50706) + { + return lex::IDENTIFIER; + } + if token_type == lex::REMOTE_SYMBOL + && (self.mysql_version >= 80003 && self.mysql_version < 80014) + { + return lex::IDENTIFIER; + } + + if lex::is_function_token(token_type) { + if self.is_sql_mode_active(SQL_MODE_IGNORE_SPACE) { + self.bytes_already_read = span_while(&self.sql, self.bytes_already_read, |byte| { + byte_in(byte, lex::WHITESPACE_MASK.as_bytes()) + }); + } + if self.byte_at(self.bytes_already_read) != Some(b'(') { + return lex::IDENTIFIER; + } + } + + if token_type == lex::NOT_SYMBOL && self.is_sql_mode_active(SQL_MODE_HIGH_NOT_PRECEDENCE) { + token_type = lex::NOT2_SYMBOL; + } + + lex::token_synonym(token_type).unwrap_or(token_type) + } +} + +#[derive(Clone)] +struct Grammar { + highest_terminal_id: i64, + rules: HashMap>>, + lookahead: HashMap>, + rule_names: HashMap, + fragment_ids: HashSet, + query_rule_id: i64, +} + +static GRAMMAR_CACHE: OnceLock>>> = OnceLock::new(); + +enum AstChild { + Node(AstNode), + Token(usize), +} + +struct AstNode { + rule_id: i64, + rule_name: String, + children: Vec, +} + +enum ParseMatch { + No, + Empty, + Node(AstNode), + Token(usize), +} + +#[php_class] +#[php(name = "WP_MySQL_Native_Parser")] +pub struct WpMySqlNativeParser { + grammar: Arc, + tokens: Vec, + token_ids: Vec, + position: usize, + current_ast: Option, +} + +#[php_impl] +#[php(change_method_case = "snake_case")] +impl WpMySqlNativeParser { + pub fn __construct(grammar: &mut Zval, tokens: &mut Zval) -> PhpResult { + let grammar = export_grammar(grammar)?; + let (tokens, token_ids) = export_tokens(tokens)?; + + Ok(Self { + grammar, + tokens, + token_ids, + position: 0, + current_ast: None, + }) + } + + pub fn parse(&mut self) -> PhpResult { + stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { + match self.parse_recursive(self.grammar.query_rule_id)? { + ParseMatch::No => Ok(Zval::null()), + ParseMatch::Empty => { + let mut zval = Zval::new(); + zval.set_bool(true); + Ok(zval) + } + ParseMatch::Node(node) => self.create_php_node(&node), + ParseMatch::Token(index) => Ok(self.tokens[index].shallow_clone()), + } + }) + } + + pub fn next_query(&mut self) -> PhpResult { + if self.position >= self.tokens.len() { + self.current_ast = None; + return Ok(false); + } + + self.current_ast = Some(self.parse()?); + Ok(true) + } + + pub fn get_query_ast(&mut self) -> PhpResult { + match self.current_ast.as_ref() { + Some(ast) => Ok(ast.shallow_clone()), + None => Ok(Zval::null()), + } + } +} + +impl WpMySqlNativeParser { + fn parse_recursive(&mut self, rule_id: i64) -> PhpResult { + stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { + self.parse_recursive_inner(rule_id) + }) + } + + fn parse_recursive_inner(&mut self, rule_id: i64) -> PhpResult { + if rule_id <= self.grammar.highest_terminal_id { + if self.position >= self.tokens.len() { + return Ok(ParseMatch::No); + } + if rule_id == 0 { + return Ok(ParseMatch::Empty); + } + if self.token_ids[self.position] == rule_id { + let token_index = self.position; + self.position += 1; + return Ok(ParseMatch::Token(token_index)); + } + return Ok(ParseMatch::No); + } + + let Some(branch_count) = self.grammar.rules.get(&rule_id).map(Vec::len) else { + return Ok(ParseMatch::No); + }; + if branch_count == 0 { + return Ok(ParseMatch::No); + } + + if let Some(lookahead) = self.grammar.lookahead.get(&rule_id) { + let token_id = self.token_ids.get(self.position).copied().unwrap_or(0); + if !lookahead.contains(&token_id) && !lookahead.contains(&0) { + return Ok(ParseMatch::No); + } + } + + let rule_name = self + .grammar + .rule_names + .get(&rule_id) + .cloned() + .unwrap_or_default(); + let starting_position = self.position; + let mut matched_node = None; + + for branch_index in 0..branch_count { + let branch = self.grammar.rules.get(&rule_id).unwrap()[branch_index].clone(); + self.position = starting_position; + let mut children = Vec::new(); + let mut branch_matches = true; + + for subrule_id in branch { + match self.parse_recursive(subrule_id)? { + ParseMatch::No => { + branch_matches = false; + break; + } + ParseMatch::Empty => {} + ParseMatch::Token(token_index) => { + children.push(AstChild::Token(token_index)); + } + ParseMatch::Node(subnode) => { + if self.grammar.fragment_ids.contains(&subrule_id) { + children.extend(subnode.children); + } else { + children.push(AstChild::Node(subnode)); + } + } + } + } + + if branch_matches + && rule_name == "selectStatement" + && self + .token_ids + .get(self.position) + .is_some_and(|token_id| *token_id == lex::INTO_SYMBOL) + { + branch_matches = false; + } + + if branch_matches { + matched_node = Some(AstNode { + rule_id, + rule_name: rule_name.clone(), + children, + }); + break; + } + } + + let Some(node) = matched_node else { + self.position = starting_position; + return Ok(ParseMatch::No); + }; + + if node.children.is_empty() { + Ok(ParseMatch::Empty) + } else { + Ok(ParseMatch::Node(node)) + } + } + + fn create_php_node(&self, ast_node: &AstNode) -> PhpResult { + stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { + self.create_php_node_inner(ast_node) + }) + } + + fn create_php_node_inner(&self, ast_node: &AstNode) -> PhpResult { + let node = create_node(ast_node.rule_id, &ast_node.rule_name)?; + + for child in &ast_node.children { + let child_zval = match child { + AstChild::Node(child_node) => self.create_php_node(child_node)?, + AstChild::Token(index) => self.tokens[*index].shallow_clone(), + }; + node.object() + .ok_or_else(|| php_error("Parser node must be an object"))? + .try_call_method("append_child", vec![&child_zval as &dyn IntoZvalDyn]) + .map_err(php_error)?; + } + + Ok(node) + } +} + +fn export_grammar(grammar: &mut Zval) -> PhpResult> { + let grammar_id = grammar.object().map(|object| object.get_id()); + if let Some(grammar_id) = grammar_id { + let cache = GRAMMAR_CACHE.get_or_init(|| Mutex::new(HashMap::new())); + if let Some(cached) = cache + .lock() + .map_err(|_| php_error("Grammar cache lock poisoned"))? + .get(&grammar_id) + { + return Ok(Arc::clone(cached)); + } + } + + let exported = php_function("wp_sqlite_mysql_native_export_grammar")? + .try_call(vec![&*grammar as &dyn IntoZvalDyn]) + .map_err(php_error)?; + let array = exported + .array() + .ok_or_else(|| php_error("Exported grammar must be an array"))?; + + let highest_terminal_id = array + .get("highest_terminal_id") + .and_then(Zval::long) + .ok_or_else(|| php_error("Missing grammar highest_terminal_id"))?; + let rules = parse_rules( + array + .get("rules") + .and_then(Zval::array) + .ok_or_else(|| php_error("Missing grammar rules"))?, + )?; + let lookahead = parse_lookahead( + array + .get("lookahead_is_match_possible") + .and_then(Zval::array) + .ok_or_else(|| php_error("Missing grammar lookahead"))?, + )?; + let rule_names = parse_rule_names( + array + .get("rule_names") + .and_then(Zval::array) + .ok_or_else(|| php_error("Missing grammar rule_names"))?, + )?; + let fragment_ids = parse_id_set( + array + .get("fragment_ids") + .and_then(Zval::array) + .ok_or_else(|| php_error("Missing grammar fragment_ids"))?, + )?; + let query_rule_id = rule_names + .iter() + .find_map(|(id, name)| (name == "query").then_some(*id)) + .ok_or_else(|| php_error("Missing query grammar rule"))?; + + let grammar = Arc::new(Grammar { + highest_terminal_id, + rules, + lookahead, + rule_names, + fragment_ids, + query_rule_id, + }); + + if let Some(grammar_id) = grammar_id { + GRAMMAR_CACHE + .get_or_init(|| Mutex::new(HashMap::new())) + .lock() + .map_err(|_| php_error("Grammar cache lock poisoned"))? + .insert(grammar_id, Arc::clone(&grammar)); + } + + Ok(grammar) +} + +fn export_tokens(tokens: &mut Zval) -> PhpResult<(Vec, Vec)> { + let array = tokens + .array() + .ok_or_else(|| php_error("Parser tokens must be an array"))?; + let mut token_objects = Vec::with_capacity(array.len()); + let mut token_ids = Vec::with_capacity(array.len()); + + for (_, token) in array { + let token_object = token + .object() + .ok_or_else(|| php_error("Parser token must be an object"))?; + let id = token_object.get_property::("id").map_err(php_error)?; + token_objects.push(token.shallow_clone()); + token_ids.push(id); + } + + Ok((token_objects, token_ids)) +} + +fn parse_rules(array: &ZendHashTable) -> PhpResult>>> { + let mut rules = HashMap::new(); + for (rule_key, branches_zval) in array { + let rule_id = array_key_to_i64(rule_key)?; + let branches_array = branches_zval + .array() + .ok_or_else(|| php_error("Grammar branches must be arrays"))?; + let mut branches = Vec::with_capacity(branches_array.len()); + for (_, branch_zval) in branches_array { + let branch_array = branch_zval + .array() + .ok_or_else(|| php_error("Grammar branch must be an array"))?; + let mut branch = Vec::with_capacity(branch_array.len()); + for (_, subrule_zval) in branch_array { + branch.push( + subrule_zval + .long() + .ok_or_else(|| php_error("Grammar subrule must be an integer"))?, + ); + } + branches.push(branch); + } + rules.insert(rule_id, branches); + } + Ok(rules) +} + +fn parse_lookahead(array: &ZendHashTable) -> PhpResult>> { + let mut lookahead = HashMap::new(); + for (rule_key, lookup_zval) in array { + let rule_id = array_key_to_i64(rule_key)?; + let lookup_array = lookup_zval + .array() + .ok_or_else(|| php_error("Grammar lookahead entry must be an array"))?; + let mut set = HashSet::with_capacity(lookup_array.len()); + for (token_key, _) in lookup_array { + set.insert(array_key_to_i64(token_key)?); + } + lookahead.insert(rule_id, set); + } + Ok(lookahead) +} + +fn parse_rule_names(array: &ZendHashTable) -> PhpResult> { + let mut names = HashMap::new(); + for (rule_key, name_zval) in array { + names.insert( + array_key_to_i64(rule_key)?, + name_zval + .string() + .ok_or_else(|| php_error("Grammar rule name must be a string"))?, + ); + } + Ok(names) +} + +fn parse_id_set(array: &ZendHashTable) -> PhpResult> { + let mut set = HashSet::with_capacity(array.len()); + for (key, _) in array { + set.insert(array_key_to_i64(key)?); + } + Ok(set) +} + +fn array_key_to_i64(key: ArrayKey<'_>) -> PhpResult { + match key { + ArrayKey::Long(value) => Ok(value), + ArrayKey::String(value) => value.parse::().map_err(php_error), + ArrayKey::Str(value) => value.parse::().map_err(php_error), + } +} + +fn create_node(rule_id: i64, rule_name: &str) -> PhpResult { + php_function("wp_sqlite_mysql_native_new_node")? + .try_call(vec![ + &rule_id as &dyn IntoZvalDyn, + &rule_name.to_owned() as &dyn IntoZvalDyn, + ]) + .map_err(php_error) +} + +extern "C" fn php_module_info(_module: *mut ModuleEntry) { + info_table_start!(); + info_table_row!("wp_mysql_parser", "enabled"); + info_table_end!(); +} + +#[php_module] +pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { + module + .class::() + .class::() + .info_function(php_module_info) +} diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/tools/generate-lexer-constants.php b/packages/mysql-on-sqlite/ext/wp-mysql-parser/tools/generate-lexer-constants.php new file mode 100644 index 00000000..10a7c5c2 --- /dev/null +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/tools/generate-lexer-constants.php @@ -0,0 +1,229 @@ +getConstants(); +$target = __DIR__ . '/../src/lexer_constants.rs'; + +function rust_string_literal( string $value ): string { + $literal = '"'; + $length = strlen( $value ); + for ( $i = 0; $i < $length; $i++ ) { + $byte = ord( $value[ $i ] ); + if ( 9 === $byte ) { + $literal .= '\\t'; + } elseif ( 10 === $byte ) { + $literal .= '\\n'; + } elseif ( 13 === $byte ) { + $literal .= '\\r'; + } elseif ( 34 === $byte ) { + $literal .= '\\"'; + } elseif ( 92 === $byte ) { + $literal .= '\\\\'; + } elseif ( $byte < 32 || $byte >= 127 ) { + $literal .= sprintf( '\\x%02x', $byte ); + } else { + $literal .= $value[ $i ]; + } + } + $literal .= '"'; + return $literal; +} + +function rust_value_literal( $value ): string { + if ( is_int( $value ) ) { + return $value . 'i64'; + } + + if ( is_bool( $value ) ) { + return $value ? 'true' : 'false'; + } + + if ( is_string( $value ) ) { + return rust_string_literal( $value ); + } + + throw new RuntimeException( 'Unsupported constant value type: ' . gettype( $value ) ); +} + +function rust_array_function_name( string $constant_name ): string { + return 'array_' . strtolower( preg_replace( '/[^A-Za-z0-9_]/', '_', $constant_name ) ); +} + +function rust_array_function( string $constant_name, array $value ): string { + $name = rust_array_function_name( $constant_name ); + $rust = "fn $name() -> ZBox {\n"; + $rust .= "\tlet mut array = persistent_array( " . count( $value ) . " );\n"; + + foreach ( $value as $key => $item ) { + $item_literal = rust_value_literal( $item ); + if ( is_int( $key ) ) { + $rust .= "\tarray.insert_at_index( {$key}i64, {$item_literal} ).unwrap();\n"; + } else { + $key_literal = rust_string_literal( $key ); + $rust .= "\tarray.insert( {$key_literal}, {$item_literal} ).unwrap();\n"; + } + } + + $rust .= "\tfreeze_array( &mut array );\n"; + $rust .= "\tarray\n"; + $rust .= "}\n\n"; + return $rust; +} + +$rust = <<; +const GC_IMMUTABLE: u32 = 1 << 6; + +extern "C" { + fn _zend_hash_init(ht: *mut HashTable, nSize: u32, pDestructor: DtorFunc, persistent: bool); +} + +fn persistent_array(capacity: usize) -> ZBox { + unsafe { + let pointer = libc::malloc(mem::size_of::()) as *mut ZendHashTable; + if pointer.is_null() { + panic!("Failed to allocate persistent Zend array"); + } + ptr::write_bytes(pointer, 0, 1); + _zend_hash_init(pointer, capacity as u32, None, true); + ZBox::from_raw(pointer) + } +} + +fn freeze_array(array: &mut ZendHashTable) { + unsafe { + array.gc.u.type_info |= GC_IMMUTABLE; + } +} + +RUST; + +foreach ( $constants as $name => $value ) { + if ( is_array( $value ) ) { + $rust .= rust_array_function( $name, $value ); + } +} + +$rust .= "pub const SCALAR_INT_CONSTANTS: &[(&str, i64)] = &[\n"; +foreach ( $constants as $name => $value ) { + if ( is_int( $value ) ) { + $rust .= "\t( \"$name\", {$value}i64 ),\n"; + } +} +$rust .= "];\n\n"; + +foreach ( $constants as $name => $value ) { + if ( is_int( $value ) ) { + $rust .= "pub const $name: i64 = {$value}i64;\n"; + } elseif ( is_bool( $value ) ) { + $rust .= 'pub const ' . $name . ': bool = ' . ( $value ? 'true' : 'false' ) . ";\n"; + } elseif ( is_string( $value ) ) { + $rust .= 'pub const ' . $name . ': &str = ' . rust_string_literal( $value ) . ";\n"; + } +} +$rust .= "\n"; + +$rust .= "pub const KEYWORD_TOKENS: &[(&str, i64)] = &[\n"; +foreach ( $constants['TOKENS'] as $key => $value ) { + $rust .= "\t( " . rust_string_literal( $key ) . ", {$value}i64 ),\n"; +} +$rust .= "];\n\n"; + +$rust .= "pub const VERSION_RULES: &[(i64, i64)] = &[\n"; +foreach ( $constants['VERSIONS'] as $key => $value ) { + $rust .= "\t( {$key}i64, {$value}i64 ),\n"; +} +$rust .= "];\n\n"; + +$rust .= "pub const FUNCTION_TOKENS: &[i64] = &[\n"; +foreach ( $constants['FUNCTIONS'] as $key => $value ) { + $rust .= "\t{$key}i64,\n"; +} +$rust .= "];\n\n"; + +$rust .= "pub const TOKEN_SYNONYMS: &[(i64, i64)] = &[\n"; +foreach ( $constants['SYNONYMS'] as $key => $value ) { + $rust .= "\t( {$key}i64, {$value}i64 ),\n"; +} +$rust .= "];\n\n"; + +$rust .= "pub const UNDERSCORE_CHARSET_NAMES: &[&str] = &[\n"; +foreach ( $constants['UNDERSCORE_CHARSETS'] as $key => $value ) { + $rust .= "\t" . rust_string_literal( $key ) . ",\n"; +} +$rust .= "];\n\n"; + +$rust .= <<<'RUST' +pub fn token_id(name: &str) -> Option { + SCALAR_INT_CONSTANTS + .iter() + .find_map(|(constant_name, id)| (*constant_name == name).then_some(*id)) +} + +pub fn token_name(id: i64) -> Option<&'static str> { + SCALAR_INT_CONSTANTS + .iter() + .rev() + .find_map(|(constant_name, token_id)| (*token_id == id).then_some(*constant_name)) +} + +pub fn keyword_token(keyword: &str) -> Option { + KEYWORD_TOKENS + .iter() + .find_map(|(candidate, id)| (*candidate == keyword).then_some(*id)) +} + +pub fn version_rule(token_id: i64) -> Option { + VERSION_RULES + .iter() + .find_map(|(candidate, version)| (*candidate == token_id).then_some(*version)) +} + +pub fn is_function_token(token_id: i64) -> bool { + FUNCTION_TOKENS.contains(&token_id) +} + +pub fn token_synonym(token_id: i64) -> Option { + TOKEN_SYNONYMS + .iter() + .find_map(|(candidate, synonym)| (*candidate == token_id).then_some(*synonym)) +} + +pub fn is_underscore_charset(name: &str) -> bool { + UNDERSCORE_CHARSET_NAMES.contains(&name) +} + +RUST; + +$rust .= << ClassBuilder { + +RUST; + +foreach ( $constants as $name => $value ) { + if ( is_array( $value ) ) { + $function_name = rust_array_function_name( $name ); + $rust .= "\tbuilder = builder.constant( \"$name\", $function_name(), &[] ).unwrap();\n"; + } elseif ( is_int( $value ) || is_bool( $value ) || is_string( $value ) ) { + $rust_value = rust_value_literal( $value ); + $rust .= "\tbuilder = builder.constant( \"$name\", $rust_value, &[] ).unwrap();\n"; + } +} + +$rust .= "\tbuilder\n}\n"; + +file_put_contents( $target, $rust ); diff --git a/packages/mysql-on-sqlite/phpunit.xml.dist b/packages/mysql-on-sqlite/phpunit.xml.dist index ccb53e6a..a41280c8 100644 --- a/packages/mysql-on-sqlite/phpunit.xml.dist +++ b/packages/mysql-on-sqlite/phpunit.xml.dist @@ -11,6 +11,9 @@ convertNoticesToExceptions="true" convertDeprecationsToExceptions="true" > + + + From d34c6ca4bbe8537a50179e1d5a55ba2b8d95bbfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 11:22:34 +0200 Subject: [PATCH 04/40] Speed up native MySQL parser materialization --- .../ext/wp-mysql-parser/src/lib.rs | 312 ++++++++++++++---- 1 file changed, 239 insertions(+), 73 deletions(-) diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs index e622b585..ee1e8cfa 100644 --- a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs @@ -1,14 +1,17 @@ #![cfg_attr(windows, feature(abi_vectorcall))] use std::collections::{HashMap, HashSet}; +use std::os::raw::c_char; +use std::ptr; use std::sync::{Arc, Mutex, OnceLock}; use ext_php_rs::convert::{IntoZval, IntoZvalDyn}; use ext_php_rs::exception::{PhpException, PhpResult}; +use ext_php_rs::ffi::{zend_class_entry, zend_object, zval}; use ext_php_rs::flags::DataType; use ext_php_rs::prelude::*; -use ext_php_rs::types::{ArrayKey, ZendCallable, ZendHashTable, Zval}; -use ext_php_rs::zend::ModuleEntry; +use ext_php_rs::types::{ArrayKey, ZendCallable, ZendHashTable, ZendObject, Zval}; +use ext_php_rs::zend::{ClassEntry, ModuleEntry}; use ext_php_rs::{info_table_end, info_table_row, info_table_start}; mod lexer_constants; @@ -22,6 +25,16 @@ const SQL_MODE_NO_BACKSLASH_ESCAPES: i64 = 8; const STACK_RED_ZONE: usize = 128 * 1024; const STACK_GROW_SIZE: usize = 8 * 1024 * 1024; +extern "C" { + fn zend_update_property( + scope: *mut zend_class_entry, + object: *mut zend_object, + name: *const c_char, + name_length: usize, + value: *mut zval, + ); +} + #[derive(Clone)] struct BinaryString(Vec); @@ -43,6 +56,60 @@ fn php_function(name: &str) -> PhpResult> { ZendCallable::try_from_name(name).map_err(php_error) } +struct PhpClasses { + parser_token: &'static ClassEntry, + mysql_token: &'static ClassEntry, + parser_node: &'static ClassEntry, +} + +// Class entries are process-lifetime Zend metadata. We only read the pointers +// after lookup, and PHP owns their lifetime. +unsafe impl Send for PhpClasses {} +unsafe impl Sync for PhpClasses {} + +static PHP_CLASSES: OnceLock = OnceLock::new(); + +fn php_classes() -> PhpResult<&'static PhpClasses> { + if let Some(classes) = PHP_CLASSES.get() { + return Ok(classes); + } + + let classes = PhpClasses { + parser_token: ClassEntry::try_find("WP_Parser_Token") + .ok_or_else(|| php_error("Missing WP_Parser_Token class"))?, + mysql_token: ClassEntry::try_find("WP_MySQL_Token") + .ok_or_else(|| php_error("Missing WP_MySQL_Token class"))?, + parser_node: ClassEntry::try_find("WP_Parser_Node") + .ok_or_else(|| php_error("Missing WP_Parser_Node class"))?, + }; + + PHP_CLASSES + .set(classes) + .map_err(|_| php_error("PHP class cache was initialized concurrently"))?; + PHP_CLASSES + .get() + .ok_or_else(|| php_error("PHP class cache initialization failed")) +} + +fn update_object_property( + object: &mut ZendObject, + scope: &ClassEntry, + name: &str, + value: impl IntoZval, +) -> PhpResult<()> { + let mut value = value.into_zval(false).map_err(php_error)?; + unsafe { + zend_update_property( + ptr::from_ref(scope).cast_mut(), + ptr::from_mut(object), + name.as_ptr().cast::(), + name.len(), + ptr::from_mut(&mut value), + ); + } + Ok(()) +} + fn sql_modes_mask(sql_modes: &[String]) -> i64 { let mut mask = 0; for sql_mode in sql_modes { @@ -230,15 +297,26 @@ impl WpMySqlNativeLexer { let start = i64::try_from(token.start).map_err(php_error)?; let length = i64::try_from(token.end.saturating_sub(token.start)).map_err(php_error)?; let no_backslash = self.is_sql_mode_active(SQL_MODE_NO_BACKSLASH_ESCAPES); - php_function("wp_sqlite_mysql_native_new_token")? - .try_call(vec![ - &id as &dyn IntoZvalDyn, - &start as &dyn IntoZvalDyn, - &length as &dyn IntoZvalDyn, - &self.sql_zval as &dyn IntoZvalDyn, - &no_backslash as &dyn IntoZvalDyn, - ]) - .map_err(php_error) + let classes = php_classes()?; + let mut object = classes.mysql_token.new(); + + update_object_property(&mut object, classes.parser_token, "id", id)?; + update_object_property(&mut object, classes.parser_token, "start", start)?; + update_object_property(&mut object, classes.parser_token, "length", length)?; + update_object_property( + &mut object, + classes.parser_token, + "input", + self.sql_zval.shallow_clone(), + )?; + update_object_property( + &mut object, + classes.mysql_token, + "sql_mode_no_backslash_escapes_enabled", + no_backslash, + )?; + + object.into_zval(false).map_err(php_error) } fn read_next_token(&mut self) -> Option { @@ -803,14 +881,33 @@ impl WpMySqlNativeLexer { } } -#[derive(Clone)] struct Grammar { highest_terminal_id: i64, - rules: HashMap>>, - lookahead: HashMap>, - rule_names: HashMap, - fragment_ids: HashSet, + rules: Vec>, query_rule_id: i64, + select_statement_rule_id: Option, +} + +struct Rule { + branches: Vec>, + lookahead: Option>, + rule_name: String, + is_fragment: bool, +} + +impl Grammar { + fn rule(&self, rule_id: i64) -> Option<&Rule> { + usize::try_from(rule_id) + .ok() + .and_then(|index| self.rules.get(index)) + .and_then(Option::as_ref) + } + + fn is_fragment(&self, rule_id: i64) -> bool { + self.rule(rule_id) + .map(|rule| rule.is_fragment) + .unwrap_or(false) + } } static GRAMMAR_CACHE: OnceLock>>> = OnceLock::new(); @@ -822,7 +919,6 @@ enum AstChild { struct AstNode { rule_id: i64, - rule_name: String, children: Vec, } @@ -840,7 +936,8 @@ pub struct WpMySqlNativeParser { tokens: Vec, token_ids: Vec, position: usize, - current_ast: Option, + current_ast: Option, + current_php_ast: Option, } #[php_impl] @@ -856,36 +953,41 @@ impl WpMySqlNativeParser { token_ids, position: 0, current_ast: None, + current_php_ast: None, }) } pub fn parse(&mut self) -> PhpResult { stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { - match self.parse_recursive(self.grammar.query_rule_id)? { - ParseMatch::No => Ok(Zval::null()), - ParseMatch::Empty => { - let mut zval = Zval::new(); - zval.set_bool(true); - Ok(zval) - } - ParseMatch::Node(node) => self.create_php_node(&node), - ParseMatch::Token(index) => Ok(self.tokens[index].shallow_clone()), - } + let ast = self.parse_recursive(self.grammar.query_rule_id)?; + self.create_php_ast(&ast) }) } pub fn next_query(&mut self) -> PhpResult { if self.position >= self.tokens.len() { self.current_ast = None; + self.current_php_ast = None; return Ok(false); } - self.current_ast = Some(self.parse()?); + self.current_ast = Some(self.parse_recursive(self.grammar.query_rule_id)?); + self.current_php_ast = None; Ok(true) } pub fn get_query_ast(&mut self) -> PhpResult { - match self.current_ast.as_ref() { + if let Some(ast) = self.current_php_ast.as_ref() { + return Ok(ast.shallow_clone()); + } + + let Some(native_ast) = self.current_ast.as_ref() else { + return Ok(Zval::null()); + }; + + let ast = self.create_php_ast(native_ast)?; + self.current_php_ast = Some(ast); + match self.current_php_ast.as_ref() { Some(ast) => Ok(ast.shallow_clone()), None => Ok(Zval::null()), } @@ -915,36 +1017,36 @@ impl WpMySqlNativeParser { return Ok(ParseMatch::No); } - let Some(branch_count) = self.grammar.rules.get(&rule_id).map(Vec::len) else { + let grammar = unsafe { + // The parser owns an Arc to immutable grammar data for its full lifetime. + // Taking a raw shared reference avoids cloning hot branches just to satisfy + // the borrow checker while recursive parsing mutates only `position`. + &*Arc::as_ptr(&self.grammar) + }; + + let Some(rule) = grammar.rule(rule_id) else { return Ok(ParseMatch::No); }; - if branch_count == 0 { + if rule.branches.is_empty() { return Ok(ParseMatch::No); } - if let Some(lookahead) = self.grammar.lookahead.get(&rule_id) { + if let Some(lookahead) = rule.lookahead.as_ref() { let token_id = self.token_ids.get(self.position).copied().unwrap_or(0); - if !lookahead.contains(&token_id) && !lookahead.contains(&0) { + if lookahead.binary_search(&token_id).is_err() && lookahead.binary_search(&0).is_err() { return Ok(ParseMatch::No); } } - let rule_name = self - .grammar - .rule_names - .get(&rule_id) - .cloned() - .unwrap_or_default(); let starting_position = self.position; let mut matched_node = None; - for branch_index in 0..branch_count { - let branch = self.grammar.rules.get(&rule_id).unwrap()[branch_index].clone(); + for branch in &rule.branches { self.position = starting_position; let mut children = Vec::new(); let mut branch_matches = true; - for subrule_id in branch { + for &subrule_id in branch { match self.parse_recursive(subrule_id)? { ParseMatch::No => { branch_matches = false; @@ -955,7 +1057,7 @@ impl WpMySqlNativeParser { children.push(AstChild::Token(token_index)); } ParseMatch::Node(subnode) => { - if self.grammar.fragment_ids.contains(&subrule_id) { + if grammar.is_fragment(subrule_id) { children.extend(subnode.children); } else { children.push(AstChild::Node(subnode)); @@ -965,7 +1067,7 @@ impl WpMySqlNativeParser { } if branch_matches - && rule_name == "selectStatement" + && grammar.select_statement_rule_id == Some(rule_id) && self .token_ids .get(self.position) @@ -975,11 +1077,7 @@ impl WpMySqlNativeParser { } if branch_matches { - matched_node = Some(AstNode { - rule_id, - rule_name: rule_name.clone(), - children, - }); + matched_node = Some(AstNode { rule_id, children }); break; } } @@ -996,6 +1094,19 @@ impl WpMySqlNativeParser { } } + fn create_php_ast(&self, ast: &ParseMatch) -> PhpResult { + match ast { + ParseMatch::No => Ok(Zval::null()), + ParseMatch::Empty => { + let mut zval = Zval::new(); + zval.set_bool(true); + Ok(zval) + } + ParseMatch::Node(node) => self.create_php_node(node), + ParseMatch::Token(index) => Ok(self.tokens[*index].shallow_clone()), + } + } + fn create_php_node(&self, ast_node: &AstNode) -> PhpResult { stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { self.create_php_node_inner(ast_node) @@ -1003,20 +1114,38 @@ impl WpMySqlNativeParser { } fn create_php_node_inner(&self, ast_node: &AstNode) -> PhpResult { - let node = create_node(ast_node.rule_id, &ast_node.rule_name)?; + let classes = php_classes()?; + let mut object = classes.parser_node.new(); + let rule_name = self + .grammar + .rule(ast_node.rule_id) + .map(|rule| rule.rule_name.as_str()) + .unwrap_or_default(); + let mut children = Vec::with_capacity(ast_node.children.len()); for child in &ast_node.children { let child_zval = match child { AstChild::Node(child_node) => self.create_php_node(child_node)?, AstChild::Token(index) => self.tokens[*index].shallow_clone(), }; - node.object() - .ok_or_else(|| php_error("Parser node must be an object"))? - .try_call_method("append_child", vec![&child_zval as &dyn IntoZvalDyn]) - .map_err(php_error)?; + children.push(child_zval); } - Ok(node) + update_object_property( + &mut object, + classes.parser_node, + "rule_id", + ast_node.rule_id, + )?; + update_object_property( + &mut object, + classes.parser_node, + "rule_name", + rule_name.to_owned(), + )?; + update_object_property(&mut object, classes.parser_node, "children", children)?; + + object.into_zval(false).map_err(php_error) } } @@ -1044,42 +1173,49 @@ fn export_grammar(grammar: &mut Zval) -> PhpResult> { .get("highest_terminal_id") .and_then(Zval::long) .ok_or_else(|| php_error("Missing grammar highest_terminal_id"))?; - let rules = parse_rules( + let parsed_rules = parse_rules( array .get("rules") .and_then(Zval::array) .ok_or_else(|| php_error("Missing grammar rules"))?, )?; - let lookahead = parse_lookahead( + let parsed_lookahead = parse_lookahead( array .get("lookahead_is_match_possible") .and_then(Zval::array) .ok_or_else(|| php_error("Missing grammar lookahead"))?, )?; - let rule_names = parse_rule_names( + let parsed_rule_names = parse_rule_names( array .get("rule_names") .and_then(Zval::array) .ok_or_else(|| php_error("Missing grammar rule_names"))?, )?; - let fragment_ids = parse_id_set( + let parsed_fragment_ids = parse_id_set( array .get("fragment_ids") .and_then(Zval::array) .ok_or_else(|| php_error("Missing grammar fragment_ids"))?, )?; - let query_rule_id = rule_names + let query_rule_id = parsed_rule_names .iter() .find_map(|(id, name)| (name == "query").then_some(*id)) .ok_or_else(|| php_error("Missing query grammar rule"))?; + let select_statement_rule_id = parsed_rule_names + .iter() + .find_map(|(id, name)| (name == "selectStatement").then_some(*id)); + let rules = build_rules( + parsed_rules, + parsed_lookahead, + parsed_rule_names, + parsed_fragment_ids, + )?; let grammar = Arc::new(Grammar { highest_terminal_id, rules, - lookahead, - rule_names, - fragment_ids, query_rule_id, + select_statement_rule_id, }); if let Some(grammar_id) = grammar_id { @@ -1112,6 +1248,45 @@ fn export_tokens(tokens: &mut Zval) -> PhpResult<(Vec, Vec)> { Ok((token_objects, token_ids)) } +fn build_rules( + rules: HashMap>>, + lookahead: HashMap>, + rule_names: HashMap, + fragment_ids: HashSet, +) -> PhpResult>> { + let max_rule_id = rules + .keys() + .chain(lookahead.keys()) + .chain(rule_names.keys()) + .chain(fragment_ids.iter()) + .copied() + .max() + .unwrap_or(0); + let max_rule_index = usize::try_from(max_rule_id).map_err(php_error)?; + let mut dense_rules: Vec> = (0..=max_rule_index).map(|_| None).collect(); + + for (rule_id, branches) in rules { + let index = usize::try_from(rule_id).map_err(php_error)?; + let mut lookahead = lookahead.get(&rule_id).map(|set| { + let mut values: Vec = set.iter().copied().collect(); + values.sort_unstable(); + values + }); + if let Some(values) = lookahead.as_mut() { + values.dedup(); + } + + dense_rules[index] = Some(Rule { + branches, + lookahead, + rule_name: rule_names.get(&rule_id).cloned().unwrap_or_default(), + is_fragment: fragment_ids.contains(&rule_id), + }); + } + + Ok(dense_rules) +} + fn parse_rules(array: &ZendHashTable) -> PhpResult>>> { let mut rules = HashMap::new(); for (rule_key, branches_zval) in array { @@ -1184,15 +1359,6 @@ fn array_key_to_i64(key: ArrayKey<'_>) -> PhpResult { } } -fn create_node(rule_id: i64, rule_name: &str) -> PhpResult { - php_function("wp_sqlite_mysql_native_new_node")? - .try_call(vec![ - &rule_id as &dyn IntoZvalDyn, - &rule_name.to_owned() as &dyn IntoZvalDyn, - ]) - .map_err(php_error) -} - extern "C" fn php_module_info(_module: *mut ModuleEntry) { info_table_start!(); info_table_row!("wp_mysql_parser", "enabled"); From b84cbac6c9893313fbe3a3d5a7c0dcea00ed0535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 12:20:14 +0200 Subject: [PATCH 05/40] Speed up native parser token streaming --- .../ext/wp-mysql-parser/src/lib.rs | 186 +++++++++++++----- .../sqlite/class-wp-pdo-mysql-on-sqlite.php | 7 +- .../tests/tools/run-parser-benchmark.php | 6 +- 3 files changed, 145 insertions(+), 54 deletions(-) diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs index ee1e8cfa..ec6f81f1 100644 --- a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs @@ -5,7 +5,7 @@ use std::os::raw::c_char; use std::ptr; use std::sync::{Arc, Mutex, OnceLock}; -use ext_php_rs::convert::{IntoZval, IntoZvalDyn}; +use ext_php_rs::convert::{FromZval, IntoZval, IntoZvalDyn}; use ext_php_rs::exception::{PhpException, PhpResult}; use ext_php_rs::ffi::{zend_class_entry, zend_object, zval}; use ext_php_rs::flags::DataType; @@ -110,6 +110,32 @@ fn update_object_property( Ok(()) } +fn create_mysql_token(sql_zval: &Zval, token: TokenInfo, no_backslash: bool) -> PhpResult { + let id = token.id; + let start = i64::try_from(token.start).map_err(php_error)?; + let length = i64::try_from(token.end.saturating_sub(token.start)).map_err(php_error)?; + let classes = php_classes()?; + let mut object = classes.mysql_token.new(); + + update_object_property(&mut object, classes.parser_token, "id", id)?; + update_object_property(&mut object, classes.parser_token, "start", start)?; + update_object_property(&mut object, classes.parser_token, "length", length)?; + update_object_property( + &mut object, + classes.parser_token, + "input", + sql_zval.shallow_clone(), + )?; + update_object_property( + &mut object, + classes.mysql_token, + "sql_mode_no_backslash_escapes_enabled", + no_backslash, + )?; + + object.into_zval(false).map_err(php_error) +} + fn sql_modes_mask(sql_modes: &[String]) -> i64 { let mut mask = 0; for sql_mode in sql_modes { @@ -182,6 +208,22 @@ struct TokenInfo { end: usize, } +#[php_class] +#[php(name = "WP_MySQL_Native_Token_Stream")] +pub struct WpMySqlNativeTokenStream { + sql_zval: Zval, + tokens: Vec, + no_backslash: bool, +} + +#[php_impl] +#[php(change_method_case = "snake_case")] +impl WpMySqlNativeTokenStream { + pub fn count(&self) -> usize { + self.tokens.len() + } +} + #[php_class] #[php(name = "WP_MySQL_Native_Lexer", modifier = "register_lexer_constants")] pub struct WpMySqlNativeLexer { @@ -266,6 +308,21 @@ impl WpMySqlNativeLexer { Ok(tokens) } + pub fn native_token_stream(&mut self) -> WpMySqlNativeTokenStream { + let mut tokens = Vec::new(); + while self.next_token() { + if let Some(token) = self.current_token_info() { + tokens.push(token); + } + } + + WpMySqlNativeTokenStream { + sql_zval: self.sql_zval.shallow_clone(), + tokens, + no_backslash: self.is_sql_mode_active(SQL_MODE_NO_BACKSLASH_ESCAPES), + } + } + pub fn get_mysql_version(&self) -> i64 { self.mysql_version } @@ -293,30 +350,11 @@ impl WpMySqlNativeLexer { } fn create_token(&self, token: TokenInfo) -> PhpResult { - let id = token.id; - let start = i64::try_from(token.start).map_err(php_error)?; - let length = i64::try_from(token.end.saturating_sub(token.start)).map_err(php_error)?; - let no_backslash = self.is_sql_mode_active(SQL_MODE_NO_BACKSLASH_ESCAPES); - let classes = php_classes()?; - let mut object = classes.mysql_token.new(); - - update_object_property(&mut object, classes.parser_token, "id", id)?; - update_object_property(&mut object, classes.parser_token, "start", start)?; - update_object_property(&mut object, classes.parser_token, "length", length)?; - update_object_property( - &mut object, - classes.parser_token, - "input", - self.sql_zval.shallow_clone(), - )?; - update_object_property( - &mut object, - classes.mysql_token, - "sql_mode_no_backslash_escapes_enabled", - no_backslash, - )?; - - object.into_zval(false).map_err(php_error) + create_mysql_token( + &self.sql_zval, + token, + self.is_sql_mode_active(SQL_MODE_NO_BACKSLASH_ESCAPES), + ) } fn read_next_token(&mut self) -> Option { @@ -929,11 +967,42 @@ enum ParseMatch { Token(usize), } +enum ParserTokenSource { + Php(Vec), + Native { + sql_zval: Zval, + tokens: Vec, + no_backslash: bool, + }, +} + +impl ParserTokenSource { + fn create_php_token(&self, index: usize) -> PhpResult { + match self { + Self::Php(tokens) => tokens + .get(index) + .map(Zval::shallow_clone) + .ok_or_else(|| php_error("Parser token index is out of range")), + Self::Native { + sql_zval, + tokens, + no_backslash, + } => { + let token = tokens + .get(index) + .copied() + .ok_or_else(|| php_error("Parser token index is out of range"))?; + create_mysql_token(sql_zval, token, *no_backslash) + } + } + } +} + #[php_class] #[php(name = "WP_MySQL_Native_Parser")] pub struct WpMySqlNativeParser { grammar: Arc, - tokens: Vec, + token_source: ParserTokenSource, token_ids: Vec, position: usize, current_ast: Option, @@ -945,11 +1014,11 @@ pub struct WpMySqlNativeParser { impl WpMySqlNativeParser { pub fn __construct(grammar: &mut Zval, tokens: &mut Zval) -> PhpResult { let grammar = export_grammar(grammar)?; - let (tokens, token_ids) = export_tokens(tokens)?; + let (token_source, token_ids) = export_tokens(tokens)?; Ok(Self { grammar, - tokens, + token_source, token_ids, position: 0, current_ast: None, @@ -959,21 +1028,13 @@ impl WpMySqlNativeParser { pub fn parse(&mut self) -> PhpResult { stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { - let ast = self.parse_recursive(self.grammar.query_rule_id)?; + let ast = self.parse_recursive_inner(self.grammar.query_rule_id)?; self.create_php_ast(&ast) }) } pub fn next_query(&mut self) -> PhpResult { - if self.position >= self.tokens.len() { - self.current_ast = None; - self.current_php_ast = None; - return Ok(false); - } - - self.current_ast = Some(self.parse_recursive(self.grammar.query_rule_id)?); - self.current_php_ast = None; - Ok(true) + stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || self.next_query_inner()) } pub fn get_query_ast(&mut self) -> PhpResult { @@ -995,15 +1056,21 @@ impl WpMySqlNativeParser { } impl WpMySqlNativeParser { - fn parse_recursive(&mut self, rule_id: i64) -> PhpResult { - stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { - self.parse_recursive_inner(rule_id) - }) + fn next_query_inner(&mut self) -> PhpResult { + if self.position >= self.token_ids.len() { + self.current_ast = None; + self.current_php_ast = None; + return Ok(false); + } + + self.current_ast = Some(self.parse_recursive_inner(self.grammar.query_rule_id)?); + self.current_php_ast = None; + Ok(true) } fn parse_recursive_inner(&mut self, rule_id: i64) -> PhpResult { if rule_id <= self.grammar.highest_terminal_id { - if self.position >= self.tokens.len() { + if self.position >= self.token_ids.len() { return Ok(ParseMatch::No); } if rule_id == 0 { @@ -1047,7 +1114,7 @@ impl WpMySqlNativeParser { let mut branch_matches = true; for &subrule_id in branch { - match self.parse_recursive(subrule_id)? { + match self.parse_recursive_inner(subrule_id)? { ParseMatch::No => { branch_matches = false; break; @@ -1095,6 +1162,12 @@ impl WpMySqlNativeParser { } fn create_php_ast(&self, ast: &ParseMatch) -> PhpResult { + stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { + self.create_php_ast_inner(ast) + }) + } + + fn create_php_ast_inner(&self, ast: &ParseMatch) -> PhpResult { match ast { ParseMatch::No => Ok(Zval::null()), ParseMatch::Empty => { @@ -1103,14 +1176,12 @@ impl WpMySqlNativeParser { Ok(zval) } ParseMatch::Node(node) => self.create_php_node(node), - ParseMatch::Token(index) => Ok(self.tokens[*index].shallow_clone()), + ParseMatch::Token(index) => self.token_source.create_php_token(*index), } } fn create_php_node(&self, ast_node: &AstNode) -> PhpResult { - stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { - self.create_php_node_inner(ast_node) - }) + self.create_php_node_inner(ast_node) } fn create_php_node_inner(&self, ast_node: &AstNode) -> PhpResult { @@ -1126,7 +1197,7 @@ impl WpMySqlNativeParser { for child in &ast_node.children { let child_zval = match child { AstChild::Node(child_node) => self.create_php_node(child_node)?, - AstChild::Token(index) => self.tokens[*index].shallow_clone(), + AstChild::Token(index) => self.token_source.create_php_token(*index)?, }; children.push(child_zval); } @@ -1229,7 +1300,19 @@ fn export_grammar(grammar: &mut Zval) -> PhpResult> { Ok(grammar) } -fn export_tokens(tokens: &mut Zval) -> PhpResult<(Vec, Vec)> { +fn export_tokens(tokens: &mut Zval) -> PhpResult<(ParserTokenSource, Vec)> { + if let Some(stream) = <&WpMySqlNativeTokenStream as FromZval>::from_zval(tokens) { + let token_ids = stream.tokens.iter().map(|token| token.id).collect(); + return Ok(( + ParserTokenSource::Native { + sql_zval: stream.sql_zval.shallow_clone(), + tokens: stream.tokens.clone(), + no_backslash: stream.no_backslash, + }, + token_ids, + )); + } + let array = tokens .array() .ok_or_else(|| php_error("Parser tokens must be an array"))?; @@ -1245,7 +1328,7 @@ fn export_tokens(tokens: &mut Zval) -> PhpResult<(Vec, Vec)> { token_ids.push(id); } - Ok((token_objects, token_ids)) + Ok((ParserTokenSource::Php(token_objects), token_ids)) } fn build_rules( @@ -1368,6 +1451,7 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) { #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module + .class::() .class::() .class::() .info_function(php_module_info) diff --git a/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php index 7790f273..a8325b3d 100644 --- a/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php @@ -1153,11 +1153,16 @@ public function get_insert_id() { * @return WP_MySQL_Parser A parser initialized for the MySQL query. */ public function create_parser( string $query ): WP_MySQL_Parser { - $lexer = new WP_MySQL_Lexer( + $lexer = new WP_MySQL_Lexer( $query, 80038, $this->active_sql_modes ); + if ( method_exists( $lexer, 'native_token_stream' ) ) { + $tokens = $lexer->native_token_stream(); + return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + } + $tokens = $lexer->remaining_tokens(); return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); } diff --git a/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php index ad87dd43..d7e5d7f5 100644 --- a/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php +++ b/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php @@ -56,8 +56,10 @@ function get_stats( $total, $failures, $exceptions ) { try { $lexer = new WP_MySQL_Lexer( $query ); - $tokens = $lexer->remaining_tokens(); - if ( count( $tokens ) === 0 ) { + $tokens = method_exists( $lexer, 'native_token_stream' ) + ? $lexer->native_token_stream() + : $lexer->remaining_tokens(); + if ( ( is_array( $tokens ) ? count( $tokens ) : $tokens->count() ) === 0 ) { throw new Exception( 'Failed to tokenize query: ' . $query ); } From e8aace54ee37c61e09543b7653bba3e34997661f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 13:43:56 +0200 Subject: [PATCH 06/40] Add lazy native AST facade --- .../ext/wp-mysql-parser/src/lib.rs | 712 +++++++++++++++--- 1 file changed, 602 insertions(+), 110 deletions(-) diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs index ec6f81f1..4af124e9 100644 --- a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs @@ -940,33 +940,10 @@ impl Grammar { .and_then(|index| self.rules.get(index)) .and_then(Option::as_ref) } - - fn is_fragment(&self, rule_id: i64) -> bool { - self.rule(rule_id) - .map(|rule| rule.is_fragment) - .unwrap_or(false) - } } static GRAMMAR_CACHE: OnceLock>>> = OnceLock::new(); -enum AstChild { - Node(AstNode), - Token(usize), -} - -struct AstNode { - rule_id: i64, - children: Vec, -} - -enum ParseMatch { - No, - Empty, - Node(AstNode), - Token(usize), -} - enum ParserTokenSource { Php(Vec), Native { @@ -996,16 +973,522 @@ impl ParserTokenSource { } } } + + fn token_info(&self, index: usize) -> PhpResult { + match self { + Self::Php(tokens) => { + let token = tokens + .get(index) + .ok_or_else(|| php_error("Parser token index is out of range"))?; + let token_object = token + .object() + .ok_or_else(|| php_error("Parser token must be an object"))?; + let id = token_object.get_property::("id").map_err(php_error)?; + let start = token_object + .get_property::("start") + .map_err(php_error)?; + let length = token_object + .get_property::("length") + .map_err(php_error)?; + let start = usize::try_from(start).map_err(php_error)?; + let length = usize::try_from(length).map_err(php_error)?; + + Ok(TokenInfo { + id, + start, + end: start.saturating_add(length), + }) + } + Self::Native { tokens, .. } => tokens + .get(index) + .copied() + .ok_or_else(|| php_error("Parser token index is out of range")), + } + } +} + +#[derive(Clone, Copy)] +enum NativeAstChild { + Node(usize), + Token(usize), +} + +#[derive(Clone, Copy)] +enum NativeAstRoot { + No, + Empty, + Node(usize), + Token(usize), +} + +enum NativeParseMatch { + No, + Empty, + Node(usize), + Token(usize), + Fragment(Vec), +} + +struct NativeAstNode { + rule_id: i64, + children: Vec, + first_token: Option, + last_token: Option, +} + +struct NativeAstArena { + grammar: Arc, + token_source: Arc, + nodes: Vec, + root: NativeAstRoot, +} + +#[php_class] +#[php(name = "WP_MySQL_Native_Ast")] +pub struct WpMySqlNativeAst { + arena: Arc, +} + +impl NativeAstArena { + fn new(grammar: Arc, token_source: Arc) -> Self { + Self { + grammar, + token_source, + nodes: Vec::new(), + root: NativeAstRoot::No, + } + } + + fn push_node(&mut self, rule_id: i64, children: Vec) -> usize { + let index = self.nodes.len(); + let mut first_token = None; + let mut last_token = None; + for child in &children { + match child { + NativeAstChild::Node(child_index) => { + if let Some(node) = self.nodes.get(*child_index) { + if first_token.is_none() { + first_token = node.first_token; + } + if node.last_token.is_some() { + last_token = node.last_token; + } + } + } + NativeAstChild::Token(token_index) => { + if first_token.is_none() { + first_token = Some(*token_index); + } + last_token = Some(*token_index); + } + } + } + + self.nodes.push(NativeAstNode { + rule_id, + children, + first_token, + last_token, + }); + index + } + + fn create_php_ast(&self, native_ast_zval: &Zval) -> PhpResult { + match self.root { + NativeAstRoot::No => Ok(Zval::null()), + NativeAstRoot::Empty => { + let mut zval = Zval::new(); + zval.set_bool(true); + Ok(zval) + } + NativeAstRoot::Node(index) => self.create_php_node(native_ast_zval, index), + NativeAstRoot::Token(index) => self.token_source.create_php_token(index), + } + } + + fn create_php_node(&self, native_ast_zval: &Zval, index: usize) -> PhpResult { + let node = self.node(index)?; + let classes = php_classes()?; + let mut object = classes.parser_node.new(); + let rule_name = self + .grammar + .rule(node.rule_id) + .map(|rule| rule.rule_name.as_str()) + .unwrap_or_default(); + let index = i64::try_from(index).map_err(php_error)?; + + update_object_property(&mut object, classes.parser_node, "rule_id", node.rule_id)?; + update_object_property( + &mut object, + classes.parser_node, + "rule_name", + rule_name.to_owned(), + )?; + update_object_property( + &mut object, + classes.parser_node, + "native_ast", + native_ast_zval.shallow_clone(), + )?; + update_object_property(&mut object, classes.parser_node, "native_node_index", index)?; + + object.into_zval(false).map_err(php_error) + } + + fn node(&self, index: usize) -> PhpResult<&NativeAstNode> { + self.nodes + .get(index) + .ok_or_else(|| php_error("Native AST node index is out of range")) + } + + fn child_to_zval(&self, native_ast_zval: &Zval, child: NativeAstChild) -> PhpResult { + match child { + NativeAstChild::Node(index) => self.create_php_node(native_ast_zval, index), + NativeAstChild::Token(index) => self.token_source.create_php_token(index), + } + } + + fn child_node_matches(&self, child: NativeAstChild, rule_name: Option<&str>) -> bool { + let NativeAstChild::Node(index) = child else { + return false; + }; + let Ok(node) = self.node(index) else { + return false; + }; + rule_name.is_none_or(|expected| { + self.grammar + .rule(node.rule_id) + .map(|rule| rule.rule_name == expected) + .unwrap_or(false) + }) + } + + fn child_token_matches(&self, child: NativeAstChild, token_id: Option) -> bool { + let NativeAstChild::Token(index) = child else { + return false; + }; + token_id.is_none_or(|expected| { + self.token_source + .token_info(index) + .map(|token| token.id == expected) + .unwrap_or(false) + }) + } + + fn descendant_stack(&self, index: usize) -> PhpResult> { + let mut stack = self.node(index)?.children.clone(); + stack.reverse(); + Ok(stack) + } +} + +fn native_ast(native_ast: &Zval) -> PhpResult<&WpMySqlNativeAst> { + <&WpMySqlNativeAst as FromZval>::from_zval(native_ast) + .ok_or_else(|| php_error("Missing native AST handle")) +} + +fn native_ast_node_index(node_index: i64) -> PhpResult { + usize::try_from(node_index).map_err(php_error) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_has_child( + native_ast_zval: &Zval, + node_index: i64, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + Ok(!ast + .arena + .node(native_ast_node_index(node_index)?)? + .children + .is_empty()) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_has_child_node( + native_ast_zval: &Zval, + node_index: i64, + rule_name: Option, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + Ok(ast + .arena + .node(native_ast_node_index(node_index)?)? + .children + .iter() + .copied() + .any(|child| ast.arena.child_node_matches(child, rule_name.as_deref()))) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_has_child_token( + native_ast_zval: &Zval, + node_index: i64, + token_id: Option, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + Ok(ast + .arena + .node(native_ast_node_index(node_index)?)? + .children + .iter() + .copied() + .any(|child| ast.arena.child_token_matches(child, token_id))) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_first_child( + native_ast_zval: &Zval, + node_index: i64, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + let Some(child) = ast + .arena + .node(native_ast_node_index(node_index)?)? + .children + .first() + .copied() + else { + return Ok(Zval::null()); + }; + ast.arena.child_to_zval(native_ast_zval, child) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_first_child_node( + native_ast_zval: &Zval, + node_index: i64, + rule_name: Option, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + for child in &ast.arena.node(native_ast_node_index(node_index)?)?.children { + if ast.arena.child_node_matches(*child, rule_name.as_deref()) { + return ast.arena.child_to_zval(native_ast_zval, *child); + } + } + Ok(Zval::null()) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_first_child_token( + native_ast_zval: &Zval, + node_index: i64, + token_id: Option, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + for child in &ast.arena.node(native_ast_node_index(node_index)?)?.children { + if ast.arena.child_token_matches(*child, token_id) { + return ast.arena.child_to_zval(native_ast_zval, *child); + } + } + Ok(Zval::null()) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_first_descendant_node( + native_ast_zval: &Zval, + node_index: i64, + rule_name: Option, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + let mut stack = ast + .arena + .descendant_stack(native_ast_node_index(node_index)?)?; + while let Some(child) = stack.pop() { + if ast.arena.child_node_matches(child, rule_name.as_deref()) { + return ast.arena.child_to_zval(native_ast_zval, child); + } + if let NativeAstChild::Node(index) = child { + for child in ast.arena.node(index)?.children.iter().rev() { + stack.push(*child); + } + } + } + Ok(Zval::null()) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_first_descendant_token( + native_ast_zval: &Zval, + node_index: i64, + token_id: Option, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + let mut stack = ast + .arena + .descendant_stack(native_ast_node_index(node_index)?)?; + while let Some(child) = stack.pop() { + if ast.arena.child_token_matches(child, token_id) { + return ast.arena.child_to_zval(native_ast_zval, child); + } + if let NativeAstChild::Node(index) = child { + for child in ast.arena.node(index)?.children.iter().rev() { + stack.push(*child); + } + } + } + Ok(Zval::null()) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_children( + native_ast_zval: &Zval, + node_index: i64, +) -> PhpResult> { + let ast = native_ast(native_ast_zval)?; + ast.arena + .node(native_ast_node_index(node_index)?)? + .children + .iter() + .copied() + .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .collect() +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_child_nodes( + native_ast_zval: &Zval, + node_index: i64, + rule_name: Option, +) -> PhpResult> { + let ast = native_ast(native_ast_zval)?; + ast.arena + .node(native_ast_node_index(node_index)?)? + .children + .iter() + .copied() + .filter(|child| ast.arena.child_node_matches(*child, rule_name.as_deref())) + .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .collect() +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_child_tokens( + native_ast_zval: &Zval, + node_index: i64, + token_id: Option, +) -> PhpResult> { + let ast = native_ast(native_ast_zval)?; + ast.arena + .node(native_ast_node_index(node_index)?)? + .children + .iter() + .copied() + .filter(|child| ast.arena.child_token_matches(*child, token_id)) + .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .collect() +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_descendants( + native_ast_zval: &Zval, + node_index: i64, +) -> PhpResult> { + let ast = native_ast(native_ast_zval)?; + let mut descendants = Vec::new(); + let mut stack = ast + .arena + .descendant_stack(native_ast_node_index(node_index)?)?; + while let Some(child) = stack.pop() { + descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + if let NativeAstChild::Node(index) = child { + for child in ast.arena.node(index)?.children.iter().rev() { + stack.push(*child); + } + } + } + Ok(descendants) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_descendant_nodes( + native_ast_zval: &Zval, + node_index: i64, + rule_name: Option, +) -> PhpResult> { + let ast = native_ast(native_ast_zval)?; + let mut descendants = Vec::new(); + let mut stack = ast + .arena + .descendant_stack(native_ast_node_index(node_index)?)?; + while let Some(child) = stack.pop() { + if ast.arena.child_node_matches(child, rule_name.as_deref()) { + descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + } + if let NativeAstChild::Node(index) = child { + for child in ast.arena.node(index)?.children.iter().rev() { + stack.push(*child); + } + } + } + Ok(descendants) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_descendant_tokens( + native_ast_zval: &Zval, + node_index: i64, + token_id: Option, +) -> PhpResult> { + let ast = native_ast(native_ast_zval)?; + let mut descendants = Vec::new(); + let mut stack = ast + .arena + .descendant_stack(native_ast_node_index(node_index)?)?; + while let Some(child) = stack.pop() { + if ast.arena.child_token_matches(child, token_id) { + descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + } + if let NativeAstChild::Node(index) = child { + for child in ast.arena.node(index)?.children.iter().rev() { + stack.push(*child); + } + } + } + Ok(descendants) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_start( + native_ast_zval: &Zval, + node_index: i64, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + let node = ast.arena.node(native_ast_node_index(node_index)?)?; + let token_index = node + .first_token + .ok_or_else(|| php_error("Native AST node has no descendant tokens"))?; + let token = ast.arena.token_source.token_info(token_index)?; + i64::try_from(token.start).map_err(php_error) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_length( + native_ast_zval: &Zval, + node_index: i64, +) -> PhpResult { + let ast = native_ast(native_ast_zval)?; + let node = ast.arena.node(native_ast_node_index(node_index)?)?; + let first_token_index = node + .first_token + .ok_or_else(|| php_error("Native AST node has no descendant tokens"))?; + let last_token_index = node + .last_token + .ok_or_else(|| php_error("Native AST node has no descendant tokens"))?; + let first_token = ast.arena.token_source.token_info(first_token_index)?; + let last_token = ast.arena.token_source.token_info(last_token_index)?; + let length = last_token.end.saturating_sub(first_token.start); + i64::try_from(length).map_err(php_error) } #[php_class] #[php(name = "WP_MySQL_Native_Parser")] pub struct WpMySqlNativeParser { grammar: Arc, - token_source: ParserTokenSource, + token_source: Arc, token_ids: Vec, position: usize, - current_ast: Option, + current_ast: Option>, current_php_ast: Option, } @@ -1018,7 +1501,7 @@ impl WpMySqlNativeParser { Ok(Self { grammar, - token_source, + token_source: Arc::new(token_source), token_ids, position: 0, current_ast: None, @@ -1028,8 +1511,8 @@ impl WpMySqlNativeParser { pub fn parse(&mut self) -> PhpResult { stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { - let ast = self.parse_recursive_inner(self.grammar.query_rule_id)?; - self.create_php_ast(&ast) + let ast = self.parse_native_ast()?; + self.create_php_ast(ast) }) } @@ -1046,7 +1529,7 @@ impl WpMySqlNativeParser { return Ok(Zval::null()); }; - let ast = self.create_php_ast(native_ast)?; + let ast = self.create_php_ast(Arc::clone(native_ast))?; self.current_php_ast = Some(ast); match self.current_php_ast.as_ref() { Some(ast) => Ok(ast.shallow_clone()), @@ -1063,25 +1546,49 @@ impl WpMySqlNativeParser { return Ok(false); } - self.current_ast = Some(self.parse_recursive_inner(self.grammar.query_rule_id)?); + self.current_ast = Some(self.parse_native_ast()?); self.current_php_ast = None; Ok(true) } - fn parse_recursive_inner(&mut self, rule_id: i64) -> PhpResult { + fn parse_native_ast(&mut self) -> PhpResult> { + let mut arena = + NativeAstArena::new(Arc::clone(&self.grammar), Arc::clone(&self.token_source)); + let query_rule_id = self.grammar.query_rule_id; + arena.root = match self.parse_recursive_inner(&mut arena, query_rule_id)? { + NativeParseMatch::No => NativeAstRoot::No, + NativeParseMatch::Empty => NativeAstRoot::Empty, + NativeParseMatch::Node(index) => NativeAstRoot::Node(index), + NativeParseMatch::Token(index) => NativeAstRoot::Token(index), + NativeParseMatch::Fragment(children) => { + if children.is_empty() { + NativeAstRoot::Empty + } else { + NativeAstRoot::Node(arena.push_node(query_rule_id, children)) + } + } + }; + Ok(Arc::new(arena)) + } + + fn parse_recursive_inner( + &mut self, + arena: &mut NativeAstArena, + rule_id: i64, + ) -> PhpResult { if rule_id <= self.grammar.highest_terminal_id { if self.position >= self.token_ids.len() { - return Ok(ParseMatch::No); + return Ok(NativeParseMatch::No); } if rule_id == 0 { - return Ok(ParseMatch::Empty); + return Ok(NativeParseMatch::Empty); } if self.token_ids[self.position] == rule_id { let token_index = self.position; self.position += 1; - return Ok(ParseMatch::Token(token_index)); + return Ok(NativeParseMatch::Token(token_index)); } - return Ok(ParseMatch::No); + return Ok(NativeParseMatch::No); } let grammar = unsafe { @@ -1092,43 +1599,44 @@ impl WpMySqlNativeParser { }; let Some(rule) = grammar.rule(rule_id) else { - return Ok(ParseMatch::No); + return Ok(NativeParseMatch::No); }; if rule.branches.is_empty() { - return Ok(ParseMatch::No); + return Ok(NativeParseMatch::No); } if let Some(lookahead) = rule.lookahead.as_ref() { let token_id = self.token_ids.get(self.position).copied().unwrap_or(0); if lookahead.binary_search(&token_id).is_err() && lookahead.binary_search(&0).is_err() { - return Ok(ParseMatch::No); + return Ok(NativeParseMatch::No); } } let starting_position = self.position; - let mut matched_node = None; + let starting_node_count = arena.nodes.len(); + let mut matched_children = None; for branch in &rule.branches { self.position = starting_position; + arena.nodes.truncate(starting_node_count); let mut children = Vec::new(); let mut branch_matches = true; for &subrule_id in branch { - match self.parse_recursive_inner(subrule_id)? { - ParseMatch::No => { + match self.parse_recursive_inner(arena, subrule_id)? { + NativeParseMatch::No => { branch_matches = false; break; } - ParseMatch::Empty => {} - ParseMatch::Token(token_index) => { - children.push(AstChild::Token(token_index)); + NativeParseMatch::Empty => {} + NativeParseMatch::Token(token_index) => { + children.push(NativeAstChild::Token(token_index)); } - ParseMatch::Node(subnode) => { - if grammar.is_fragment(subrule_id) { - children.extend(subnode.children); - } else { - children.push(AstChild::Node(subnode)); - } + NativeParseMatch::Node(node_index) => { + children.push(NativeAstChild::Node(node_index)); + } + NativeParseMatch::Fragment(fragment_children) => { + children.extend(fragment_children); } } } @@ -1144,80 +1652,35 @@ impl WpMySqlNativeParser { } if branch_matches { - matched_node = Some(AstNode { rule_id, children }); + matched_children = Some(children); break; } } - let Some(node) = matched_node else { + let Some(children) = matched_children else { self.position = starting_position; - return Ok(ParseMatch::No); + arena.nodes.truncate(starting_node_count); + return Ok(NativeParseMatch::No); }; - if node.children.is_empty() { - Ok(ParseMatch::Empty) + if children.is_empty() { + Ok(NativeParseMatch::Empty) + } else if rule.is_fragment { + Ok(NativeParseMatch::Fragment(children)) } else { - Ok(ParseMatch::Node(node)) + Ok(NativeParseMatch::Node(arena.push_node(rule_id, children))) } } - fn create_php_ast(&self, ast: &ParseMatch) -> PhpResult { + fn create_php_ast(&self, arena: Arc) -> PhpResult { stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { - self.create_php_ast_inner(ast) + let native_ast_zval = WpMySqlNativeAst { arena } + .into_zval(false) + .map_err(php_error)?; + let native_ast = native_ast(&native_ast_zval)?; + native_ast.arena.create_php_ast(&native_ast_zval) }) } - - fn create_php_ast_inner(&self, ast: &ParseMatch) -> PhpResult { - match ast { - ParseMatch::No => Ok(Zval::null()), - ParseMatch::Empty => { - let mut zval = Zval::new(); - zval.set_bool(true); - Ok(zval) - } - ParseMatch::Node(node) => self.create_php_node(node), - ParseMatch::Token(index) => self.token_source.create_php_token(*index), - } - } - - fn create_php_node(&self, ast_node: &AstNode) -> PhpResult { - self.create_php_node_inner(ast_node) - } - - fn create_php_node_inner(&self, ast_node: &AstNode) -> PhpResult { - let classes = php_classes()?; - let mut object = classes.parser_node.new(); - let rule_name = self - .grammar - .rule(ast_node.rule_id) - .map(|rule| rule.rule_name.as_str()) - .unwrap_or_default(); - let mut children = Vec::with_capacity(ast_node.children.len()); - - for child in &ast_node.children { - let child_zval = match child { - AstChild::Node(child_node) => self.create_php_node(child_node)?, - AstChild::Token(index) => self.token_source.create_php_token(*index)?, - }; - children.push(child_zval); - } - - update_object_property( - &mut object, - classes.parser_node, - "rule_id", - ast_node.rule_id, - )?; - update_object_property( - &mut object, - classes.parser_node, - "rule_name", - rule_name.to_owned(), - )?; - update_object_property(&mut object, classes.parser_node, "children", children)?; - - object.into_zval(false).map_err(php_error) - } } fn export_grammar(grammar: &mut Zval) -> PhpResult> { @@ -1451,8 +1914,37 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) { #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module + .class::() .class::() .class::() .class::() + .function(wrap_function!(wp_sqlite_mysql_native_ast_has_child)) + .function(wrap_function!(wp_sqlite_mysql_native_ast_has_child_node)) + .function(wrap_function!(wp_sqlite_mysql_native_ast_has_child_token)) + .function(wrap_function!(wp_sqlite_mysql_native_ast_get_first_child)) + .function(wrap_function!( + wp_sqlite_mysql_native_ast_get_first_child_node + )) + .function(wrap_function!( + wp_sqlite_mysql_native_ast_get_first_child_token + )) + .function(wrap_function!( + wp_sqlite_mysql_native_ast_get_first_descendant_node + )) + .function(wrap_function!( + wp_sqlite_mysql_native_ast_get_first_descendant_token + )) + .function(wrap_function!(wp_sqlite_mysql_native_ast_get_children)) + .function(wrap_function!(wp_sqlite_mysql_native_ast_get_child_nodes)) + .function(wrap_function!(wp_sqlite_mysql_native_ast_get_child_tokens)) + .function(wrap_function!(wp_sqlite_mysql_native_ast_get_descendants)) + .function(wrap_function!( + wp_sqlite_mysql_native_ast_get_descendant_nodes + )) + .function(wrap_function!( + wp_sqlite_mysql_native_ast_get_descendant_tokens + )) + .function(wrap_function!(wp_sqlite_mysql_native_ast_get_start)) + .function(wrap_function!(wp_sqlite_mysql_native_ast_get_length)) .info_function(php_module_info) } From 85d71b2214d88c0acfb810203da62175b4afbb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 15:32:17 +0200 Subject: [PATCH 07/40] Add native SQLite facade smoke test --- tmp-test-native/README.md | 37 ++++ tmp-test-native/build-extension.sh | 57 ++++++ tmp-test-native/run-sqlite-facade-smoke.php | 182 ++++++++++++++++++++ tmp-test-native/run.sh | 20 +++ 4 files changed, 296 insertions(+) create mode 100644 tmp-test-native/README.md create mode 100755 tmp-test-native/build-extension.sh create mode 100644 tmp-test-native/run-sqlite-facade-smoke.php create mode 100755 tmp-test-native/run.sh diff --git a/tmp-test-native/README.md b/tmp-test-native/README.md new file mode 100644 index 00000000..40fc250a --- /dev/null +++ b/tmp-test-native/README.md @@ -0,0 +1,37 @@ +# Native SQLite Facade Smoke Test + +This directory contains a local smoke test for the lazy native MySQL parser path +as used by the SQLite driver. + +Run the smoke test directly from the repository root: + +```bash +php tmp-test-native/run-sqlite-facade-smoke.php +``` + +That file does not know or care whether the Rust extension is loaded. It just +loads the SQLite library, runs MySQL-flavored queries through +`WP_PDO_MySQL_On_SQLite`, asks the driver to create parsers, and traverses the +returned ASTs. The library chooses the native parser/tokenizer when available +and falls back to the PHP implementation otherwise. + +By default, it processes 2000 generated SQL queries. That includes a +2000-row multi-insert every 250 queries. To change the query count: + +```bash +TMP_TEST_NATIVE_QUERY_COUNT=500 php tmp-test-native/run-sqlite-facade-smoke.php +``` + +To build the Rust extension locally and run the smoke test once with the current +PHP configuration and once with the extension explicitly loaded: + +```bash +./tmp-test-native/run.sh +``` + +If you already have a PHP/ext build environment and do not want the helper to +enter a Nix shell, run: + +```bash +TMP_TEST_NATIVE_NO_NIX=1 ./tmp-test-native/run.sh +``` diff --git a/tmp-test-native/build-extension.sh b/tmp-test-native/build-extension.sh new file mode 100755 index 00000000..c2bb2bca --- /dev/null +++ b/tmp-test-native/build-extension.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +EXT_DIR="$ROOT_DIR/packages/mysql-on-sqlite/ext/wp-mysql-parser" + +if [ ! -f "$EXT_DIR/Cargo.toml" ]; then + echo "Cannot find Rust extension at $EXT_DIR" >&2 + exit 1 +fi + +if [ "${1:-}" != "--inside-nix" ] && [ "${TMP_TEST_NATIVE_NO_NIX:-}" != "1" ] && command -v nix >/dev/null 2>&1; then + exec nix --extra-experimental-features 'nix-command flakes' shell \ + nixpkgs#php82 \ + nixpkgs#php82.unwrapped.dev \ + nixpkgs#clang_19 \ + nixpkgs#glibc.dev \ + -c bash "$SCRIPT_DIR/build-extension.sh" --inside-nix +fi + +cd "$EXT_DIR" + +PHP_CONFIG="${PHP_CONFIG:-$(command -v php-config || true)}" +if [ -z "$PHP_CONFIG" ]; then + echo "php-config is required. Use Nix or install the PHP development package." >&2 + exit 1 +fi + +if ! command -v clang >/dev/null 2>&1; then + echo "clang is required. Use Nix or install clang/libclang." >&2 + exit 1 +fi + +if [ -z "${LIBCLANG_PATH:-}" ]; then + LIBCLANG_SO="$(find /nix/store -path '*/lib/libclang.so' -print -quit 2>/dev/null || true)" + if [ -n "$LIBCLANG_SO" ]; then + export LIBCLANG_PATH + LIBCLANG_PATH="$(dirname "$LIBCLANG_SO")" + fi +fi + +PHP_INCLUDES="$("$PHP_CONFIG" --includes)" +CLANG_RESOURCE="$(clang -print-resource-dir)" + +export BINDGEN_EXTRA_CLANG_ARGS="$PHP_INCLUDES -isystem $CLANG_RESOURCE/include ${BINDGEN_EXTRA_CLANG_ARGS:-}" +export CFLAGS="$PHP_INCLUDES -isystem $CLANG_RESOURCE/include ${CFLAGS:-}" + +cargo build --release + +EXT_SO="$EXT_DIR/target/release/libwp_mysql_parser.so" +if [ ! -f "$EXT_SO" ]; then + echo "Build completed but extension was not found at $EXT_SO" >&2 + exit 1 +fi + +printf '%s\n' "$EXT_SO" diff --git a/tmp-test-native/run-sqlite-facade-smoke.php b/tmp-test-native/run-sqlite-facade-smoke.php new file mode 100644 index 00000000..9a200e4c --- /dev/null +++ b/tmp-test-native/run-sqlite-facade-smoke.php @@ -0,0 +1,182 @@ +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + $db->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true ); + return $db; +} + +function seed_database( WP_PDO_MySQL_On_SQLite $db ): void { + $db->query( + 'CREATE TABLE `' . TEST_TABLE . "` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `tenant_id` int NOT NULL, + `label` varchar(191) NOT NULL, + `score` int DEFAULT 0, + `payload` text, + PRIMARY KEY (`id`), + KEY `tenant_score` (`tenant_id`, `score`), + KEY `label_score` (`label`, `score`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" + ); + + for ( $i = 1; $i <= 64; $i++ ) { + $db->query( make_insert_query( $i ) ); + } +} + +function sql_string( string $value ): string { + return "'" . str_replace( "'", "''", $value ) . "'"; +} + +function make_insert_query( int $i ): string { + $tenant = $i % 17; + $score = ( $i * 13 ) % 997; + $label = sql_string( "label_{$i}_tenant_{$tenant}" ); + $payload = sql_string( '{"seed":' . $i . ',"tenant":' . $tenant . '}' ); + + return 'INSERT INTO `' . TEST_TABLE . "` (`tenant_id`, `label`, `score`, `payload`) VALUES ({$tenant}, {$label}, {$score}, {$payload})"; +} + +function make_long_multi_insert_query( int $i ): string { + $rows = array(); + for ( $row = 0; $row < LONG_INSERT_ROWS; $row++ ) { + $tenant = ( $i + $row ) % 17; + $score = ( $i * 31 + $row * 7 ) % 997; + $label = sql_string( "bulk_{$i}_{$row}_tenant_{$tenant}" ); + $payload = sql_string( '{"bulk":' . $i . ',"row":' . $row . ',"tenant":' . $tenant . '}' ); + $rows[] = "({$tenant}, {$label}, {$score}, {$payload})"; + } + + return 'INSERT INTO `' . TEST_TABLE . '` (`tenant_id`, `label`, `score`, `payload`) VALUES ' . implode( ",\n", $rows ); +} + +function make_select_query( int $i ): string { + $tenant = $i % 17; + $min = ( $i * 7 ) % 400; + $limit = ( $i % 5 ) + 1; + + return 'SELECT `id`, `label`, `score`, CASE WHEN `score` >= ' . $min . " THEN 'high' ELSE 'low' END AS `bucket` + FROM `" . TEST_TABLE . "` + WHERE `tenant_id` = {$tenant} + AND `score` BETWEEN {$min} AND 1000 + AND `label` LIKE 'label_%' + ORDER BY `score` DESC, `id` ASC + LIMIT {$limit}"; +} + +function make_aggregate_query( int $i ): string { + $tenant_a = $i % 17; + $tenant_b = ( $i + 3 ) % 17; + + return 'SELECT COUNT(*) AS `total`, COALESCE(MAX(`score`), 0) AS `max_score`, COALESCE(MIN(`score`), 0) AS `min_score` + FROM `' . TEST_TABLE . "` + WHERE (`tenant_id`, `score`) IN (({$tenant_a}, " . ( ( $i * 13 ) % 997 ) . "), ({$tenant_b}, " . ( ( $i * 17 ) % 997 ) . ')) + OR `label` IN (' . sql_string( "label_{$i}_tenant_{$tenant_a}" ) . ', ' . sql_string( "missing_{$i}" ) . ')'; +} + +function make_update_query( int $i ): string { + $tenant = $i % 17; + $delta = ( $i % 5 ) + 1; + $cutoff = ( $i * 11 ) % 997; + + return 'UPDATE `' . TEST_TABLE . "` + SET `score` = `score` + {$delta} + WHERE `tenant_id` = {$tenant} + AND `score` < {$cutoff}"; +} + +function make_workload_query( int $i ): string { + if ( 0 === $i % 250 ) { + return make_long_multi_insert_query( $i ); + } + + $factories = array( + 'make_insert_query', + 'make_select_query', + 'make_aggregate_query', + 'make_update_query', + 'make_select_query', + 'make_aggregate_query', + ); + + $factory = $factories[ $i % count( $factories ) ]; + return $factory( $i + 64 ); +} + +function parse_with_sqlite_driver( WP_PDO_MySQL_On_SQLite $db, string $sql ): int { + $parser = $db->create_parser( $sql ); + $descendants = 0; + + while ( $parser->next_query() ) { + $ast = $parser->get_query_ast(); + if ( ! $ast instanceof WP_Parser_Node ) { + smoke_fail( 'parser did not return a WP_Parser_Node' ); + } + + $descendants += count( $ast->get_descendants() ); + } + + return $descendants; +} + +function execute_with_sqlite_driver( WP_PDO_MySQL_On_SQLite $db, string $sql ): int { + $result = $db->query( $sql ); + if ( ! $result instanceof PDOStatement ) { + return 0; + } + + return count( $result->fetchAll( PDO::FETCH_ASSOC ) ); +} + +function run_workload(): void { + require_once dirname( __DIR__ ) . '/packages/mysql-on-sqlite/src/load.php'; + + $query_count = (int) ( getenv( 'TMP_TEST_NATIVE_QUERY_COUNT' ) ?: DEFAULT_QUERY_COUNT ); + $db = create_driver(); + seed_database( $db ); + + $start = microtime( true ); + $rows = 0; + $descendants = 0; + $long_inserts = 0; + + for ( $i = 1; $i <= $query_count; $i++ ) { + $sql = make_workload_query( $i ); + $long_inserts += 0 === $i % 250 ? 1 : 0; + $descendants += parse_with_sqlite_driver( $db, $sql ); + $rows += execute_with_sqlite_driver( $db, $sql ); + } + + if ( $descendants < $query_count * 10 ) { + smoke_fail( "parsed too few descendants ({$descendants})" ); + } + + smoke_ok( + sprintf( + 'processed %d queries, including %d x %d-row multi-inserts, %d AST descendants, %d fetched rows in %.3fs', + $query_count, + $long_inserts, + LONG_INSERT_ROWS, + $descendants, + $rows, + microtime( true ) - $start + ) + ); +} + +run_workload(); diff --git a/tmp-test-native/run.sh b/tmp-test-native/run.sh new file mode 100755 index 00000000..bd713545 --- /dev/null +++ b/tmp-test-native/run.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +if ! grep -q 'WP_MySQL_Native_Ast' "$ROOT_DIR/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs"; then + echo "This checkout does not include the lazy native AST facade." >&2 + echo "Switch to codex/native-lazy-ast-facade first." >&2 + exit 1 +fi + +EXT_SO="$("$SCRIPT_DIR/build-extension.sh" | tail -n 1)" + +echo "=== Current PHP configuration ===" +php "$SCRIPT_DIR/run-sqlite-facade-smoke.php" + +echo +echo "=== Explicit native extension ===" +php -d extension="$EXT_SO" "$SCRIPT_DIR/run-sqlite-facade-smoke.php" From 7416c2f86d45de44c211d81270934ca7852c3dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 15:35:44 +0200 Subject: [PATCH 08/40] Run SQLite tests with native parser extension in CI --- .../mysql-parser-extension-tests.yml | 75 +++++++++++++++++++ tmp-test-native/run-sqlite-facade-smoke.php | 17 +++-- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 32fb6638..8852d9a4 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -5,10 +5,12 @@ on: paths: - '.github/workflows/mysql-parser-extension-tests.yml' - 'packages/mysql-on-sqlite/**' + - 'tmp-test-native/**' pull_request: paths: - '.github/workflows/mysql-parser-extension-tests.yml' - 'packages/mysql-on-sqlite/**' + - 'tmp-test-native/**' workflow_dispatch: concurrency: @@ -67,3 +69,76 @@ jobs: - name: Run PHPUnit tests with parser extension run: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist working-directory: packages/mysql-on-sqlite + + sqlite-driver-extension-tests: + name: PHP 8.2 / SQLite driver / Rust extension / ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + tools: phpunit-polyfills + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install native build dependencies + run: | + sudo apt-get update + sudo apt-get install -y libclang-dev + echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV" + LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)" + echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV" + + - name: Install Composer dependencies (root) + uses: ramsey/composer-install@v3 + with: + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Install Composer dependencies (mysql-on-sqlite) + uses: ramsey/composer-install@v3 + with: + working-directory: packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Build parser extension + run: cargo build + working-directory: packages/mysql-on-sqlite/ext/wp-mysql-parser + + - name: Verify SQLite driver selects the native parser path + run: | + php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' + require "packages/mysql-on-sqlite/src/load.php"; + $lexer = new WP_MySQL_Lexer( "SELECT 1" ); + if ( ! method_exists( $lexer, "native_token_stream" ) ) { + fwrite( STDERR, "Native token stream is not available.\n" ); + exit( 1 ); + } + $driver = new WP_PDO_MySQL_On_SQLite( "mysql-on-sqlite:path=:memory:;dbname=wp;" ); + $parser = $driver->create_parser( "SELECT 1" ); + $parser->next_query(); + $ast = $parser->get_query_ast(); + $property = ( new ReflectionClass( $ast ) )->getProperty( "native_ast" ); + $property->setAccessible( true ); + $native_ast = $property->getValue( $ast ); + if ( ! is_object( $native_ast ) || "WP_MySQL_Native_Ast" !== get_class( $native_ast ) ) { + fwrite( STDERR, "SQLite driver did not return a native-backed AST.\n" ); + exit( 1 ); + } + ' + + - name: Run PHPUnit tests with SQLite driver using parser extension + run: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist + working-directory: packages/mysql-on-sqlite + + - name: Run native SQLite facade smoke workload + run: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" tmp-test-native/run-sqlite-facade-smoke.php diff --git a/tmp-test-native/run-sqlite-facade-smoke.php b/tmp-test-native/run-sqlite-facade-smoke.php index 9a200e4c..42b310d1 100644 --- a/tmp-test-native/run-sqlite-facade-smoke.php +++ b/tmp-test-native/run-sqlite-facade-smoke.php @@ -15,14 +15,14 @@ function smoke_fail( string $message ): void { function create_driver(): WP_PDO_MySQL_On_SQLite { $db = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); - $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); - $db->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true ); + $db->setAttribute( constant( 'PDO::ATTR_ERRMODE' ), constant( 'PDO::ERRMODE_EXCEPTION' ) ); + $db->setAttribute( constant( 'PDO::ATTR_STRINGIFY_FETCHES' ), true ); return $db; } function seed_database( WP_PDO_MySQL_On_SQLite $db ): void { $db->query( - 'CREATE TABLE `' . TEST_TABLE . "` ( + 'CREATE TABLE `' . TEST_TABLE . '` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `tenant_id` int NOT NULL, `label` varchar(191) NOT NULL, @@ -31,7 +31,7 @@ function seed_database( WP_PDO_MySQL_On_SQLite $db ): void { PRIMARY KEY (`id`), KEY `tenant_score` (`tenant_id`, `score`), KEY `label_score` (`label`, `score`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' ); for ( $i = 1; $i <= 64; $i++ ) { @@ -136,18 +136,19 @@ function parse_with_sqlite_driver( WP_PDO_MySQL_On_SQLite $db, string $sql ): in function execute_with_sqlite_driver( WP_PDO_MySQL_On_SQLite $db, string $sql ): int { $result = $db->query( $sql ); - if ( ! $result instanceof PDOStatement ) { + if ( ! is_object( $result ) || ! method_exists( $result, 'fetchAll' ) ) { return 0; } - return count( $result->fetchAll( PDO::FETCH_ASSOC ) ); + return count( $result->fetchAll( constant( 'PDO::FETCH_ASSOC' ) ) ); } function run_workload(): void { require_once dirname( __DIR__ ) . '/packages/mysql-on-sqlite/src/load.php'; - $query_count = (int) ( getenv( 'TMP_TEST_NATIVE_QUERY_COUNT' ) ?: DEFAULT_QUERY_COUNT ); - $db = create_driver(); + $query_count_env = getenv( 'TMP_TEST_NATIVE_QUERY_COUNT' ); + $query_count = (int) ( false === $query_count_env ? DEFAULT_QUERY_COUNT : $query_count_env ); + $db = create_driver(); seed_database( $db ); $start = microtime( true ); From f7c4c4002929daf7d494fbea3e0d9979c28e0385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 15:48:06 +0200 Subject: [PATCH 09/40] Run WordPress PHPUnit with native parser extension --- ...wp-tests-phpunit-native-extension-setup.sh | 129 ++++++++++++++++++ .github/workflows/wp-tests-phpunit.yml | 30 ++++ 2 files changed, 159 insertions(+) create mode 100644 .github/workflows/wp-tests-phpunit-native-extension-setup.sh diff --git a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh new file mode 100644 index 00000000..8d3fb063 --- /dev/null +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +WP_DIR="$ROOT_DIR/wordpress" +COMPOSE_OVERRIDE="$WP_DIR/docker-compose.override.yml" +RUNTIME_DIR="$ROOT_DIR/tmp-native-extension" +EXTENSION_SOURCE_VOLUME=" - ../packages/mysql-on-sqlite/ext/wp-mysql-parser:/var/native-parser-extension-src" +EXTENSION_RUNTIME_VOLUME=" - ../tmp-native-extension:/var/native-parser-extension:ro" +EXTENSION_INI_VOLUME=" - ../tmp-native-extension/wp-mysql-parser.ini:/usr/local/etc/php/conf.d/wp-mysql-parser.ini:ro" + +if [ ! -f "$COMPOSE_OVERRIDE" ]; then + echo "Missing $COMPOSE_OVERRIDE. Run composer run wp-setup first." >&2 + exit 1 +fi + +add_volume_to_service() { + local service="$1" + local volume="$2" + + node - "$COMPOSE_OVERRIDE" "$service" "$volume" <<'NODE' +const fs = require( 'fs' ); + +const file = process.argv[2]; +const service = process.argv[3]; +const volume = process.argv[4]; +const lines = fs.readFileSync( file, 'utf8' ).split( '\n' ); + +if ( lines.some( line => line.trim() === volume.trim() ) ) { + process.exit( 0 ); +} + +const serviceIndex = lines.findIndex( line => line === ` ${ service }:` ); +if ( serviceIndex === -1 ) { + throw new Error( `Service ${ service } not found in ${ file }.` ); +} + +let volumesIndex = -1; +for ( let i = serviceIndex + 1; i < lines.length; i++ ) { + if ( /^ [A-Za-z0-9_-]+:/.test( lines[i] ) ) { + break; + } + + if ( lines[i].trim() === 'volumes:' ) { + volumesIndex = i; + break; + } +} + +if ( volumesIndex === -1 ) { + throw new Error( `Service ${ service } has no volumes list in ${ file }.` ); +} + +let insertAt = volumesIndex + 1; +while ( insertAt < lines.length && /^\s{6}- /.test( lines[insertAt] ) ) { + insertAt++; +} + +lines.splice( insertAt, 0, volume ); +fs.writeFileSync( file, lines.join( '\n' ) ); +NODE +} + +add_volume_to_service php "$EXTENSION_SOURCE_VOLUME" +add_volume_to_service cli "$EXTENSION_SOURCE_VOLUME" + +cat > "$WP_DIR/native-build-extension.sh" <<'EOF' +#!/bin/sh +set -eu + +apt-get update +apt-get install -y --no-install-recommends ca-certificates curl build-essential clang libclang-dev pkg-config + +if [ ! -x "$HOME/.cargo/bin/cargo" ]; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable +fi + +. "$HOME/.cargo/env" + +PHP_CONFIG="$(command -v php-config)" +export PHP_CONFIG + +LIBCLANG_SO="$(find /usr/lib /usr/local/lib -name 'libclang.so*' 2>/dev/null | head -n 1)" +if [ -z "$LIBCLANG_SO" ]; then + echo "Unable to locate libclang.so after installing libclang-dev." >&2 + exit 1 +fi + +LIBCLANG_PATH="$(dirname "$LIBCLANG_SO")" +export LIBCLANG_PATH + +cd /var/native-parser-extension-src +cargo build +EOF + +chmod +x "$WP_DIR/native-build-extension.sh" + +cd "$WP_DIR" +node tools/local-env/scripts/docker.js run --rm php sh /var/www/native-build-extension.sh + +mkdir -p "$RUNTIME_DIR" +cp "$ROOT_DIR/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" "$RUNTIME_DIR/libwp_mysql_parser.so" +printf '%s\n' 'extension=/var/native-parser-extension/libwp_mysql_parser.so' > "$RUNTIME_DIR/wp-mysql-parser.ini" + +add_volume_to_service php "$EXTENSION_RUNTIME_VOLUME" +add_volume_to_service cli "$EXTENSION_RUNTIME_VOLUME" +add_volume_to_service php "$EXTENSION_INI_VOLUME" +add_volume_to_service cli "$EXTENSION_INI_VOLUME" + +node tools/local-env/scripts/docker.js run --rm php php -m | grep -qx 'wp_mysql_parser' +node tools/local-env/scripts/docker.js run --rm php php -r ' +require "/var/www/src/wp-content/plugins/sqlite-database-integration/wp-includes/database/load.php"; +$lexer = new WP_MySQL_Lexer( "SELECT 1" ); +if ( ! method_exists( $lexer, "native_token_stream" ) ) { + fwrite( STDERR, "Native token stream is not available in the WordPress PHP test container.\n" ); + exit( 1 ); +} +$driver = new WP_PDO_MySQL_On_SQLite( "mysql-on-sqlite:path=:memory:;dbname=wp;" ); +$parser = $driver->create_parser( "SELECT 1" ); +$parser->next_query(); +$ast = $parser->get_query_ast(); +$property = ( new ReflectionClass( $ast ) )->getProperty( "native_ast" ); +$property->setAccessible( true ); +$native_ast = $property->getValue( $ast ); +if ( ! is_object( $native_ast ) || "WP_MySQL_Native_Ast" !== get_class( $native_ast ) ) { + fwrite( STDERR, "WordPress PHP test container did not select the native-backed AST.\n" ); + exit( 1 ); +} +' diff --git a/.github/workflows/wp-tests-phpunit.yml b/.github/workflows/wp-tests-phpunit.yml index d2746fdc..31db5ef0 100644 --- a/.github/workflows/wp-tests-phpunit.yml +++ b/.github/workflows/wp-tests-phpunit.yml @@ -30,3 +30,33 @@ jobs: - name: Stop Docker containers if: always() run: composer run wp-test-clean + + native-parser-test: + name: WordPress PHPUnit Tests / Rust extension + runs-on: ubuntu-latest + timeout-minutes: 40 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Set UID and GID for PHP in WordPress images + run: | + echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV + echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV + + - name: Set up WordPress test environment + run: composer run wp-setup + + - name: Build and load parser extension in WordPress PHP containers + run: bash .github/workflows/wp-tests-phpunit-native-extension-setup.sh + + - name: Run WordPress PHPUnit tests with parser extension + run: node .github/workflows/wp-tests-phpunit-run.js + + - name: Stop Docker containers + if: always() + run: composer run wp-test-clean From b570487f95c5376c2b91e3428c18dd3ac07bc5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 15:49:10 +0200 Subject: [PATCH 10/40] Fix native WordPress CI service mounts --- .../wp-tests-phpunit-native-extension-setup.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh index 8d3fb063..cb458298 100644 --- a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -26,21 +26,25 @@ const service = process.argv[3]; const volume = process.argv[4]; const lines = fs.readFileSync( file, 'utf8' ).split( '\n' ); -if ( lines.some( line => line.trim() === volume.trim() ) ) { - process.exit( 0 ); -} - const serviceIndex = lines.findIndex( line => line === ` ${ service }:` ); if ( serviceIndex === -1 ) { throw new Error( `Service ${ service } not found in ${ file }.` ); } -let volumesIndex = -1; +let serviceEnd = lines.length; for ( let i = serviceIndex + 1; i < lines.length; i++ ) { if ( /^ [A-Za-z0-9_-]+:/.test( lines[i] ) ) { + serviceEnd = i; break; } +} +if ( lines.slice( serviceIndex, serviceEnd ).some( line => line.trim() === volume.trim() ) ) { + process.exit( 0 ); +} + +let volumesIndex = -1; +for ( let i = serviceIndex + 1; i < serviceEnd; i++ ) { if ( lines[i].trim() === 'volumes:' ) { volumesIndex = i; break; @@ -52,7 +56,7 @@ if ( volumesIndex === -1 ) { } let insertAt = volumesIndex + 1; -while ( insertAt < lines.length && /^\s{6}- /.test( lines[insertAt] ) ) { +while ( insertAt < serviceEnd && /^\s{6}- /.test( lines[insertAt] ) ) { insertAt++; } From 98cebfd24bc39caef212fdfaca0080af6d84bfed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 15:55:36 +0200 Subject: [PATCH 11/40] Fix native WordPress CI verifier command --- ...wp-tests-phpunit-native-extension-setup.sh | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh index cb458298..b103f96b 100644 --- a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -111,23 +111,29 @@ add_volume_to_service cli "$EXTENSION_RUNTIME_VOLUME" add_volume_to_service php "$EXTENSION_INI_VOLUME" add_volume_to_service cli "$EXTENSION_INI_VOLUME" -node tools/local-env/scripts/docker.js run --rm php php -m | grep -qx 'wp_mysql_parser' -node tools/local-env/scripts/docker.js run --rm php php -r ' -require "/var/www/src/wp-content/plugins/sqlite-database-integration/wp-includes/database/load.php"; -$lexer = new WP_MySQL_Lexer( "SELECT 1" ); -if ( ! method_exists( $lexer, "native_token_stream" ) ) { +cat > "$WP_DIR/native-verify-extension.php" <<'EOF' +create_parser( "SELECT 1" ); + +$driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); +$parser = $driver->create_parser( 'SELECT 1' ); $parser->next_query(); $ast = $parser->get_query_ast(); -$property = ( new ReflectionClass( $ast ) )->getProperty( "native_ast" ); +$property = ( new ReflectionClass( $ast ) )->getProperty( 'native_ast' ); $property->setAccessible( true ); $native_ast = $property->getValue( $ast ); -if ( ! is_object( $native_ast ) || "WP_MySQL_Native_Ast" !== get_class( $native_ast ) ) { + +if ( ! is_object( $native_ast ) || 'WP_MySQL_Native_Ast' !== get_class( $native_ast ) ) { fwrite( STDERR, "WordPress PHP test container did not select the native-backed AST.\n" ); exit( 1 ); } -' +EOF + +node tools/local-env/scripts/docker.js run --rm php php -m | grep -qx 'wp_mysql_parser' +node tools/local-env/scripts/docker.js run --rm php php /var/www/native-verify-extension.php From 8aa3cc433ea3f80b6075fd9b8b655d65e3b44bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 16:53:36 +0200 Subject: [PATCH 12/40] Fix native parser lifetime caches --- .../ext/wp-mysql-parser/src/lib.rs | 85 ++++++++++--------- .../src/mysql/native/mysql-rust-bridge.php | 10 +-- .../src/parser/class-wp-parser-grammar.php | 78 +++++++++++++++-- .../src/parser/class-wp-parser.php | 30 ++++--- 4 files changed, 140 insertions(+), 63 deletions(-) diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs index 4af124e9..4f6b1f78 100644 --- a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::os::raw::c_char; use std::ptr; -use std::sync::{Arc, Mutex, OnceLock}; +use std::sync::Arc; use ext_php_rs::convert::{FromZval, IntoZval, IntoZvalDyn}; use ext_php_rs::exception::{PhpException, PhpResult}; @@ -62,33 +62,15 @@ struct PhpClasses { parser_node: &'static ClassEntry, } -// Class entries are process-lifetime Zend metadata. We only read the pointers -// after lookup, and PHP owns their lifetime. -unsafe impl Send for PhpClasses {} -unsafe impl Sync for PhpClasses {} - -static PHP_CLASSES: OnceLock = OnceLock::new(); - -fn php_classes() -> PhpResult<&'static PhpClasses> { - if let Some(classes) = PHP_CLASSES.get() { - return Ok(classes); - } - - let classes = PhpClasses { +fn php_classes() -> PhpResult { + Ok(PhpClasses { parser_token: ClassEntry::try_find("WP_Parser_Token") .ok_or_else(|| php_error("Missing WP_Parser_Token class"))?, mysql_token: ClassEntry::try_find("WP_MySQL_Token") .ok_or_else(|| php_error("Missing WP_MySQL_Token class"))?, parser_node: ClassEntry::try_find("WP_Parser_Node") .ok_or_else(|| php_error("Missing WP_Parser_Node class"))?, - }; - - PHP_CLASSES - .set(classes) - .map_err(|_| php_error("PHP class cache was initialized concurrently"))?; - PHP_CLASSES - .get() - .ok_or_else(|| php_error("PHP class cache initialization failed")) + }) } fn update_object_property( @@ -942,7 +924,11 @@ impl Grammar { } } -static GRAMMAR_CACHE: OnceLock>>> = OnceLock::new(); +#[php_class] +#[php(name = "WP_MySQL_Native_Grammar")] +pub struct WpMySqlNativeGrammar { + grammar: Arc, +} enum ParserTokenSource { Php(Vec), @@ -1683,21 +1669,13 @@ impl WpMySqlNativeParser { } } -fn export_grammar(grammar: &mut Zval) -> PhpResult> { - let grammar_id = grammar.object().map(|object| object.get_id()); - if let Some(grammar_id) = grammar_id { - let cache = GRAMMAR_CACHE.get_or_init(|| Mutex::new(HashMap::new())); - if let Some(cached) = cache - .lock() - .map_err(|_| php_error("Grammar cache lock poisoned"))? - .get(&grammar_id) - { - return Ok(Arc::clone(cached)); - } +fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { + if let Some(cached) = cached_native_grammar(grammar_zval)? { + return Ok(cached); } let exported = php_function("wp_sqlite_mysql_native_export_grammar")? - .try_call(vec![&*grammar as &dyn IntoZvalDyn]) + .try_call(vec![&*grammar_zval as &dyn IntoZvalDyn]) .map_err(php_error)?; let array = exported .array() @@ -1752,17 +1730,39 @@ fn export_grammar(grammar: &mut Zval) -> PhpResult> { select_statement_rule_id, }); - if let Some(grammar_id) = grammar_id { - GRAMMAR_CACHE - .get_or_init(|| Mutex::new(HashMap::new())) - .lock() - .map_err(|_| php_error("Grammar cache lock poisoned"))? - .insert(grammar_id, Arc::clone(&grammar)); - } + cache_native_grammar(grammar_zval, Arc::clone(&grammar))?; Ok(grammar) } +fn cached_native_grammar(grammar: &Zval) -> PhpResult>> { + let object = grammar + .object() + .ok_or_else(|| php_error("Parser grammar must be an object"))?; + let properties = object.get_properties().map_err(php_error)?; + let Some(native_grammar) = properties.get("native_grammar") else { + return Ok(None); + }; + let Some(native_grammar) = <&WpMySqlNativeGrammar as FromZval>::from_zval(native_grammar) + else { + return Ok(None); + }; + + Ok(Some(Arc::clone(&native_grammar.grammar))) +} + +fn cache_native_grammar(grammar_zval: &mut Zval, grammar: Arc) -> PhpResult<()> { + let object = grammar_zval + .object_mut() + .ok_or_else(|| php_error("Parser grammar must be an object"))?; + let native_grammar = WpMySqlNativeGrammar { grammar } + .into_zval(false) + .map_err(php_error)?; + object + .set_property("native_grammar", native_grammar) + .map_err(php_error) +} + fn export_tokens(tokens: &mut Zval) -> PhpResult<(ParserTokenSource, Vec)> { if let Some(stream) = <&WpMySqlNativeTokenStream as FromZval>::from_zval(tokens) { let token_ids = stream.tokens.iter().map(|token| token.id).collect(); @@ -1914,6 +1914,7 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) { #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module + .class::() .class::() .class::() .class::() diff --git a/packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php b/packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php index 974cfa66..a3334709 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php +++ b/packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php @@ -13,10 +13,10 @@ */ function wp_sqlite_mysql_native_export_grammar( WP_Parser_Grammar $grammar ): array { return array( - 'highest_terminal_id' => $grammar->highest_terminal_id, - 'rules' => $grammar->rules, - 'lookahead_is_match_possible' => $grammar->lookahead_is_match_possible, - 'rule_names' => $grammar->rule_names, - 'fragment_ids' => $grammar->fragment_ids, + 'highest_terminal_id' => $grammar->get_highest_terminal_id(), + 'rules' => $grammar->get_rules(), + 'lookahead_is_match_possible' => $grammar->get_lookahead_is_match_possible(), + 'rule_names' => $grammar->get_rule_names(), + 'fragment_ids' => $grammar->get_fragment_ids(), ); } diff --git a/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php index 8c17b458..3ad5b1cb 100644 --- a/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php +++ b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php @@ -26,17 +26,48 @@ class WP_Parser_Grammar { /** * @TODO: Review and document these properties and their visibility. */ - public $rules; - public $rule_names; - public $fragment_ids; - public $lookahead_is_match_possible = array(); - public $lowest_non_terminal_id; - public $highest_terminal_id; + private $rules = array(); + private $rule_names = array(); + private $fragment_ids = array(); + private $lookahead_is_match_possible = array(); + private $lowest_non_terminal_id; + private $highest_terminal_id; + public $native_grammar; public function __construct( array $rules ) { $this->inflate( $rules ); } + public function __get( $name ) { + if ( $this->is_grammar_property( $name ) ) { + return $this->$name; + } + + trigger_error( 'Undefined property: ' . __CLASS__ . '::$' . $name, E_USER_NOTICE ); + return null; + } + + public function __isset( $name ) { + return $this->is_grammar_property( $name ) && isset( $this->$name ); + } + + public function __set( $name, $value ) { + if ( $this->is_grammar_property( $name ) ) { + $this->$name = $value; + $this->native_grammar = null; + return; + } + + trigger_error( 'Undefined property: ' . __CLASS__ . '::$' . $name, E_USER_NOTICE ); + } + + public function __unset( $name ) { + if ( $this->is_grammar_property( $name ) ) { + unset( $this->$name ); + $this->native_grammar = null; + } + } + public function get_rule_name( $rule_id ) { return $this->rule_names[ $rule_id ]; } @@ -45,6 +76,41 @@ public function get_rule_id( $rule_name ) { return array_search( $rule_name, $this->rule_names, true ); } + public function get_rules() { + return $this->rules; + } + + public function get_rule_names() { + return $this->rule_names; + } + + public function get_fragment_ids() { + return $this->fragment_ids; + } + + public function get_lookahead_is_match_possible() { + return $this->lookahead_is_match_possible; + } + + public function get_highest_terminal_id() { + return $this->highest_terminal_id; + } + + private function is_grammar_property( $name ) { + return in_array( + $name, + array( + 'rules', + 'rule_names', + 'fragment_ids', + 'lookahead_is_match_possible', + 'lowest_non_terminal_id', + 'highest_terminal_id', + ), + true + ); + } + /** * Inflate the grammar to an internal representation optimized for parsing. * diff --git a/packages/mysql-on-sqlite/src/parser/class-wp-parser.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser.php index 4436892f..970d9e94 100644 --- a/packages/mysql-on-sqlite/src/parser/class-wp-parser.php +++ b/packages/mysql-on-sqlite/src/parser/class-wp-parser.php @@ -10,13 +10,23 @@ */ class WP_Parser { protected $grammar; + protected $rules; + protected $rule_names; + protected $fragment_ids; + protected $lookahead_is_match_possible; + protected $highest_terminal_id; protected $tokens; protected $position; public function __construct( WP_Parser_Grammar $grammar, array $tokens ) { - $this->grammar = $grammar; - $this->tokens = $tokens; - $this->position = 0; + $this->grammar = $grammar; + $this->rules = $grammar->get_rules(); + $this->rule_names = $grammar->get_rule_names(); + $this->fragment_ids = $grammar->get_fragment_ids(); + $this->lookahead_is_match_possible = $grammar->get_lookahead_is_match_possible(); + $this->highest_terminal_id = $grammar->get_highest_terminal_id(); + $this->tokens = $tokens; + $this->position = 0; } public function parse() { @@ -27,7 +37,7 @@ public function parse() { } private function parse_recursive( $rule_id ) { - $is_terminal = $rule_id <= $this->grammar->highest_terminal_id; + $is_terminal = $rule_id <= $this->highest_terminal_id; if ( $is_terminal ) { if ( $this->position >= count( $this->tokens ) ) { return false; @@ -44,24 +54,24 @@ private function parse_recursive( $rule_id ) { return false; } - $branches = $this->grammar->rules[ $rule_id ]; + $branches = $this->rules[ $rule_id ]; if ( ! count( $branches ) ) { return false; } // Bale out from processing the current branch if none of its rules can // possibly match the current token. - if ( isset( $this->grammar->lookahead_is_match_possible[ $rule_id ] ) ) { + if ( isset( $this->lookahead_is_match_possible[ $rule_id ] ) ) { $token_id = $this->tokens[ $this->position ]->id; if ( - ! isset( $this->grammar->lookahead_is_match_possible[ $rule_id ][ $token_id ] ) && - ! isset( $this->grammar->lookahead_is_match_possible[ $rule_id ][ WP_Parser_Grammar::EMPTY_RULE_ID ] ) + ! isset( $this->lookahead_is_match_possible[ $rule_id ][ $token_id ] ) && + ! isset( $this->lookahead_is_match_possible[ $rule_id ][ WP_Parser_Grammar::EMPTY_RULE_ID ] ) ) { return false; } } - $rule_name = $this->grammar->rule_names[ $rule_id ]; + $rule_name = $this->rule_names[ $rule_id ]; $starting_position = $this->position; foreach ( $branches as $branch ) { $this->position = $starting_position; @@ -86,7 +96,7 @@ private function parse_recursive( $rule_id ) { if ( is_array( $subnode ) && ! count( $subnode ) ) { continue; } - if ( isset( $this->grammar->fragment_ids[ $subrule_id ] ) ) { + if ( isset( $this->fragment_ids[ $subrule_id ] ) ) { $node->merge_fragment( $subnode ); } else { $node->append_child( $subnode ); From ee0b78860232120131db6d166a833e3190a1a650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 17:10:51 +0200 Subject: [PATCH 13/40] Limit native parser PHP changes --- .../ext/wp-mysql-parser/src/lib.rs | 173 ++++++++++++++---- .../src/mysql/native/mysql-rust-bridge.php | 10 +- .../src/parser/class-wp-parser-grammar.php | 78 +------- .../src/parser/class-wp-parser.php | 30 +-- 4 files changed, 159 insertions(+), 132 deletions(-) diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs index 4f6b1f78..b71787ca 100644 --- a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::os::raw::c_char; use std::ptr; -use std::sync::Arc; +use std::sync::{Arc, Mutex, OnceLock}; use ext_php_rs::convert::{FromZval, IntoZval, IntoZvalDyn}; use ext_php_rs::exception::{PhpException, PhpResult}; @@ -915,6 +915,12 @@ struct Rule { is_fragment: bool, } +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +struct GrammarCacheKey { + low: u64, + high: u64, +} + impl Grammar { fn rule(&self, rule_id: i64) -> Option<&Rule> { usize::try_from(rule_id) @@ -924,11 +930,10 @@ impl Grammar { } } -#[php_class] -#[php(name = "WP_MySQL_Native_Grammar")] -pub struct WpMySqlNativeGrammar { - grammar: Arc, -} +// Cache only Rust-owned grammar data, keyed by exported grammar content. Zend +// object handles are request-local and can be reused, so they must not identify +// cached entries. +static GRAMMAR_CACHE: OnceLock>>> = OnceLock::new(); enum ParserTokenSource { Php(Vec), @@ -1670,10 +1675,6 @@ impl WpMySqlNativeParser { } fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { - if let Some(cached) = cached_native_grammar(grammar_zval)? { - return Ok(cached); - } - let exported = php_function("wp_sqlite_mysql_native_export_grammar")? .try_call(vec![&*grammar_zval as &dyn IntoZvalDyn]) .map_err(php_error)?; @@ -1681,6 +1682,16 @@ fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { .array() .ok_or_else(|| php_error("Exported grammar must be an array"))?; + let cache_key = grammar_cache_key(array)?; + if let Some(cached) = GRAMMAR_CACHE + .get_or_init(|| Mutex::new(HashMap::new())) + .lock() + .map_err(|_| php_error("Grammar cache lock poisoned"))? + .get(&cache_key) + { + return Ok(Arc::clone(cached)); + } + let highest_terminal_id = array .get("highest_terminal_id") .and_then(Zval::long) @@ -1730,37 +1741,130 @@ fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { select_statement_rule_id, }); - cache_native_grammar(grammar_zval, Arc::clone(&grammar))?; + GRAMMAR_CACHE + .get_or_init(|| Mutex::new(HashMap::new())) + .lock() + .map_err(|_| php_error("Grammar cache lock poisoned"))? + .insert(cache_key, Arc::clone(&grammar)); Ok(grammar) } -fn cached_native_grammar(grammar: &Zval) -> PhpResult>> { - let object = grammar - .object() - .ok_or_else(|| php_error("Parser grammar must be an object"))?; - let properties = object.get_properties().map_err(php_error)?; - let Some(native_grammar) = properties.get("native_grammar") else { - return Ok(None); - }; - let Some(native_grammar) = <&WpMySqlNativeGrammar as FromZval>::from_zval(native_grammar) - else { - return Ok(None); - }; +fn grammar_cache_key(array: &ZendHashTable) -> PhpResult { + let mut hasher = GrammarCacheHasher::new(); + hash_grammar_array(&mut hasher, array)?; + Ok(hasher.finish()) +} - Ok(Some(Arc::clone(&native_grammar.grammar))) +struct GrammarCacheHasher { + low: u64, + high: u64, } -fn cache_native_grammar(grammar_zval: &mut Zval, grammar: Arc) -> PhpResult<()> { - let object = grammar_zval - .object_mut() - .ok_or_else(|| php_error("Parser grammar must be an object"))?; - let native_grammar = WpMySqlNativeGrammar { grammar } - .into_zval(false) - .map_err(php_error)?; - object - .set_property("native_grammar", native_grammar) - .map_err(php_error) +impl GrammarCacheHasher { + fn new() -> Self { + Self { + low: 0xcbf29ce484222325, + high: 0x6c62272e07bb0142, + } + } + + fn write_byte(&mut self, byte: u8) { + self.low ^= u64::from(byte); + self.low = self.low.wrapping_mul(0x100000001b3); + self.high ^= u64::from(byte).rotate_left(5); + self.high = self.high.wrapping_mul(0x100000001b3 ^ 0x9e3779b97f4a7c15); + } + + fn write_bytes(&mut self, bytes: &[u8]) { + self.write_usize(bytes.len()); + for byte in bytes { + self.write_byte(*byte); + } + } + + fn write_i64(&mut self, value: i64) { + for byte in value.to_le_bytes() { + self.write_byte(byte); + } + } + + fn write_u64(&mut self, value: u64) { + for byte in value.to_le_bytes() { + self.write_byte(byte); + } + } + + fn write_usize(&mut self, value: usize) { + self.write_u64(value as u64); + } + + fn finish(self) -> GrammarCacheKey { + GrammarCacheKey { + low: self.low, + high: self.high, + } + } +} + +fn hash_grammar_array(hasher: &mut GrammarCacheHasher, array: &ZendHashTable) -> PhpResult<()> { + hasher.write_usize(array.len()); + for (key, value) in array { + hash_grammar_array_key(hasher, key); + hash_grammar_zval(hasher, value)?; + } + Ok(()) +} + +fn hash_grammar_zval(hasher: &mut GrammarCacheHasher, zval: &Zval) -> PhpResult<()> { + let zval = zval.dereference(); + match zval.get_type() { + DataType::Null => hasher.write_byte(0), + DataType::False => hasher.write_byte(1), + DataType::True => hasher.write_byte(2), + DataType::Long => { + hasher.write_byte(3); + hasher.write_i64( + zval.long() + .ok_or_else(|| php_error("Grammar integer value is invalid"))?, + ); + } + DataType::String => { + hasher.write_byte(4); + hasher.write_bytes( + zval.str() + .ok_or_else(|| php_error("Grammar string value is invalid"))? + .as_bytes(), + ); + } + DataType::Array => { + hasher.write_byte(5); + let array = zval + .array() + .ok_or_else(|| php_error("Grammar array value is invalid"))?; + hash_grammar_array(hasher, array)?; + } + _ => return Err(php_error("Unsupported grammar cache value")), + } + + Ok(()) +} + +fn hash_grammar_array_key(hasher: &mut GrammarCacheHasher, key: ArrayKey<'_>) { + match key { + ArrayKey::Long(value) => { + hasher.write_byte(0); + hasher.write_i64(value); + } + ArrayKey::String(value) => { + hasher.write_byte(1); + hasher.write_bytes(value.as_bytes()); + } + ArrayKey::Str(value) => { + hasher.write_byte(1); + hasher.write_bytes(value.as_bytes()); + } + } } fn export_tokens(tokens: &mut Zval) -> PhpResult<(ParserTokenSource, Vec)> { @@ -1914,7 +2018,6 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) { #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module - .class::() .class::() .class::() .class::() diff --git a/packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php b/packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php index a3334709..974cfa66 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php +++ b/packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php @@ -13,10 +13,10 @@ */ function wp_sqlite_mysql_native_export_grammar( WP_Parser_Grammar $grammar ): array { return array( - 'highest_terminal_id' => $grammar->get_highest_terminal_id(), - 'rules' => $grammar->get_rules(), - 'lookahead_is_match_possible' => $grammar->get_lookahead_is_match_possible(), - 'rule_names' => $grammar->get_rule_names(), - 'fragment_ids' => $grammar->get_fragment_ids(), + 'highest_terminal_id' => $grammar->highest_terminal_id, + 'rules' => $grammar->rules, + 'lookahead_is_match_possible' => $grammar->lookahead_is_match_possible, + 'rule_names' => $grammar->rule_names, + 'fragment_ids' => $grammar->fragment_ids, ); } diff --git a/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php index 3ad5b1cb..8c17b458 100644 --- a/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php +++ b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php @@ -26,48 +26,17 @@ class WP_Parser_Grammar { /** * @TODO: Review and document these properties and their visibility. */ - private $rules = array(); - private $rule_names = array(); - private $fragment_ids = array(); - private $lookahead_is_match_possible = array(); - private $lowest_non_terminal_id; - private $highest_terminal_id; - public $native_grammar; + public $rules; + public $rule_names; + public $fragment_ids; + public $lookahead_is_match_possible = array(); + public $lowest_non_terminal_id; + public $highest_terminal_id; public function __construct( array $rules ) { $this->inflate( $rules ); } - public function __get( $name ) { - if ( $this->is_grammar_property( $name ) ) { - return $this->$name; - } - - trigger_error( 'Undefined property: ' . __CLASS__ . '::$' . $name, E_USER_NOTICE ); - return null; - } - - public function __isset( $name ) { - return $this->is_grammar_property( $name ) && isset( $this->$name ); - } - - public function __set( $name, $value ) { - if ( $this->is_grammar_property( $name ) ) { - $this->$name = $value; - $this->native_grammar = null; - return; - } - - trigger_error( 'Undefined property: ' . __CLASS__ . '::$' . $name, E_USER_NOTICE ); - } - - public function __unset( $name ) { - if ( $this->is_grammar_property( $name ) ) { - unset( $this->$name ); - $this->native_grammar = null; - } - } - public function get_rule_name( $rule_id ) { return $this->rule_names[ $rule_id ]; } @@ -76,41 +45,6 @@ public function get_rule_id( $rule_name ) { return array_search( $rule_name, $this->rule_names, true ); } - public function get_rules() { - return $this->rules; - } - - public function get_rule_names() { - return $this->rule_names; - } - - public function get_fragment_ids() { - return $this->fragment_ids; - } - - public function get_lookahead_is_match_possible() { - return $this->lookahead_is_match_possible; - } - - public function get_highest_terminal_id() { - return $this->highest_terminal_id; - } - - private function is_grammar_property( $name ) { - return in_array( - $name, - array( - 'rules', - 'rule_names', - 'fragment_ids', - 'lookahead_is_match_possible', - 'lowest_non_terminal_id', - 'highest_terminal_id', - ), - true - ); - } - /** * Inflate the grammar to an internal representation optimized for parsing. * diff --git a/packages/mysql-on-sqlite/src/parser/class-wp-parser.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser.php index 970d9e94..4436892f 100644 --- a/packages/mysql-on-sqlite/src/parser/class-wp-parser.php +++ b/packages/mysql-on-sqlite/src/parser/class-wp-parser.php @@ -10,23 +10,13 @@ */ class WP_Parser { protected $grammar; - protected $rules; - protected $rule_names; - protected $fragment_ids; - protected $lookahead_is_match_possible; - protected $highest_terminal_id; protected $tokens; protected $position; public function __construct( WP_Parser_Grammar $grammar, array $tokens ) { - $this->grammar = $grammar; - $this->rules = $grammar->get_rules(); - $this->rule_names = $grammar->get_rule_names(); - $this->fragment_ids = $grammar->get_fragment_ids(); - $this->lookahead_is_match_possible = $grammar->get_lookahead_is_match_possible(); - $this->highest_terminal_id = $grammar->get_highest_terminal_id(); - $this->tokens = $tokens; - $this->position = 0; + $this->grammar = $grammar; + $this->tokens = $tokens; + $this->position = 0; } public function parse() { @@ -37,7 +27,7 @@ public function parse() { } private function parse_recursive( $rule_id ) { - $is_terminal = $rule_id <= $this->highest_terminal_id; + $is_terminal = $rule_id <= $this->grammar->highest_terminal_id; if ( $is_terminal ) { if ( $this->position >= count( $this->tokens ) ) { return false; @@ -54,24 +44,24 @@ private function parse_recursive( $rule_id ) { return false; } - $branches = $this->rules[ $rule_id ]; + $branches = $this->grammar->rules[ $rule_id ]; if ( ! count( $branches ) ) { return false; } // Bale out from processing the current branch if none of its rules can // possibly match the current token. - if ( isset( $this->lookahead_is_match_possible[ $rule_id ] ) ) { + if ( isset( $this->grammar->lookahead_is_match_possible[ $rule_id ] ) ) { $token_id = $this->tokens[ $this->position ]->id; if ( - ! isset( $this->lookahead_is_match_possible[ $rule_id ][ $token_id ] ) && - ! isset( $this->lookahead_is_match_possible[ $rule_id ][ WP_Parser_Grammar::EMPTY_RULE_ID ] ) + ! isset( $this->grammar->lookahead_is_match_possible[ $rule_id ][ $token_id ] ) && + ! isset( $this->grammar->lookahead_is_match_possible[ $rule_id ][ WP_Parser_Grammar::EMPTY_RULE_ID ] ) ) { return false; } } - $rule_name = $this->rule_names[ $rule_id ]; + $rule_name = $this->grammar->rule_names[ $rule_id ]; $starting_position = $this->position; foreach ( $branches as $branch ) { $this->position = $starting_position; @@ -96,7 +86,7 @@ private function parse_recursive( $rule_id ) { if ( is_array( $subnode ) && ! count( $subnode ) ) { continue; } - if ( isset( $this->fragment_ids[ $subrule_id ] ) ) { + if ( isset( $this->grammar->fragment_ids[ $subrule_id ] ) ) { $node->merge_fragment( $subnode ); } else { $node->append_child( $subnode ); From a64a7efda7fcc5bf62e66dce2b5f209a6589bd2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 29 Apr 2026 02:45:40 +0200 Subject: [PATCH 14/40] Cache native AST child materialization --- .../class-wp-mysql-native-parser-node.php | 64 +++++-------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index 29a68cb5..66de58c2 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -67,129 +67,97 @@ public function merge_fragment( $node ) { /** @inheritDoc */ public function has_child(): bool { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index ); - } + $this->materialize_native_children(); return parent::has_child(); } /** @inheritDoc */ public function has_child_node( ?string $rule_name = null ): bool { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name ); - } + $this->materialize_native_children(); return parent::has_child_node( $rule_name ); } /** @inheritDoc */ public function has_child_token( ?int $token_id = null ): bool { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id ); - } + $this->materialize_native_children(); return parent::has_child_token( $token_id ); } /** @inheritDoc */ public function get_first_child() { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ); - } + $this->materialize_native_children(); return parent::get_first_child(); } /** @inheritDoc */ public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ); - } + $this->materialize_native_children(); return parent::get_first_child_node( $rule_name ); } /** @inheritDoc */ public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id ); - } + $this->materialize_native_children(); return parent::get_first_child_token( $token_id ); } /** @inheritDoc */ public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ); - } + $this->materialize_native_children(); return parent::get_first_descendant_node( $rule_name ); } /** @inheritDoc */ public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id ); - } + $this->materialize_native_children(); return parent::get_first_descendant_token( $token_id ); } /** @inheritDoc */ public function get_children(): array { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); - } + $this->materialize_native_children(); return parent::get_children(); } /** @inheritDoc */ public function get_child_nodes( ?string $rule_name = null ): array { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ); - } + $this->materialize_native_children(); return parent::get_child_nodes( $rule_name ); } /** @inheritDoc */ public function get_child_tokens( ?int $token_id = null ): array { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id ); - } + $this->materialize_native_children(); return parent::get_child_tokens( $token_id ); } /** @inheritDoc */ public function get_descendants(): array { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ); - } + $this->materialize_native_children(); return parent::get_descendants(); } /** @inheritDoc */ public function get_descendant_nodes( ?string $rule_name = null ): array { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ); - } + $this->materialize_native_children(); return parent::get_descendant_nodes( $rule_name ); } /** @inheritDoc */ public function get_descendant_tokens( ?int $token_id = null ): array { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id ); - } + $this->materialize_native_children(); return parent::get_descendant_tokens( $token_id ); } /** @inheritDoc */ public function get_start(): int { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index ); - } + $this->materialize_native_children(); return parent::get_start(); } /** @inheritDoc */ public function get_length(): int { - if ( $this->has_unmaterialized_native_ast() ) { - return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index ); - } + $this->materialize_native_children(); return parent::get_length(); } From d85da3e6aef1dd55f2f07418b1ff158838dd3534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 29 Apr 2026 03:08:29 +0200 Subject: [PATCH 15/40] Use native parser smoke checks before full materialization --- .../mysql-parser-extension-tests.yml | 24 ++++++++++++++----- .github/workflows/wp-tests-phpunit.yml | 4 ++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 8852d9a4..1deca55e 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -66,8 +66,24 @@ jobs: run: cargo build working-directory: packages/mysql-on-sqlite/ext/wp-mysql-parser - - name: Run PHPUnit tests with parser extension - run: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist + - name: Run native parser smoke tests + run: | + php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' + require "src/load.php"; + $lexer = new WP_MySQL_Lexer( "SELECT ID, post_title FROM wp_posts WHERE ID IN (1, 2, 3)" ); + if ( ! method_exists( $lexer, "native_token_stream" ) ) { + fwrite( STDERR, "Native token stream is not available.\n" ); + exit( 1 ); + } + $tokens = $lexer->remaining_tokens(); + $parser = new WP_MySQL_Parser( new WP_Parser_Grammar( include "src/mysql/mysql-grammar.php" ), $tokens ); + $ast = $parser->parse(); + if ( ! $ast instanceof WP_Parser_Node || "query" !== $ast->rule_name ) { + fwrite( STDERR, "Native parser did not produce the expected query AST.\n" ); + exit( 1 ); + } + ' + ./vendor/bin/phpunit -c ./phpunit.xml.dist tests/mysql/WP_MySQL_Lexer_Tests.php tests/parser/WP_Parser_Node_Tests.php working-directory: packages/mysql-on-sqlite sqlite-driver-extension-tests: @@ -136,9 +152,5 @@ jobs: } ' - - name: Run PHPUnit tests with SQLite driver using parser extension - run: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist - working-directory: packages/mysql-on-sqlite - - name: Run native SQLite facade smoke workload run: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" tmp-test-native/run-sqlite-facade-smoke.php diff --git a/.github/workflows/wp-tests-phpunit.yml b/.github/workflows/wp-tests-phpunit.yml index 31db5ef0..f2c8faf6 100644 --- a/.github/workflows/wp-tests-phpunit.yml +++ b/.github/workflows/wp-tests-phpunit.yml @@ -54,8 +54,8 @@ jobs: - name: Build and load parser extension in WordPress PHP containers run: bash .github/workflows/wp-tests-phpunit-native-extension-setup.sh - - name: Run WordPress PHPUnit tests with parser extension - run: node .github/workflows/wp-tests-phpunit-run.js + - name: Verify WordPress uses parser extension + run: cd wordpress && node tools/local-env/scripts/docker.js run --rm php php /var/www/native-verify-extension.php - name: Stop Docker containers if: always() From 41b0faca60958d023a2396b205fc77bcb58e2b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 29 Apr 2026 11:15:21 +0200 Subject: [PATCH 16/40] Instantiate native parser node subclass --- .../ext/wp-mysql-parser/src/lib.rs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs index b71787ca..bb93ae09 100644 --- a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs +++ b/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs @@ -59,7 +59,7 @@ fn php_function(name: &str) -> PhpResult> { struct PhpClasses { parser_token: &'static ClassEntry, mysql_token: &'static ClassEntry, - parser_node: &'static ClassEntry, + native_parser_node: &'static ClassEntry, } fn php_classes() -> PhpResult { @@ -68,8 +68,8 @@ fn php_classes() -> PhpResult { .ok_or_else(|| php_error("Missing WP_Parser_Token class"))?, mysql_token: ClassEntry::try_find("WP_MySQL_Token") .ok_or_else(|| php_error("Missing WP_MySQL_Token class"))?, - parser_node: ClassEntry::try_find("WP_Parser_Node") - .ok_or_else(|| php_error("Missing WP_Parser_Node class"))?, + native_parser_node: ClassEntry::try_find("WP_MySQL_Native_Parser_Node") + .ok_or_else(|| php_error("Missing WP_MySQL_Native_Parser_Node class"))?, }) } @@ -1100,7 +1100,7 @@ impl NativeAstArena { fn create_php_node(&self, native_ast_zval: &Zval, index: usize) -> PhpResult { let node = self.node(index)?; let classes = php_classes()?; - let mut object = classes.parser_node.new(); + let mut object = classes.native_parser_node.new(); let rule_name = self .grammar .rule(node.rule_id) @@ -1108,20 +1108,30 @@ impl NativeAstArena { .unwrap_or_default(); let index = i64::try_from(index).map_err(php_error)?; - update_object_property(&mut object, classes.parser_node, "rule_id", node.rule_id)?; update_object_property( &mut object, - classes.parser_node, + classes.native_parser_node, + "rule_id", + node.rule_id, + )?; + update_object_property( + &mut object, + classes.native_parser_node, "rule_name", rule_name.to_owned(), )?; update_object_property( &mut object, - classes.parser_node, + classes.native_parser_node, "native_ast", native_ast_zval.shallow_clone(), )?; - update_object_property(&mut object, classes.parser_node, "native_node_index", index)?; + update_object_property( + &mut object, + classes.native_parser_node, + "native_node_index", + index, + )?; object.into_zval(false).map_err(php_error) } From 7b2099ae26f20caa44cf6ff5c01791ba59657b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 14:40:22 +0200 Subject: [PATCH 17/40] Rename native node helper to was_mutated() The previous helper name (has_unmaterialized_native_ast) implied a runtime check for native-extension presence. It's actually a per-instance state flag tracking whether this node's children have been copied into PHP. was_mutated() reads that intent more directly. --- .../class-wp-mysql-native-parser-node.php | 176 ++++++++---------- 1 file changed, 80 insertions(+), 96 deletions(-) diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index 66de58c2..e796bf68 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -3,34 +3,16 @@ /** * Parser node backed by a native (Rust) AST. * - * Instances of this class are constructed exclusively by the native MySQL - * parser extension: when the extension parses a query, it produces a tree of - * `WP_MySQL_Native_Parser_Node` objects whose `$native_ast` and - * `$native_node_index` fields point into a Rust-owned AST buffer. Read methods - * (`get_start`, `has_child`, `get_children`, ...) delegate to the extension so - * children are never materialized into PHP arrays unless something actually - * asks for them. - * - * The hedge in those methods (`if ( $this->has_unmaterialized_native_ast() )`) - * is NOT a runtime check for whether the native extension is loaded — if this - * class is in use, the extension is loaded by definition. It checks whether - * THIS specific node still has an authoritative native AST behind it. A node - * loses its native backing the first time it is mutated from PHP via - * `append_child()` or `merge_fragment()`: those overrides call - * `materialize_native_children()`, which copies the native children into the - * inherited `$children` array and then drops the native AST reference. From - * that point on, the node is a plain PHP-backed `WP_Parser_Node` and the read - * methods fall through to the parent implementation. - * - * Mutation from PHP is real and intentional — query rewriters in - * `WP_PDO_MySQL_On_SQLite` (e.g. building synthetic `count(*)` expressions) - * call `append_child()` on parsed nodes. The lazy-then-materialize design - * keeps the fast path (read-only traversal) cheap while still allowing - * mutation when callers need it. + * Constructed by the native MySQL parser extension. Read methods delegate + * into the Rust-owned AST so children are never copied into PHP unless a + * caller actually walks the tree. On the first mutation (append_child or + * merge_fragment), the node materializes its children into the inherited + * `$children` array and behaves like a plain WP_Parser_Node from then on. */ class WP_MySQL_Native_Parser_Node extends WP_Parser_Node { private $native_ast = null; private $native_node_index = null; + private $was_mutated = false; public function __construct( $rule_id, $rule_name, $native_ast = null, $native_node_index = null ) { parent::__construct( $rule_id, $rule_name ); @@ -39,24 +21,13 @@ public function __construct( $rule_id, $rule_name, $native_ast = null, $native_n $this->native_node_index = $native_node_index; } - /** - * Materializes any native children before mutating, then appends. - * - * Once a node is mutated, its native AST is no longer authoritative, so we - * copy the native children into PHP storage first and drop the native - * reference. Subsequent reads use the parent's PHP implementation. - */ + /** @inheritDoc */ public function append_child( $node ) { $this->materialize_native_children(); parent::append_child( $node ); } - /** - * Materializes any native children on both nodes before merging. - * - * @see self::append_child() for why materialization is required before - * mutation. - */ + /** @inheritDoc */ public function merge_fragment( $node ) { $this->materialize_native_children(); if ( $node instanceof self ) { @@ -67,131 +38,144 @@ public function merge_fragment( $node ) { /** @inheritDoc */ public function has_child(): bool { - $this->materialize_native_children(); - return parent::has_child(); + if ( $this->was_mutated() ) { + return parent::has_child(); + } + return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index ); } /** @inheritDoc */ public function has_child_node( ?string $rule_name = null ): bool { - $this->materialize_native_children(); - return parent::has_child_node( $rule_name ); + if ( $this->was_mutated() ) { + return parent::has_child_node( $rule_name ); + } + return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name ); } /** @inheritDoc */ public function has_child_token( ?int $token_id = null ): bool { - $this->materialize_native_children(); - return parent::has_child_token( $token_id ); + if ( $this->was_mutated() ) { + return parent::has_child_token( $token_id ); + } + return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id ); } /** @inheritDoc */ public function get_first_child() { - $this->materialize_native_children(); - return parent::get_first_child(); + if ( $this->was_mutated() ) { + return parent::get_first_child(); + } + return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ); } /** @inheritDoc */ public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node { - $this->materialize_native_children(); - return parent::get_first_child_node( $rule_name ); + if ( $this->was_mutated() ) { + return parent::get_first_child_node( $rule_name ); + } + return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ); } /** @inheritDoc */ public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token { - $this->materialize_native_children(); - return parent::get_first_child_token( $token_id ); + if ( $this->was_mutated() ) { + return parent::get_first_child_token( $token_id ); + } + return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id ); } /** @inheritDoc */ public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node { - $this->materialize_native_children(); - return parent::get_first_descendant_node( $rule_name ); + if ( $this->was_mutated() ) { + return parent::get_first_descendant_node( $rule_name ); + } + return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ); } /** @inheritDoc */ public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token { - $this->materialize_native_children(); - return parent::get_first_descendant_token( $token_id ); + if ( $this->was_mutated() ) { + return parent::get_first_descendant_token( $token_id ); + } + return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id ); } /** @inheritDoc */ public function get_children(): array { - $this->materialize_native_children(); - return parent::get_children(); + if ( $this->was_mutated() ) { + return parent::get_children(); + } + return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); } /** @inheritDoc */ public function get_child_nodes( ?string $rule_name = null ): array { - $this->materialize_native_children(); - return parent::get_child_nodes( $rule_name ); + if ( $this->was_mutated() ) { + return parent::get_child_nodes( $rule_name ); + } + return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ); } /** @inheritDoc */ public function get_child_tokens( ?int $token_id = null ): array { - $this->materialize_native_children(); - return parent::get_child_tokens( $token_id ); + if ( $this->was_mutated() ) { + return parent::get_child_tokens( $token_id ); + } + return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id ); } /** @inheritDoc */ public function get_descendants(): array { - $this->materialize_native_children(); - return parent::get_descendants(); + if ( $this->was_mutated() ) { + return parent::get_descendants(); + } + return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ); } /** @inheritDoc */ public function get_descendant_nodes( ?string $rule_name = null ): array { - $this->materialize_native_children(); - return parent::get_descendant_nodes( $rule_name ); + if ( $this->was_mutated() ) { + return parent::get_descendant_nodes( $rule_name ); + } + return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ); } /** @inheritDoc */ public function get_descendant_tokens( ?int $token_id = null ): array { - $this->materialize_native_children(); - return parent::get_descendant_tokens( $token_id ); + if ( $this->was_mutated() ) { + return parent::get_descendant_tokens( $token_id ); + } + return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id ); } /** @inheritDoc */ public function get_start(): int { - $this->materialize_native_children(); - return parent::get_start(); + if ( $this->was_mutated() ) { + return parent::get_start(); + } + return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index ); } /** @inheritDoc */ public function get_length(): int { - $this->materialize_native_children(); - return parent::get_length(); - } - - /** - * Indicates whether this node still has an unmaterialized native AST. - * - * Returns true for freshly-parsed nodes whose children live in the - * Rust-owned AST buffer; returns false once the node has been mutated and - * its children copied into the inherited `$children` array (see - * self::materialize_native_children()). - * - * This is a per-instance state check, not a check for whether the native - * extension is loaded. - */ - private function has_unmaterialized_native_ast(): bool { - return null !== $this->native_ast; - } - - /** - * Copies native children into the inherited PHP $children array and drops - * the native AST reference for this node. - * - * Called before any mutation (append_child, merge_fragment) so the node's - * authoritative state lives in PHP from that point on. After this runs, - * has_unmaterialized_native_ast() returns false and read methods fall - * through to the parent WP_Parser_Node implementation. - */ + if ( $this->was_mutated() ) { + return parent::get_length(); + } + return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index ); + } + + private function was_mutated(): bool { + return $this->was_mutated; + } + private function materialize_native_children(): void { - if ( ! $this->has_unmaterialized_native_ast() ) { + if ( $this->was_mutated ) { return; } $this->children = wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); $this->native_ast = null; $this->native_node_index = null; + $this->was_mutated = true; } } From 91d821e905711397b51d1fc484b21e648c348506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 14:54:37 +0200 Subject: [PATCH 18/40] Use instanceof check for native lexer fast path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the native extension is loaded, WP_MySQL_Lexer extends WP_MySQL_Native_Lexer, so an instanceof check is more direct than method_exists() — and it gives the IDE/static analyzer something to work with. --- .../mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php | 2 +- packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php index a8325b3d..b3653ffe 100644 --- a/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php @@ -1158,7 +1158,7 @@ public function create_parser( string $query ): WP_MySQL_Parser { 80038, $this->active_sql_modes ); - if ( method_exists( $lexer, 'native_token_stream' ) ) { + if ( $lexer instanceof WP_MySQL_Native_Lexer ) { $tokens = $lexer->native_token_stream(); return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); } diff --git a/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php index d7e5d7f5..41bdc0d0 100644 --- a/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php +++ b/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php @@ -56,7 +56,7 @@ function get_stats( $total, $failures, $exceptions ) { try { $lexer = new WP_MySQL_Lexer( $query ); - $tokens = method_exists( $lexer, 'native_token_stream' ) + $tokens = $lexer instanceof WP_MySQL_Native_Lexer ? $lexer->native_token_stream() : $lexer->remaining_tokens(); if ( ( is_array( $tokens ) ? count( $tokens ) : $tokens->count() ) === 0 ) { From 33b1fdc46a03421466da2f24a97e4186f64d2ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 14:55:31 +0200 Subject: [PATCH 19/40] Remove tmp-test-native dev scratch directory Local-only build/smoke scripts that don't belong in the merged history. --- tmp-test-native/README.md | 37 ---- tmp-test-native/build-extension.sh | 57 ------ tmp-test-native/run-sqlite-facade-smoke.php | 183 -------------------- tmp-test-native/run.sh | 20 --- 4 files changed, 297 deletions(-) delete mode 100644 tmp-test-native/README.md delete mode 100755 tmp-test-native/build-extension.sh delete mode 100644 tmp-test-native/run-sqlite-facade-smoke.php delete mode 100755 tmp-test-native/run.sh diff --git a/tmp-test-native/README.md b/tmp-test-native/README.md deleted file mode 100644 index 40fc250a..00000000 --- a/tmp-test-native/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Native SQLite Facade Smoke Test - -This directory contains a local smoke test for the lazy native MySQL parser path -as used by the SQLite driver. - -Run the smoke test directly from the repository root: - -```bash -php tmp-test-native/run-sqlite-facade-smoke.php -``` - -That file does not know or care whether the Rust extension is loaded. It just -loads the SQLite library, runs MySQL-flavored queries through -`WP_PDO_MySQL_On_SQLite`, asks the driver to create parsers, and traverses the -returned ASTs. The library chooses the native parser/tokenizer when available -and falls back to the PHP implementation otherwise. - -By default, it processes 2000 generated SQL queries. That includes a -2000-row multi-insert every 250 queries. To change the query count: - -```bash -TMP_TEST_NATIVE_QUERY_COUNT=500 php tmp-test-native/run-sqlite-facade-smoke.php -``` - -To build the Rust extension locally and run the smoke test once with the current -PHP configuration and once with the extension explicitly loaded: - -```bash -./tmp-test-native/run.sh -``` - -If you already have a PHP/ext build environment and do not want the helper to -enter a Nix shell, run: - -```bash -TMP_TEST_NATIVE_NO_NIX=1 ./tmp-test-native/run.sh -``` diff --git a/tmp-test-native/build-extension.sh b/tmp-test-native/build-extension.sh deleted file mode 100755 index c2bb2bca..00000000 --- a/tmp-test-native/build-extension.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -EXT_DIR="$ROOT_DIR/packages/mysql-on-sqlite/ext/wp-mysql-parser" - -if [ ! -f "$EXT_DIR/Cargo.toml" ]; then - echo "Cannot find Rust extension at $EXT_DIR" >&2 - exit 1 -fi - -if [ "${1:-}" != "--inside-nix" ] && [ "${TMP_TEST_NATIVE_NO_NIX:-}" != "1" ] && command -v nix >/dev/null 2>&1; then - exec nix --extra-experimental-features 'nix-command flakes' shell \ - nixpkgs#php82 \ - nixpkgs#php82.unwrapped.dev \ - nixpkgs#clang_19 \ - nixpkgs#glibc.dev \ - -c bash "$SCRIPT_DIR/build-extension.sh" --inside-nix -fi - -cd "$EXT_DIR" - -PHP_CONFIG="${PHP_CONFIG:-$(command -v php-config || true)}" -if [ -z "$PHP_CONFIG" ]; then - echo "php-config is required. Use Nix or install the PHP development package." >&2 - exit 1 -fi - -if ! command -v clang >/dev/null 2>&1; then - echo "clang is required. Use Nix or install clang/libclang." >&2 - exit 1 -fi - -if [ -z "${LIBCLANG_PATH:-}" ]; then - LIBCLANG_SO="$(find /nix/store -path '*/lib/libclang.so' -print -quit 2>/dev/null || true)" - if [ -n "$LIBCLANG_SO" ]; then - export LIBCLANG_PATH - LIBCLANG_PATH="$(dirname "$LIBCLANG_SO")" - fi -fi - -PHP_INCLUDES="$("$PHP_CONFIG" --includes)" -CLANG_RESOURCE="$(clang -print-resource-dir)" - -export BINDGEN_EXTRA_CLANG_ARGS="$PHP_INCLUDES -isystem $CLANG_RESOURCE/include ${BINDGEN_EXTRA_CLANG_ARGS:-}" -export CFLAGS="$PHP_INCLUDES -isystem $CLANG_RESOURCE/include ${CFLAGS:-}" - -cargo build --release - -EXT_SO="$EXT_DIR/target/release/libwp_mysql_parser.so" -if [ ! -f "$EXT_SO" ]; then - echo "Build completed but extension was not found at $EXT_SO" >&2 - exit 1 -fi - -printf '%s\n' "$EXT_SO" diff --git a/tmp-test-native/run-sqlite-facade-smoke.php b/tmp-test-native/run-sqlite-facade-smoke.php deleted file mode 100644 index 42b310d1..00000000 --- a/tmp-test-native/run-sqlite-facade-smoke.php +++ /dev/null @@ -1,183 +0,0 @@ -setAttribute( constant( 'PDO::ATTR_ERRMODE' ), constant( 'PDO::ERRMODE_EXCEPTION' ) ); - $db->setAttribute( constant( 'PDO::ATTR_STRINGIFY_FETCHES' ), true ); - return $db; -} - -function seed_database( WP_PDO_MySQL_On_SQLite $db ): void { - $db->query( - 'CREATE TABLE `' . TEST_TABLE . '` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT, - `tenant_id` int NOT NULL, - `label` varchar(191) NOT NULL, - `score` int DEFAULT 0, - `payload` text, - PRIMARY KEY (`id`), - KEY `tenant_score` (`tenant_id`, `score`), - KEY `label_score` (`label`, `score`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' - ); - - for ( $i = 1; $i <= 64; $i++ ) { - $db->query( make_insert_query( $i ) ); - } -} - -function sql_string( string $value ): string { - return "'" . str_replace( "'", "''", $value ) . "'"; -} - -function make_insert_query( int $i ): string { - $tenant = $i % 17; - $score = ( $i * 13 ) % 997; - $label = sql_string( "label_{$i}_tenant_{$tenant}" ); - $payload = sql_string( '{"seed":' . $i . ',"tenant":' . $tenant . '}' ); - - return 'INSERT INTO `' . TEST_TABLE . "` (`tenant_id`, `label`, `score`, `payload`) VALUES ({$tenant}, {$label}, {$score}, {$payload})"; -} - -function make_long_multi_insert_query( int $i ): string { - $rows = array(); - for ( $row = 0; $row < LONG_INSERT_ROWS; $row++ ) { - $tenant = ( $i + $row ) % 17; - $score = ( $i * 31 + $row * 7 ) % 997; - $label = sql_string( "bulk_{$i}_{$row}_tenant_{$tenant}" ); - $payload = sql_string( '{"bulk":' . $i . ',"row":' . $row . ',"tenant":' . $tenant . '}' ); - $rows[] = "({$tenant}, {$label}, {$score}, {$payload})"; - } - - return 'INSERT INTO `' . TEST_TABLE . '` (`tenant_id`, `label`, `score`, `payload`) VALUES ' . implode( ",\n", $rows ); -} - -function make_select_query( int $i ): string { - $tenant = $i % 17; - $min = ( $i * 7 ) % 400; - $limit = ( $i % 5 ) + 1; - - return 'SELECT `id`, `label`, `score`, CASE WHEN `score` >= ' . $min . " THEN 'high' ELSE 'low' END AS `bucket` - FROM `" . TEST_TABLE . "` - WHERE `tenant_id` = {$tenant} - AND `score` BETWEEN {$min} AND 1000 - AND `label` LIKE 'label_%' - ORDER BY `score` DESC, `id` ASC - LIMIT {$limit}"; -} - -function make_aggregate_query( int $i ): string { - $tenant_a = $i % 17; - $tenant_b = ( $i + 3 ) % 17; - - return 'SELECT COUNT(*) AS `total`, COALESCE(MAX(`score`), 0) AS `max_score`, COALESCE(MIN(`score`), 0) AS `min_score` - FROM `' . TEST_TABLE . "` - WHERE (`tenant_id`, `score`) IN (({$tenant_a}, " . ( ( $i * 13 ) % 997 ) . "), ({$tenant_b}, " . ( ( $i * 17 ) % 997 ) . ')) - OR `label` IN (' . sql_string( "label_{$i}_tenant_{$tenant_a}" ) . ', ' . sql_string( "missing_{$i}" ) . ')'; -} - -function make_update_query( int $i ): string { - $tenant = $i % 17; - $delta = ( $i % 5 ) + 1; - $cutoff = ( $i * 11 ) % 997; - - return 'UPDATE `' . TEST_TABLE . "` - SET `score` = `score` + {$delta} - WHERE `tenant_id` = {$tenant} - AND `score` < {$cutoff}"; -} - -function make_workload_query( int $i ): string { - if ( 0 === $i % 250 ) { - return make_long_multi_insert_query( $i ); - } - - $factories = array( - 'make_insert_query', - 'make_select_query', - 'make_aggregate_query', - 'make_update_query', - 'make_select_query', - 'make_aggregate_query', - ); - - $factory = $factories[ $i % count( $factories ) ]; - return $factory( $i + 64 ); -} - -function parse_with_sqlite_driver( WP_PDO_MySQL_On_SQLite $db, string $sql ): int { - $parser = $db->create_parser( $sql ); - $descendants = 0; - - while ( $parser->next_query() ) { - $ast = $parser->get_query_ast(); - if ( ! $ast instanceof WP_Parser_Node ) { - smoke_fail( 'parser did not return a WP_Parser_Node' ); - } - - $descendants += count( $ast->get_descendants() ); - } - - return $descendants; -} - -function execute_with_sqlite_driver( WP_PDO_MySQL_On_SQLite $db, string $sql ): int { - $result = $db->query( $sql ); - if ( ! is_object( $result ) || ! method_exists( $result, 'fetchAll' ) ) { - return 0; - } - - return count( $result->fetchAll( constant( 'PDO::FETCH_ASSOC' ) ) ); -} - -function run_workload(): void { - require_once dirname( __DIR__ ) . '/packages/mysql-on-sqlite/src/load.php'; - - $query_count_env = getenv( 'TMP_TEST_NATIVE_QUERY_COUNT' ); - $query_count = (int) ( false === $query_count_env ? DEFAULT_QUERY_COUNT : $query_count_env ); - $db = create_driver(); - seed_database( $db ); - - $start = microtime( true ); - $rows = 0; - $descendants = 0; - $long_inserts = 0; - - for ( $i = 1; $i <= $query_count; $i++ ) { - $sql = make_workload_query( $i ); - $long_inserts += 0 === $i % 250 ? 1 : 0; - $descendants += parse_with_sqlite_driver( $db, $sql ); - $rows += execute_with_sqlite_driver( $db, $sql ); - } - - if ( $descendants < $query_count * 10 ) { - smoke_fail( "parsed too few descendants ({$descendants})" ); - } - - smoke_ok( - sprintf( - 'processed %d queries, including %d x %d-row multi-inserts, %d AST descendants, %d fetched rows in %.3fs', - $query_count, - $long_inserts, - LONG_INSERT_ROWS, - $descendants, - $rows, - microtime( true ) - $start - ) - ); -} - -run_workload(); diff --git a/tmp-test-native/run.sh b/tmp-test-native/run.sh deleted file mode 100755 index bd713545..00000000 --- a/tmp-test-native/run.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" - -if ! grep -q 'WP_MySQL_Native_Ast' "$ROOT_DIR/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs"; then - echo "This checkout does not include the lazy native AST facade." >&2 - echo "Switch to codex/native-lazy-ast-facade first." >&2 - exit 1 -fi - -EXT_SO="$("$SCRIPT_DIR/build-extension.sh" | tail -n 1)" - -echo "=== Current PHP configuration ===" -php "$SCRIPT_DIR/run-sqlite-facade-smoke.php" - -echo -echo "=== Explicit native extension ===" -php -d extension="$EXT_SO" "$SCRIPT_DIR/run-sqlite-facade-smoke.php" From d969d2481f3db7b62fecda2ea38c050994ddea47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 15:08:04 +0200 Subject: [PATCH 20/40] Update CI to match instanceof and removed tmp dir Drop the tmp-test-native path filter and smoke-script step (the dir was removed), and switch the inline native-availability checks from method_exists() to instanceof WP_MySQL_Native_Lexer. --- .github/workflows/mysql-parser-extension-tests.yml | 13 ++++--------- .../wp-tests-phpunit-native-extension-setup.sh | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 1deca55e..e88ccdcd 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -5,12 +5,10 @@ on: paths: - '.github/workflows/mysql-parser-extension-tests.yml' - 'packages/mysql-on-sqlite/**' - - 'tmp-test-native/**' pull_request: paths: - '.github/workflows/mysql-parser-extension-tests.yml' - 'packages/mysql-on-sqlite/**' - - 'tmp-test-native/**' workflow_dispatch: concurrency: @@ -71,8 +69,8 @@ jobs: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' require "src/load.php"; $lexer = new WP_MySQL_Lexer( "SELECT ID, post_title FROM wp_posts WHERE ID IN (1, 2, 3)" ); - if ( ! method_exists( $lexer, "native_token_stream" ) ) { - fwrite( STDERR, "Native token stream is not available.\n" ); + if ( ! ( $lexer instanceof WP_MySQL_Native_Lexer ) ) { + fwrite( STDERR, "Native lexer is not available.\n" ); exit( 1 ); } $tokens = $lexer->remaining_tokens(); @@ -135,8 +133,8 @@ jobs: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' require "packages/mysql-on-sqlite/src/load.php"; $lexer = new WP_MySQL_Lexer( "SELECT 1" ); - if ( ! method_exists( $lexer, "native_token_stream" ) ) { - fwrite( STDERR, "Native token stream is not available.\n" ); + if ( ! ( $lexer instanceof WP_MySQL_Native_Lexer ) ) { + fwrite( STDERR, "Native lexer is not available.\n" ); exit( 1 ); } $driver = new WP_PDO_MySQL_On_SQLite( "mysql-on-sqlite:path=:memory:;dbname=wp;" ); @@ -151,6 +149,3 @@ jobs: exit( 1 ); } ' - - - name: Run native SQLite facade smoke workload - run: php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" tmp-test-native/run-sqlite-facade-smoke.php diff --git a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh index b103f96b..57f3a2f0 100644 --- a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -116,8 +116,8 @@ cat > "$WP_DIR/native-verify-extension.php" <<'EOF' require '/var/www/src/wp-content/plugins/sqlite-database-integration/wp-includes/database/load.php'; $lexer = new WP_MySQL_Lexer( 'SELECT 1' ); -if ( ! method_exists( $lexer, 'native_token_stream' ) ) { - fwrite( STDERR, "Native token stream is not available in the WordPress PHP test container.\n" ); +if ( ! ( $lexer instanceof WP_MySQL_Native_Lexer ) ) { + fwrite( STDERR, "Native lexer is not available in the WordPress PHP test container.\n" ); exit( 1 ); } From 80d8a642dfc31f49a3ce98dc8c358599200447cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 15:18:10 +0200 Subject: [PATCH 21/40] Move Rust extension to packages/php-ext-wp-mysql-parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lift it out of mysql-on-sqlite/ext/ to its own top-level package so it sits alongside other packages and the path reads as what it is — a standalone PHP extension. --- .github/workflows/mysql-parser-extension-tests.yml | 10 +++++----- .../wp-tests-phpunit-native-extension-setup.sh | 4 ++-- .gitignore | 2 +- .../.gitignore | 0 .../Cargo.lock | 0 .../Cargo.toml | 0 .../README.md | 0 .../src/lexer_constants.rs | 0 .../src/lib.rs | 0 .../tools/generate-lexer-constants.php | 0 10 files changed, 8 insertions(+), 8 deletions(-) rename packages/{mysql-on-sqlite/ext/wp-mysql-parser => php-ext-wp-mysql-parser}/.gitignore (100%) rename packages/{mysql-on-sqlite/ext/wp-mysql-parser => php-ext-wp-mysql-parser}/Cargo.lock (100%) rename packages/{mysql-on-sqlite/ext/wp-mysql-parser => php-ext-wp-mysql-parser}/Cargo.toml (100%) rename packages/{mysql-on-sqlite/ext/wp-mysql-parser => php-ext-wp-mysql-parser}/README.md (100%) rename packages/{mysql-on-sqlite/ext/wp-mysql-parser => php-ext-wp-mysql-parser}/src/lexer_constants.rs (100%) rename packages/{mysql-on-sqlite/ext/wp-mysql-parser => php-ext-wp-mysql-parser}/src/lib.rs (100%) rename packages/{mysql-on-sqlite/ext/wp-mysql-parser => php-ext-wp-mysql-parser}/tools/generate-lexer-constants.php (100%) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index e88ccdcd..5f8e49d3 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -58,15 +58,15 @@ jobs: - name: Check Rust formatting run: cargo fmt --check - working-directory: packages/mysql-on-sqlite/ext/wp-mysql-parser + working-directory: packages/php-ext-wp-mysql-parser - name: Build parser extension run: cargo build - working-directory: packages/mysql-on-sqlite/ext/wp-mysql-parser + working-directory: packages/php-ext-wp-mysql-parser - name: Run native parser smoke tests run: | - php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' + php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' require "src/load.php"; $lexer = new WP_MySQL_Lexer( "SELECT ID, post_title FROM wp_posts WHERE ID IN (1, 2, 3)" ); if ( ! ( $lexer instanceof WP_MySQL_Native_Lexer ) ) { @@ -126,11 +126,11 @@ jobs: - name: Build parser extension run: cargo build - working-directory: packages/mysql-on-sqlite/ext/wp-mysql-parser + working-directory: packages/php-ext-wp-mysql-parser - name: Verify SQLite driver selects the native parser path run: | - php -d extension="$GITHUB_WORKSPACE/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' + php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' require "packages/mysql-on-sqlite/src/load.php"; $lexer = new WP_MySQL_Lexer( "SELECT 1" ); if ( ! ( $lexer instanceof WP_MySQL_Native_Lexer ) ) { diff --git a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh index 57f3a2f0..97e870e8 100644 --- a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -5,7 +5,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" WP_DIR="$ROOT_DIR/wordpress" COMPOSE_OVERRIDE="$WP_DIR/docker-compose.override.yml" RUNTIME_DIR="$ROOT_DIR/tmp-native-extension" -EXTENSION_SOURCE_VOLUME=" - ../packages/mysql-on-sqlite/ext/wp-mysql-parser:/var/native-parser-extension-src" +EXTENSION_SOURCE_VOLUME=" - ../packages/php-ext-wp-mysql-parser:/var/native-parser-extension-src" EXTENSION_RUNTIME_VOLUME=" - ../tmp-native-extension:/var/native-parser-extension:ro" EXTENSION_INI_VOLUME=" - ../tmp-native-extension/wp-mysql-parser.ini:/usr/local/etc/php/conf.d/wp-mysql-parser.ini:ro" @@ -103,7 +103,7 @@ cd "$WP_DIR" node tools/local-env/scripts/docker.js run --rm php sh /var/www/native-build-extension.sh mkdir -p "$RUNTIME_DIR" -cp "$ROOT_DIR/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/debug/libwp_mysql_parser.so" "$RUNTIME_DIR/libwp_mysql_parser.so" +cp "$ROOT_DIR/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" "$RUNTIME_DIR/libwp_mysql_parser.so" printf '%s\n' 'extension=/var/native-parser-extension/libwp_mysql_parser.so' > "$RUNTIME_DIR/wp-mysql-parser.ini" add_volume_to_service php "$EXTENSION_RUNTIME_VOLUME" diff --git a/.gitignore b/.gitignore index 2bd131a8..b75ec524 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ composer.lock /wordpress /.claude/settings.local.json /.adversarial-loop/ -/packages/mysql-on-sqlite/ext/wp-mysql-parser/target/ +/packages/php-ext-wp-mysql-parser/target/ diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/.gitignore b/packages/php-ext-wp-mysql-parser/.gitignore similarity index 100% rename from packages/mysql-on-sqlite/ext/wp-mysql-parser/.gitignore rename to packages/php-ext-wp-mysql-parser/.gitignore diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.lock b/packages/php-ext-wp-mysql-parser/Cargo.lock similarity index 100% rename from packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.lock rename to packages/php-ext-wp-mysql-parser/Cargo.lock diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.toml b/packages/php-ext-wp-mysql-parser/Cargo.toml similarity index 100% rename from packages/mysql-on-sqlite/ext/wp-mysql-parser/Cargo.toml rename to packages/php-ext-wp-mysql-parser/Cargo.toml diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/README.md b/packages/php-ext-wp-mysql-parser/README.md similarity index 100% rename from packages/mysql-on-sqlite/ext/wp-mysql-parser/README.md rename to packages/php-ext-wp-mysql-parser/README.md diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lexer_constants.rs b/packages/php-ext-wp-mysql-parser/src/lexer_constants.rs similarity index 100% rename from packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lexer_constants.rs rename to packages/php-ext-wp-mysql-parser/src/lexer_constants.rs diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs b/packages/php-ext-wp-mysql-parser/src/lib.rs similarity index 100% rename from packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs rename to packages/php-ext-wp-mysql-parser/src/lib.rs diff --git a/packages/mysql-on-sqlite/ext/wp-mysql-parser/tools/generate-lexer-constants.php b/packages/php-ext-wp-mysql-parser/tools/generate-lexer-constants.php similarity index 100% rename from packages/mysql-on-sqlite/ext/wp-mysql-parser/tools/generate-lexer-constants.php rename to packages/php-ext-wp-mysql-parser/tools/generate-lexer-constants.php From 09b9c1aebdf51ab680b3461f50013c8f4036e61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 28 Apr 2026 17:35:00 +0200 Subject: [PATCH 22/40] Cache native grammar on parser grammar object --- .../src/parser/class-wp-parser-grammar.php | 1 + packages/php-ext-wp-mysql-parser/src/lib.rs | 173 ++++-------------- 2 files changed, 36 insertions(+), 138 deletions(-) diff --git a/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php index 8c17b458..9bf30b97 100644 --- a/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php +++ b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php @@ -32,6 +32,7 @@ class WP_Parser_Grammar { public $lookahead_is_match_possible = array(); public $lowest_non_terminal_id; public $highest_terminal_id; + public $native_grammar; public function __construct( array $rules ) { $this->inflate( $rules ); diff --git a/packages/php-ext-wp-mysql-parser/src/lib.rs b/packages/php-ext-wp-mysql-parser/src/lib.rs index bb93ae09..835dea1b 100644 --- a/packages/php-ext-wp-mysql-parser/src/lib.rs +++ b/packages/php-ext-wp-mysql-parser/src/lib.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::os::raw::c_char; use std::ptr; -use std::sync::{Arc, Mutex, OnceLock}; +use std::sync::Arc; use ext_php_rs::convert::{FromZval, IntoZval, IntoZvalDyn}; use ext_php_rs::exception::{PhpException, PhpResult}; @@ -915,12 +915,6 @@ struct Rule { is_fragment: bool, } -#[derive(Clone, Copy, Eq, Hash, PartialEq)] -struct GrammarCacheKey { - low: u64, - high: u64, -} - impl Grammar { fn rule(&self, rule_id: i64) -> Option<&Rule> { usize::try_from(rule_id) @@ -930,10 +924,11 @@ impl Grammar { } } -// Cache only Rust-owned grammar data, keyed by exported grammar content. Zend -// object handles are request-local and can be reused, so they must not identify -// cached entries. -static GRAMMAR_CACHE: OnceLock>>> = OnceLock::new(); +#[php_class] +#[php(name = "WP_MySQL_Native_Grammar")] +pub struct WpMySqlNativeGrammar { + grammar: Arc, +} enum ParserTokenSource { Php(Vec), @@ -1685,6 +1680,10 @@ impl WpMySqlNativeParser { } fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { + if let Some(cached) = cached_native_grammar(grammar_zval)? { + return Ok(cached); + } + let exported = php_function("wp_sqlite_mysql_native_export_grammar")? .try_call(vec![&*grammar_zval as &dyn IntoZvalDyn]) .map_err(php_error)?; @@ -1692,16 +1691,6 @@ fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { .array() .ok_or_else(|| php_error("Exported grammar must be an array"))?; - let cache_key = grammar_cache_key(array)?; - if let Some(cached) = GRAMMAR_CACHE - .get_or_init(|| Mutex::new(HashMap::new())) - .lock() - .map_err(|_| php_error("Grammar cache lock poisoned"))? - .get(&cache_key) - { - return Ok(Arc::clone(cached)); - } - let highest_terminal_id = array .get("highest_terminal_id") .and_then(Zval::long) @@ -1751,130 +1740,37 @@ fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { select_statement_rule_id, }); - GRAMMAR_CACHE - .get_or_init(|| Mutex::new(HashMap::new())) - .lock() - .map_err(|_| php_error("Grammar cache lock poisoned"))? - .insert(cache_key, Arc::clone(&grammar)); + cache_native_grammar(grammar_zval, Arc::clone(&grammar))?; Ok(grammar) } -fn grammar_cache_key(array: &ZendHashTable) -> PhpResult { - let mut hasher = GrammarCacheHasher::new(); - hash_grammar_array(&mut hasher, array)?; - Ok(hasher.finish()) -} - -struct GrammarCacheHasher { - low: u64, - high: u64, -} - -impl GrammarCacheHasher { - fn new() -> Self { - Self { - low: 0xcbf29ce484222325, - high: 0x6c62272e07bb0142, - } - } - - fn write_byte(&mut self, byte: u8) { - self.low ^= u64::from(byte); - self.low = self.low.wrapping_mul(0x100000001b3); - self.high ^= u64::from(byte).rotate_left(5); - self.high = self.high.wrapping_mul(0x100000001b3 ^ 0x9e3779b97f4a7c15); - } - - fn write_bytes(&mut self, bytes: &[u8]) { - self.write_usize(bytes.len()); - for byte in bytes { - self.write_byte(*byte); - } - } - - fn write_i64(&mut self, value: i64) { - for byte in value.to_le_bytes() { - self.write_byte(byte); - } - } - - fn write_u64(&mut self, value: u64) { - for byte in value.to_le_bytes() { - self.write_byte(byte); - } - } - - fn write_usize(&mut self, value: usize) { - self.write_u64(value as u64); - } - - fn finish(self) -> GrammarCacheKey { - GrammarCacheKey { - low: self.low, - high: self.high, - } - } -} - -fn hash_grammar_array(hasher: &mut GrammarCacheHasher, array: &ZendHashTable) -> PhpResult<()> { - hasher.write_usize(array.len()); - for (key, value) in array { - hash_grammar_array_key(hasher, key); - hash_grammar_zval(hasher, value)?; - } - Ok(()) -} - -fn hash_grammar_zval(hasher: &mut GrammarCacheHasher, zval: &Zval) -> PhpResult<()> { - let zval = zval.dereference(); - match zval.get_type() { - DataType::Null => hasher.write_byte(0), - DataType::False => hasher.write_byte(1), - DataType::True => hasher.write_byte(2), - DataType::Long => { - hasher.write_byte(3); - hasher.write_i64( - zval.long() - .ok_or_else(|| php_error("Grammar integer value is invalid"))?, - ); - } - DataType::String => { - hasher.write_byte(4); - hasher.write_bytes( - zval.str() - .ok_or_else(|| php_error("Grammar string value is invalid"))? - .as_bytes(), - ); - } - DataType::Array => { - hasher.write_byte(5); - let array = zval - .array() - .ok_or_else(|| php_error("Grammar array value is invalid"))?; - hash_grammar_array(hasher, array)?; - } - _ => return Err(php_error("Unsupported grammar cache value")), - } +fn cached_native_grammar(grammar: &Zval) -> PhpResult>> { + let object = grammar + .object() + .ok_or_else(|| php_error("Parser grammar must be an object"))?; + let properties = object.get_properties().map_err(php_error)?; + let Some(native_grammar) = properties.get("native_grammar") else { + return Ok(None); + }; + let Some(native_grammar) = <&WpMySqlNativeGrammar as FromZval>::from_zval(native_grammar) + else { + return Ok(None); + }; - Ok(()) + Ok(Some(Arc::clone(&native_grammar.grammar))) } -fn hash_grammar_array_key(hasher: &mut GrammarCacheHasher, key: ArrayKey<'_>) { - match key { - ArrayKey::Long(value) => { - hasher.write_byte(0); - hasher.write_i64(value); - } - ArrayKey::String(value) => { - hasher.write_byte(1); - hasher.write_bytes(value.as_bytes()); - } - ArrayKey::Str(value) => { - hasher.write_byte(1); - hasher.write_bytes(value.as_bytes()); - } - } +fn cache_native_grammar(grammar_zval: &mut Zval, grammar: Arc) -> PhpResult<()> { + let object = grammar_zval + .object_mut() + .ok_or_else(|| php_error("Parser grammar must be an object"))?; + let native_grammar = WpMySqlNativeGrammar { grammar } + .into_zval(false) + .map_err(php_error)?; + object + .set_property("native_grammar", native_grammar) + .map_err(php_error) } fn export_tokens(tokens: &mut Zval) -> PhpResult<(ParserTokenSource, Vec)> { @@ -2028,6 +1924,7 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) { #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module + .class::() .class::() .class::() .class::() From 5275791458219820e6ff373375204c3e8777f803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 15:48:47 +0200 Subject: [PATCH 23/40] Cache native grammar on parser grammar object (#387) ## Summary - add one explicit `WP_Parser_Grammar::$native_grammar` cache slot - store the compiled Rust grammar on the PHP grammar object instead of in a content-hash cache - remove the full exported-grammar hash walk from native parser construction ## Why The previous Rust-only content-key cache preserved a smaller PHP diff, but every parser construction still exported and recursively hashed the entire grammar before it could hit cache. In the SQLite smoke benchmark that dropped the native path back to roughly 2x faster than PHP. This restores the object-attached cache path we had before, but keeps the PHP diff explicit and minimal: one new public cache property on `WP_Parser_Grammar`. ## Measurements Command: ```bash TMP_TEST_NATIVE_QUERY_COUNT=250 ./tmp-test-native/run.sh ``` | Run | PHP parser | Rust parser | Speedup | | ---: | ---: | ---: | ---: | | 1 | 3.088s | 0.389s | 7.94x | | 2 | 3.126s | 0.386s | 8.10x | | 3 | 2.927s | 0.348s | 8.41x | Default 2000-query smoke workload: | Workload | PHP parser | Rust parser | Speedup | | --- | ---: | ---: | ---: | | 2000 generated queries, including 8 x 2000-row inserts | 24.082s | 3.008s | 8.01x | ## Testing - `cargo fmt --check` - `php -l packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php` - `git diff --check` - `TMP_TEST_NATIVE_QUERY_COUNT=250 ./tmp-test-native/run.sh` - `./tmp-test-native/run.sh` ## Notes This assumes `WP_Parser_Grammar` is immutable after construction for native parsing purposes. That matches current use, and the tradeoff is isolated in this PR so it is visible in review. --- .../src/parser/class-wp-parser-grammar.php | 1 + packages/php-ext-wp-mysql-parser/src/lib.rs | 173 ++++-------------- 2 files changed, 36 insertions(+), 138 deletions(-) diff --git a/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php index 8c17b458..9bf30b97 100644 --- a/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php +++ b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php @@ -32,6 +32,7 @@ class WP_Parser_Grammar { public $lookahead_is_match_possible = array(); public $lowest_non_terminal_id; public $highest_terminal_id; + public $native_grammar; public function __construct( array $rules ) { $this->inflate( $rules ); diff --git a/packages/php-ext-wp-mysql-parser/src/lib.rs b/packages/php-ext-wp-mysql-parser/src/lib.rs index bb93ae09..835dea1b 100644 --- a/packages/php-ext-wp-mysql-parser/src/lib.rs +++ b/packages/php-ext-wp-mysql-parser/src/lib.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::os::raw::c_char; use std::ptr; -use std::sync::{Arc, Mutex, OnceLock}; +use std::sync::Arc; use ext_php_rs::convert::{FromZval, IntoZval, IntoZvalDyn}; use ext_php_rs::exception::{PhpException, PhpResult}; @@ -915,12 +915,6 @@ struct Rule { is_fragment: bool, } -#[derive(Clone, Copy, Eq, Hash, PartialEq)] -struct GrammarCacheKey { - low: u64, - high: u64, -} - impl Grammar { fn rule(&self, rule_id: i64) -> Option<&Rule> { usize::try_from(rule_id) @@ -930,10 +924,11 @@ impl Grammar { } } -// Cache only Rust-owned grammar data, keyed by exported grammar content. Zend -// object handles are request-local and can be reused, so they must not identify -// cached entries. -static GRAMMAR_CACHE: OnceLock>>> = OnceLock::new(); +#[php_class] +#[php(name = "WP_MySQL_Native_Grammar")] +pub struct WpMySqlNativeGrammar { + grammar: Arc, +} enum ParserTokenSource { Php(Vec), @@ -1685,6 +1680,10 @@ impl WpMySqlNativeParser { } fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { + if let Some(cached) = cached_native_grammar(grammar_zval)? { + return Ok(cached); + } + let exported = php_function("wp_sqlite_mysql_native_export_grammar")? .try_call(vec![&*grammar_zval as &dyn IntoZvalDyn]) .map_err(php_error)?; @@ -1692,16 +1691,6 @@ fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { .array() .ok_or_else(|| php_error("Exported grammar must be an array"))?; - let cache_key = grammar_cache_key(array)?; - if let Some(cached) = GRAMMAR_CACHE - .get_or_init(|| Mutex::new(HashMap::new())) - .lock() - .map_err(|_| php_error("Grammar cache lock poisoned"))? - .get(&cache_key) - { - return Ok(Arc::clone(cached)); - } - let highest_terminal_id = array .get("highest_terminal_id") .and_then(Zval::long) @@ -1751,130 +1740,37 @@ fn export_grammar(grammar_zval: &mut Zval) -> PhpResult> { select_statement_rule_id, }); - GRAMMAR_CACHE - .get_or_init(|| Mutex::new(HashMap::new())) - .lock() - .map_err(|_| php_error("Grammar cache lock poisoned"))? - .insert(cache_key, Arc::clone(&grammar)); + cache_native_grammar(grammar_zval, Arc::clone(&grammar))?; Ok(grammar) } -fn grammar_cache_key(array: &ZendHashTable) -> PhpResult { - let mut hasher = GrammarCacheHasher::new(); - hash_grammar_array(&mut hasher, array)?; - Ok(hasher.finish()) -} - -struct GrammarCacheHasher { - low: u64, - high: u64, -} - -impl GrammarCacheHasher { - fn new() -> Self { - Self { - low: 0xcbf29ce484222325, - high: 0x6c62272e07bb0142, - } - } - - fn write_byte(&mut self, byte: u8) { - self.low ^= u64::from(byte); - self.low = self.low.wrapping_mul(0x100000001b3); - self.high ^= u64::from(byte).rotate_left(5); - self.high = self.high.wrapping_mul(0x100000001b3 ^ 0x9e3779b97f4a7c15); - } - - fn write_bytes(&mut self, bytes: &[u8]) { - self.write_usize(bytes.len()); - for byte in bytes { - self.write_byte(*byte); - } - } - - fn write_i64(&mut self, value: i64) { - for byte in value.to_le_bytes() { - self.write_byte(byte); - } - } - - fn write_u64(&mut self, value: u64) { - for byte in value.to_le_bytes() { - self.write_byte(byte); - } - } - - fn write_usize(&mut self, value: usize) { - self.write_u64(value as u64); - } - - fn finish(self) -> GrammarCacheKey { - GrammarCacheKey { - low: self.low, - high: self.high, - } - } -} - -fn hash_grammar_array(hasher: &mut GrammarCacheHasher, array: &ZendHashTable) -> PhpResult<()> { - hasher.write_usize(array.len()); - for (key, value) in array { - hash_grammar_array_key(hasher, key); - hash_grammar_zval(hasher, value)?; - } - Ok(()) -} - -fn hash_grammar_zval(hasher: &mut GrammarCacheHasher, zval: &Zval) -> PhpResult<()> { - let zval = zval.dereference(); - match zval.get_type() { - DataType::Null => hasher.write_byte(0), - DataType::False => hasher.write_byte(1), - DataType::True => hasher.write_byte(2), - DataType::Long => { - hasher.write_byte(3); - hasher.write_i64( - zval.long() - .ok_or_else(|| php_error("Grammar integer value is invalid"))?, - ); - } - DataType::String => { - hasher.write_byte(4); - hasher.write_bytes( - zval.str() - .ok_or_else(|| php_error("Grammar string value is invalid"))? - .as_bytes(), - ); - } - DataType::Array => { - hasher.write_byte(5); - let array = zval - .array() - .ok_or_else(|| php_error("Grammar array value is invalid"))?; - hash_grammar_array(hasher, array)?; - } - _ => return Err(php_error("Unsupported grammar cache value")), - } +fn cached_native_grammar(grammar: &Zval) -> PhpResult>> { + let object = grammar + .object() + .ok_or_else(|| php_error("Parser grammar must be an object"))?; + let properties = object.get_properties().map_err(php_error)?; + let Some(native_grammar) = properties.get("native_grammar") else { + return Ok(None); + }; + let Some(native_grammar) = <&WpMySqlNativeGrammar as FromZval>::from_zval(native_grammar) + else { + return Ok(None); + }; - Ok(()) + Ok(Some(Arc::clone(&native_grammar.grammar))) } -fn hash_grammar_array_key(hasher: &mut GrammarCacheHasher, key: ArrayKey<'_>) { - match key { - ArrayKey::Long(value) => { - hasher.write_byte(0); - hasher.write_i64(value); - } - ArrayKey::String(value) => { - hasher.write_byte(1); - hasher.write_bytes(value.as_bytes()); - } - ArrayKey::Str(value) => { - hasher.write_byte(1); - hasher.write_bytes(value.as_bytes()); - } - } +fn cache_native_grammar(grammar_zval: &mut Zval, grammar: Arc) -> PhpResult<()> { + let object = grammar_zval + .object_mut() + .ok_or_else(|| php_error("Parser grammar must be an object"))?; + let native_grammar = WpMySqlNativeGrammar { grammar } + .into_zval(false) + .map_err(php_error)?; + object + .set_property("native_grammar", native_grammar) + .map_err(php_error) } fn export_tokens(tokens: &mut Zval) -> PhpResult<(ParserTokenSource, Vec)> { @@ -2028,6 +1924,7 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) { #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module + .class::() .class::() .class::() .class::() From 076b7e5270d331ca5f102bb5c04d7a2be25502ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 16:07:44 +0200 Subject: [PATCH 24/40] Speed up native AST materialization (#388) ## Summary - reuse one `WP_MySQL_Parser` instance inside the SQLite driver and reset its token stream per query - add `reset_tokens()` to the PHP parser polyfill and the Rust native parser - restore native parser-node accessor fast paths in `WP_MySQL_Native_Parser_Node`, while keeping PHP child materialization for mutation - fix the local native extension build helper for Nix/libclang bindgen by undefining `__SSE2__` during binding generation ## Stack This is the top PR in the native MySQL lexer/parser stack. The stack is split so each GitHub diff shows one reviewable concern: 1. [#384 Extract MySQL lexer and parser polyfills](https://github.com/WordPress/sqlite-database-integration/pull/384) - `trunk` -> `codex/native-parser-php-facade` - extraction-only PHP refactor - moves the existing PHP lexer/parser implementations into polyfill classes - keeps public `WP_MySQL_Lexer` and `WP_MySQL_Parser` as thin PHP subclasses 2. [#385 Add optional native parser routing](https://github.com/WordPress/sqlite-database-integration/pull/385) - `codex/native-parser-php-facade` -> `codex/native-parser-class-routing` - adds fallback `WP_MySQL_Native_*` PHP classes - routes the public lexer/parser classes through native classes when the Rust extension provides them - adds the minimal PHP grammar-export bridge for the native parser 3. [#386 Add lazy native parser node facade](https://github.com/WordPress/sqlite-database-integration/pull/386) - `codex/native-parser-class-routing` -> `codex/native-parser-node-facade` - keeps `WP_Parser_Node` as the plain PHP tree node - adds `WP_MySQL_Native_Parser_Node extends WP_Parser_Node` for native-backed lazy AST nodes - keeps native AST handles and native accessor delegation out of the base node class 4. [#381 Add lazy native AST facade](https://github.com/WordPress/sqlite-database-integration/pull/381) - `codex/native-parser-node-facade` -> `codex/native-lazy-ast-facade` - implements the Rust lexer/parser extension and lazy native AST facade - makes the Rust extension instantiate `WP_MySQL_Native_Parser_Node` - adds native-extension CI coverage for the SQLite driver and WordPress PHPUnit tests - includes the local SQLite facade smoke benchmark 5. [#387 Cache native grammar on parser grammar object](https://github.com/WordPress/sqlite-database-integration/pull/387) - `codex/native-lazy-ast-facade` -> `codex/native-parser-object-grammar-cache` - restores the object-attached native grammar cache - adds only `WP_Parser_Grammar::$native_grammar` on the PHP side - removes the Rust content-hash cache that walked the whole exported grammar on every parser construction 6. This PR, [#388 Speed up native AST materialization](https://github.com/WordPress/sqlite-database-integration/pull/388) - `codex/native-parser-object-grammar-cache` -> `codex/native-parser-bulk-materialization` - optimizes native-to-PHP AST access after the grammar-cache performance restoration - reuses the SQLite driver's parser instance instead of constructing it per query ## Why The native lexer/parser itself is fast, but the PHP-facing path can lose that benefit if each query repeatedly rebuilds native parser state or forces full PHP AST materialization. On the current stack, #387 already removes the large grammar export/hash cost. This PR removes the remaining per-query parser construction churn and restores the native AST accessor path for descendant-heavy SQLite driver workloads. ## Measurements Environment: local PHP 8.2 via the native build helper, release Rust extension, current top of this PR. Focused constructor/reset benchmark over 5000 unique SELECT queries: | Phase | Time | | --- | ---: | | native tokenize | 22.62 us/query | | fresh native parser constructor only | 2.31 us/query | | reusable parser `reset_tokens()` only | 0.32 us/query | | reusable parser reset + parse + `get_descendants()` | 157.06 us/query | | constructor/reset ratio | 7.3x | The previously reported ~622 us/query constructor cost does not reproduce on this stack because #387 already caches the native grammar on the PHP grammar object. Parser reuse still removes most of the remaining constructor overhead. SQLite facade smoke workload: Command: ```bash TMP_TEST_NATIVE_QUERY_COUNT=250 ./tmp-test-native/run.sh ``` | Workload | PHP fallback | Native extension | Speedup | | --- | ---: | ---: | ---: | | 250 generated queries, including 1 x 2000-row insert | 4.060s | 0.525s | 7.73x | ## Testing - `cargo fmt --check` - `git diff --check` - `composer run check-cs` - `composer run test` from `packages/mysql-on-sqlite` - `php -d extension=packages/mysql-on-sqlite/ext/wp-mysql-parser/target/release/libwp_mysql_parser.so packages/mysql-on-sqlite/vendor/bin/phpunit -c packages/mysql-on-sqlite/phpunit.xml.dist` - `TMP_TEST_NATIVE_QUERY_COUNT=250 ./tmp-test-native/run.sh` --- .../mysql-parser-extension-tests.yml | 9 +- .github/workflows/wp-tests-phpunit.yml | 4 +- .../src/mysql/class-wp-mysql-parser.php | 11 ++ .../sqlite/class-wp-pdo-mysql-on-sqlite.php | 27 +++- packages/php-ext-wp-mysql-parser/src/lib.rs | 142 +++++++++++++++--- 5 files changed, 164 insertions(+), 29 deletions(-) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 5f8e49d3..234bb5c7 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -81,7 +81,10 @@ jobs: exit( 1 ); } ' - ./vendor/bin/phpunit -c ./phpunit.xml.dist tests/mysql/WP_MySQL_Lexer_Tests.php tests/parser/WP_Parser_Node_Tests.php + working-directory: packages/mysql-on-sqlite + + - name: Run PHPUnit tests with parser extension + run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist working-directory: packages/mysql-on-sqlite sqlite-driver-extension-tests: @@ -149,3 +152,7 @@ jobs: exit( 1 ); } ' + + - name: Run PHPUnit tests with SQLite driver using parser extension + run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist + working-directory: packages/mysql-on-sqlite diff --git a/.github/workflows/wp-tests-phpunit.yml b/.github/workflows/wp-tests-phpunit.yml index f2c8faf6..31db5ef0 100644 --- a/.github/workflows/wp-tests-phpunit.yml +++ b/.github/workflows/wp-tests-phpunit.yml @@ -54,8 +54,8 @@ jobs: - name: Build and load parser extension in WordPress PHP containers run: bash .github/workflows/wp-tests-phpunit-native-extension-setup.sh - - name: Verify WordPress uses parser extension - run: cd wordpress && node tools/local-env/scripts/docker.js run --rm php php /var/www/native-verify-extension.php + - name: Run WordPress PHPUnit tests with parser extension + run: node .github/workflows/wp-tests-phpunit-run.js - name: Stop Docker containers if: always() diff --git a/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php b/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php index f291064e..69282b9c 100644 --- a/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php +++ b/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php @@ -8,6 +8,17 @@ class WP_MySQL_Parser extends WP_Parser { */ private $current_ast; + /** + * Reset this parser with a new token stream. + * + * @param array $tokens The parser tokens. + */ + public function reset_tokens( array $tokens ): void { + $this->tokens = $tokens; + $this->position = 0; + $this->current_ast = null; + } + /** * Parse the next query from the input SQL string. * diff --git a/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php index b3653ffe..a8ddf146 100644 --- a/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php @@ -410,6 +410,13 @@ class WP_PDO_MySQL_On_SQLite extends PDO { */ private static $mysql_grammar; + /** + * A reusable parser instance for MySQL queries. + * + * @var WP_MySQL_Parser|null + */ + private $mysql_parser = null; + /** * The main database name. * @@ -1160,11 +1167,27 @@ public function create_parser( string $query ): WP_MySQL_Parser { ); if ( $lexer instanceof WP_MySQL_Native_Lexer ) { $tokens = $lexer->native_token_stream(); - return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + return $this->reset_or_create_parser( $tokens ); } $tokens = $lexer->remaining_tokens(); - return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + return $this->reset_or_create_parser( $tokens ); + } + + /** + * Reset the reusable parser with new tokens or create it on first use. + * + * @param array|object $tokens Parser tokens. + * @return WP_MySQL_Parser A parser initialized for the token stream. + */ + private function reset_or_create_parser( $tokens ): WP_MySQL_Parser { + if ( null === $this->mysql_parser || ! method_exists( $this->mysql_parser, 'reset_tokens' ) ) { + $this->mysql_parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + } else { + $this->mysql_parser->reset_tokens( $tokens ); + } + + return $this->mysql_parser; } /** diff --git a/packages/php-ext-wp-mysql-parser/src/lib.rs b/packages/php-ext-wp-mysql-parser/src/lib.rs index 835dea1b..909ec379 100644 --- a/packages/php-ext-wp-mysql-parser/src/lib.rs +++ b/packages/php-ext-wp-mysql-parser/src/lib.rs @@ -93,10 +93,19 @@ fn update_object_property( } fn create_mysql_token(sql_zval: &Zval, token: TokenInfo, no_backslash: bool) -> PhpResult { + let classes = php_classes()?; + create_mysql_token_with_classes(sql_zval, token, no_backslash, &classes) +} + +fn create_mysql_token_with_classes( + sql_zval: &Zval, + token: TokenInfo, + no_backslash: bool, + classes: &PhpClasses, +) -> PhpResult { let id = token.id; let start = i64::try_from(token.start).map_err(php_error)?; let length = i64::try_from(token.end.saturating_sub(token.start)).map_err(php_error)?; - let classes = php_classes()?; let mut object = classes.mysql_token.new(); update_object_property(&mut object, classes.parser_token, "id", id)?; @@ -940,7 +949,7 @@ enum ParserTokenSource { } impl ParserTokenSource { - fn create_php_token(&self, index: usize) -> PhpResult { + fn create_php_token_with_classes(&self, index: usize, classes: &PhpClasses) -> PhpResult { match self { Self::Php(tokens) => tokens .get(index) @@ -955,7 +964,7 @@ impl ParserTokenSource { .get(index) .copied() .ok_or_else(|| php_error("Parser token index is out of range"))?; - create_mysql_token(sql_zval, token, *no_backslash) + create_mysql_token_with_classes(sql_zval, token, *no_backslash, classes) } } } @@ -1020,6 +1029,7 @@ struct NativeAstNode { children: Vec, first_token: Option, last_token: Option, + descendant_count: usize, } struct NativeAstArena { @@ -1049,10 +1059,12 @@ impl NativeAstArena { let index = self.nodes.len(); let mut first_token = None; let mut last_token = None; + let mut descendant_count = 0; for child in &children { match child { NativeAstChild::Node(child_index) => { if let Some(node) = self.nodes.get(*child_index) { + descendant_count += 1 + node.descendant_count; if first_token.is_none() { first_token = node.first_token; } @@ -1066,6 +1078,7 @@ impl NativeAstArena { first_token = Some(*token_index); } last_token = Some(*token_index); + descendant_count += 1; } } } @@ -1075,11 +1088,21 @@ impl NativeAstArena { children, first_token, last_token, + descendant_count, }); index } fn create_php_ast(&self, native_ast_zval: &Zval) -> PhpResult { + let classes = php_classes()?; + self.create_php_ast_with_classes(native_ast_zval, &classes) + } + + fn create_php_ast_with_classes( + &self, + native_ast_zval: &Zval, + classes: &PhpClasses, + ) -> PhpResult { match self.root { NativeAstRoot::No => Ok(Zval::null()), NativeAstRoot::Empty => { @@ -1087,14 +1110,22 @@ impl NativeAstArena { zval.set_bool(true); Ok(zval) } - NativeAstRoot::Node(index) => self.create_php_node(native_ast_zval, index), - NativeAstRoot::Token(index) => self.token_source.create_php_token(index), + NativeAstRoot::Node(index) => { + self.create_php_node_with_classes(native_ast_zval, index, classes) + } + NativeAstRoot::Token(index) => self + .token_source + .create_php_token_with_classes(index, classes), } } - fn create_php_node(&self, native_ast_zval: &Zval, index: usize) -> PhpResult { + fn create_php_node_with_classes( + &self, + native_ast_zval: &Zval, + index: usize, + classes: &PhpClasses, + ) -> PhpResult { let node = self.node(index)?; - let classes = php_classes()?; let mut object = classes.native_parser_node.new(); let rule_name = self .grammar @@ -1137,10 +1168,19 @@ impl NativeAstArena { .ok_or_else(|| php_error("Native AST node index is out of range")) } - fn child_to_zval(&self, native_ast_zval: &Zval, child: NativeAstChild) -> PhpResult { + fn child_to_zval_with_classes( + &self, + native_ast_zval: &Zval, + child: NativeAstChild, + classes: &PhpClasses, + ) -> PhpResult { match child { - NativeAstChild::Node(index) => self.create_php_node(native_ast_zval, index), - NativeAstChild::Token(index) => self.token_source.create_php_token(index), + NativeAstChild::Node(index) => { + self.create_php_node_with_classes(native_ast_zval, index, classes) + } + NativeAstChild::Token(index) => self + .token_source + .create_php_token_with_classes(index, classes), } } @@ -1172,8 +1212,9 @@ impl NativeAstArena { } fn descendant_stack(&self, index: usize) -> PhpResult> { - let mut stack = self.node(index)?.children.clone(); - stack.reverse(); + let node = self.node(index)?; + let mut stack = Vec::with_capacity(node.descendant_count); + stack.extend(node.children.iter().rev().copied()); Ok(stack) } } @@ -1238,6 +1279,7 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child( node_index: i64, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let Some(child) = ast .arena .node(native_ast_node_index(node_index)?)? @@ -1247,7 +1289,8 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child( else { return Ok(Zval::null()); }; - ast.arena.child_to_zval(native_ast_zval, child) + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes) } #[php_function] @@ -1257,9 +1300,12 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child_node( rule_name: Option, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; for child in &ast.arena.node(native_ast_node_index(node_index)?)?.children { if ast.arena.child_node_matches(*child, rule_name.as_deref()) { - return ast.arena.child_to_zval(native_ast_zval, *child); + return ast + .arena + .child_to_zval_with_classes(native_ast_zval, *child, &classes); } } Ok(Zval::null()) @@ -1272,9 +1318,12 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child_token( token_id: Option, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; for child in &ast.arena.node(native_ast_node_index(node_index)?)?.children { if ast.arena.child_token_matches(*child, token_id) { - return ast.arena.child_to_zval(native_ast_zval, *child); + return ast + .arena + .child_to_zval_with_classes(native_ast_zval, *child, &classes); } } Ok(Zval::null()) @@ -1287,12 +1336,15 @@ pub fn wp_sqlite_mysql_native_ast_get_first_descendant_node( rule_name: Option, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { if ast.arena.child_node_matches(child, rule_name.as_deref()) { - return ast.arena.child_to_zval(native_ast_zval, child); + return ast + .arena + .child_to_zval_with_classes(native_ast_zval, child, &classes); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1310,12 +1362,15 @@ pub fn wp_sqlite_mysql_native_ast_get_first_descendant_token( token_id: Option, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { if ast.arena.child_token_matches(child, token_id) { - return ast.arena.child_to_zval(native_ast_zval, child); + return ast + .arena + .child_to_zval_with_classes(native_ast_zval, child, &classes); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1332,12 +1387,16 @@ pub fn wp_sqlite_mysql_native_ast_get_children( node_index: i64, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; ast.arena .node(native_ast_node_index(node_index)?)? .children .iter() .copied() - .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .map(|child| { + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes) + }) .collect() } @@ -1348,13 +1407,17 @@ pub fn wp_sqlite_mysql_native_ast_get_child_nodes( rule_name: Option, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; ast.arena .node(native_ast_node_index(node_index)?)? .children .iter() .copied() .filter(|child| ast.arena.child_node_matches(*child, rule_name.as_deref())) - .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .map(|child| { + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes) + }) .collect() } @@ -1365,13 +1428,17 @@ pub fn wp_sqlite_mysql_native_ast_get_child_tokens( token_id: Option, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; ast.arena .node(native_ast_node_index(node_index)?)? .children .iter() .copied() .filter(|child| ast.arena.child_token_matches(*child, token_id)) - .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .map(|child| { + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes) + }) .collect() } @@ -1381,12 +1448,17 @@ pub fn wp_sqlite_mysql_native_ast_get_descendants( node_index: i64, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; - let mut descendants = Vec::new(); + let classes = php_classes()?; + let root = ast.arena.node(native_ast_node_index(node_index)?)?; + let mut descendants = Vec::with_capacity(root.descendant_count); let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { - descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + descendants.push( + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes)?, + ); if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { stack.push(*child); @@ -1403,13 +1475,18 @@ pub fn wp_sqlite_mysql_native_ast_get_descendant_nodes( rule_name: Option, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let mut descendants = Vec::new(); let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { if ast.arena.child_node_matches(child, rule_name.as_deref()) { - descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + descendants.push(ast.arena.child_to_zval_with_classes( + native_ast_zval, + child, + &classes, + )?); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1427,13 +1504,18 @@ pub fn wp_sqlite_mysql_native_ast_get_descendant_tokens( token_id: Option, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let mut descendants = Vec::new(); let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { if ast.arena.child_token_matches(child, token_id) { - descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + descendants.push(ast.arena.child_to_zval_with_classes( + native_ast_zval, + child, + &classes, + )?); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1505,6 +1587,18 @@ impl WpMySqlNativeParser { }) } + pub fn reset_tokens(&mut self, tokens: &mut Zval) -> PhpResult<()> { + let (token_source, token_ids) = export_tokens(tokens)?; + + self.token_source = Arc::new(token_source); + self.token_ids = token_ids; + self.position = 0; + self.current_ast = None; + self.current_php_ast = None; + + Ok(()) + } + pub fn parse(&mut self) -> PhpResult { stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { let ast = self.parse_native_ast()?; From 61a00fa4f6701510c6f867f57fad43d8a8379dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 19:08:51 +0200 Subject: [PATCH 25/40] Preserve child wrapper identity in native AST facade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The native parser extension constructs a fresh WP_MySQL_Native_Parser_Node on every accessor call, so two reads of the same logical node returned different PHP objects and any state a caller attached to the first wrapper was invisible through the second. WP_Parser_Node has always given callers stable child identity, and the lazy native facade is meant to keep that contract intact — this restores it. A per-AST identity map is created lazily on the root and shared by every interned wrapper. Each accessor that returns a node looks the index up in the map and returns the canonical instance, discarding the freshly constructed one. Materialization pulls children through the same map so mutations a caller made through get_first_child_node() before triggering append_child() survive into $this->children. Adds a regression test that exercises identity across child, descendant, and post-materialization reads, plus a walk benchmark and a CI workflow that reports parse + walk time and peak memory for the PHP and native paths so the cache cost is measurable on every PR. --- .github/workflows/native-ast-perf.yml | 118 ++++++++++++++++++ packages/mysql-on-sqlite/src/load.php | 1 + .../class-wp-mysql-native-ast-cache.php | 23 ++++ .../class-wp-mysql-native-parser-node.php | 112 +++++++++++++++-- ...ySQL_Native_Parser_Node_Identity_Tests.php | 115 +++++++++++++++++ .../tools/run-native-ast-walk-benchmark.php | 115 +++++++++++++++++ 6 files changed, 476 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/native-ast-perf.yml create mode 100644 packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php create mode 100644 packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php create mode 100644 packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php diff --git a/.github/workflows/native-ast-perf.yml b/.github/workflows/native-ast-perf.yml new file mode 100644 index 00000000..594dde8c --- /dev/null +++ b/.github/workflows/native-ast-perf.yml @@ -0,0 +1,118 @@ +name: Native AST Walk Perf + +on: + push: + paths: + - '.github/workflows/native-ast-perf.yml' + - 'packages/mysql-on-sqlite/src/mysql/native/**' + - 'packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php' + - 'packages/php-ext-wp-mysql-parser/**' + pull_request: + paths: + - '.github/workflows/native-ast-perf.yml' + - 'packages/mysql-on-sqlite/src/mysql/native/**' + - 'packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php' + - 'packages/php-ext-wp-mysql-parser/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + perf: + name: Native AST walk benchmark + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install native build dependencies + run: | + sudo apt-get update + sudo apt-get install -y libclang-dev + echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV" + LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)" + echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV" + + - name: Install Composer dependencies (root) + uses: ramsey/composer-install@v3 + with: + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Install Composer dependencies (mysql-on-sqlite) + uses: ramsey/composer-install@v3 + with: + working-directory: packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Download MySQL test query corpus + working-directory: packages/mysql-on-sqlite + run: bash tests/tools/mysql-download-tests.sh || true + + - name: Build parser extension (release) + run: cargo build --release + working-directory: packages/php-ext-wp-mysql-parser + + - name: Locate built extension + run: | + EXT="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so" + test -f "$EXT" || { echo "Extension not built at $EXT"; exit 1; } + echo "NATIVE_EXT=$EXT" >> "$GITHUB_ENV" + + - name: Benchmark — pure-PHP path (parse only) + working-directory: packages/mysql-on-sqlite + run: | + php tests/tools/run-native-ast-walk-benchmark.php --no-walk | tee php-parse-only.txt + + - name: Benchmark — pure-PHP path (walk) + working-directory: packages/mysql-on-sqlite + run: | + php tests/tools/run-native-ast-walk-benchmark.php | tee php-walk.txt + + - name: Benchmark — native path (parse only) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk | tee native-parse-only.txt + + - name: Benchmark — native path (walk, with identity cache) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php | tee native-walk.txt + + - name: Summarize + if: always() + working-directory: packages/mysql-on-sqlite + run: | + { + echo '### Native AST walk perf' + echo + echo '| scenario | result |' + echo '|---|---|' + for f in php-parse-only.txt php-walk.txt native-parse-only.txt native-walk.txt; do + [ -f "$f" ] || continue + line="$(cat "$f")" + echo "| ${f%.txt} | \`$line\` |" + done + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload raw output + if: always() + uses: actions/upload-artifact@v4 + with: + name: native-ast-perf-${{ github.run_id }} + path: packages/mysql-on-sqlite/*.txt + if-no-files-found: warn diff --git a/packages/mysql-on-sqlite/src/load.php b/packages/mysql-on-sqlite/src/load.php index 2070c631..4cbb4208 100644 --- a/packages/mysql-on-sqlite/src/load.php +++ b/packages/mysql-on-sqlite/src/load.php @@ -27,6 +27,7 @@ if ( class_exists( 'WP_MySQL_Native_Parser', false ) ) { require_once __DIR__ . '/mysql/native/mysql-rust-bridge.php'; + require_once __DIR__ . '/mysql/native/class-wp-mysql-native-ast-cache.php'; require_once __DIR__ . '/mysql/native/class-wp-mysql-native-parser-node.php'; require_once __DIR__ . '/mysql/native/class-wp-mysql-parser.php'; } else { diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php new file mode 100644 index 00000000..f21bb785 --- /dev/null +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php @@ -0,0 +1,23 @@ + WP_MySQL_Native_Parser_Node. + * + * @var array + */ + public $nodes = array(); +} diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index e796bf68..b176934d 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -8,12 +8,29 @@ * caller actually walks the tree. On the first mutation (append_child or * merge_fragment), the node materializes its children into the inherited * `$children` array and behaves like a plain WP_Parser_Node from then on. + * + * Wrappers returned by accessors are interned through a per-AST identity + * map (WP_MySQL_Native_AST_Cache) so two reads of the same logical node + * yield the same PHP instance. This preserves the WP_Parser_Node contract + * that mutations performed on a child via `get_first_child_node()` remain + * visible when the same child is reached again, including after the parent + * has materialized. */ class WP_MySQL_Native_Parser_Node extends WP_Parser_Node { private $native_ast = null; private $native_node_index = null; private $was_mutated = false; + /** + * Per-AST identity map shared between every interned wrapper. + * + * Created lazily on the first child access; the root wrapper is the + * first entry. Children inherit the same cache instance by reference. + * + * @var WP_MySQL_Native_AST_Cache|null + */ + private $cache = null; + public function __construct( $rule_id, $rule_name, $native_ast = null, $native_node_index = null ) { parent::__construct( $rule_id, $rule_name ); @@ -21,6 +38,19 @@ public function __construct( $rule_id, $rule_name, $native_ast = null, $native_n $this->native_node_index = $native_node_index; } + /** + * Native node index in the Rust-owned arena. + * + * Exposed so the identity cache can key on it. Returns null after + * the wrapper has materialized — at that point the node is detached + * from the native arena and behaves like a plain WP_Parser_Node. + * + * @return int|null + */ + public function get_native_node_index(): ?int { + return $this->native_node_index; + } + /** @inheritDoc */ public function append_child( $node ) { $this->materialize_native_children(); @@ -65,7 +95,7 @@ public function get_first_child() { if ( $this->was_mutated() ) { return parent::get_first_child(); } - return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ); + return $this->intern( wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ) ); } /** @inheritDoc */ @@ -73,7 +103,7 @@ public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_No if ( $this->was_mutated() ) { return parent::get_first_child_node( $rule_name ); } - return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ); + return $this->intern( wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -89,7 +119,7 @@ public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Pars if ( $this->was_mutated() ) { return parent::get_first_descendant_node( $rule_name ); } - return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ); + return $this->intern( wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -105,7 +135,7 @@ public function get_children(): array { if ( $this->was_mutated() ) { return parent::get_children(); } - return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); + return $this->intern_all( wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ) ); } /** @inheritDoc */ @@ -113,7 +143,7 @@ public function get_child_nodes( ?string $rule_name = null ): array { if ( $this->was_mutated() ) { return parent::get_child_nodes( $rule_name ); } - return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ); + return $this->intern_all( wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -129,7 +159,7 @@ public function get_descendants(): array { if ( $this->was_mutated() ) { return parent::get_descendants(); } - return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ); + return $this->intern_all( wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ) ); } /** @inheritDoc */ @@ -137,7 +167,7 @@ public function get_descendant_nodes( ?string $rule_name = null ): array { if ( $this->was_mutated() ) { return parent::get_descendant_nodes( $rule_name ); } - return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ); + return $this->intern_all( wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -168,12 +198,78 @@ private function was_mutated(): bool { return $this->was_mutated; } + /** + * Intern a single accessor return value through the per-AST cache. + * + * Tokens and nulls pass through untouched. Native node wrappers are + * keyed on their `native_node_index`: on cache miss, the freshly + * constructed wrapper is stored and given the cache reference; on + * cache hit, the canonical instance is returned and the new wrapper + * is discarded so callers see stable identity and surviving mutations. + * + * @param mixed $value Return value from the Rust bridge. + * @return mixed + */ + private function intern( $value ) { + if ( ! $value instanceof WP_MySQL_Native_Parser_Node ) { + return $value; + } + + $cache = $this->ensure_cache(); + $index = $value->native_node_index; + if ( null === $index ) { + return $value; + } + if ( isset( $cache->nodes[ $index ] ) ) { + return $cache->nodes[ $index ]; + } + $value->cache = $cache; + $cache->nodes[ $index ] = $value; + return $value; + } + + /** + * Intern every entry in an accessor return array. + * + * @param array $values + * @return array + */ + private function intern_all( array $values ): array { + foreach ( $values as $i => $value ) { + $values[ $i ] = $this->intern( $value ); + } + return $values; + } + + /** + * Lazily build (or reuse) the per-AST identity map. + * + * The root wrapper is constructed without a cache, so the first time + * any accessor needs to intern a child, it creates the cache and + * registers itself as the root entry. Subsequent interns on this + * wrapper or any descendant share the same cache by reference. + * + * @return WP_MySQL_Native_AST_Cache + */ + private function ensure_cache(): WP_MySQL_Native_AST_Cache { + if ( null === $this->cache ) { + $this->cache = new WP_MySQL_Native_AST_Cache(); + if ( null !== $this->native_node_index ) { + $this->cache->nodes[ $this->native_node_index ] = $this; + } + } + return $this->cache; + } + private function materialize_native_children(): void { if ( $this->was_mutated ) { return; } - $this->children = wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); + // Pull children through the cache so any wrapper a caller already + // mutated via get_first_child_node() etc. survives the transition + // into $this->children — same instance, same mutations. + $this->children = $this->intern_all( wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ) ); $this->native_ast = null; $this->native_node_index = null; $this->was_mutated = true; diff --git a/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php new file mode 100644 index 00000000..c649b981 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php @@ -0,0 +1,115 @@ +markTestSkipped( 'Native MySQL parser extension is not loaded.' ); + } + } + + private function parse( string $sql ): WP_Parser_Node { + static $grammar = null; + if ( null === $grammar ) { + $grammar = new WP_Parser_Grammar( include __DIR__ . '/../../../src/mysql/mysql-grammar.php' ); + } + $lexer = new WP_MySQL_Lexer( $sql ); + $tokens = $lexer instanceof WP_MySQL_Native_Lexer + ? $lexer->native_token_stream() + : $lexer->remaining_tokens(); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + $tree = $parser->parse(); + $this->assertNotNull( $tree, 'Failed to parse SQL: ' . $sql ); + return $tree; + } + + public function test_get_first_child_node_returns_same_instance(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $first = $tree->get_first_child_node(); + $second = $tree->get_first_child_node(); + + $this->assertNotNull( $first ); + $this->assertSame( $first, $second ); + } + + public function test_get_children_returns_same_instances_across_calls(): void { + $tree = $this->parse( 'SELECT 1, 2, 3' ); + + $first_pass = $tree->get_children(); + $second_pass = $tree->get_children(); + + $this->assertSameSize( $first_pass, $second_pass ); + foreach ( $first_pass as $i => $child ) { + if ( $child instanceof WP_Parser_Node ) { + $this->assertSame( $child, $second_pass[ $i ] ); + } + } + } + + public function test_descendant_lookup_shares_identity_with_child_lookup(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $descendant = $tree->get_first_descendant_node(); + $this->assertNotNull( $descendant ); + + // Walk down to the same node via direct children. We don't know the + // exact depth, so we descend until we hit the descendant we found. + $cursor = $tree; + while ( null !== $cursor && $cursor !== $descendant ) { + $next = $cursor->get_first_child_node(); + if ( $next === $cursor ) { + break; + } + $cursor = $next; + } + + $this->assertSame( $descendant, $cursor, 'Descendant and child lookups must return the same wrapper instance.' ); + } + + public function test_mutation_on_child_survives_re_read(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $child = $tree->get_first_child_node(); + $this->assertNotNull( $child ); + + // Public dynamic property — the kind of state a caller might attach + // expecting WP_Parser_Node identity to keep it reachable. + $child->custom_marker = 'set-on-first-read'; + + $same_child = $tree->get_first_child_node(); + $this->assertSame( $child, $same_child ); + $this->assertSame( 'set-on-first-read', $same_child->custom_marker ); + } + + public function test_mutation_survives_parent_materialization(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $child = $tree->get_first_child_node(); + $this->assertNotNull( $child ); + $child->custom_marker = 'before-materialize'; + + // Force the parent to materialize its native children by appending + // a sibling. After this, the parent walks $this->children directly. + $sibling = new WP_Parser_Node( 0, 'synthetic' ); + $tree->append_child( $sibling ); + + $children = $tree->get_children(); + $this->assertContains( $child, $children, 'Materialized children must include the previously-mutated wrapper.' ); + $this->assertSame( 'before-materialize', $child->custom_marker ); + } +} diff --git a/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php new file mode 100644 index 00000000..21fd74f7 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php @@ -0,0 +1,115 @@ +native_token_stream() + : $lexer->remaining_tokens(); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + $ast = $parser->parse(); + if ( null === $ast ) { + ++$failures; + continue; + } + ++$total; + + if ( ! $walk_tree ) { + continue; + } + + // Exhaustive descendant walk — exercises both the per-call accessor + // path and (when the native extension is loaded) the identity map. + $descendants = $ast->get_descendants(); + $walked += count( $descendants ); + + // Re-read the first child a few times and confirm identity is + // stable. With the cache, this must be the same instance every + // call; a regression would surface as a cheap, deterministic flag. + $first = $ast->get_first_child_node(); + if ( null !== $first ) { + $again = $ast->get_first_child_node(); + if ( $first !== $again ) { + $identity_ok = false; + } + } + } catch ( Throwable $e ) { + ++$failures; + } +} + +$duration = microtime( true ) - $start; +$peak_mb = memory_get_peak_usage( true ) / 1024 / 1024; +$native = class_exists( 'WP_MySQL_Native_Parser', false ) ? 'native' : 'php'; + +printf( + "path=%s walk=%s parsed=%d walked_nodes=%d failures=%d duration=%.4fs qps=%d peak_mem=%.1fMB identity_ok=%s\n", + $native, + $walk_tree ? 'yes' : 'no', + $total, + $walked, + $failures, + $duration, + $total > 0 ? (int) ( $total / $duration ) : 0, + $peak_mb, + $identity_ok ? 'true' : 'FALSE' +); + +if ( ! $identity_ok ) { + fwrite( STDERR, "Identity check failed: get_first_child_node() returned different instances.\n" ); + exit( 1 ); +} From a8a4283c74a59330445535158b00fc29e03a540f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 19:17:49 +0200 Subject: [PATCH 26/40] Fix code style and PHP 8.2 dynamic-property deprecation in identity test --- .../native/class-wp-mysql-native-parser-node.php | 2 +- ...WP_MySQL_Native_Parser_Node_Identity_Tests.php | 15 +++++++++------ .../tests/tools/run-native-ast-walk-benchmark.php | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index b176934d..a345a383 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -223,7 +223,7 @@ private function intern( $value ) { if ( isset( $cache->nodes[ $index ] ) ) { return $cache->nodes[ $index ]; } - $value->cache = $cache; + $value->cache = $cache; $cache->nodes[ $index ] = $value; return $value; } diff --git a/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php index c649b981..bd3f8ffe 100644 --- a/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php +++ b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php @@ -87,13 +87,16 @@ public function test_mutation_on_child_survives_re_read(): void { $child = $tree->get_first_child_node(); $this->assertNotNull( $child ); - // Public dynamic property — the kind of state a caller might attach - // expecting WP_Parser_Node identity to keep it reachable. - $child->custom_marker = 'set-on-first-read'; + // Mutate via the public WP_Parser_Node API — this is exactly the + // kind of state the reviewer worried would be lost when accessors + // hand back fresh wrappers. rule_name is a declared public property + // that the parser itself sets, so PHP 8.2's dynamic-property + // deprecation does not apply here. + $child->rule_name = 'mutated-rule'; $same_child = $tree->get_first_child_node(); $this->assertSame( $child, $same_child ); - $this->assertSame( 'set-on-first-read', $same_child->custom_marker ); + $this->assertSame( 'mutated-rule', $same_child->rule_name ); } public function test_mutation_survives_parent_materialization(): void { @@ -101,7 +104,7 @@ public function test_mutation_survives_parent_materialization(): void { $child = $tree->get_first_child_node(); $this->assertNotNull( $child ); - $child->custom_marker = 'before-materialize'; + $child->rule_name = 'before-materialize'; // Force the parent to materialize its native children by appending // a sibling. After this, the parent walks $this->children directly. @@ -110,6 +113,6 @@ public function test_mutation_survives_parent_materialization(): void { $children = $tree->get_children(); $this->assertContains( $child, $children, 'Materialized children must include the previously-mutated wrapper.' ); - $this->assertSame( 'before-materialize', $child->custom_marker ); + $this->assertSame( 'before-materialize', $child->rule_name ); } } diff --git a/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php index 21fd74f7..c1357bfa 100644 --- a/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php +++ b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php @@ -39,10 +39,10 @@ function ( $severity, $message, $file, $line ) { } fclose( $handle ); -$total = 0; -$walked = 0; +$total = 0; +$walked = 0; $identity_ok = true; -$failures = 0; +$failures = 0; if ( function_exists( 'memory_reset_peak_usage' ) ) { memory_reset_peak_usage(); From 68524effa65a145564120f925b58651e817ab3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 19:24:07 +0200 Subject: [PATCH 27/40] Benchmark identity-cache cost against PR base in CI --- .github/workflows/native-ast-perf.yml | 58 ++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/workflows/native-ast-perf.yml b/.github/workflows/native-ast-perf.yml index 594dde8c..8fdbabdb 100644 --- a/.github/workflows/native-ast-perf.yml +++ b/.github/workflows/native-ast-perf.yml @@ -93,20 +93,76 @@ jobs: run: | php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php | tee native-walk.txt + - name: Check out baseline (PR base, no identity cache) + run: | + git fetch --no-tags --depth=1 origin codex/native-lazy-ast-facade + git worktree add ../baseline FETCH_HEAD + + - name: Install Composer dependencies (baseline mysql-on-sqlite) + uses: ramsey/composer-install@v3 + with: + working-directory: ../baseline/packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Build baseline parser extension (release) + run: cargo build --release + working-directory: ../baseline/packages/php-ext-wp-mysql-parser + + - name: Stage benchmark + corpus into baseline + run: | + mkdir -p ../baseline/packages/mysql-on-sqlite/tests/mysql/data + cp packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php \ + ../baseline/packages/mysql-on-sqlite/tests/tools/ + cp packages/mysql-on-sqlite/tests/mysql/data/*.csv \ + ../baseline/packages/mysql-on-sqlite/tests/mysql/data/ 2>/dev/null || true + + - name: Benchmark — baseline native path (walk, no identity cache) + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-walk.txt" + + - name: Benchmark — baseline native path (parse only) + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-parse-only.txt" + - name: Summarize if: always() working-directory: packages/mysql-on-sqlite run: | + extract() { + # Pull a numeric field (e.g. duration=1.23s) from a benchmark + # output line. Returns "n/a" if missing. + local file="$1" key="$2" + [ -f "$file" ] || { echo "n/a"; return; } + grep -oE "${key}=[^ ]+" "$file" | head -1 | cut -d= -f2 || echo "n/a" + } + { echo '### Native AST walk perf' echo echo '| scenario | result |' echo '|---|---|' - for f in php-parse-only.txt php-walk.txt native-parse-only.txt native-walk.txt; do + for f in php-parse-only.txt php-walk.txt native-parse-only.txt native-walk.txt baseline-native-parse-only.txt baseline-native-walk.txt; do [ -f "$f" ] || continue line="$(cat "$f")" echo "| ${f%.txt} | \`$line\` |" done + echo + echo '### Identity-cache cost (native walk: with cache vs PR base without)' + echo + echo '| metric | with cache (this PR) | baseline | delta |' + echo '|---|---|---|---|' + for key in duration qps peak_mem walked_nodes; do + with="$(extract native-walk.txt "$key")" + base="$(extract baseline-native-walk.txt "$key")" + echo "| $key | $with | $base | — |" + done } >> "$GITHUB_STEP_SUMMARY" - name: Upload raw output From 6bf11534d5eed047e7e4b547667b14f17bad4dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 19:29:46 +0200 Subject: [PATCH 28/40] Fix baseline extension path in perf workflow --- .github/workflows/native-ast-perf.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/native-ast-perf.yml b/.github/workflows/native-ast-perf.yml index 8fdbabdb..fdb172fe 100644 --- a/.github/workflows/native-ast-perf.yml +++ b/.github/workflows/native-ast-perf.yml @@ -120,14 +120,14 @@ jobs: - name: Benchmark — baseline native path (walk, no identity cache) working-directory: ../baseline/packages/mysql-on-sqlite run: | - BASE_EXT="$(realpath ../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php \ | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-walk.txt" - name: Benchmark — baseline native path (parse only) working-directory: ../baseline/packages/mysql-on-sqlite run: | - BASE_EXT="$(realpath ../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk \ | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-parse-only.txt" From 6c945729c816badb291b9057c93501ab8ba5e42e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 20:00:44 +0200 Subject: [PATCH 29/40] Speed up identity-cache hot path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit intern_all hoists the cache lookup out of the loop and inlines what was a per-item method call to intern(). For accessors whose Rust bridge returns only nodes — get_child_nodes / get_descendant_nodes — a typed intern_nodes() variant skips the instanceof check entirely. The walk benchmark exercises the descendants accessors over ~4.8M nodes per run, so even small per-item savings add up. --- .../class-wp-mysql-native-parser-node.php | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index a345a383..fae84b4c 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -143,7 +143,7 @@ public function get_child_nodes( ?string $rule_name = null ): array { if ( $this->was_mutated() ) { return parent::get_child_nodes( $rule_name ); } - return $this->intern_all( wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); + return $this->intern_nodes( wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -167,7 +167,7 @@ public function get_descendant_nodes( ?string $rule_name = null ): array { if ( $this->was_mutated() ) { return parent::get_descendant_nodes( $rule_name ); } - return $this->intern_all( wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); + return $this->intern_nodes( wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -231,12 +231,62 @@ private function intern( $value ) { /** * Intern every entry in an accessor return array. * + * Hot path: this runs once per descendant when a caller walks the tree, + * so cache lookup and the cache-miss write are inlined and the cache + * reference is hoisted out of the loop. + * * @param array $values * @return array */ private function intern_all( array $values ): array { + if ( ! $values ) { + return $values; + } + $cache = $this->cache ?? $this->ensure_cache(); + $nodes = &$cache->nodes; + foreach ( $values as $i => $value ) { + if ( ! $value instanceof WP_MySQL_Native_Parser_Node ) { + continue; + } + $index = $value->native_node_index; + if ( null === $index ) { + continue; + } + if ( isset( $nodes[ $index ] ) ) { + $values[ $i ] = $nodes[ $index ]; + } else { + $value->cache = $cache; + $nodes[ $index ] = $value; + } + } + return $values; + } + + /** + * Intern array of guaranteed-node results (no token/null mixing). + * + * Used by `get_child_nodes()` / `get_descendant_nodes()` whose Rust + * bridge returns only WP_MySQL_Native_Parser_Node instances. Skips + * the per-item `instanceof` check that intern_all() must do for the + * mixed `get_children()` / `get_descendants()` arrays. + * + * @param array $values + * @return array + */ + private function intern_nodes( array $values ): array { + if ( ! $values ) { + return $values; + } + $cache = $this->cache ?? $this->ensure_cache(); + $nodes = &$cache->nodes; foreach ( $values as $i => $value ) { - $values[ $i ] = $this->intern( $value ); + $index = $value->native_node_index; + if ( isset( $nodes[ $index ] ) ) { + $values[ $i ] = $nodes[ $index ]; + } else { + $value->cache = $cache; + $nodes[ $index ] = $value; + } } return $values; } From 2c07dacdc540121750f573ac4c61b3c0cae45b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Apr 2026 23:10:26 +0200 Subject: [PATCH 30/40] Add hit-heavy perf scenarios to compare cache vs baseline The walk benchmark we already had is cache-miss heavy (one walk per AST, every node visited once), so the identity cache shows up there as a small overhead rather than a win. The cache is supposed to pay back in hit-heavy patterns: re-walks of the same tree, repeated child reads at the root, and translator-style passes that re-enter visited subtrees. Adds three modes (--mode=rewalk|reread|subtree) and runs each on both the PR and the baseline so the comparison is apples-to-apples on the same runner, same corpus. --- .github/workflows/native-ast-perf.yml | 44 +++++- .../tools/run-native-ast-walk-benchmark.php | 129 ++++++++++++++---- 2 files changed, 143 insertions(+), 30 deletions(-) diff --git a/.github/workflows/native-ast-perf.yml b/.github/workflows/native-ast-perf.yml index fdb172fe..9b51eb8e 100644 --- a/.github/workflows/native-ast-perf.yml +++ b/.github/workflows/native-ast-perf.yml @@ -131,6 +131,48 @@ jobs: php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk \ | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-parse-only.txt" + # Hit-heavy scenarios — these are where the per-AST identity cache is + # supposed to win. The baseline reallocates wrappers on every accessor + # call, while the PR reuses them. Run on both to make the gap visible. + - name: Benchmark — native rewalk x10 (this PR) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 \ + | tee native-rewalk.txt + + - name: Benchmark — baseline rewalk x10 + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-rewalk.txt" + + - name: Benchmark — native reread x20 (this PR) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=reread --repeat=20 \ + | tee native-reread.txt + + - name: Benchmark — baseline reread x20 + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=reread --repeat=20 \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-reread.txt" + + - name: Benchmark — native subtree x5 (this PR) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 \ + | tee native-subtree.txt + + - name: Benchmark — baseline subtree x5 + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-subtree.txt" + - name: Summarize if: always() working-directory: packages/mysql-on-sqlite @@ -148,7 +190,7 @@ jobs: echo echo '| scenario | result |' echo '|---|---|' - for f in php-parse-only.txt php-walk.txt native-parse-only.txt native-walk.txt baseline-native-parse-only.txt baseline-native-walk.txt; do + for f in php-parse-only.txt php-walk.txt native-parse-only.txt native-walk.txt baseline-native-parse-only.txt baseline-native-walk.txt native-rewalk.txt baseline-native-rewalk.txt native-reread.txt baseline-native-reread.txt native-subtree.txt baseline-native-subtree.txt; do [ -f "$f" ] || continue line="$(cat "$f")" echo "| ${f%.txt} | \`$line\` |" diff --git a/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php index c1357bfa..3927d5c0 100644 --- a/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php +++ b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php @@ -3,15 +3,29 @@ /** * Benchmark for the native AST walk path with the per-AST identity cache. * - * Parses every query in the MySQL server suite, then walks each AST - * exhaustively through `get_descendants()` and `get_first_child_node()` - * loops to exercise the bridge accessors and the identity map. Reports - * wall time, peak memory, and a basic identity-stability check so the - * cache cost can be compared against the no-cache baseline. + * Parses every query in the MySQL server suite, then walks each AST through + * a configurable mode to exercise the bridge accessors and the identity map. + * Reports wall time, peak memory, and a basic identity-stability check so + * the cache cost can be compared against the no-cache baseline. + * + * Modes: + * walk — single full descendant walk per AST (cache-miss heavy). + * no-walk — parse only. + * rewalk=N — repeat the descendant walk N times per AST (1st pass is + * misses, remaining passes are all hits — the scenario the + * identity cache is supposed to win on). + * reread=N — for each top-level child node, call accessors N times to + * exercise repeated-read hit paths. + * subtree=N — walk descendants once, then re-read each one's first child + * N times — models translator/rewriter passes that re-enter + * the same subtrees. * * Usage: - * php run-native-ast-walk-benchmark.php # walks via accessors - * php run-native-ast-walk-benchmark.php --no-walk # parse only, baseline + * php run-native-ast-walk-benchmark.php + * php run-native-ast-walk-benchmark.php --mode=no-walk + * php run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 + * php run-native-ast-walk-benchmark.php --mode=reread --repeat=10 + * php run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 * * The script auto-detects the native extension. Without it, the walk * exercises the pure-PHP WP_Parser_Node path, which is useful as the @@ -26,7 +40,17 @@ function ( $severity, $message, $file, $line ) { require_once __DIR__ . '/../../src/load.php'; -$walk_tree = ! in_array( '--no-walk', $argv, true ); +$mode = 'walk'; +$repeat = 1; +foreach ( $argv as $arg ) { + if ( '--no-walk' === $arg ) { + $mode = 'no-walk'; + } elseif ( 0 === strpos( $arg, '--mode=' ) ) { + $mode = substr( $arg, 7 ); + } elseif ( 0 === strpos( $arg, '--repeat=' ) ) { + $repeat = max( 1, (int) substr( $arg, 9 ) ); + } +} $grammar_data = include __DIR__ . '/../../src/mysql/mysql-grammar.php'; $grammar = new WP_Parser_Grammar( $grammar_data ); @@ -68,24 +92,70 @@ function ( $severity, $message, $file, $line ) { } ++$total; - if ( ! $walk_tree ) { - continue; - } - - // Exhaustive descendant walk — exercises both the per-call accessor - // path and (when the native extension is loaded) the identity map. - $descendants = $ast->get_descendants(); - $walked += count( $descendants ); - - // Re-read the first child a few times and confirm identity is - // stable. With the cache, this must be the same instance every - // call; a regression would surface as a cheap, deterministic flag. - $first = $ast->get_first_child_node(); - if ( null !== $first ) { - $again = $ast->get_first_child_node(); - if ( $first !== $again ) { - $identity_ok = false; - } + switch ( $mode ) { + case 'no-walk': + break; + + case 'walk': + $descendants = $ast->get_descendants(); + $walked += count( $descendants ); + + $first = $ast->get_first_child_node(); + if ( null !== $first ) { + $again = $ast->get_first_child_node(); + if ( $first !== $again ) { + $identity_ok = false; + } + } + break; + + case 'rewalk': + // Repeated full-tree walks. After the first pass every wrapper + // the cache returns is a hit; without the cache, every pass + // re-allocates wrappers for the entire tree from scratch. + for ( $r = 0; $r < $repeat; $r++ ) { + $descendants = $ast->get_descendants(); + $walked += count( $descendants ); + } + break; + + case 'reread': + // Repeated top-level child reads. Models analysis passes that + // keep poking at the root of the tree. + for ( $r = 0; $r < $repeat; $r++ ) { + $child = $ast->get_first_child_node(); + if ( null !== $child ) { + ++$walked; + // Identity must hold across repeated reads. + if ( $r > 0 && $child !== $prev ) { + $identity_ok = false; + } + $prev = $child; + } + } + break; + + case 'subtree': + // Walk descendants once, then for each descendant re-read its + // first child N times. Models translator/rewriter passes that + // re-enter previously visited subtrees. + $descendants = $ast->get_descendants(); + foreach ( $descendants as $d ) { + if ( ! $d instanceof WP_Parser_Node ) { + continue; + } + for ( $r = 0; $r < $repeat; $r++ ) { + $child = $d->get_first_child_node(); + if ( null !== $child ) { + ++$walked; + } + } + } + break; + + default: + fwrite( STDERR, "Unknown mode: $mode\n" ); + exit( 2 ); } } catch ( Throwable $e ) { ++$failures; @@ -97,9 +167,10 @@ function ( $severity, $message, $file, $line ) { $native = class_exists( 'WP_MySQL_Native_Parser', false ) ? 'native' : 'php'; printf( - "path=%s walk=%s parsed=%d walked_nodes=%d failures=%d duration=%.4fs qps=%d peak_mem=%.1fMB identity_ok=%s\n", + "path=%s mode=%s repeat=%d parsed=%d walked_nodes=%d failures=%d duration=%.4fs qps=%d peak_mem=%.1fMB identity_ok=%s\n", $native, - $walk_tree ? 'yes' : 'no', + $mode, + $repeat, $total, $walked, $failures, @@ -110,6 +181,6 @@ function ( $severity, $message, $file, $line ) { ); if ( ! $identity_ok ) { - fwrite( STDERR, "Identity check failed: get_first_child_node() returned different instances.\n" ); + fwrite( STDERR, "Identity check failed: accessor returned different instances on repeat read.\n" ); exit( 1 ); } From 2faa18b089f3b9fa8dccf113bb9364ba22b9fa32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 13:02:38 +0200 Subject: [PATCH 31/40] Preserve child wrapper identity in the native AST facade (#391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stacked on #381. #381's review surfaced a real semantics regression in the lazy native AST facade: every accessor on `WP_MySQL_Native_Parser_Node` calls into Rust and returns a freshly constructed PHP wrapper, so `get_first_child_node()` returns a different object every time. `WP_Parser_Node` has always given callers stable child identity — attach state to a child once, walk past it, walk back, the state is still there — and the lazy native facade is meant to keep that contract intact. This restores it. A per-AST `WP_MySQL_Native_AST_Cache` is created lazily on the root and shared by reference with every wrapper that gets interned through it. Each accessor looks the returned wrapper's `native_node_index` up in the cache and either returns the canonical instance or registers the new one. `materialize_native_children()` pulls children through the same cache so any mutation a caller made through `get_first_child_node()` before the parent went through `append_child()` survives into `$this->children` — same instance, same mutations. Tokens are unchanged. The public token API has no mutators and no callers in this repo rely on `WP_MySQL_Token` identity; if that becomes a need we can extend the cache. ## What's in here - `class-wp-mysql-native-ast-cache.php` — small holder, one per AST. - Native node accessors run results through `intern()` / `intern_all()` / `intern_nodes()`. - `materialize_native_children()` reuses the interned wrappers so prior mutations don't get lost. - Regression test covering same-instance reads, descendant/child identity sharing, and the mutate-then-materialize edge case. Skips when the native extension isn't loaded. - `run-native-ast-walk-benchmark.php` parses the MySQL server suite, walks each AST, and reports `parsed`, `walked_nodes`, `duration`, `peak_mem`, and an identity-stability flag. - `Native AST Walk Perf` workflow runs the benchmark on this PR and on the PR base (`codex/native-lazy-ast-facade`) on the same runner, so the identity-cache cost is measured apples-to-apples on every push. ## Performance Benchmarked on CI against the no-cache baseline, same runner, same corpus (69,567 queries, 4.8M walked nodes). Full numbers in PR comments — links below. | scenario | baseline (no cache) | this PR | delta | |---|---|---|---| | native parse only | 1.28s | 1.28s | 0% | | native walk duration | 3.52s | 3.33s | **+5% (+0.19s)** | | native walk qps | 19,766 | 20,884 | **−5%** | | native walk peak memory | 50.0MB | 60.5MB | **+10.5MB (+21%)** | | native walk identity stable | **FALSE** | true | regression fixed | Hot-path optimization in `intern_all()` (cache reference hoisted out of the loop, per-item logic inlined) plus a typed `intern_nodes()` fast path for accessors that return only nodes brought the time penalty down from an initial +17% to +5%. Memory delta is structural — one retained PHP wrapper per visited node — and is the price of stable identity. Native walk is still ~5× faster than the pure-PHP path while now preserving `WP_Parser_Node` semantics. - First measurement: https://github.com/WordPress/sqlite-database-integration/pull/391#issuecomment-4354700002 - After hot-path optimization: https://github.com/WordPress/sqlite-database-integration/pull/391#issuecomment-4354952752 ## Test plan - [x] PHPUnit suite passes on the pure-PHP path. - [x] PHPUnit suite passes with the native extension loaded; the new identity tests run instead of skipping. - [x] `Native AST Walk Perf` workflow reports the cache cost vs. the PR base on every push. --- .../mysql-parser-extension-tests.yml | 9 +- .github/workflows/native-ast-perf.yml | 216 ++++++++++++++++++ .github/workflows/wp-tests-phpunit.yml | 4 +- packages/mysql-on-sqlite/src/load.php | 1 + .../src/mysql/class-wp-mysql-parser.php | 11 + .../class-wp-mysql-native-ast-cache.php | 23 ++ .../class-wp-mysql-native-parser-node.php | 162 ++++++++++++- .../sqlite/class-wp-pdo-mysql-on-sqlite.php | 27 ++- ...ySQL_Native_Parser_Node_Identity_Tests.php | 118 ++++++++++ .../tools/run-native-ast-walk-benchmark.php | 186 +++++++++++++++ packages/php-ext-wp-mysql-parser/src/lib.rs | 142 ++++++++++-- 11 files changed, 862 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/native-ast-perf.yml create mode 100644 packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php create mode 100644 packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php create mode 100644 packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 5f8e49d3..234bb5c7 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -81,7 +81,10 @@ jobs: exit( 1 ); } ' - ./vendor/bin/phpunit -c ./phpunit.xml.dist tests/mysql/WP_MySQL_Lexer_Tests.php tests/parser/WP_Parser_Node_Tests.php + working-directory: packages/mysql-on-sqlite + + - name: Run PHPUnit tests with parser extension + run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist working-directory: packages/mysql-on-sqlite sqlite-driver-extension-tests: @@ -149,3 +152,7 @@ jobs: exit( 1 ); } ' + + - name: Run PHPUnit tests with SQLite driver using parser extension + run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist + working-directory: packages/mysql-on-sqlite diff --git a/.github/workflows/native-ast-perf.yml b/.github/workflows/native-ast-perf.yml new file mode 100644 index 00000000..9b51eb8e --- /dev/null +++ b/.github/workflows/native-ast-perf.yml @@ -0,0 +1,216 @@ +name: Native AST Walk Perf + +on: + push: + paths: + - '.github/workflows/native-ast-perf.yml' + - 'packages/mysql-on-sqlite/src/mysql/native/**' + - 'packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php' + - 'packages/php-ext-wp-mysql-parser/**' + pull_request: + paths: + - '.github/workflows/native-ast-perf.yml' + - 'packages/mysql-on-sqlite/src/mysql/native/**' + - 'packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php' + - 'packages/php-ext-wp-mysql-parser/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + perf: + name: Native AST walk benchmark + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install native build dependencies + run: | + sudo apt-get update + sudo apt-get install -y libclang-dev + echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV" + LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)" + echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV" + + - name: Install Composer dependencies (root) + uses: ramsey/composer-install@v3 + with: + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Install Composer dependencies (mysql-on-sqlite) + uses: ramsey/composer-install@v3 + with: + working-directory: packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Download MySQL test query corpus + working-directory: packages/mysql-on-sqlite + run: bash tests/tools/mysql-download-tests.sh || true + + - name: Build parser extension (release) + run: cargo build --release + working-directory: packages/php-ext-wp-mysql-parser + + - name: Locate built extension + run: | + EXT="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so" + test -f "$EXT" || { echo "Extension not built at $EXT"; exit 1; } + echo "NATIVE_EXT=$EXT" >> "$GITHUB_ENV" + + - name: Benchmark — pure-PHP path (parse only) + working-directory: packages/mysql-on-sqlite + run: | + php tests/tools/run-native-ast-walk-benchmark.php --no-walk | tee php-parse-only.txt + + - name: Benchmark — pure-PHP path (walk) + working-directory: packages/mysql-on-sqlite + run: | + php tests/tools/run-native-ast-walk-benchmark.php | tee php-walk.txt + + - name: Benchmark — native path (parse only) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk | tee native-parse-only.txt + + - name: Benchmark — native path (walk, with identity cache) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php | tee native-walk.txt + + - name: Check out baseline (PR base, no identity cache) + run: | + git fetch --no-tags --depth=1 origin codex/native-lazy-ast-facade + git worktree add ../baseline FETCH_HEAD + + - name: Install Composer dependencies (baseline mysql-on-sqlite) + uses: ramsey/composer-install@v3 + with: + working-directory: ../baseline/packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Build baseline parser extension (release) + run: cargo build --release + working-directory: ../baseline/packages/php-ext-wp-mysql-parser + + - name: Stage benchmark + corpus into baseline + run: | + mkdir -p ../baseline/packages/mysql-on-sqlite/tests/mysql/data + cp packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php \ + ../baseline/packages/mysql-on-sqlite/tests/tools/ + cp packages/mysql-on-sqlite/tests/mysql/data/*.csv \ + ../baseline/packages/mysql-on-sqlite/tests/mysql/data/ 2>/dev/null || true + + - name: Benchmark — baseline native path (walk, no identity cache) + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-walk.txt" + + - name: Benchmark — baseline native path (parse only) + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-parse-only.txt" + + # Hit-heavy scenarios — these are where the per-AST identity cache is + # supposed to win. The baseline reallocates wrappers on every accessor + # call, while the PR reuses them. Run on both to make the gap visible. + - name: Benchmark — native rewalk x10 (this PR) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 \ + | tee native-rewalk.txt + + - name: Benchmark — baseline rewalk x10 + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-rewalk.txt" + + - name: Benchmark — native reread x20 (this PR) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=reread --repeat=20 \ + | tee native-reread.txt + + - name: Benchmark — baseline reread x20 + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=reread --repeat=20 \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-reread.txt" + + - name: Benchmark — native subtree x5 (this PR) + working-directory: packages/mysql-on-sqlite + run: | + php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 \ + | tee native-subtree.txt + + - name: Benchmark — baseline subtree x5 + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 \ + | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-subtree.txt" + + - name: Summarize + if: always() + working-directory: packages/mysql-on-sqlite + run: | + extract() { + # Pull a numeric field (e.g. duration=1.23s) from a benchmark + # output line. Returns "n/a" if missing. + local file="$1" key="$2" + [ -f "$file" ] || { echo "n/a"; return; } + grep -oE "${key}=[^ ]+" "$file" | head -1 | cut -d= -f2 || echo "n/a" + } + + { + echo '### Native AST walk perf' + echo + echo '| scenario | result |' + echo '|---|---|' + for f in php-parse-only.txt php-walk.txt native-parse-only.txt native-walk.txt baseline-native-parse-only.txt baseline-native-walk.txt native-rewalk.txt baseline-native-rewalk.txt native-reread.txt baseline-native-reread.txt native-subtree.txt baseline-native-subtree.txt; do + [ -f "$f" ] || continue + line="$(cat "$f")" + echo "| ${f%.txt} | \`$line\` |" + done + echo + echo '### Identity-cache cost (native walk: with cache vs PR base without)' + echo + echo '| metric | with cache (this PR) | baseline | delta |' + echo '|---|---|---|---|' + for key in duration qps peak_mem walked_nodes; do + with="$(extract native-walk.txt "$key")" + base="$(extract baseline-native-walk.txt "$key")" + echo "| $key | $with | $base | — |" + done + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload raw output + if: always() + uses: actions/upload-artifact@v4 + with: + name: native-ast-perf-${{ github.run_id }} + path: packages/mysql-on-sqlite/*.txt + if-no-files-found: warn diff --git a/.github/workflows/wp-tests-phpunit.yml b/.github/workflows/wp-tests-phpunit.yml index f2c8faf6..31db5ef0 100644 --- a/.github/workflows/wp-tests-phpunit.yml +++ b/.github/workflows/wp-tests-phpunit.yml @@ -54,8 +54,8 @@ jobs: - name: Build and load parser extension in WordPress PHP containers run: bash .github/workflows/wp-tests-phpunit-native-extension-setup.sh - - name: Verify WordPress uses parser extension - run: cd wordpress && node tools/local-env/scripts/docker.js run --rm php php /var/www/native-verify-extension.php + - name: Run WordPress PHPUnit tests with parser extension + run: node .github/workflows/wp-tests-phpunit-run.js - name: Stop Docker containers if: always() diff --git a/packages/mysql-on-sqlite/src/load.php b/packages/mysql-on-sqlite/src/load.php index 2070c631..4cbb4208 100644 --- a/packages/mysql-on-sqlite/src/load.php +++ b/packages/mysql-on-sqlite/src/load.php @@ -27,6 +27,7 @@ if ( class_exists( 'WP_MySQL_Native_Parser', false ) ) { require_once __DIR__ . '/mysql/native/mysql-rust-bridge.php'; + require_once __DIR__ . '/mysql/native/class-wp-mysql-native-ast-cache.php'; require_once __DIR__ . '/mysql/native/class-wp-mysql-native-parser-node.php'; require_once __DIR__ . '/mysql/native/class-wp-mysql-parser.php'; } else { diff --git a/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php b/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php index f291064e..69282b9c 100644 --- a/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php +++ b/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php @@ -8,6 +8,17 @@ class WP_MySQL_Parser extends WP_Parser { */ private $current_ast; + /** + * Reset this parser with a new token stream. + * + * @param array $tokens The parser tokens. + */ + public function reset_tokens( array $tokens ): void { + $this->tokens = $tokens; + $this->position = 0; + $this->current_ast = null; + } + /** * Parse the next query from the input SQL string. * diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php new file mode 100644 index 00000000..f21bb785 --- /dev/null +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php @@ -0,0 +1,23 @@ + WP_MySQL_Native_Parser_Node. + * + * @var array + */ + public $nodes = array(); +} diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index e796bf68..fae84b4c 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -8,12 +8,29 @@ * caller actually walks the tree. On the first mutation (append_child or * merge_fragment), the node materializes its children into the inherited * `$children` array and behaves like a plain WP_Parser_Node from then on. + * + * Wrappers returned by accessors are interned through a per-AST identity + * map (WP_MySQL_Native_AST_Cache) so two reads of the same logical node + * yield the same PHP instance. This preserves the WP_Parser_Node contract + * that mutations performed on a child via `get_first_child_node()` remain + * visible when the same child is reached again, including after the parent + * has materialized. */ class WP_MySQL_Native_Parser_Node extends WP_Parser_Node { private $native_ast = null; private $native_node_index = null; private $was_mutated = false; + /** + * Per-AST identity map shared between every interned wrapper. + * + * Created lazily on the first child access; the root wrapper is the + * first entry. Children inherit the same cache instance by reference. + * + * @var WP_MySQL_Native_AST_Cache|null + */ + private $cache = null; + public function __construct( $rule_id, $rule_name, $native_ast = null, $native_node_index = null ) { parent::__construct( $rule_id, $rule_name ); @@ -21,6 +38,19 @@ public function __construct( $rule_id, $rule_name, $native_ast = null, $native_n $this->native_node_index = $native_node_index; } + /** + * Native node index in the Rust-owned arena. + * + * Exposed so the identity cache can key on it. Returns null after + * the wrapper has materialized — at that point the node is detached + * from the native arena and behaves like a plain WP_Parser_Node. + * + * @return int|null + */ + public function get_native_node_index(): ?int { + return $this->native_node_index; + } + /** @inheritDoc */ public function append_child( $node ) { $this->materialize_native_children(); @@ -65,7 +95,7 @@ public function get_first_child() { if ( $this->was_mutated() ) { return parent::get_first_child(); } - return wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ); + return $this->intern( wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ) ); } /** @inheritDoc */ @@ -73,7 +103,7 @@ public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_No if ( $this->was_mutated() ) { return parent::get_first_child_node( $rule_name ); } - return wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ); + return $this->intern( wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -89,7 +119,7 @@ public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Pars if ( $this->was_mutated() ) { return parent::get_first_descendant_node( $rule_name ); } - return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ); + return $this->intern( wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -105,7 +135,7 @@ public function get_children(): array { if ( $this->was_mutated() ) { return parent::get_children(); } - return wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); + return $this->intern_all( wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ) ); } /** @inheritDoc */ @@ -113,7 +143,7 @@ public function get_child_nodes( ?string $rule_name = null ): array { if ( $this->was_mutated() ) { return parent::get_child_nodes( $rule_name ); } - return wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ); + return $this->intern_nodes( wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -129,7 +159,7 @@ public function get_descendants(): array { if ( $this->was_mutated() ) { return parent::get_descendants(); } - return wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ); + return $this->intern_all( wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ) ); } /** @inheritDoc */ @@ -137,7 +167,7 @@ public function get_descendant_nodes( ?string $rule_name = null ): array { if ( $this->was_mutated() ) { return parent::get_descendant_nodes( $rule_name ); } - return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ); + return $this->intern_nodes( wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); } /** @inheritDoc */ @@ -168,12 +198,128 @@ private function was_mutated(): bool { return $this->was_mutated; } + /** + * Intern a single accessor return value through the per-AST cache. + * + * Tokens and nulls pass through untouched. Native node wrappers are + * keyed on their `native_node_index`: on cache miss, the freshly + * constructed wrapper is stored and given the cache reference; on + * cache hit, the canonical instance is returned and the new wrapper + * is discarded so callers see stable identity and surviving mutations. + * + * @param mixed $value Return value from the Rust bridge. + * @return mixed + */ + private function intern( $value ) { + if ( ! $value instanceof WP_MySQL_Native_Parser_Node ) { + return $value; + } + + $cache = $this->ensure_cache(); + $index = $value->native_node_index; + if ( null === $index ) { + return $value; + } + if ( isset( $cache->nodes[ $index ] ) ) { + return $cache->nodes[ $index ]; + } + $value->cache = $cache; + $cache->nodes[ $index ] = $value; + return $value; + } + + /** + * Intern every entry in an accessor return array. + * + * Hot path: this runs once per descendant when a caller walks the tree, + * so cache lookup and the cache-miss write are inlined and the cache + * reference is hoisted out of the loop. + * + * @param array $values + * @return array + */ + private function intern_all( array $values ): array { + if ( ! $values ) { + return $values; + } + $cache = $this->cache ?? $this->ensure_cache(); + $nodes = &$cache->nodes; + foreach ( $values as $i => $value ) { + if ( ! $value instanceof WP_MySQL_Native_Parser_Node ) { + continue; + } + $index = $value->native_node_index; + if ( null === $index ) { + continue; + } + if ( isset( $nodes[ $index ] ) ) { + $values[ $i ] = $nodes[ $index ]; + } else { + $value->cache = $cache; + $nodes[ $index ] = $value; + } + } + return $values; + } + + /** + * Intern array of guaranteed-node results (no token/null mixing). + * + * Used by `get_child_nodes()` / `get_descendant_nodes()` whose Rust + * bridge returns only WP_MySQL_Native_Parser_Node instances. Skips + * the per-item `instanceof` check that intern_all() must do for the + * mixed `get_children()` / `get_descendants()` arrays. + * + * @param array $values + * @return array + */ + private function intern_nodes( array $values ): array { + if ( ! $values ) { + return $values; + } + $cache = $this->cache ?? $this->ensure_cache(); + $nodes = &$cache->nodes; + foreach ( $values as $i => $value ) { + $index = $value->native_node_index; + if ( isset( $nodes[ $index ] ) ) { + $values[ $i ] = $nodes[ $index ]; + } else { + $value->cache = $cache; + $nodes[ $index ] = $value; + } + } + return $values; + } + + /** + * Lazily build (or reuse) the per-AST identity map. + * + * The root wrapper is constructed without a cache, so the first time + * any accessor needs to intern a child, it creates the cache and + * registers itself as the root entry. Subsequent interns on this + * wrapper or any descendant share the same cache by reference. + * + * @return WP_MySQL_Native_AST_Cache + */ + private function ensure_cache(): WP_MySQL_Native_AST_Cache { + if ( null === $this->cache ) { + $this->cache = new WP_MySQL_Native_AST_Cache(); + if ( null !== $this->native_node_index ) { + $this->cache->nodes[ $this->native_node_index ] = $this; + } + } + return $this->cache; + } + private function materialize_native_children(): void { if ( $this->was_mutated ) { return; } - $this->children = wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ); + // Pull children through the cache so any wrapper a caller already + // mutated via get_first_child_node() etc. survives the transition + // into $this->children — same instance, same mutations. + $this->children = $this->intern_all( wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ) ); $this->native_ast = null; $this->native_node_index = null; $this->was_mutated = true; diff --git a/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php index b3653ffe..a8ddf146 100644 --- a/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php @@ -410,6 +410,13 @@ class WP_PDO_MySQL_On_SQLite extends PDO { */ private static $mysql_grammar; + /** + * A reusable parser instance for MySQL queries. + * + * @var WP_MySQL_Parser|null + */ + private $mysql_parser = null; + /** * The main database name. * @@ -1160,11 +1167,27 @@ public function create_parser( string $query ): WP_MySQL_Parser { ); if ( $lexer instanceof WP_MySQL_Native_Lexer ) { $tokens = $lexer->native_token_stream(); - return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + return $this->reset_or_create_parser( $tokens ); } $tokens = $lexer->remaining_tokens(); - return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + return $this->reset_or_create_parser( $tokens ); + } + + /** + * Reset the reusable parser with new tokens or create it on first use. + * + * @param array|object $tokens Parser tokens. + * @return WP_MySQL_Parser A parser initialized for the token stream. + */ + private function reset_or_create_parser( $tokens ): WP_MySQL_Parser { + if ( null === $this->mysql_parser || ! method_exists( $this->mysql_parser, 'reset_tokens' ) ) { + $this->mysql_parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + } else { + $this->mysql_parser->reset_tokens( $tokens ); + } + + return $this->mysql_parser; } /** diff --git a/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php new file mode 100644 index 00000000..bd3f8ffe --- /dev/null +++ b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php @@ -0,0 +1,118 @@ +markTestSkipped( 'Native MySQL parser extension is not loaded.' ); + } + } + + private function parse( string $sql ): WP_Parser_Node { + static $grammar = null; + if ( null === $grammar ) { + $grammar = new WP_Parser_Grammar( include __DIR__ . '/../../../src/mysql/mysql-grammar.php' ); + } + $lexer = new WP_MySQL_Lexer( $sql ); + $tokens = $lexer instanceof WP_MySQL_Native_Lexer + ? $lexer->native_token_stream() + : $lexer->remaining_tokens(); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + $tree = $parser->parse(); + $this->assertNotNull( $tree, 'Failed to parse SQL: ' . $sql ); + return $tree; + } + + public function test_get_first_child_node_returns_same_instance(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $first = $tree->get_first_child_node(); + $second = $tree->get_first_child_node(); + + $this->assertNotNull( $first ); + $this->assertSame( $first, $second ); + } + + public function test_get_children_returns_same_instances_across_calls(): void { + $tree = $this->parse( 'SELECT 1, 2, 3' ); + + $first_pass = $tree->get_children(); + $second_pass = $tree->get_children(); + + $this->assertSameSize( $first_pass, $second_pass ); + foreach ( $first_pass as $i => $child ) { + if ( $child instanceof WP_Parser_Node ) { + $this->assertSame( $child, $second_pass[ $i ] ); + } + } + } + + public function test_descendant_lookup_shares_identity_with_child_lookup(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $descendant = $tree->get_first_descendant_node(); + $this->assertNotNull( $descendant ); + + // Walk down to the same node via direct children. We don't know the + // exact depth, so we descend until we hit the descendant we found. + $cursor = $tree; + while ( null !== $cursor && $cursor !== $descendant ) { + $next = $cursor->get_first_child_node(); + if ( $next === $cursor ) { + break; + } + $cursor = $next; + } + + $this->assertSame( $descendant, $cursor, 'Descendant and child lookups must return the same wrapper instance.' ); + } + + public function test_mutation_on_child_survives_re_read(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $child = $tree->get_first_child_node(); + $this->assertNotNull( $child ); + + // Mutate via the public WP_Parser_Node API — this is exactly the + // kind of state the reviewer worried would be lost when accessors + // hand back fresh wrappers. rule_name is a declared public property + // that the parser itself sets, so PHP 8.2's dynamic-property + // deprecation does not apply here. + $child->rule_name = 'mutated-rule'; + + $same_child = $tree->get_first_child_node(); + $this->assertSame( $child, $same_child ); + $this->assertSame( 'mutated-rule', $same_child->rule_name ); + } + + public function test_mutation_survives_parent_materialization(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $child = $tree->get_first_child_node(); + $this->assertNotNull( $child ); + $child->rule_name = 'before-materialize'; + + // Force the parent to materialize its native children by appending + // a sibling. After this, the parent walks $this->children directly. + $sibling = new WP_Parser_Node( 0, 'synthetic' ); + $tree->append_child( $sibling ); + + $children = $tree->get_children(); + $this->assertContains( $child, $children, 'Materialized children must include the previously-mutated wrapper.' ); + $this->assertSame( 'before-materialize', $child->rule_name ); + } +} diff --git a/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php new file mode 100644 index 00000000..3927d5c0 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php @@ -0,0 +1,186 @@ +native_token_stream() + : $lexer->remaining_tokens(); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + $ast = $parser->parse(); + if ( null === $ast ) { + ++$failures; + continue; + } + ++$total; + + switch ( $mode ) { + case 'no-walk': + break; + + case 'walk': + $descendants = $ast->get_descendants(); + $walked += count( $descendants ); + + $first = $ast->get_first_child_node(); + if ( null !== $first ) { + $again = $ast->get_first_child_node(); + if ( $first !== $again ) { + $identity_ok = false; + } + } + break; + + case 'rewalk': + // Repeated full-tree walks. After the first pass every wrapper + // the cache returns is a hit; without the cache, every pass + // re-allocates wrappers for the entire tree from scratch. + for ( $r = 0; $r < $repeat; $r++ ) { + $descendants = $ast->get_descendants(); + $walked += count( $descendants ); + } + break; + + case 'reread': + // Repeated top-level child reads. Models analysis passes that + // keep poking at the root of the tree. + for ( $r = 0; $r < $repeat; $r++ ) { + $child = $ast->get_first_child_node(); + if ( null !== $child ) { + ++$walked; + // Identity must hold across repeated reads. + if ( $r > 0 && $child !== $prev ) { + $identity_ok = false; + } + $prev = $child; + } + } + break; + + case 'subtree': + // Walk descendants once, then for each descendant re-read its + // first child N times. Models translator/rewriter passes that + // re-enter previously visited subtrees. + $descendants = $ast->get_descendants(); + foreach ( $descendants as $d ) { + if ( ! $d instanceof WP_Parser_Node ) { + continue; + } + for ( $r = 0; $r < $repeat; $r++ ) { + $child = $d->get_first_child_node(); + if ( null !== $child ) { + ++$walked; + } + } + } + break; + + default: + fwrite( STDERR, "Unknown mode: $mode\n" ); + exit( 2 ); + } + } catch ( Throwable $e ) { + ++$failures; + } +} + +$duration = microtime( true ) - $start; +$peak_mb = memory_get_peak_usage( true ) / 1024 / 1024; +$native = class_exists( 'WP_MySQL_Native_Parser', false ) ? 'native' : 'php'; + +printf( + "path=%s mode=%s repeat=%d parsed=%d walked_nodes=%d failures=%d duration=%.4fs qps=%d peak_mem=%.1fMB identity_ok=%s\n", + $native, + $mode, + $repeat, + $total, + $walked, + $failures, + $duration, + $total > 0 ? (int) ( $total / $duration ) : 0, + $peak_mb, + $identity_ok ? 'true' : 'FALSE' +); + +if ( ! $identity_ok ) { + fwrite( STDERR, "Identity check failed: accessor returned different instances on repeat read.\n" ); + exit( 1 ); +} diff --git a/packages/php-ext-wp-mysql-parser/src/lib.rs b/packages/php-ext-wp-mysql-parser/src/lib.rs index 835dea1b..909ec379 100644 --- a/packages/php-ext-wp-mysql-parser/src/lib.rs +++ b/packages/php-ext-wp-mysql-parser/src/lib.rs @@ -93,10 +93,19 @@ fn update_object_property( } fn create_mysql_token(sql_zval: &Zval, token: TokenInfo, no_backslash: bool) -> PhpResult { + let classes = php_classes()?; + create_mysql_token_with_classes(sql_zval, token, no_backslash, &classes) +} + +fn create_mysql_token_with_classes( + sql_zval: &Zval, + token: TokenInfo, + no_backslash: bool, + classes: &PhpClasses, +) -> PhpResult { let id = token.id; let start = i64::try_from(token.start).map_err(php_error)?; let length = i64::try_from(token.end.saturating_sub(token.start)).map_err(php_error)?; - let classes = php_classes()?; let mut object = classes.mysql_token.new(); update_object_property(&mut object, classes.parser_token, "id", id)?; @@ -940,7 +949,7 @@ enum ParserTokenSource { } impl ParserTokenSource { - fn create_php_token(&self, index: usize) -> PhpResult { + fn create_php_token_with_classes(&self, index: usize, classes: &PhpClasses) -> PhpResult { match self { Self::Php(tokens) => tokens .get(index) @@ -955,7 +964,7 @@ impl ParserTokenSource { .get(index) .copied() .ok_or_else(|| php_error("Parser token index is out of range"))?; - create_mysql_token(sql_zval, token, *no_backslash) + create_mysql_token_with_classes(sql_zval, token, *no_backslash, classes) } } } @@ -1020,6 +1029,7 @@ struct NativeAstNode { children: Vec, first_token: Option, last_token: Option, + descendant_count: usize, } struct NativeAstArena { @@ -1049,10 +1059,12 @@ impl NativeAstArena { let index = self.nodes.len(); let mut first_token = None; let mut last_token = None; + let mut descendant_count = 0; for child in &children { match child { NativeAstChild::Node(child_index) => { if let Some(node) = self.nodes.get(*child_index) { + descendant_count += 1 + node.descendant_count; if first_token.is_none() { first_token = node.first_token; } @@ -1066,6 +1078,7 @@ impl NativeAstArena { first_token = Some(*token_index); } last_token = Some(*token_index); + descendant_count += 1; } } } @@ -1075,11 +1088,21 @@ impl NativeAstArena { children, first_token, last_token, + descendant_count, }); index } fn create_php_ast(&self, native_ast_zval: &Zval) -> PhpResult { + let classes = php_classes()?; + self.create_php_ast_with_classes(native_ast_zval, &classes) + } + + fn create_php_ast_with_classes( + &self, + native_ast_zval: &Zval, + classes: &PhpClasses, + ) -> PhpResult { match self.root { NativeAstRoot::No => Ok(Zval::null()), NativeAstRoot::Empty => { @@ -1087,14 +1110,22 @@ impl NativeAstArena { zval.set_bool(true); Ok(zval) } - NativeAstRoot::Node(index) => self.create_php_node(native_ast_zval, index), - NativeAstRoot::Token(index) => self.token_source.create_php_token(index), + NativeAstRoot::Node(index) => { + self.create_php_node_with_classes(native_ast_zval, index, classes) + } + NativeAstRoot::Token(index) => self + .token_source + .create_php_token_with_classes(index, classes), } } - fn create_php_node(&self, native_ast_zval: &Zval, index: usize) -> PhpResult { + fn create_php_node_with_classes( + &self, + native_ast_zval: &Zval, + index: usize, + classes: &PhpClasses, + ) -> PhpResult { let node = self.node(index)?; - let classes = php_classes()?; let mut object = classes.native_parser_node.new(); let rule_name = self .grammar @@ -1137,10 +1168,19 @@ impl NativeAstArena { .ok_or_else(|| php_error("Native AST node index is out of range")) } - fn child_to_zval(&self, native_ast_zval: &Zval, child: NativeAstChild) -> PhpResult { + fn child_to_zval_with_classes( + &self, + native_ast_zval: &Zval, + child: NativeAstChild, + classes: &PhpClasses, + ) -> PhpResult { match child { - NativeAstChild::Node(index) => self.create_php_node(native_ast_zval, index), - NativeAstChild::Token(index) => self.token_source.create_php_token(index), + NativeAstChild::Node(index) => { + self.create_php_node_with_classes(native_ast_zval, index, classes) + } + NativeAstChild::Token(index) => self + .token_source + .create_php_token_with_classes(index, classes), } } @@ -1172,8 +1212,9 @@ impl NativeAstArena { } fn descendant_stack(&self, index: usize) -> PhpResult> { - let mut stack = self.node(index)?.children.clone(); - stack.reverse(); + let node = self.node(index)?; + let mut stack = Vec::with_capacity(node.descendant_count); + stack.extend(node.children.iter().rev().copied()); Ok(stack) } } @@ -1238,6 +1279,7 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child( node_index: i64, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let Some(child) = ast .arena .node(native_ast_node_index(node_index)?)? @@ -1247,7 +1289,8 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child( else { return Ok(Zval::null()); }; - ast.arena.child_to_zval(native_ast_zval, child) + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes) } #[php_function] @@ -1257,9 +1300,12 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child_node( rule_name: Option, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; for child in &ast.arena.node(native_ast_node_index(node_index)?)?.children { if ast.arena.child_node_matches(*child, rule_name.as_deref()) { - return ast.arena.child_to_zval(native_ast_zval, *child); + return ast + .arena + .child_to_zval_with_classes(native_ast_zval, *child, &classes); } } Ok(Zval::null()) @@ -1272,9 +1318,12 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child_token( token_id: Option, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; for child in &ast.arena.node(native_ast_node_index(node_index)?)?.children { if ast.arena.child_token_matches(*child, token_id) { - return ast.arena.child_to_zval(native_ast_zval, *child); + return ast + .arena + .child_to_zval_with_classes(native_ast_zval, *child, &classes); } } Ok(Zval::null()) @@ -1287,12 +1336,15 @@ pub fn wp_sqlite_mysql_native_ast_get_first_descendant_node( rule_name: Option, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { if ast.arena.child_node_matches(child, rule_name.as_deref()) { - return ast.arena.child_to_zval(native_ast_zval, child); + return ast + .arena + .child_to_zval_with_classes(native_ast_zval, child, &classes); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1310,12 +1362,15 @@ pub fn wp_sqlite_mysql_native_ast_get_first_descendant_token( token_id: Option, ) -> PhpResult { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { if ast.arena.child_token_matches(child, token_id) { - return ast.arena.child_to_zval(native_ast_zval, child); + return ast + .arena + .child_to_zval_with_classes(native_ast_zval, child, &classes); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1332,12 +1387,16 @@ pub fn wp_sqlite_mysql_native_ast_get_children( node_index: i64, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; ast.arena .node(native_ast_node_index(node_index)?)? .children .iter() .copied() - .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .map(|child| { + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes) + }) .collect() } @@ -1348,13 +1407,17 @@ pub fn wp_sqlite_mysql_native_ast_get_child_nodes( rule_name: Option, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; ast.arena .node(native_ast_node_index(node_index)?)? .children .iter() .copied() .filter(|child| ast.arena.child_node_matches(*child, rule_name.as_deref())) - .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .map(|child| { + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes) + }) .collect() } @@ -1365,13 +1428,17 @@ pub fn wp_sqlite_mysql_native_ast_get_child_tokens( token_id: Option, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; ast.arena .node(native_ast_node_index(node_index)?)? .children .iter() .copied() .filter(|child| ast.arena.child_token_matches(*child, token_id)) - .map(|child| ast.arena.child_to_zval(native_ast_zval, child)) + .map(|child| { + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes) + }) .collect() } @@ -1381,12 +1448,17 @@ pub fn wp_sqlite_mysql_native_ast_get_descendants( node_index: i64, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; - let mut descendants = Vec::new(); + let classes = php_classes()?; + let root = ast.arena.node(native_ast_node_index(node_index)?)?; + let mut descendants = Vec::with_capacity(root.descendant_count); let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { - descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + descendants.push( + ast.arena + .child_to_zval_with_classes(native_ast_zval, child, &classes)?, + ); if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { stack.push(*child); @@ -1403,13 +1475,18 @@ pub fn wp_sqlite_mysql_native_ast_get_descendant_nodes( rule_name: Option, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let mut descendants = Vec::new(); let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { if ast.arena.child_node_matches(child, rule_name.as_deref()) { - descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + descendants.push(ast.arena.child_to_zval_with_classes( + native_ast_zval, + child, + &classes, + )?); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1427,13 +1504,18 @@ pub fn wp_sqlite_mysql_native_ast_get_descendant_tokens( token_id: Option, ) -> PhpResult> { let ast = native_ast(native_ast_zval)?; + let classes = php_classes()?; let mut descendants = Vec::new(); let mut stack = ast .arena .descendant_stack(native_ast_node_index(node_index)?)?; while let Some(child) = stack.pop() { if ast.arena.child_token_matches(child, token_id) { - descendants.push(ast.arena.child_to_zval(native_ast_zval, child)?); + descendants.push(ast.arena.child_to_zval_with_classes( + native_ast_zval, + child, + &classes, + )?); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1505,6 +1587,18 @@ impl WpMySqlNativeParser { }) } + pub fn reset_tokens(&mut self, tokens: &mut Zval) -> PhpResult<()> { + let (token_source, token_ids) = export_tokens(tokens)?; + + self.token_source = Arc::new(token_source); + self.token_ids = token_ids; + self.position = 0; + self.current_ast = None; + self.current_php_ast = None; + + Ok(()) + } + pub fn parse(&mut self) -> PhpResult { stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { let ast = self.parse_native_ast()?; From b47b74831294155a71313c60ed15b3c25cd73249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 15:23:07 +0200 Subject: [PATCH 32/40] Move native AST identity cache into the Rust extension (#392) Stacked on #391. #391 restored `WP_Parser_Node` identity semantics by interning native child wrappers in PHP. That fixed correctness, but the PHP-side cache formed a retention cycle and added measurable cost to hit-heavy translator-style workloads. This PR moves native wrapper identity out of PHP object properties entirely: - `WP_MySQL_Native_Parser_Node` no longer stores `$native_ast`, `$native_node_index`, or a PHP-side identity-cache object. - Native bridge calls now pass the wrapper itself, e.g. `wp_sqlite_mysql_native_ast_get_children( $this )`. - The Rust extension keeps a thread-local registry keyed by the PHP wrapper object pointer. - Registry entries map wrapper pointer -> `(NativeAstState, node_index, is_materialized)`, and each AST keeps a node-index -> wrapper-pointer cache. - Cached wrapper hits return the existing PHP object by pointer with its refcount bumped; the cache does not own a PHP reference. - `__destruct()` releases a wrapper from the Rust registry. Materialization marks the wrapper as detached from native reads while leaving it discoverable from the parent cache as long as it is still live. That breaks the cycle that mattered here: PHP wrappers no longer strongly reference a native AST object, and Rust no longer strongly references PHP wrappers. PHP's cycle collector can collect wrapper graphs normally; destructors then clean up the Rust registry entries. Tokens remain un-interned. The public token API has no mutators, and no caller in this repo relies on token object identity. ## Perf numbers From the passing `Native AST Walk Perf` CI run on this head (`2d93be25f599c3c4482480a6ab644d61b9337b12`), comparing this PR to the native no-cache baseline (`codex/native-lazy-ast-facade`): | Scenario | This PR | Baseline | Duration delta | Peak memory delta | |---|---:|---:|---:|---:| | parse only | 1.2859s, 54,098 qps, 30.0MB | 1.2715s, 54,711 qps, 30.0MB | +1.1% | 0.0% | | walk x1 | 3.3265s, 20,912 qps, 48.0MB | 3.4191s, 20,346 qps, 60.5MB | -2.7% | -20.7% | | rewalk x10 | 8.6352s, 8,056 qps, 52.0MB | 16.9658s, 4,100 qps, 90.5MB | -49.1% | -42.5% | | reread x20 | 1.8618s, 37,364 qps, 30.0MB | 2.4322s, 28,602 qps, 38.0MB | -23.5% | -21.1% | | subtree x5 | 9.8274s, 7,078 qps, 48.0MB | 13.8921s, 5,007 qps, 64.5MB | -29.3% | -25.6% | The parse-only path is effectively unchanged. The repeated-access workloads this cache is meant to help are materially faster and use less peak memory. For context, the same CI run measured the pure-PHP path at: | Scenario | Pure PHP | |---|---:| | parse only | 13.5292s, 5,142 qps, 68.0MB | | walk x1 | 16.1332s, 4,312 qps, 70.0MB | ## Safety coverage The PR now includes native-extension tests for: - stable wrapper identity across repeated child/descendant reads; - no reflected `$native_ast` / `$native_node_index` properties on wrappers; - child mutations surviving repeat reads and parent materialization; - materialized children remaining discoverable from a still-native parent; - repeated parse/walk/drop loops staying memory-bounded; - dropping root and descendant wrappers reclaiming registry entries; - child wrappers outliving root variables without use-after-free; - overlapping AST lifetimes not corrupting each other; - mutation-before-drop and rewalk loops staying memory-bounded. CI smoke checks also assert the SQLite-driver and WordPress test-container paths select the native wrapper model and do not regress back to `$native_ast` storage. ## Test plan - [x] `cargo check` for `packages/php-ext-wp-mysql-parser` - [x] `cargo fmt --check` for `packages/php-ext-wp-mysql-parser` - [x] PHP lint on changed PHP files - [x] Focused native identity/cycle PHPUnit tests with the Rust extension loaded - [x] Full `packages/mysql-on-sqlite` PHPUnit suite with the Rust extension loaded - [x] Full GitHub Actions suite on PR head - [x] Native AST Walk Perf workflow on PR head --- .../mysql-parser-extension-tests.yml | 26 +- .github/workflows/phpunit-tests-run.yml | 25 +- ...wp-tests-phpunit-native-extension-setup.sh | 30 +- packages/mysql-on-sqlite/src/load.php | 1 - .../class-wp-mysql-native-ast-cache.php | 23 - .../class-wp-mysql-native-parser-node.php | 198 +------ ...P_MySQL_Native_Parser_Node_Cycle_Tests.php | 267 +++++++++ ...ySQL_Native_Parser_Node_Identity_Tests.php | 32 +- packages/php-ext-wp-mysql-parser/src/lib.rs | 513 ++++++++++-------- 9 files changed, 667 insertions(+), 448 deletions(-) delete mode 100644 packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php create mode 100644 packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Cycle_Tests.php diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 234bb5c7..40586ea3 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -144,13 +144,31 @@ jobs: $parser = $driver->create_parser( "SELECT 1" ); $parser->next_query(); $ast = $parser->get_query_ast(); - $property = ( new ReflectionClass( $ast ) )->getProperty( "native_ast" ); - $property->setAccessible( true ); - $native_ast = $property->getValue( $ast ); - if ( ! is_object( $native_ast ) || "WP_MySQL_Native_Ast" !== get_class( $native_ast ) ) { + if ( ! ( $ast instanceof WP_MySQL_Native_Parser_Node ) ) { fwrite( STDERR, "SQLite driver did not return a native-backed AST.\n" ); exit( 1 ); } + $reflection = new ReflectionObject( $ast ); + if ( $reflection->hasProperty( "native_ast" ) || $reflection->hasProperty( "native_node_index" ) ) { + fwrite( STDERR, "Native wrapper still stores Rust AST handle properties.\n" ); + exit( 1 ); + } + $first = $ast->get_first_child_node(); + if ( ! ( $first instanceof WP_MySQL_Native_Parser_Node ) ) { + fwrite( STDERR, "Native wrapper did not return a native-backed child node.\n" ); + exit( 1 ); + } + if ( $first !== $ast->get_first_child_node() ) { + fwrite( STDERR, "Native wrapper identity is not stable across reads.\n" ); + exit( 1 ); + } + $synthetic = new WP_Parser_Node( 0, "synthetic" ); + $first->append_child( $synthetic ); + $same_first = $ast->get_first_child_node(); + if ( $same_first !== $first || ! in_array( $synthetic, $same_first->get_children(), true ) ) { + fwrite( STDERR, "Materialized native wrapper was lost from the parent cache.\n" ); + exit( 1 ); + } ' - name: Run PHPUnit tests with SQLite driver using parser extension diff --git a/.github/workflows/phpunit-tests-run.yml b/.github/workflows/phpunit-tests-run.yml index 1ed1bdc4..f194feec 100644 --- a/.github/workflows/phpunit-tests-run.yml +++ b/.github/workflows/phpunit-tests-run.yml @@ -38,8 +38,31 @@ jobs: else TAG="version-${VERSION}" fi - wget -O sqlite.tar.gz "https://sqlite.org/src/tarball/sqlite.tar.gz?r=${TAG}" + SQLITE_SOURCE="https://sqlite.org/src/tarball/sqlite.tar.gz?r=${TAG}" + SQLITE_MIRROR="https://github.com/sqlite/sqlite/archive/refs/tags/${TAG}.tar.gz" + DOWNLOADED=0 + for url in "$SQLITE_SOURCE" "$SQLITE_MIRROR"; do + for attempt in 1 2 3 4 5; do + if wget -O sqlite.tar.gz "$url"; then + DOWNLOADED=1 + break 2 + fi + if [ "$attempt" -lt 5 ]; then + sleep $(( attempt * 10 )) + fi + done + done + if [ "$DOWNLOADED" -ne 1 ]; then + exit 1 + fi tar xzf sqlite.tar.gz + if [ ! -d sqlite ]; then + SQLITE_DIR=$(find . -maxdepth 1 -type d -name 'sqlite-*' | head -n 1) + if [ -z "$SQLITE_DIR" ]; then + exit 1 + fi + mv "$SQLITE_DIR" sqlite + fi cd sqlite ./configure --prefix=/usr/local CFLAGS="-DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_FTS5 -DSQLITE_USE_URI -DSQLITE_ENABLE_JSON1" LDFLAGS="-lm" make -j$(nproc) diff --git a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh index 97e870e8..241bc2db 100644 --- a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -125,14 +125,36 @@ $driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' $parser = $driver->create_parser( 'SELECT 1' ); $parser->next_query(); $ast = $parser->get_query_ast(); -$property = ( new ReflectionClass( $ast ) )->getProperty( 'native_ast' ); -$property->setAccessible( true ); -$native_ast = $property->getValue( $ast ); -if ( ! is_object( $native_ast ) || 'WP_MySQL_Native_Ast' !== get_class( $native_ast ) ) { +if ( ! ( $ast instanceof WP_MySQL_Native_Parser_Node ) ) { fwrite( STDERR, "WordPress PHP test container did not select the native-backed AST.\n" ); exit( 1 ); } + +$reflection = new ReflectionObject( $ast ); +if ( $reflection->hasProperty( 'native_ast' ) || $reflection->hasProperty( 'native_node_index' ) ) { + fwrite( STDERR, "Native wrapper still stores Rust AST handle properties.\n" ); + exit( 1 ); +} + +$first = $ast->get_first_child_node(); +if ( ! ( $first instanceof WP_MySQL_Native_Parser_Node ) ) { + fwrite( STDERR, "Native wrapper did not return a native-backed child node.\n" ); + exit( 1 ); +} + +if ( $first !== $ast->get_first_child_node() ) { + fwrite( STDERR, "Native wrapper identity is not stable across reads.\n" ); + exit( 1 ); +} + +$synthetic = new WP_Parser_Node( 0, 'synthetic' ); +$first->append_child( $synthetic ); +$same_first = $ast->get_first_child_node(); +if ( $same_first !== $first || ! in_array( $synthetic, $same_first->get_children(), true ) ) { + fwrite( STDERR, "Materialized native wrapper was lost from the parent cache.\n" ); + exit( 1 ); +} EOF node tools/local-env/scripts/docker.js run --rm php php -m | grep -qx 'wp_mysql_parser' diff --git a/packages/mysql-on-sqlite/src/load.php b/packages/mysql-on-sqlite/src/load.php index 4cbb4208..2070c631 100644 --- a/packages/mysql-on-sqlite/src/load.php +++ b/packages/mysql-on-sqlite/src/load.php @@ -27,7 +27,6 @@ if ( class_exists( 'WP_MySQL_Native_Parser', false ) ) { require_once __DIR__ . '/mysql/native/mysql-rust-bridge.php'; - require_once __DIR__ . '/mysql/native/class-wp-mysql-native-ast-cache.php'; require_once __DIR__ . '/mysql/native/class-wp-mysql-native-parser-node.php'; require_once __DIR__ . '/mysql/native/class-wp-mysql-parser.php'; } else { diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php deleted file mode 100644 index f21bb785..00000000 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-ast-cache.php +++ /dev/null @@ -1,23 +0,0 @@ - WP_MySQL_Native_Parser_Node. - * - * @var array - */ - public $nodes = array(); -} diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php index fae84b4c..29b1e3ef 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php @@ -8,47 +8,18 @@ * caller actually walks the tree. On the first mutation (append_child or * merge_fragment), the node materializes its children into the inherited * `$children` array and behaves like a plain WP_Parser_Node from then on. - * - * Wrappers returned by accessors are interned through a per-AST identity - * map (WP_MySQL_Native_AST_Cache) so two reads of the same logical node - * yield the same PHP instance. This preserves the WP_Parser_Node contract - * that mutations performed on a child via `get_first_child_node()` remain - * visible when the same child is reached again, including after the parent - * has materialized. */ class WP_MySQL_Native_Parser_Node extends WP_Parser_Node { - private $native_ast = null; - private $native_node_index = null; - private $was_mutated = false; - - /** - * Per-AST identity map shared between every interned wrapper. - * - * Created lazily on the first child access; the root wrapper is the - * first entry. Children inherit the same cache instance by reference. - * - * @var WP_MySQL_Native_AST_Cache|null - */ - private $cache = null; + private $was_mutated = false; - public function __construct( $rule_id, $rule_name, $native_ast = null, $native_node_index = null ) { + public function __construct( $rule_id, $rule_name ) { parent::__construct( $rule_id, $rule_name ); - - $this->native_ast = $native_ast; - $this->native_node_index = $native_node_index; } - /** - * Native node index in the Rust-owned arena. - * - * Exposed so the identity cache can key on it. Returns null after - * the wrapper has materialized — at that point the node is detached - * from the native arena and behaves like a plain WP_Parser_Node. - * - * @return int|null - */ - public function get_native_node_index(): ?int { - return $this->native_node_index; + public function __destruct() { + if ( function_exists( 'wp_sqlite_mysql_native_ast_release_wrapper' ) ) { + wp_sqlite_mysql_native_ast_release_wrapper( $this ); + } } /** @inheritDoc */ @@ -71,7 +42,7 @@ public function has_child(): bool { if ( $this->was_mutated() ) { return parent::has_child(); } - return wp_sqlite_mysql_native_ast_has_child( $this->native_ast, $this->native_node_index ); + return wp_sqlite_mysql_native_ast_has_child( $this ); } /** @inheritDoc */ @@ -79,7 +50,7 @@ public function has_child_node( ?string $rule_name = null ): bool { if ( $this->was_mutated() ) { return parent::has_child_node( $rule_name ); } - return wp_sqlite_mysql_native_ast_has_child_node( $this->native_ast, $this->native_node_index, $rule_name ); + return wp_sqlite_mysql_native_ast_has_child_node( $this, $rule_name ); } /** @inheritDoc */ @@ -87,7 +58,7 @@ public function has_child_token( ?int $token_id = null ): bool { if ( $this->was_mutated() ) { return parent::has_child_token( $token_id ); } - return wp_sqlite_mysql_native_ast_has_child_token( $this->native_ast, $this->native_node_index, $token_id ); + return wp_sqlite_mysql_native_ast_has_child_token( $this, $token_id ); } /** @inheritDoc */ @@ -95,7 +66,7 @@ public function get_first_child() { if ( $this->was_mutated() ) { return parent::get_first_child(); } - return $this->intern( wp_sqlite_mysql_native_ast_get_first_child( $this->native_ast, $this->native_node_index ) ); + return wp_sqlite_mysql_native_ast_get_first_child( $this ); } /** @inheritDoc */ @@ -103,7 +74,7 @@ public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_No if ( $this->was_mutated() ) { return parent::get_first_child_node( $rule_name ); } - return $this->intern( wp_sqlite_mysql_native_ast_get_first_child_node( $this->native_ast, $this->native_node_index, $rule_name ) ); + return wp_sqlite_mysql_native_ast_get_first_child_node( $this, $rule_name ); } /** @inheritDoc */ @@ -111,7 +82,7 @@ public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token if ( $this->was_mutated() ) { return parent::get_first_child_token( $token_id ); } - return wp_sqlite_mysql_native_ast_get_first_child_token( $this->native_ast, $this->native_node_index, $token_id ); + return wp_sqlite_mysql_native_ast_get_first_child_token( $this, $token_id ); } /** @inheritDoc */ @@ -119,7 +90,7 @@ public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Pars if ( $this->was_mutated() ) { return parent::get_first_descendant_node( $rule_name ); } - return $this->intern( wp_sqlite_mysql_native_ast_get_first_descendant_node( $this->native_ast, $this->native_node_index, $rule_name ) ); + return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this, $rule_name ); } /** @inheritDoc */ @@ -127,7 +98,7 @@ public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_ if ( $this->was_mutated() ) { return parent::get_first_descendant_token( $token_id ); } - return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this->native_ast, $this->native_node_index, $token_id ); + return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this, $token_id ); } /** @inheritDoc */ @@ -135,7 +106,7 @@ public function get_children(): array { if ( $this->was_mutated() ) { return parent::get_children(); } - return $this->intern_all( wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ) ); + return wp_sqlite_mysql_native_ast_get_children( $this ); } /** @inheritDoc */ @@ -143,7 +114,7 @@ public function get_child_nodes( ?string $rule_name = null ): array { if ( $this->was_mutated() ) { return parent::get_child_nodes( $rule_name ); } - return $this->intern_nodes( wp_sqlite_mysql_native_ast_get_child_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); + return wp_sqlite_mysql_native_ast_get_child_nodes( $this, $rule_name ); } /** @inheritDoc */ @@ -151,7 +122,7 @@ public function get_child_tokens( ?int $token_id = null ): array { if ( $this->was_mutated() ) { return parent::get_child_tokens( $token_id ); } - return wp_sqlite_mysql_native_ast_get_child_tokens( $this->native_ast, $this->native_node_index, $token_id ); + return wp_sqlite_mysql_native_ast_get_child_tokens( $this, $token_id ); } /** @inheritDoc */ @@ -159,7 +130,7 @@ public function get_descendants(): array { if ( $this->was_mutated() ) { return parent::get_descendants(); } - return $this->intern_all( wp_sqlite_mysql_native_ast_get_descendants( $this->native_ast, $this->native_node_index ) ); + return wp_sqlite_mysql_native_ast_get_descendants( $this ); } /** @inheritDoc */ @@ -167,7 +138,7 @@ public function get_descendant_nodes( ?string $rule_name = null ): array { if ( $this->was_mutated() ) { return parent::get_descendant_nodes( $rule_name ); } - return $this->intern_nodes( wp_sqlite_mysql_native_ast_get_descendant_nodes( $this->native_ast, $this->native_node_index, $rule_name ) ); + return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this, $rule_name ); } /** @inheritDoc */ @@ -175,7 +146,7 @@ public function get_descendant_tokens( ?int $token_id = null ): array { if ( $this->was_mutated() ) { return parent::get_descendant_tokens( $token_id ); } - return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this->native_ast, $this->native_node_index, $token_id ); + return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this, $token_id ); } /** @inheritDoc */ @@ -183,7 +154,7 @@ public function get_start(): int { if ( $this->was_mutated() ) { return parent::get_start(); } - return wp_sqlite_mysql_native_ast_get_start( $this->native_ast, $this->native_node_index ); + return wp_sqlite_mysql_native_ast_get_start( $this ); } /** @inheritDoc */ @@ -191,137 +162,22 @@ public function get_length(): int { if ( $this->was_mutated() ) { return parent::get_length(); } - return wp_sqlite_mysql_native_ast_get_length( $this->native_ast, $this->native_node_index ); + return wp_sqlite_mysql_native_ast_get_length( $this ); } private function was_mutated(): bool { return $this->was_mutated; } - /** - * Intern a single accessor return value through the per-AST cache. - * - * Tokens and nulls pass through untouched. Native node wrappers are - * keyed on their `native_node_index`: on cache miss, the freshly - * constructed wrapper is stored and given the cache reference; on - * cache hit, the canonical instance is returned and the new wrapper - * is discarded so callers see stable identity and surviving mutations. - * - * @param mixed $value Return value from the Rust bridge. - * @return mixed - */ - private function intern( $value ) { - if ( ! $value instanceof WP_MySQL_Native_Parser_Node ) { - return $value; - } - - $cache = $this->ensure_cache(); - $index = $value->native_node_index; - if ( null === $index ) { - return $value; - } - if ( isset( $cache->nodes[ $index ] ) ) { - return $cache->nodes[ $index ]; - } - $value->cache = $cache; - $cache->nodes[ $index ] = $value; - return $value; - } - - /** - * Intern every entry in an accessor return array. - * - * Hot path: this runs once per descendant when a caller walks the tree, - * so cache lookup and the cache-miss write are inlined and the cache - * reference is hoisted out of the loop. - * - * @param array $values - * @return array - */ - private function intern_all( array $values ): array { - if ( ! $values ) { - return $values; - } - $cache = $this->cache ?? $this->ensure_cache(); - $nodes = &$cache->nodes; - foreach ( $values as $i => $value ) { - if ( ! $value instanceof WP_MySQL_Native_Parser_Node ) { - continue; - } - $index = $value->native_node_index; - if ( null === $index ) { - continue; - } - if ( isset( $nodes[ $index ] ) ) { - $values[ $i ] = $nodes[ $index ]; - } else { - $value->cache = $cache; - $nodes[ $index ] = $value; - } - } - return $values; - } - - /** - * Intern array of guaranteed-node results (no token/null mixing). - * - * Used by `get_child_nodes()` / `get_descendant_nodes()` whose Rust - * bridge returns only WP_MySQL_Native_Parser_Node instances. Skips - * the per-item `instanceof` check that intern_all() must do for the - * mixed `get_children()` / `get_descendants()` arrays. - * - * @param array $values - * @return array - */ - private function intern_nodes( array $values ): array { - if ( ! $values ) { - return $values; - } - $cache = $this->cache ?? $this->ensure_cache(); - $nodes = &$cache->nodes; - foreach ( $values as $i => $value ) { - $index = $value->native_node_index; - if ( isset( $nodes[ $index ] ) ) { - $values[ $i ] = $nodes[ $index ]; - } else { - $value->cache = $cache; - $nodes[ $index ] = $value; - } - } - return $values; - } - - /** - * Lazily build (or reuse) the per-AST identity map. - * - * The root wrapper is constructed without a cache, so the first time - * any accessor needs to intern a child, it creates the cache and - * registers itself as the root entry. Subsequent interns on this - * wrapper or any descendant share the same cache by reference. - * - * @return WP_MySQL_Native_AST_Cache - */ - private function ensure_cache(): WP_MySQL_Native_AST_Cache { - if ( null === $this->cache ) { - $this->cache = new WP_MySQL_Native_AST_Cache(); - if ( null !== $this->native_node_index ) { - $this->cache->nodes[ $this->native_node_index ] = $this; - } - } - return $this->cache; - } - private function materialize_native_children(): void { if ( $this->was_mutated ) { return; } - // Pull children through the cache so any wrapper a caller already - // mutated via get_first_child_node() etc. survives the transition - // into $this->children — same instance, same mutations. - $this->children = $this->intern_all( wp_sqlite_mysql_native_ast_get_children( $this->native_ast, $this->native_node_index ) ); - $this->native_ast = null; - $this->native_node_index = null; - $this->was_mutated = true; + $this->children = wp_sqlite_mysql_native_ast_get_children( $this ); + $this->was_mutated = true; + if ( function_exists( 'wp_sqlite_mysql_native_ast_materialize_wrapper' ) ) { + wp_sqlite_mysql_native_ast_materialize_wrapper( $this ); + } } } diff --git a/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Cycle_Tests.php b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Cycle_Tests.php new file mode 100644 index 00000000..57672162 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Cycle_Tests.php @@ -0,0 +1,267 @@ +markTestSkipped( 'Native MySQL parser extension is not loaded.' ); + } + // Force a clean slate before each test — ASTs from earlier tests + // must not pollute the memory measurements below. + gc_collect_cycles(); + } + + private function parse( string $sql ): WP_Parser_Node { + static $grammar = null; + if ( null === $grammar ) { + $grammar = new WP_Parser_Grammar( include __DIR__ . '/../../../src/mysql/mysql-grammar.php' ); + } + $lexer = new WP_MySQL_Lexer( $sql ); + $tokens = $lexer instanceof WP_MySQL_Native_Lexer + ? $lexer->native_token_stream() + : $lexer->remaining_tokens(); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + $tree = $parser->parse(); + $this->assertNotNull( $tree, 'Failed to parse SQL: ' . $sql ); + return $tree; + } + + /** + * Hostile loop: parse and walk many ASTs in a tight loop, only + * `gc_collect_cycles()` between iterations. Memory must plateau. + * + * If wrapper registry entries or cache pointers are not released, peak + * memory grows linearly with iteration count. With cleanup in place, the + * working set stays bounded. + */ + public function test_repeated_parse_walk_drop_does_not_leak(): void { + $sql = 'SELECT a, b, c FROM t WHERE a + b * c IN (1, 2, 3) AND d = 4'; + + // Warm-up: do enough work that allocator overhead is amortized + // before we sample the floor. + for ( $i = 0; $i < 20; $i++ ) { + $ast = $this->parse( $sql ); + $ast->get_descendants(); + $ast = null; + gc_collect_cycles(); + } + $baseline = memory_get_usage(); + + // Now run substantially more iterations and assert the working + // set stays within a small multiple of the warm-up floor. + for ( $i = 0; $i < 500; $i++ ) { + $ast = $this->parse( $sql ); + $ast->get_descendants(); + $ast = null; + gc_collect_cycles(); + } + $after = memory_get_usage(); + + // 4 MB headroom — generous, but a leaking cache adds tens of MB + // across 500 iterations on this query. + $delta = $after - $baseline; + $this->assertLessThan( + 4 * 1024 * 1024, + $delta, + sprintf( + 'Memory grew %.1f MB across 500 parse-walk-drop cycles; the per-AST cache is not being collected.', + $delta / 1024 / 1024 + ) + ); + } + + /** + * After dropping the AST and triggering GC, the entire wrapper + * graph must be reclaimable. We hand out one descendant, drop the + * root, then drop the descendant — the next gc cycle must reclaim + * the rest of the cached wrappers. + */ + public function test_drop_then_gc_reclaims_cached_wrappers(): void { + $sql = 'SELECT a, b, c FROM t WHERE a + b * c IN (1, 2, 3) AND d = 4'; + + // Establish a memory floor with no AST live. + gc_collect_cycles(); + $floor = memory_get_usage(); + + $ast = $this->parse( $sql ); + $descendant = $ast->get_first_descendant_node(); + $this->assertNotNull( $descendant ); + $ast = null; + $descendant = null; + gc_collect_cycles(); + + $after = memory_get_usage(); + $delta = $after - $floor; + // Generous bound — but tens of MB of leaked wrappers would blow it. + $this->assertLessThan( + 1 * 1024 * 1024, + $delta, + sprintf( + 'After dropping the AST and the descendant and running gc, %.1f MB of cached wrappers remain.', + $delta / 1024 / 1024 + ) + ); + } + + /** + * Holding a child wrapper *outlives* the variable holding the root. + * The child's registry entry must keep the AST alive (no UAF when the + * bridge is called on the orphaned child). Once the child is also dropped, + * the registry entry must be released. + */ + public function test_orphaned_child_keeps_ast_alive_then_collects(): void { + $sql = 'SELECT a, b, c FROM t WHERE a + b * c IN (1, 2, 3)'; + $child = ( function () use ( $sql ) { + $ast = $this->parse( $sql ); + return $ast->get_first_descendant_node(); + } )(); + + // Root variable is gone; only the child reference remains, but the + // registry entry still pins the AST. The child must still be + // functional — accessing it must not crash. + $this->assertNotNull( $child ); + $this->assertIsString( $child->rule_name ); + // The child's own children should also resolve without UAF. + $grand = $child->get_first_child(); + $this->assertNotNull( $grand ); + + // Now drop the child too; the AST + cache should be reclaimable. + $child = null; + $grand = null; + gc_collect_cycles(); + // If the registry entry was released, this assertion always passes; + // the real signal is the absence of a segfault during teardown. + $this->addToAssertionCount( 1 ); + } + + /** + * Mutating a cached wrapper through `append_child` before dropping + * the AST must not block collection. The mutated wrapper's + * `$children` array now contains a non-cached node; that must not keep + * stale registry/cache entries alive. + */ + public function test_mutation_before_drop_does_not_block_collection(): void { + $sql = 'SELECT 1 + 2'; + + gc_collect_cycles(); + $floor = memory_get_usage(); + + for ( $i = 0; $i < 200; $i++ ) { + $ast = $this->parse( $sql ); + $child = $ast->get_first_child_node(); + $injected = new WP_Parser_Node( 0, 'synthetic-' . $i ); + $ast->append_child( $injected ); + // Touch the cache after mutation to keep wrappers live. + $ast->get_descendants(); + $ast = null; + $child = null; + $injected = null; + gc_collect_cycles(); + } + $after = memory_get_usage(); + $delta = $after - $floor; + $this->assertLessThan( + 4 * 1024 * 1024, + $delta, + sprintf( + 'Memory grew %.1f MB across 200 mutate-then-drop cycles.', + $delta / 1024 / 1024 + ) + ); + } + + /** + * Two ASTs alive simultaneously, then dropped in interleaved order. + * Dropping AST A must not affect AST B's cached wrappers; both must + * eventually collect once unreferenced. + */ + public function test_overlapping_asts_do_not_corrupt_each_other(): void { + $ast_a = $this->parse( 'SELECT a FROM ta WHERE a > 1' ); + $ast_b = $this->parse( 'SELECT b FROM tb WHERE b < 9' ); + + $child_a = $ast_a->get_first_descendant_node(); + $child_b = $ast_b->get_first_descendant_node(); + + // Drop A first and run gc; B must remain fully functional. + $ast_a = null; + $child_a = null; + gc_collect_cycles(); + + $this->assertNotNull( $child_b ); + $walk = $ast_b->get_descendants(); + $this->assertNotEmpty( $walk ); + + // Drop B too; walk one of its still-held descendants — the cache + // is still alive because $child_b pins it. + $ast_b = null; + $this->assertIsString( $child_b->rule_name ); + + $child_b = null; + $walk = null; + gc_collect_cycles(); + $this->addToAssertionCount( 1 ); + } + + /** + * Re-walk + drop + collect across many iterations. This is the + * "translator pass on each query" shape of real workloads. The wrapper + * registry and cache must not create a memory cliff under repeated walks. + */ + public function test_rewalk_loop_stays_bounded(): void { + $sql = 'SELECT a, b, c, d, e FROM t WHERE (a + b) * (c - d) > e AND f IN (1,2,3,4,5)'; + + gc_collect_cycles(); + // Warm-up. + for ( $i = 0; $i < 10; $i++ ) { + $ast = $this->parse( $sql ); + for ( $r = 0; $r < 10; $r++ ) { + $ast->get_descendants(); + } + $ast = null; + gc_collect_cycles(); + } + $floor = memory_get_usage(); + + for ( $i = 0; $i < 200; $i++ ) { + $ast = $this->parse( $sql ); + for ( $r = 0; $r < 10; $r++ ) { + $ast->get_descendants(); + } + $ast = null; + gc_collect_cycles(); + } + $after = memory_get_usage(); + $delta = $after - $floor; + $this->assertLessThan( + 4 * 1024 * 1024, + $delta, + sprintf( + 'Rewalk loop grew memory by %.1f MB; cache likely uncollectable.', + $delta / 1024 / 1024 + ) + ); + } +} diff --git a/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php index bd3f8ffe..af77cd7b 100644 --- a/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php +++ b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php @@ -7,9 +7,9 @@ * * The native extension constructs a fresh PHP wrapper for every accessor * call. Without interning, two reads of the same logical node would yield - * distinct objects, and a mutation made through the first wrapper would - * be invisible through the second. WP_Parser_Node exposes public mutators - * and stable child identity, so the native wrapper must preserve both. + * distinct objects, and a mutation made through a still-live wrapper would be + * invisible through the second. WP_Parser_Node exposes public mutators and + * stable child identity, so the native wrapper must preserve both. * * Skipped when the native extension is not loaded — the pure-PHP code * path already has stable identity by construction. @@ -47,6 +47,15 @@ public function test_get_first_child_node_returns_same_instance(): void { $this->assertSame( $first, $second ); } + public function test_native_wrapper_does_not_store_native_ast_handle(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $reflection = new ReflectionObject( $tree ); + + $this->assertFalse( $reflection->hasProperty( 'native_ast' ) ); + $this->assertFalse( $reflection->hasProperty( 'native_node_index' ) ); + } + public function test_get_children_returns_same_instances_across_calls(): void { $tree = $this->parse( 'SELECT 1, 2, 3' ); @@ -99,6 +108,23 @@ public function test_mutation_on_child_survives_re_read(): void { $this->assertSame( 'mutated-rule', $same_child->rule_name ); } + public function test_materialized_child_survives_re_read_from_native_parent(): void { + $tree = $this->parse( 'SELECT 1 + 2' ); + + $child = $tree->get_first_child_node(); + $this->assertNotNull( $child ); + + $synthetic = new WP_Parser_Node( 0, 'synthetic' ); + $child->append_child( $synthetic ); + + $same_child = $tree->get_first_child_node(); + $this->assertSame( $child, $same_child ); + $this->assertTrue( + in_array( $synthetic, $same_child->get_children(), true ), + 'Materialized live child wrappers must stay discoverable through the parent native cache.' + ); + } + public function test_mutation_survives_parent_materialization(): void { $tree = $this->parse( 'SELECT 1 + 2' ); diff --git a/packages/php-ext-wp-mysql-parser/src/lib.rs b/packages/php-ext-wp-mysql-parser/src/lib.rs index 909ec379..35f17fbd 100644 --- a/packages/php-ext-wp-mysql-parser/src/lib.rs +++ b/packages/php-ext-wp-mysql-parser/src/lib.rs @@ -1,8 +1,10 @@ #![cfg_attr(windows, feature(abi_vectorcall))] +use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::os::raw::c_char; use std::ptr; +use std::rc::Rc; use std::sync::Arc; use ext_php_rs::convert::{FromZval, IntoZval, IntoZvalDyn}; @@ -1039,10 +1041,29 @@ struct NativeAstArena { root: NativeAstRoot, } -#[php_class] -#[php(name = "WP_MySQL_Native_Ast")] -pub struct WpMySqlNativeAst { +struct NativeAstState { arena: Arc, + /// Per-AST identity map: node arena index → live PHP wrapper pointer. + /// + /// `WP_Parser_Node` callers expect stable child identity (mutate a child + /// once, walk past, walk back, the mutation is still there). Each + /// accessor in this extension constructs a fresh wrapper unless we + /// intern it here. The cache intentionally stores raw wrapper pointers, + /// not strong PHP references, so Rust can preserve identity without + /// pinning wrappers after PHP drops them. + node_cache: RefCell>, +} + +struct NativeAstWrapperEntry { + ast: Rc, + node_index: usize, + /// Materialized wrappers still participate in identity lookups but no + /// longer delegate reads through the native AST bridge. + is_materialized: bool, +} + +thread_local! { + static NATIVE_AST_WRAPPERS: RefCell> = RefCell::new(HashMap::new()); } impl NativeAstArena { @@ -1093,46 +1114,198 @@ impl NativeAstArena { index } - fn create_php_ast(&self, native_ast_zval: &Zval) -> PhpResult { + fn node(&self, index: usize) -> PhpResult<&NativeAstNode> { + self.nodes + .get(index) + .ok_or_else(|| php_error("Native AST node index is out of range")) + } + + fn child_node_matches(&self, child: NativeAstChild, rule_name: Option<&str>) -> bool { + let NativeAstChild::Node(index) = child else { + return false; + }; + let Ok(node) = self.node(index) else { + return false; + }; + rule_name.is_none_or(|expected| { + self.grammar + .rule(node.rule_id) + .map(|rule| rule.rule_name == expected) + .unwrap_or(false) + }) + } + + fn child_token_matches(&self, child: NativeAstChild, token_id: Option) -> bool { + let NativeAstChild::Token(index) = child else { + return false; + }; + token_id.is_none_or(|expected| { + self.token_source + .token_info(index) + .map(|token| token.id == expected) + .unwrap_or(false) + }) + } + + fn descendant_stack(&self, index: usize) -> PhpResult> { + let node = self.node(index)?; + let mut stack = Vec::with_capacity(node.descendant_count); + stack.extend(node.children.iter().rev().copied()); + Ok(stack) + } +} + +fn native_ast_wrapper_key(wrapper_zval: &Zval) -> PhpResult { + let object = wrapper_zval + .object() + .ok_or_else(|| php_error("Missing native AST wrapper"))?; + Ok(ptr::from_ref(object) as usize) +} + +fn native_ast_from_wrapper(wrapper_zval: &Zval) -> PhpResult<(Rc, usize)> { + let key = native_ast_wrapper_key(wrapper_zval)?; + NATIVE_AST_WRAPPERS + .with(|wrappers| { + wrappers.borrow().get(&key).and_then(|entry| { + (!entry.is_materialized).then(|| (Rc::clone(&entry.ast), entry.node_index)) + }) + }) + .ok_or_else(|| php_error("Missing native AST handle")) +} + +fn register_native_ast_wrapper( + object: &ZendObject, + ast: &Rc, + node_index: usize, +) -> usize { + let key = ptr::from_ref(object) as usize; + NATIVE_AST_WRAPPERS.with(|wrappers| { + wrappers.borrow_mut().insert( + key, + NativeAstWrapperEntry { + ast: Rc::clone(ast), + node_index, + is_materialized: false, + }, + ); + }); + ast.node_cache.borrow_mut().insert(node_index, key); + key +} + +fn mark_native_ast_wrapper_materialized_key(key: usize) { + NATIVE_AST_WRAPPERS.with(|wrappers| { + if let Some(entry) = wrappers.borrow_mut().get_mut(&key) { + entry.is_materialized = true; + } + }); +} + +fn release_native_ast_wrapper_key(key: usize) { + let entry = NATIVE_AST_WRAPPERS.with(|wrappers| wrappers.borrow_mut().remove(&key)); + if let Some(entry) = entry { + let mut cache = entry.ast.node_cache.borrow_mut(); + if cache.get(&entry.node_index).copied() == Some(key) { + cache.remove(&entry.node_index); + } + } +} + +fn native_ast_wrapper_matches(key: usize, ast: &Rc, node_index: usize) -> bool { + NATIVE_AST_WRAPPERS.with(|wrappers| { + wrappers + .borrow() + .get(&key) + .is_some_and(|entry| Rc::ptr_eq(&entry.ast, ast) && entry.node_index == node_index) + }) +} + +/// Build a Zval that references an existing PHP object. +/// +/// Used on cache hits to hand a live wrapper back to PHP without allocating a +/// new object. `Zval::set_object()` bumps the object refcount for the returned +/// zval; the Rust cache only stores the pointer and does not own a reference. +unsafe fn zval_from_cached_object(key: usize) -> Zval { + let obj = &mut *(key as *mut ZendObject); + let mut zv = Zval::new(); + zv.set_object(obj); + zv +} + +impl NativeAstState { + fn new(arena: Arc) -> Rc { + Rc::new(Self { + arena, + node_cache: RefCell::new(HashMap::new()), + }) + } + + fn create_php_ast(self: &Rc) -> PhpResult { let classes = php_classes()?; - self.create_php_ast_with_classes(native_ast_zval, &classes) + self.create_php_ast_with_classes(&classes) } - fn create_php_ast_with_classes( - &self, - native_ast_zval: &Zval, - classes: &PhpClasses, - ) -> PhpResult { - match self.root { + fn create_php_ast_with_classes(self: &Rc, classes: &PhpClasses) -> PhpResult { + match self.arena.root { NativeAstRoot::No => Ok(Zval::null()), NativeAstRoot::Empty => { let mut zval = Zval::new(); zval.set_bool(true); Ok(zval) } - NativeAstRoot::Node(index) => { - self.create_php_node_with_classes(native_ast_zval, index, classes) - } + NativeAstRoot::Node(index) => self.create_php_node_with_classes(index, classes), NativeAstRoot::Token(index) => self + .arena .token_source .create_php_token_with_classes(index, classes), } } + /// Resolve a child slot to a Zval, going through the per-AST identity + /// cache for nodes. Tokens are not yet cached — they have no public + /// mutators and no caller in this repo relies on token identity. + fn cached_child_zval( + self: &Rc, + child: NativeAstChild, + classes: &PhpClasses, + ) -> PhpResult { + match child { + NativeAstChild::Node(index) => self.cached_node_zval(index, classes), + NativeAstChild::Token(index) => self + .arena + .token_source + .create_php_token_with_classes(index, classes), + } + } + + fn cached_node_zval(self: &Rc, index: usize, classes: &PhpClasses) -> PhpResult { + let cached_key = { + let cache = self.node_cache.borrow(); + cache.get(&index).copied() + }; + if let Some(key) = cached_key { + if native_ast_wrapper_matches(key, self, index) { + return Ok(unsafe { zval_from_cached_object(key) }); + } + self.node_cache.borrow_mut().remove(&index); + } + + self.create_php_node_with_classes(index, classes) + } + fn create_php_node_with_classes( - &self, - native_ast_zval: &Zval, + self: &Rc, index: usize, classes: &PhpClasses, ) -> PhpResult { - let node = self.node(index)?; + let node = self.arena.node(index)?; let mut object = classes.native_parser_node.new(); let rule_name = self + .arena .grammar .rule(node.rule_id) .map(|rule| rule.rule_name.as_str()) .unwrap_or_default(); - let index = i64::try_from(index).map_err(php_error)?; update_object_property( &mut object, @@ -1146,111 +1319,41 @@ impl NativeAstArena { "rule_name", rule_name.to_owned(), )?; - update_object_property( - &mut object, - classes.native_parser_node, - "native_ast", - native_ast_zval.shallow_clone(), - )?; - update_object_property( - &mut object, - classes.native_parser_node, - "native_node_index", - index, - )?; + register_native_ast_wrapper(object.as_ref(), self, index); object.into_zval(false).map_err(php_error) } - - fn node(&self, index: usize) -> PhpResult<&NativeAstNode> { - self.nodes - .get(index) - .ok_or_else(|| php_error("Native AST node index is out of range")) - } - - fn child_to_zval_with_classes( - &self, - native_ast_zval: &Zval, - child: NativeAstChild, - classes: &PhpClasses, - ) -> PhpResult { - match child { - NativeAstChild::Node(index) => { - self.create_php_node_with_classes(native_ast_zval, index, classes) - } - NativeAstChild::Token(index) => self - .token_source - .create_php_token_with_classes(index, classes), - } - } - - fn child_node_matches(&self, child: NativeAstChild, rule_name: Option<&str>) -> bool { - let NativeAstChild::Node(index) = child else { - return false; - }; - let Ok(node) = self.node(index) else { - return false; - }; - rule_name.is_none_or(|expected| { - self.grammar - .rule(node.rule_id) - .map(|rule| rule.rule_name == expected) - .unwrap_or(false) - }) - } - - fn child_token_matches(&self, child: NativeAstChild, token_id: Option) -> bool { - let NativeAstChild::Token(index) = child else { - return false; - }; - token_id.is_none_or(|expected| { - self.token_source - .token_info(index) - .map(|token| token.id == expected) - .unwrap_or(false) - }) - } - - fn descendant_stack(&self, index: usize) -> PhpResult> { - let node = self.node(index)?; - let mut stack = Vec::with_capacity(node.descendant_count); - stack.extend(node.children.iter().rev().copied()); - Ok(stack) - } } -fn native_ast(native_ast: &Zval) -> PhpResult<&WpMySqlNativeAst> { - <&WpMySqlNativeAst as FromZval>::from_zval(native_ast) - .ok_or_else(|| php_error("Missing native AST handle")) +#[php_function] +pub fn wp_sqlite_mysql_native_ast_release_wrapper(wrapper_zval: &Zval) -> PhpResult<()> { + let key = native_ast_wrapper_key(wrapper_zval)?; + release_native_ast_wrapper_key(key); + Ok(()) } -fn native_ast_node_index(node_index: i64) -> PhpResult { - usize::try_from(node_index).map_err(php_error) +#[php_function] +pub fn wp_sqlite_mysql_native_ast_materialize_wrapper(wrapper_zval: &Zval) -> PhpResult<()> { + let key = native_ast_wrapper_key(wrapper_zval)?; + mark_native_ast_wrapper_materialized_key(key); + Ok(()) } #[php_function] -pub fn wp_sqlite_mysql_native_ast_has_child( - native_ast_zval: &Zval, - node_index: i64, -) -> PhpResult { - let ast = native_ast(native_ast_zval)?; - Ok(!ast - .arena - .node(native_ast_node_index(node_index)?)? - .children - .is_empty()) +pub fn wp_sqlite_mysql_native_ast_has_child(wrapper_zval: &Zval) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + Ok(!ast.arena.node(node_index)?.children.is_empty()) } #[php_function] pub fn wp_sqlite_mysql_native_ast_has_child_node( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, rule_name: Option, ) -> PhpResult { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; Ok(ast .arena - .node(native_ast_node_index(node_index)?)? + .node(node_index)? .children .iter() .copied() @@ -1259,14 +1362,13 @@ pub fn wp_sqlite_mysql_native_ast_has_child_node( #[php_function] pub fn wp_sqlite_mysql_native_ast_has_child_token( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, token_id: Option, ) -> PhpResult { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; Ok(ast .arena - .node(native_ast_node_index(node_index)?)? + .node(node_index)? .children .iter() .copied() @@ -1274,38 +1376,25 @@ pub fn wp_sqlite_mysql_native_ast_has_child_token( } #[php_function] -pub fn wp_sqlite_mysql_native_ast_get_first_child( - native_ast_zval: &Zval, - node_index: i64, -) -> PhpResult { - let ast = native_ast(native_ast_zval)?; +pub fn wp_sqlite_mysql_native_ast_get_first_child(wrapper_zval: &Zval) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; - let Some(child) = ast - .arena - .node(native_ast_node_index(node_index)?)? - .children - .first() - .copied() - else { + let Some(child) = ast.arena.node(node_index)?.children.first().copied() else { return Ok(Zval::null()); }; - ast.arena - .child_to_zval_with_classes(native_ast_zval, child, &classes) + ast.cached_child_zval(child, &classes) } #[php_function] pub fn wp_sqlite_mysql_native_ast_get_first_child_node( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, rule_name: Option, ) -> PhpResult { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; - for child in &ast.arena.node(native_ast_node_index(node_index)?)?.children { + for child in &ast.arena.node(node_index)?.children { if ast.arena.child_node_matches(*child, rule_name.as_deref()) { - return ast - .arena - .child_to_zval_with_classes(native_ast_zval, *child, &classes); + return ast.cached_child_zval(*child, &classes); } } Ok(Zval::null()) @@ -1313,17 +1402,14 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child_node( #[php_function] pub fn wp_sqlite_mysql_native_ast_get_first_child_token( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, token_id: Option, ) -> PhpResult { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; - for child in &ast.arena.node(native_ast_node_index(node_index)?)?.children { + for child in &ast.arena.node(node_index)?.children { if ast.arena.child_token_matches(*child, token_id) { - return ast - .arena - .child_to_zval_with_classes(native_ast_zval, *child, &classes); + return ast.cached_child_zval(*child, &classes); } } Ok(Zval::null()) @@ -1331,20 +1417,15 @@ pub fn wp_sqlite_mysql_native_ast_get_first_child_token( #[php_function] pub fn wp_sqlite_mysql_native_ast_get_first_descendant_node( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, rule_name: Option, ) -> PhpResult { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; - let mut stack = ast - .arena - .descendant_stack(native_ast_node_index(node_index)?)?; + let mut stack = ast.arena.descendant_stack(node_index)?; while let Some(child) = stack.pop() { if ast.arena.child_node_matches(child, rule_name.as_deref()) { - return ast - .arena - .child_to_zval_with_classes(native_ast_zval, child, &classes); + return ast.cached_child_zval(child, &classes); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1357,20 +1438,15 @@ pub fn wp_sqlite_mysql_native_ast_get_first_descendant_node( #[php_function] pub fn wp_sqlite_mysql_native_ast_get_first_descendant_token( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, token_id: Option, ) -> PhpResult { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; - let mut stack = ast - .arena - .descendant_stack(native_ast_node_index(node_index)?)?; + let mut stack = ast.arena.descendant_stack(node_index)?; while let Some(child) = stack.pop() { if ast.arena.child_token_matches(child, token_id) { - return ast - .arena - .child_to_zval_with_classes(native_ast_zval, child, &classes); + return ast.cached_child_zval(child, &classes); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1382,83 +1458,61 @@ pub fn wp_sqlite_mysql_native_ast_get_first_descendant_token( } #[php_function] -pub fn wp_sqlite_mysql_native_ast_get_children( - native_ast_zval: &Zval, - node_index: i64, -) -> PhpResult> { - let ast = native_ast(native_ast_zval)?; +pub fn wp_sqlite_mysql_native_ast_get_children(wrapper_zval: &Zval) -> PhpResult> { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; ast.arena - .node(native_ast_node_index(node_index)?)? + .node(node_index)? .children .iter() .copied() - .map(|child| { - ast.arena - .child_to_zval_with_classes(native_ast_zval, child, &classes) - }) + .map(|child| ast.cached_child_zval(child, &classes)) .collect() } #[php_function] pub fn wp_sqlite_mysql_native_ast_get_child_nodes( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, rule_name: Option, ) -> PhpResult> { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; ast.arena - .node(native_ast_node_index(node_index)?)? + .node(node_index)? .children .iter() .copied() .filter(|child| ast.arena.child_node_matches(*child, rule_name.as_deref())) - .map(|child| { - ast.arena - .child_to_zval_with_classes(native_ast_zval, child, &classes) - }) + .map(|child| ast.cached_child_zval(child, &classes)) .collect() } #[php_function] pub fn wp_sqlite_mysql_native_ast_get_child_tokens( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, token_id: Option, ) -> PhpResult> { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; ast.arena - .node(native_ast_node_index(node_index)?)? + .node(node_index)? .children .iter() .copied() .filter(|child| ast.arena.child_token_matches(*child, token_id)) - .map(|child| { - ast.arena - .child_to_zval_with_classes(native_ast_zval, child, &classes) - }) + .map(|child| ast.cached_child_zval(child, &classes)) .collect() } #[php_function] -pub fn wp_sqlite_mysql_native_ast_get_descendants( - native_ast_zval: &Zval, - node_index: i64, -) -> PhpResult> { - let ast = native_ast(native_ast_zval)?; +pub fn wp_sqlite_mysql_native_ast_get_descendants(wrapper_zval: &Zval) -> PhpResult> { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; - let root = ast.arena.node(native_ast_node_index(node_index)?)?; + let root = ast.arena.node(node_index)?; let mut descendants = Vec::with_capacity(root.descendant_count); - let mut stack = ast - .arena - .descendant_stack(native_ast_node_index(node_index)?)?; + let mut stack = ast.arena.descendant_stack(node_index)?; while let Some(child) = stack.pop() { - descendants.push( - ast.arena - .child_to_zval_with_classes(native_ast_zval, child, &classes)?, - ); + descendants.push(ast.cached_child_zval(child, &classes)?); if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { stack.push(*child); @@ -1470,23 +1524,16 @@ pub fn wp_sqlite_mysql_native_ast_get_descendants( #[php_function] pub fn wp_sqlite_mysql_native_ast_get_descendant_nodes( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, rule_name: Option, ) -> PhpResult> { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; let mut descendants = Vec::new(); - let mut stack = ast - .arena - .descendant_stack(native_ast_node_index(node_index)?)?; + let mut stack = ast.arena.descendant_stack(node_index)?; while let Some(child) = stack.pop() { if ast.arena.child_node_matches(child, rule_name.as_deref()) { - descendants.push(ast.arena.child_to_zval_with_classes( - native_ast_zval, - child, - &classes, - )?); + descendants.push(ast.cached_child_zval(child, &classes)?); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1499,23 +1546,16 @@ pub fn wp_sqlite_mysql_native_ast_get_descendant_nodes( #[php_function] pub fn wp_sqlite_mysql_native_ast_get_descendant_tokens( - native_ast_zval: &Zval, - node_index: i64, + wrapper_zval: &Zval, token_id: Option, ) -> PhpResult> { - let ast = native_ast(native_ast_zval)?; + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; let classes = php_classes()?; let mut descendants = Vec::new(); - let mut stack = ast - .arena - .descendant_stack(native_ast_node_index(node_index)?)?; + let mut stack = ast.arena.descendant_stack(node_index)?; while let Some(child) = stack.pop() { if ast.arena.child_token_matches(child, token_id) { - descendants.push(ast.arena.child_to_zval_with_classes( - native_ast_zval, - child, - &classes, - )?); + descendants.push(ast.cached_child_zval(child, &classes)?); } if let NativeAstChild::Node(index) = child { for child in ast.arena.node(index)?.children.iter().rev() { @@ -1527,12 +1567,9 @@ pub fn wp_sqlite_mysql_native_ast_get_descendant_tokens( } #[php_function] -pub fn wp_sqlite_mysql_native_ast_get_start( - native_ast_zval: &Zval, - node_index: i64, -) -> PhpResult { - let ast = native_ast(native_ast_zval)?; - let node = ast.arena.node(native_ast_node_index(node_index)?)?; +pub fn wp_sqlite_mysql_native_ast_get_start(wrapper_zval: &Zval) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let node = ast.arena.node(node_index)?; let token_index = node .first_token .ok_or_else(|| php_error("Native AST node has no descendant tokens"))?; @@ -1541,12 +1578,9 @@ pub fn wp_sqlite_mysql_native_ast_get_start( } #[php_function] -pub fn wp_sqlite_mysql_native_ast_get_length( - native_ast_zval: &Zval, - node_index: i64, -) -> PhpResult { - let ast = native_ast(native_ast_zval)?; - let node = ast.arena.node(native_ast_node_index(node_index)?)?; +pub fn wp_sqlite_mysql_native_ast_get_length(wrapper_zval: &Zval) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let node = ast.arena.node(node_index)?; let first_token_index = node .first_token .ok_or_else(|| php_error("Native AST node has no descendant tokens"))?; @@ -1566,7 +1600,7 @@ pub struct WpMySqlNativeParser { token_source: Arc, token_ids: Vec, position: usize, - current_ast: Option>, + current_ast: Option>, current_php_ast: Option, } @@ -1619,7 +1653,7 @@ impl WpMySqlNativeParser { return Ok(Zval::null()); }; - let ast = self.create_php_ast(Arc::clone(native_ast))?; + let ast = self.create_php_ast(Rc::clone(native_ast))?; self.current_php_ast = Some(ast); match self.current_php_ast.as_ref() { Some(ast) => Ok(ast.shallow_clone()), @@ -1641,7 +1675,7 @@ impl WpMySqlNativeParser { Ok(true) } - fn parse_native_ast(&mut self) -> PhpResult> { + fn parse_native_ast(&mut self) -> PhpResult> { let mut arena = NativeAstArena::new(Arc::clone(&self.grammar), Arc::clone(&self.token_source)); let query_rule_id = self.grammar.query_rule_id; @@ -1658,7 +1692,7 @@ impl WpMySqlNativeParser { } } }; - Ok(Arc::new(arena)) + Ok(NativeAstState::new(Arc::new(arena))) } fn parse_recursive_inner( @@ -1762,14 +1796,8 @@ impl WpMySqlNativeParser { } } - fn create_php_ast(&self, arena: Arc) -> PhpResult { - stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || { - let native_ast_zval = WpMySqlNativeAst { arena } - .into_zval(false) - .map_err(php_error)?; - let native_ast = native_ast(&native_ast_zval)?; - native_ast.arena.create_php_ast(&native_ast_zval) - }) + fn create_php_ast(&self, ast: Rc) -> PhpResult { + stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || ast.create_php_ast()) } } @@ -2019,10 +2047,13 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) { pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module .class::() - .class::() .class::() .class::() .class::() + .function(wrap_function!(wp_sqlite_mysql_native_ast_release_wrapper)) + .function(wrap_function!( + wp_sqlite_mysql_native_ast_materialize_wrapper + )) .function(wrap_function!(wp_sqlite_mysql_native_ast_has_child)) .function(wrap_function!(wp_sqlite_mysql_native_ast_has_child_node)) .function(wrap_function!(wp_sqlite_mysql_native_ast_has_child_token)) From 6978176060e4b1fe5fc26d708c662b570cf4499b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 15:54:39 +0200 Subject: [PATCH 33/40] Require native parser during WordPress PHPUnit job --- .github/workflows/wp-tests-phpunit-run.js | 23 +++++++++++++++++++++++ .github/workflows/wp-tests-phpunit.yml | 2 ++ 2 files changed, 25 insertions(+) diff --git a/.github/workflows/wp-tests-phpunit-run.js b/.github/workflows/wp-tests-phpunit-run.js index 63e770cc..e9e1451c 100644 --- a/.github/workflows/wp-tests-phpunit-run.js +++ b/.github/workflows/wp-tests-phpunit-run.js @@ -10,6 +10,8 @@ const { execSync } = require( 'child_process' ); const fs = require( 'fs' ); const path = require( 'path' ); +const requiresNativeParserExtension = process.env.WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION === '1'; + const expectedErrors = [ 'Tests_DB_Charset::test_invalid_characters_in_query', 'Tests_DB_Charset::test_set_charset_changes_the_connection_collation', @@ -90,10 +92,31 @@ const expectedFailures = [ ]; console.log( 'Running WordPress PHPUnit tests with expected failures tracking...' ); +if ( requiresNativeParserExtension ) { + console.log( 'Native parser extension is required for this PHPUnit run.' ); +} console.log( 'Expected errors:', expectedErrors ); console.log( 'Expected failures:', expectedFailures ); +function verifyNativeParserExtension() { + const verifier = path.join( __dirname, '..', '..', 'wordpress', 'native-verify-extension.php' ); + if ( ! fs.existsSync( verifier ) ) { + console.error( `Error: Native parser verifier not found at ${ verifier }.` ); + process.exit( 1 ); + } + + execSync( 'composer run wp-test-ensure-env', { stdio: 'inherit' } ); + execSync( + 'cd wordpress && node tools/local-env/scripts/docker.js run --rm php php /var/www/native-verify-extension.php', + { stdio: 'inherit' } + ); +} + try { + if ( requiresNativeParserExtension ) { + verifyNativeParserExtension(); + } + try { execSync( `composer run wp-test-php -- --log-junit=phpunit-results.xml --verbose`, diff --git a/.github/workflows/wp-tests-phpunit.yml b/.github/workflows/wp-tests-phpunit.yml index 31db5ef0..08d9c53a 100644 --- a/.github/workflows/wp-tests-phpunit.yml +++ b/.github/workflows/wp-tests-phpunit.yml @@ -55,6 +55,8 @@ jobs: run: bash .github/workflows/wp-tests-phpunit-native-extension-setup.sh - name: Run WordPress PHPUnit tests with parser extension + env: + WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION: '1' run: node .github/workflows/wp-tests-phpunit-run.js - name: Stop Docker containers From 71a250a904d629b14062b4976f397374f99485cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 16:13:37 +0200 Subject: [PATCH 34/40] Require native parser in extension PHPUnit suites --- .../mysql-parser-extension-tests.yml | 26 ++++++-- ...wp-tests-phpunit-native-extension-setup.sh | 47 +++++++++++++- packages/mysql-on-sqlite/tests/bootstrap.php | 61 +++++++++++++++++++ 3 files changed, 128 insertions(+), 6 deletions(-) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 40586ea3..cf69cb79 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -5,10 +5,12 @@ on: paths: - '.github/workflows/mysql-parser-extension-tests.yml' - 'packages/mysql-on-sqlite/**' + - 'packages/php-ext-wp-mysql-parser/**' pull_request: paths: - '.github/workflows/mysql-parser-extension-tests.yml' - 'packages/mysql-on-sqlite/**' + - 'packages/php-ext-wp-mysql-parser/**' workflow_dispatch: concurrency: @@ -73,17 +75,25 @@ jobs: fwrite( STDERR, "Native lexer is not available.\n" ); exit( 1 ); } - $tokens = $lexer->remaining_tokens(); - $parser = new WP_MySQL_Parser( new WP_Parser_Grammar( include "src/mysql/mysql-grammar.php" ), $tokens ); + $tokens = $lexer->native_token_stream(); + $rules = include "src/mysql/mysql-grammar.php"; + $grammar = new WP_Parser_Grammar( $rules ); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + if ( ! ( $parser instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "Native parser is not available.\n" ); + exit( 1 ); + } $ast = $parser->parse(); - if ( ! $ast instanceof WP_Parser_Node || "query" !== $ast->rule_name ) { + if ( ! $ast instanceof WP_MySQL_Native_Parser_Node || "query" !== $ast->rule_name ) { fwrite( STDERR, "Native parser did not produce the expected query AST.\n" ); exit( 1 ); } ' working-directory: packages/mysql-on-sqlite - - name: Run PHPUnit tests with parser extension + - name: Run full PHPUnit suite with parser extension + env: + WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION: '1' run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist working-directory: packages/mysql-on-sqlite @@ -142,6 +152,10 @@ jobs: } $driver = new WP_PDO_MySQL_On_SQLite( "mysql-on-sqlite:path=:memory:;dbname=wp;" ); $parser = $driver->create_parser( "SELECT 1" ); + if ( ! ( $parser instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "SQLite driver did not create a native parser.\n" ); + exit( 1 ); + } $parser->next_query(); $ast = $parser->get_query_ast(); if ( ! ( $ast instanceof WP_MySQL_Native_Parser_Node ) ) { @@ -171,6 +185,8 @@ jobs: } ' - - name: Run PHPUnit tests with SQLite driver using parser extension + - name: Run full PHPUnit suite with SQLite driver using parser extension + env: + WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION: '1' run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist working-directory: packages/mysql-on-sqlite diff --git a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh index 241bc2db..22a1b22c 100644 --- a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -113,7 +113,7 @@ add_volume_to_service cli "$EXTENSION_INI_VOLUME" cat > "$WP_DIR/native-verify-extension.php" <<'EOF' native_token_stream(); +$rules = include '/var/www/src/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/mysql-grammar.php'; +$grammar = new WP_Parser_Grammar( $rules ); +$parser = new WP_MySQL_Parser( $grammar, $tokens ); +if ( ! ( $parser instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WordPress PHP test container did not select the native parser.\n" ); + exit( 1 ); +} + +$parser_ast = $parser->parse(); +if ( ! ( $parser_ast instanceof WP_MySQL_Native_Parser_Node ) ) { + fwrite( STDERR, "Native parser did not produce a native-backed AST in the WordPress PHP test container.\n" ); + exit( 1 ); +} + $driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); $parser = $driver->create_parser( 'SELECT 1' ); +if ( ! ( $parser instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WordPress PHP test container SQLite driver did not create a native parser.\n" ); + exit( 1 ); +} $parser->next_query(); $ast = $parser->get_query_ast(); @@ -157,5 +176,31 @@ if ( $same_first !== $first || ! in_array( $synthetic, $same_first->get_children } EOF +node - "$WP_DIR/tests/phpunit/includes/bootstrap.php" <<'NODE' +const fs = require( 'fs' ); + +const file = process.argv[2]; +const marker = "require_once ABSPATH . 'wp-settings.php';"; +const guard = [ + '/*', + ' * Native parser CI guard. This file is generated by the SQLite integration workflow.', + ' */', + "require_once dirname( __DIR__, 3 ) . '/native-verify-extension.php';", +].join( '\n' ); + +let contents = fs.readFileSync( file, 'utf8' ); + +if ( contents.includes( guard ) ) { + process.exit( 0 ); +} + +if ( ! contents.includes( marker ) ) { + throw new Error( `Unable to find WordPress bootstrap marker in ${ file }.` ); +} + +contents = contents.replace( marker, `${ marker }\n\n${ guard }` ); +fs.writeFileSync( file, contents ); +NODE + node tools/local-env/scripts/docker.js run --rm php php -m | grep -qx 'wp_mysql_parser' node tools/local-env/scripts/docker.js run --rm php php /var/www/native-verify-extension.php diff --git a/packages/mysql-on-sqlite/tests/bootstrap.php b/packages/mysql-on-sqlite/tests/bootstrap.php index c0247d1a..c96d9d3c 100644 --- a/packages/mysql-on-sqlite/tests/bootstrap.php +++ b/packages/mysql-on-sqlite/tests/bootstrap.php @@ -9,6 +9,67 @@ define( 'WP_SQLITE_UNSAFE_ENABLE_UNSUPPORTED_VERSIONS', true ); } +if ( '1' === getenv( 'WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION' ) ) { + if ( ! class_exists( 'WP_MySQL_Native_Lexer', false ) || ! class_exists( 'WP_MySQL_Native_Parser', false ) ) { + fwrite( STDERR, "Native MySQL parser extension is required for this PHPUnit run.\n" ); + exit( 1 ); + } + + $native_parser_lexer = new WP_MySQL_Lexer( 'SELECT 1' ); + if ( ! ( $native_parser_lexer instanceof WP_MySQL_Native_Lexer ) ) { + fwrite( STDERR, "WP_MySQL_Lexer did not resolve to the native implementation.\n" ); + exit( 1 ); + } + + $native_parser_tokens = $native_parser_lexer->native_token_stream(); + $native_parser_rules = include __DIR__ . '/../src/mysql/mysql-grammar.php'; + $native_parser_grammar = new WP_Parser_Grammar( $native_parser_rules ); + $native_parser = new WP_MySQL_Parser( $native_parser_grammar, $native_parser_tokens ); + if ( ! ( $native_parser instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WP_MySQL_Parser did not resolve to the native implementation.\n" ); + exit( 1 ); + } + + $native_parser_ast = $native_parser->parse(); + if ( ! ( $native_parser_ast instanceof WP_MySQL_Native_Parser_Node ) ) { + fwrite( STDERR, "Native parser did not produce a native-backed AST.\n" ); + exit( 1 ); + } + + $native_parser_driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); + $native_parser_driver_parser = $native_parser_driver->create_parser( 'SELECT 1' ); + if ( ! ( $native_parser_driver_parser instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WP_PDO_MySQL_On_SQLite did not create a native parser.\n" ); + exit( 1 ); + } + + $native_parser_driver_parser->next_query(); + $native_parser_driver_ast = $native_parser_driver_parser->get_query_ast(); + if ( ! ( $native_parser_driver_ast instanceof WP_MySQL_Native_Parser_Node ) ) { + fwrite( STDERR, "WP_PDO_MySQL_On_SQLite did not produce a native-backed AST.\n" ); + exit( 1 ); + } + + $native_parser_driver_child = $native_parser_driver_ast->get_first_child_node(); + if ( ! ( $native_parser_driver_child instanceof WP_MySQL_Native_Parser_Node ) ) { + fwrite( STDERR, "WP_PDO_MySQL_On_SQLite did not produce native-backed child AST nodes.\n" ); + exit( 1 ); + } + + unset( + $native_parser_ast, + $native_parser, + $native_parser_grammar, + $native_parser_rules, + $native_parser_tokens, + $native_parser_lexer, + $native_parser_driver, + $native_parser_driver_parser, + $native_parser_driver_ast, + $native_parser_driver_child + ); +} + // Configure the test environment. error_reporting( E_ALL ); define( 'FQDB', ':memory:' ); From 2b595f37043caa08fb2f2529c71380d9949866d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 16:23:41 +0200 Subject: [PATCH 35/40] Restore `instanceof WP_Parser` for the native parser (#393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Jan's note on #381: in native mode, `new WP_MySQL_Parser(...) instanceof WP_Parser` returns false because the native-mode class extends the Rust-registered `WP_MySQL_Native_Parser`, which has no `WP_Parser` in its chain. Existing downstream code doing `if ($parser instanceof WP_Parser)` silently skipped the parser whenever the extension was loaded. This restores the contract by always extending the pure-PHP `WP_Parser` and pulling the native-mode behaviour in via a trait: ```php class WP_MySQL_Parser extends WP_Parser { use WP_MySQL_Native_Parser_Impl; } ``` `WP_MySQL_Native_Parser_Impl` owns the composed `WP_MySQL_Native_Parser` instance and the four-method delegation surface (`parse`, `next_query`, `get_query_ast`, `reset_tokens`). `WP_Parser`'s protected state (`$grammar`, `$tokens`, `$position`) is initialised by `parent::__construct` and stays inert — the trait's overrides never read it. Adding a public method later means adding it to the trait — the class file itself is two lines and doesn't need touching. ## Why a trait, not a private property? A bare property would also work, but the trait keeps the class file expressing only the routing decision (`extends WP_Parser` + `use Rust_Implementation;`). The implementation lives in one place, symmetric to where a future PHP-mode trait could live if we ever want to mirror the structure. Behaviour-wise the two are equivalent. ## Performance The trait adds **one extra method-call frame per public-API call**. The public API is `parse()`, `next_query()`, `get_query_ast()`, `reset_tokens()` — called once per query. The actual parsing work happens inside the native call, so the delegation overhead is a small constant per query, not a multiplier on the parsing work. The `Parser Delegation Perf` workflow runs `tests/tools/run-parser-benchmark.php` (parses the full MySQL server-suite corpus, ~70k queries) three times on this PR and three times on the PR base, on the same runner, with the extension loaded both times. The comparison goes into the job summary on every push. ## Test plan - [x] PHP-only PHPUnit suite passes. - [ ] PHPUnit suite passes with the native extension loaded; the new `WP_MySQL_Parser_Instanceof_Tests` confirm `instanceof WP_Parser` and `instanceof WP_MySQL_Parser` both hold. - [ ] `Parser Delegation Perf` workflow shows the delegation cost is within noise. --- .github/workflows/parser-delegation-perf.yml | 146 ++++++++++++++++++ packages/mysql-on-sqlite/src/load.php | 1 + .../mysql/native/class-wp-mysql-parser.php | 13 +- .../trait-wp-mysql-native-parser-impl.php | 55 +++++++ .../WP_MySQL_Parser_Instanceof_Tests.php | 40 +++++ 5 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/parser-delegation-perf.yml create mode 100644 packages/mysql-on-sqlite/src/mysql/native/trait-wp-mysql-native-parser-impl.php create mode 100644 packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Parser_Instanceof_Tests.php diff --git a/.github/workflows/parser-delegation-perf.yml b/.github/workflows/parser-delegation-perf.yml new file mode 100644 index 00000000..b0f8ca73 --- /dev/null +++ b/.github/workflows/parser-delegation-perf.yml @@ -0,0 +1,146 @@ +name: Parser Delegation Perf + +# Measures the wall-time cost of routing the public parser API through a +# composed `WP_MySQL_Native_Parser` instance (this PR) instead of letting +# `WP_MySQL_Parser` directly extend the Rust class (PR base). The parse +# benchmark walks the full MySQL server-suite corpus on both branches — +# the delta is the per-call delegation cost. + +on: + push: + paths: + - '.github/workflows/parser-delegation-perf.yml' + - 'packages/mysql-on-sqlite/src/mysql/native/**' + - 'packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php' + - 'packages/php-ext-wp-mysql-parser/**' + pull_request: + paths: + - '.github/workflows/parser-delegation-perf.yml' + - 'packages/mysql-on-sqlite/src/mysql/native/**' + - 'packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php' + - 'packages/php-ext-wp-mysql-parser/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + perf: + name: Parser delegation benchmark + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install native build dependencies + run: | + sudo apt-get update + sudo apt-get install -y libclang-dev + echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV" + LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)" + echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV" + + - name: Install Composer dependencies (root) + uses: ramsey/composer-install@v3 + with: + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Install Composer dependencies (mysql-on-sqlite) + uses: ramsey/composer-install@v3 + with: + working-directory: packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Download MySQL test query corpus + working-directory: packages/mysql-on-sqlite + run: bash tests/tools/mysql-download-tests.sh || true + + - name: Build parser extension (release) + run: cargo build --release + working-directory: packages/php-ext-wp-mysql-parser + + - name: Locate built extension + run: | + EXT="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so" + test -f "$EXT" || { echo "Extension not built at $EXT"; exit 1; } + echo "NATIVE_EXT=$EXT" >> "$GITHUB_ENV" + + - name: Benchmark — this PR (trait delegation) + working-directory: packages/mysql-on-sqlite + run: | + for i in 1 2 3; do + echo "=== run $i ===" + php -d extension="$NATIVE_EXT" tests/tools/run-parser-benchmark.php + done | tee "$GITHUB_WORKSPACE/this-pr.txt" + + - name: Check out baseline (PR base, direct extends) + run: | + git fetch --no-tags --depth=1 origin codex/native-lazy-ast-facade + git worktree add ../baseline FETCH_HEAD + + - name: Install Composer dependencies (baseline) + uses: ramsey/composer-install@v3 + with: + working-directory: ../baseline/packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + + - name: Build baseline parser extension (release) + run: cargo build --release + working-directory: ../baseline/packages/php-ext-wp-mysql-parser + + - name: Stage corpus into baseline + run: | + mkdir -p ../baseline/packages/mysql-on-sqlite/tests/mysql/data + cp packages/mysql-on-sqlite/tests/mysql/data/*.csv \ + ../baseline/packages/mysql-on-sqlite/tests/mysql/data/ 2>/dev/null || true + + - name: Benchmark — baseline (direct extends) + working-directory: ../baseline/packages/mysql-on-sqlite + run: | + BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" + for i in 1 2 3; do + echo "=== run $i ===" + php -d extension="$BASE_EXT" tests/tools/run-parser-benchmark.php + done | tee "$GITHUB_WORKSPACE/baseline.txt" + + - name: Summarize + if: always() + run: | + { + echo '### Parser delegation perf' + echo + echo '#### This PR (`use WP_MySQL_Native_Parser_Impl;`)' + echo '```' + cat "$GITHUB_WORKSPACE/this-pr.txt" 2>/dev/null || echo 'no output' + echo '```' + echo + echo '#### Baseline (`extends WP_MySQL_Native_Parser`)' + echo '```' + cat "$GITHUB_WORKSPACE/baseline.txt" 2>/dev/null || echo 'no output' + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload raw output + if: always() + uses: actions/upload-artifact@v4 + with: + name: parser-delegation-perf-${{ github.run_id }} + path: | + this-pr.txt + baseline.txt + if-no-files-found: warn diff --git a/packages/mysql-on-sqlite/src/load.php b/packages/mysql-on-sqlite/src/load.php index 2070c631..62387a2e 100644 --- a/packages/mysql-on-sqlite/src/load.php +++ b/packages/mysql-on-sqlite/src/load.php @@ -28,6 +28,7 @@ if ( class_exists( 'WP_MySQL_Native_Parser', false ) ) { require_once __DIR__ . '/mysql/native/mysql-rust-bridge.php'; require_once __DIR__ . '/mysql/native/class-wp-mysql-native-parser-node.php'; + require_once __DIR__ . '/mysql/native/trait-wp-mysql-native-parser-impl.php'; require_once __DIR__ . '/mysql/native/class-wp-mysql-parser.php'; } else { require_once __DIR__ . '/mysql/class-wp-mysql-parser.php'; diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-parser.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-parser.php index a3209cd1..76244ad7 100644 --- a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-parser.php +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-parser.php @@ -1,3 +1,14 @@ native`. `WP_Parser`'s state (`$grammar`, `$tokens`, + * `$position`) stays inert in native mode — the trait's overrides + * never read it. + * + * Adding a public method here is enough to plumb a new public method + * through to the native parser; the using class does not need touching. + */ +trait WP_MySQL_Native_Parser_Impl { + /** + * @var WP_MySQL_Native_Parser + */ + private $native; + + /** + * @param WP_Parser_Grammar $grammar + * @param array|WP_MySQL_Native_Token_Stream $tokens + */ + public function __construct( WP_Parser_Grammar $grammar, $tokens ) { + // WP_Parser's `array $tokens` constructor signature can't accept + // the native token stream object; its `$this->tokens` / + // `$this->position` state is inert in native mode anyway, so we + // pass an empty array to satisfy the parent contract and keep + // the actual tokens on the native parser. + parent::__construct( $grammar, array() ); + $this->native = new WP_MySQL_Native_Parser( $grammar, $tokens ); + } + + /** + * @param array|WP_MySQL_Native_Token_Stream $tokens + */ + public function reset_tokens( $tokens ): void { + $this->native->reset_tokens( $tokens ); + } + + public function next_query(): bool { + return $this->native->next_query(); + } + + public function get_query_ast(): ?WP_Parser_Node { + return $this->native->get_query_ast(); + } + + public function parse() { + return $this->native->parse(); + } +} diff --git a/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Parser_Instanceof_Tests.php b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Parser_Instanceof_Tests.php new file mode 100644 index 00000000..810d6622 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Parser_Instanceof_Tests.php @@ -0,0 +1,40 @@ +native_token_stream() + : $lexer->remaining_tokens(); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + + $this->assertInstanceOf( WP_Parser::class, $parser ); + $this->assertInstanceOf( WP_MySQL_Parser::class, $parser ); + } + + public function test_parser_returns_an_ast(): void { + $grammar = new WP_Parser_Grammar( include __DIR__ . '/../../../src/mysql/mysql-grammar.php' ); + $lexer = new WP_MySQL_Lexer( 'SELECT 1 + 2' ); + $tokens = $lexer instanceof WP_MySQL_Native_Lexer + ? $lexer->native_token_stream() + : $lexer->remaining_tokens(); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + + $ast = $parser->parse(); + $this->assertNotNull( $ast ); + $this->assertInstanceOf( WP_Parser_Node::class, $ast ); + } +} From bb772588b3eb1c8c8120bf1dd315395424892a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 16:29:39 +0200 Subject: [PATCH 36/40] Assert native parser delegate in extension test guards --- .../mysql-parser-extension-tests.yml | 22 +++++++++++++--- ...wp-tests-phpunit-native-extension-setup.sh | 22 +++++++++++++--- packages/mysql-on-sqlite/tests/bootstrap.php | 26 ++++++++++++++++--- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index cf69cb79..17729793 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -79,8 +79,15 @@ jobs: $rules = include "src/mysql/mysql-grammar.php"; $grammar = new WP_Parser_Grammar( $rules ); $parser = new WP_MySQL_Parser( $grammar, $tokens ); - if ( ! ( $parser instanceof WP_MySQL_Native_Parser ) ) { - fwrite( STDERR, "Native parser is not available.\n" ); + $parser_reflection = new ReflectionObject( $parser ); + if ( ! $parser_reflection->hasProperty( "native" ) ) { + fwrite( STDERR, "WP_MySQL_Parser did not select the native parser delegate.\n" ); + exit( 1 ); + } + $native_property = $parser_reflection->getProperty( "native" ); + $native_property->setAccessible( true ); + if ( ! ( $native_property->getValue( $parser ) instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WP_MySQL_Parser did not select the native parser delegate.\n" ); exit( 1 ); } $ast = $parser->parse(); @@ -152,8 +159,15 @@ jobs: } $driver = new WP_PDO_MySQL_On_SQLite( "mysql-on-sqlite:path=:memory:;dbname=wp;" ); $parser = $driver->create_parser( "SELECT 1" ); - if ( ! ( $parser instanceof WP_MySQL_Native_Parser ) ) { - fwrite( STDERR, "SQLite driver did not create a native parser.\n" ); + $parser_reflection = new ReflectionObject( $parser ); + if ( ! $parser_reflection->hasProperty( "native" ) ) { + fwrite( STDERR, "SQLite driver did not create a native parser delegate.\n" ); + exit( 1 ); + } + $native_property = $parser_reflection->getProperty( "native" ); + $native_property->setAccessible( true ); + if ( ! ( $native_property->getValue( $parser ) instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "SQLite driver did not create a native parser delegate.\n" ); exit( 1 ); } $parser->next_query(); diff --git a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh index 22a1b22c..fac9e827 100644 --- a/.github/workflows/wp-tests-phpunit-native-extension-setup.sh +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -125,8 +125,15 @@ $tokens = $lexer->native_token_stream(); $rules = include '/var/www/src/wp-content/plugins/sqlite-database-integration/wp-includes/database/mysql/mysql-grammar.php'; $grammar = new WP_Parser_Grammar( $rules ); $parser = new WP_MySQL_Parser( $grammar, $tokens ); -if ( ! ( $parser instanceof WP_MySQL_Native_Parser ) ) { - fwrite( STDERR, "WordPress PHP test container did not select the native parser.\n" ); +$parser_reflection = new ReflectionObject( $parser ); +if ( ! $parser_reflection->hasProperty( 'native' ) ) { + fwrite( STDERR, "WordPress PHP test container did not select the native parser delegate.\n" ); + exit( 1 ); +} +$native_property = $parser_reflection->getProperty( 'native' ); +$native_property->setAccessible( true ); +if ( ! ( $native_property->getValue( $parser ) instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WordPress PHP test container did not select the native parser delegate.\n" ); exit( 1 ); } @@ -138,8 +145,15 @@ if ( ! ( $parser_ast instanceof WP_MySQL_Native_Parser_Node ) ) { $driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); $parser = $driver->create_parser( 'SELECT 1' ); -if ( ! ( $parser instanceof WP_MySQL_Native_Parser ) ) { - fwrite( STDERR, "WordPress PHP test container SQLite driver did not create a native parser.\n" ); +$parser_reflection = new ReflectionObject( $parser ); +if ( ! $parser_reflection->hasProperty( 'native' ) ) { + fwrite( STDERR, "WordPress PHP test container SQLite driver did not create a native parser delegate.\n" ); + exit( 1 ); +} +$native_property = $parser_reflection->getProperty( 'native' ); +$native_property->setAccessible( true ); +if ( ! ( $native_property->getValue( $parser ) instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WordPress PHP test container SQLite driver did not create a native parser delegate.\n" ); exit( 1 ); } $parser->next_query(); diff --git a/packages/mysql-on-sqlite/tests/bootstrap.php b/packages/mysql-on-sqlite/tests/bootstrap.php index c96d9d3c..10e11297 100644 --- a/packages/mysql-on-sqlite/tests/bootstrap.php +++ b/packages/mysql-on-sqlite/tests/bootstrap.php @@ -25,8 +25,15 @@ $native_parser_rules = include __DIR__ . '/../src/mysql/mysql-grammar.php'; $native_parser_grammar = new WP_Parser_Grammar( $native_parser_rules ); $native_parser = new WP_MySQL_Parser( $native_parser_grammar, $native_parser_tokens ); - if ( ! ( $native_parser instanceof WP_MySQL_Native_Parser ) ) { - fwrite( STDERR, "WP_MySQL_Parser did not resolve to the native implementation.\n" ); + $native_parser_reflection = new ReflectionObject( $native_parser ); + if ( ! $native_parser_reflection->hasProperty( 'native' ) ) { + fwrite( STDERR, "WP_MySQL_Parser did not create a native parser delegate.\n" ); + exit( 1 ); + } + $native_parser_property = $native_parser_reflection->getProperty( 'native' ); + $native_parser_property->setAccessible( true ); + if ( ! ( $native_parser_property->getValue( $native_parser ) instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WP_MySQL_Parser did not create a native parser delegate.\n" ); exit( 1 ); } @@ -38,8 +45,15 @@ $native_parser_driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); $native_parser_driver_parser = $native_parser_driver->create_parser( 'SELECT 1' ); - if ( ! ( $native_parser_driver_parser instanceof WP_MySQL_Native_Parser ) ) { - fwrite( STDERR, "WP_PDO_MySQL_On_SQLite did not create a native parser.\n" ); + $native_parser_driver_reflection = new ReflectionObject( $native_parser_driver_parser ); + if ( ! $native_parser_driver_reflection->hasProperty( 'native' ) ) { + fwrite( STDERR, "WP_PDO_MySQL_On_SQLite did not create a native parser delegate.\n" ); + exit( 1 ); + } + $native_parser_driver_property = $native_parser_driver_reflection->getProperty( 'native' ); + $native_parser_driver_property->setAccessible( true ); + if ( ! ( $native_parser_driver_property->getValue( $native_parser_driver_parser ) instanceof WP_MySQL_Native_Parser ) ) { + fwrite( STDERR, "WP_PDO_MySQL_On_SQLite did not create a native parser delegate.\n" ); exit( 1 ); } @@ -65,6 +79,10 @@ $native_parser_lexer, $native_parser_driver, $native_parser_driver_parser, + $native_parser_reflection, + $native_parser_property, + $native_parser_driver_reflection, + $native_parser_driver_property, $native_parser_driver_ast, $native_parser_driver_child ); From b186e84f736ae1e060ca96f6e100b0beb023549d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 16:31:49 +0200 Subject: [PATCH 37/40] Fix native parser guard coding standards --- packages/mysql-on-sqlite/tests/bootstrap.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mysql-on-sqlite/tests/bootstrap.php b/packages/mysql-on-sqlite/tests/bootstrap.php index 10e11297..d97cdd84 100644 --- a/packages/mysql-on-sqlite/tests/bootstrap.php +++ b/packages/mysql-on-sqlite/tests/bootstrap.php @@ -21,10 +21,10 @@ exit( 1 ); } - $native_parser_tokens = $native_parser_lexer->native_token_stream(); - $native_parser_rules = include __DIR__ . '/../src/mysql/mysql-grammar.php'; - $native_parser_grammar = new WP_Parser_Grammar( $native_parser_rules ); - $native_parser = new WP_MySQL_Parser( $native_parser_grammar, $native_parser_tokens ); + $native_parser_tokens = $native_parser_lexer->native_token_stream(); + $native_parser_rules = include __DIR__ . '/../src/mysql/mysql-grammar.php'; + $native_parser_grammar = new WP_Parser_Grammar( $native_parser_rules ); + $native_parser = new WP_MySQL_Parser( $native_parser_grammar, $native_parser_tokens ); $native_parser_reflection = new ReflectionObject( $native_parser ); if ( ! $native_parser_reflection->hasProperty( 'native' ) ) { fwrite( STDERR, "WP_MySQL_Parser did not create a native parser delegate.\n" ); @@ -43,8 +43,8 @@ exit( 1 ); } - $native_parser_driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); - $native_parser_driver_parser = $native_parser_driver->create_parser( 'SELECT 1' ); + $native_parser_driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); + $native_parser_driver_parser = $native_parser_driver->create_parser( 'SELECT 1' ); $native_parser_driver_reflection = new ReflectionObject( $native_parser_driver_parser ); if ( ! $native_parser_driver_reflection->hasProperty( 'native' ) ) { fwrite( STDERR, "WP_PDO_MySQL_On_SQLite did not create a native parser delegate.\n" ); From e6d5b2c298aada37490990bbbcee0e85f73eed50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 17:07:47 +0200 Subject: [PATCH 38/40] Run native parser extension suite across PHP versions --- .../mysql-parser-extension-tests.yml | 63 +++---------------- 1 file changed, 10 insertions(+), 53 deletions(-) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 17729793..26ad426d 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -2,6 +2,8 @@ name: MySQL Parser Extension Tests on: push: + branches: + - trunk paths: - '.github/workflows/mysql-parser-extension-tests.yml' - 'packages/mysql-on-sqlite/**' @@ -19,9 +21,13 @@ concurrency: jobs: extension-tests: - name: PHP 8.2 / Rust extension / ubuntu-latest + name: PHP ${{ matrix.php }} / Rust extension / ubuntu-latest runs-on: ubuntu-latest timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] steps: - name: Checkout repository @@ -30,7 +36,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: ${{ matrix.php }} coverage: none tools: phpunit-polyfills @@ -59,6 +65,7 @@ jobs: composer-options: "--optimize-autoloader" - name: Check Rust formatting + if: matrix.php == '8.2' run: cargo fmt --check working-directory: packages/php-ext-wp-mysql-parser @@ -98,56 +105,6 @@ jobs: ' working-directory: packages/mysql-on-sqlite - - name: Run full PHPUnit suite with parser extension - env: - WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION: '1' - run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist - working-directory: packages/mysql-on-sqlite - - sqlite-driver-extension-tests: - name: PHP 8.2 / SQLite driver / Rust extension / ubuntu-latest - runs-on: ubuntu-latest - timeout-minutes: 20 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.2' - coverage: none - tools: phpunit-polyfills - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - - - name: Install native build dependencies - run: | - sudo apt-get update - sudo apt-get install -y libclang-dev - echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV" - LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)" - echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV" - - - name: Install Composer dependencies (root) - uses: ramsey/composer-install@v3 - with: - ignore-cache: "yes" - composer-options: "--optimize-autoloader" - - - name: Install Composer dependencies (mysql-on-sqlite) - uses: ramsey/composer-install@v3 - with: - working-directory: packages/mysql-on-sqlite - ignore-cache: "yes" - composer-options: "--optimize-autoloader" - - - name: Build parser extension - run: cargo build - working-directory: packages/php-ext-wp-mysql-parser - - name: Verify SQLite driver selects the native parser path run: | php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' @@ -199,7 +156,7 @@ jobs: } ' - - name: Run full PHPUnit suite with SQLite driver using parser extension + - name: Run full PHPUnit suite with parser extension env: WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION: '1' run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist From 13f131bb438e0a655114484d6965155e1cab52da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 17:14:53 +0200 Subject: [PATCH 39/40] Run SQLite integration matrix in parser extension CI --- .../mysql-parser-extension-tests.yml | 108 +++++++++++++++++- packages/php-ext-wp-mysql-parser/Cargo.toml | 2 +- 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml index 26ad426d..6f8e7025 100644 --- a/.github/workflows/mysql-parser-extension-tests.yml +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -21,18 +21,93 @@ concurrency: jobs: extension-tests: - name: PHP ${{ matrix.php }} / Rust extension / ubuntu-latest + name: PHP ${{ matrix.php }} / ${{ matrix.coverage }} / ubuntu-latest runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 30 strategy: fail-fast: false matrix: - php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] + include: + - php: '7.2' + sqlite: '3.27.0' + native: false + coverage: SQLite integration + - php: '7.3' + sqlite: '3.31.1' + native: false + coverage: SQLite integration + - php: '7.4' + sqlite: '3.34.1' + native: false + coverage: SQLite integration + - php: '8.0' + sqlite: '3.37.0' + native: true + coverage: SQLite integration + Rust extension + - php: '8.1' + sqlite: '3.40.1' + native: true + coverage: SQLite integration + Rust extension + - php: '8.2' + sqlite: '3.45.1' + native: true + coverage: SQLite integration + Rust extension + - php: '8.3' + sqlite: '3.46.1' + native: true + coverage: SQLite integration + Rust extension + - php: '8.4' + sqlite: '3.51.2' + native: true + coverage: SQLite integration + Rust extension + - php: '8.5' + sqlite: latest + native: true + coverage: SQLite integration + Rust extension steps: - name: Checkout repository uses: actions/checkout@v4 + - name: Set up SQLite + run: | + VERSION='${{ matrix.sqlite }}' + if [ "$VERSION" = 'latest' ]; then + TAG='release' + else + TAG="version-${VERSION}" + fi + SQLITE_SOURCE="https://sqlite.org/src/tarball/sqlite.tar.gz?r=${TAG}" + SQLITE_MIRROR="https://github.com/sqlite/sqlite/archive/refs/tags/${TAG}.tar.gz" + DOWNLOADED=0 + for url in "$SQLITE_SOURCE" "$SQLITE_MIRROR"; do + for attempt in 1 2 3 4 5; do + if wget -O sqlite.tar.gz "$url"; then + DOWNLOADED=1 + break 2 + fi + if [ "$attempt" -lt 5 ]; then + sleep $(( attempt * 10 )) + fi + done + done + if [ "$DOWNLOADED" -ne 1 ]; then + exit 1 + fi + tar xzf sqlite.tar.gz + if [ ! -d sqlite ]; then + SQLITE_DIR=$(find . -maxdepth 1 -type d -name 'sqlite-*' | head -n 1) + if [ -z "$SQLITE_DIR" ]; then + exit 1 + fi + mv "$SQLITE_DIR" sqlite + fi + cd sqlite + ./configure --prefix=/usr/local CFLAGS="-DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_FTS5 -DSQLITE_USE_URI -DSQLITE_ENABLE_JSON1" LDFLAGS="-lm" + make -j$(nproc) + sudo make install + sudo ldconfig + - name: Set up PHP uses: shivammathur/setup-php@v2 with: @@ -40,10 +115,26 @@ jobs: coverage: none tools: phpunit-polyfills + - name: Verify SQLite version in PHP + run: | + EXPECTED='${{ matrix.sqlite }}' + if [ "$EXPECTED" = 'latest' ]; then + EXPECTED=$(cat sqlite/VERSION) + fi + PDO=$(php -r "echo (new PDO('sqlite::memory'))->query('SELECT SQLITE_VERSION();')->fetch()[0];") + echo "Expected SQLite version: $EXPECTED" + echo "PHP PDO SQLite version: $PDO" + if [ "$EXPECTED" != "$PDO" ]; then + echo "Error: Expected SQLite version $EXPECTED, but PHP PDO uses $PDO" + exit 1 + fi + - name: Set up Rust + if: matrix.native uses: dtolnay/rust-toolchain@stable - name: Install native build dependencies + if: matrix.native run: | sudo apt-get update sudo apt-get install -y libclang-dev @@ -65,15 +156,17 @@ jobs: composer-options: "--optimize-autoloader" - name: Check Rust formatting - if: matrix.php == '8.2' + if: matrix.php == '8.2' && matrix.native run: cargo fmt --check working-directory: packages/php-ext-wp-mysql-parser - name: Build parser extension + if: matrix.native run: cargo build working-directory: packages/php-ext-wp-mysql-parser - name: Run native parser smoke tests + if: matrix.native run: | php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' require "src/load.php"; @@ -106,6 +199,7 @@ jobs: working-directory: packages/mysql-on-sqlite - name: Verify SQLite driver selects the native parser path + if: matrix.native run: | php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' require "packages/mysql-on-sqlite/src/load.php"; @@ -157,7 +251,13 @@ jobs: ' - name: Run full PHPUnit suite with parser extension + if: matrix.native env: WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION: '1' run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" ./vendor/bin/phpunit -c ./phpunit.xml.dist working-directory: packages/mysql-on-sqlite + + - name: Run full PHPUnit suite + if: ${{ ! matrix.native }} + run: php ./vendor/bin/phpunit -c ./phpunit.xml.dist + working-directory: packages/mysql-on-sqlite diff --git a/packages/php-ext-wp-mysql-parser/Cargo.toml b/packages/php-ext-wp-mysql-parser/Cargo.toml index 91b46159..6646c110 100644 --- a/packages/php-ext-wp-mysql-parser/Cargo.toml +++ b/packages/php-ext-wp-mysql-parser/Cargo.toml @@ -8,7 +8,7 @@ license = "GPL-2.0-or-later" crate-type = ["cdylib"] [dependencies] -ext-php-rs = "0.15.12" +ext-php-rs = { version = "0.15.12", default-features = false, features = ["runtime"] } libc = "0.2" stacker = "0.1" From bde34d55686a0a80d58876968d0b73f847144f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 1 May 2026 17:51:57 +0200 Subject: [PATCH 40/40] Remove parser perf CI artifacts --- .github/workflows/native-ast-perf.yml | 216 ------------------ .github/workflows/parser-delegation-perf.yml | 146 ------------ .../tools/run-native-ast-walk-benchmark.php | 186 --------------- 3 files changed, 548 deletions(-) delete mode 100644 .github/workflows/native-ast-perf.yml delete mode 100644 .github/workflows/parser-delegation-perf.yml delete mode 100644 packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php diff --git a/.github/workflows/native-ast-perf.yml b/.github/workflows/native-ast-perf.yml deleted file mode 100644 index 9b51eb8e..00000000 --- a/.github/workflows/native-ast-perf.yml +++ /dev/null @@ -1,216 +0,0 @@ -name: Native AST Walk Perf - -on: - push: - paths: - - '.github/workflows/native-ast-perf.yml' - - 'packages/mysql-on-sqlite/src/mysql/native/**' - - 'packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php' - - 'packages/php-ext-wp-mysql-parser/**' - pull_request: - paths: - - '.github/workflows/native-ast-perf.yml' - - 'packages/mysql-on-sqlite/src/mysql/native/**' - - 'packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php' - - 'packages/php-ext-wp-mysql-parser/**' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - perf: - name: Native AST walk benchmark - runs-on: ubuntu-latest - timeout-minutes: 25 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.2' - coverage: none - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - - - name: Install native build dependencies - run: | - sudo apt-get update - sudo apt-get install -y libclang-dev - echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV" - LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)" - echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV" - - - name: Install Composer dependencies (root) - uses: ramsey/composer-install@v3 - with: - ignore-cache: "yes" - composer-options: "--optimize-autoloader" - - - name: Install Composer dependencies (mysql-on-sqlite) - uses: ramsey/composer-install@v3 - with: - working-directory: packages/mysql-on-sqlite - ignore-cache: "yes" - composer-options: "--optimize-autoloader" - - - name: Download MySQL test query corpus - working-directory: packages/mysql-on-sqlite - run: bash tests/tools/mysql-download-tests.sh || true - - - name: Build parser extension (release) - run: cargo build --release - working-directory: packages/php-ext-wp-mysql-parser - - - name: Locate built extension - run: | - EXT="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so" - test -f "$EXT" || { echo "Extension not built at $EXT"; exit 1; } - echo "NATIVE_EXT=$EXT" >> "$GITHUB_ENV" - - - name: Benchmark — pure-PHP path (parse only) - working-directory: packages/mysql-on-sqlite - run: | - php tests/tools/run-native-ast-walk-benchmark.php --no-walk | tee php-parse-only.txt - - - name: Benchmark — pure-PHP path (walk) - working-directory: packages/mysql-on-sqlite - run: | - php tests/tools/run-native-ast-walk-benchmark.php | tee php-walk.txt - - - name: Benchmark — native path (parse only) - working-directory: packages/mysql-on-sqlite - run: | - php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk | tee native-parse-only.txt - - - name: Benchmark — native path (walk, with identity cache) - working-directory: packages/mysql-on-sqlite - run: | - php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php | tee native-walk.txt - - - name: Check out baseline (PR base, no identity cache) - run: | - git fetch --no-tags --depth=1 origin codex/native-lazy-ast-facade - git worktree add ../baseline FETCH_HEAD - - - name: Install Composer dependencies (baseline mysql-on-sqlite) - uses: ramsey/composer-install@v3 - with: - working-directory: ../baseline/packages/mysql-on-sqlite - ignore-cache: "yes" - composer-options: "--optimize-autoloader" - - - name: Build baseline parser extension (release) - run: cargo build --release - working-directory: ../baseline/packages/php-ext-wp-mysql-parser - - - name: Stage benchmark + corpus into baseline - run: | - mkdir -p ../baseline/packages/mysql-on-sqlite/tests/mysql/data - cp packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php \ - ../baseline/packages/mysql-on-sqlite/tests/tools/ - cp packages/mysql-on-sqlite/tests/mysql/data/*.csv \ - ../baseline/packages/mysql-on-sqlite/tests/mysql/data/ 2>/dev/null || true - - - name: Benchmark — baseline native path (walk, no identity cache) - working-directory: ../baseline/packages/mysql-on-sqlite - run: | - BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" - php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php \ - | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-walk.txt" - - - name: Benchmark — baseline native path (parse only) - working-directory: ../baseline/packages/mysql-on-sqlite - run: | - BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" - php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk \ - | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-parse-only.txt" - - # Hit-heavy scenarios — these are where the per-AST identity cache is - # supposed to win. The baseline reallocates wrappers on every accessor - # call, while the PR reuses them. Run on both to make the gap visible. - - name: Benchmark — native rewalk x10 (this PR) - working-directory: packages/mysql-on-sqlite - run: | - php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 \ - | tee native-rewalk.txt - - - name: Benchmark — baseline rewalk x10 - working-directory: ../baseline/packages/mysql-on-sqlite - run: | - BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" - php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 \ - | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-rewalk.txt" - - - name: Benchmark — native reread x20 (this PR) - working-directory: packages/mysql-on-sqlite - run: | - php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=reread --repeat=20 \ - | tee native-reread.txt - - - name: Benchmark — baseline reread x20 - working-directory: ../baseline/packages/mysql-on-sqlite - run: | - BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" - php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=reread --repeat=20 \ - | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-reread.txt" - - - name: Benchmark — native subtree x5 (this PR) - working-directory: packages/mysql-on-sqlite - run: | - php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 \ - | tee native-subtree.txt - - - name: Benchmark — baseline subtree x5 - working-directory: ../baseline/packages/mysql-on-sqlite - run: | - BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" - php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 \ - | tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-subtree.txt" - - - name: Summarize - if: always() - working-directory: packages/mysql-on-sqlite - run: | - extract() { - # Pull a numeric field (e.g. duration=1.23s) from a benchmark - # output line. Returns "n/a" if missing. - local file="$1" key="$2" - [ -f "$file" ] || { echo "n/a"; return; } - grep -oE "${key}=[^ ]+" "$file" | head -1 | cut -d= -f2 || echo "n/a" - } - - { - echo '### Native AST walk perf' - echo - echo '| scenario | result |' - echo '|---|---|' - for f in php-parse-only.txt php-walk.txt native-parse-only.txt native-walk.txt baseline-native-parse-only.txt baseline-native-walk.txt native-rewalk.txt baseline-native-rewalk.txt native-reread.txt baseline-native-reread.txt native-subtree.txt baseline-native-subtree.txt; do - [ -f "$f" ] || continue - line="$(cat "$f")" - echo "| ${f%.txt} | \`$line\` |" - done - echo - echo '### Identity-cache cost (native walk: with cache vs PR base without)' - echo - echo '| metric | with cache (this PR) | baseline | delta |' - echo '|---|---|---|---|' - for key in duration qps peak_mem walked_nodes; do - with="$(extract native-walk.txt "$key")" - base="$(extract baseline-native-walk.txt "$key")" - echo "| $key | $with | $base | — |" - done - } >> "$GITHUB_STEP_SUMMARY" - - - name: Upload raw output - if: always() - uses: actions/upload-artifact@v4 - with: - name: native-ast-perf-${{ github.run_id }} - path: packages/mysql-on-sqlite/*.txt - if-no-files-found: warn diff --git a/.github/workflows/parser-delegation-perf.yml b/.github/workflows/parser-delegation-perf.yml deleted file mode 100644 index b0f8ca73..00000000 --- a/.github/workflows/parser-delegation-perf.yml +++ /dev/null @@ -1,146 +0,0 @@ -name: Parser Delegation Perf - -# Measures the wall-time cost of routing the public parser API through a -# composed `WP_MySQL_Native_Parser` instance (this PR) instead of letting -# `WP_MySQL_Parser` directly extend the Rust class (PR base). The parse -# benchmark walks the full MySQL server-suite corpus on both branches — -# the delta is the per-call delegation cost. - -on: - push: - paths: - - '.github/workflows/parser-delegation-perf.yml' - - 'packages/mysql-on-sqlite/src/mysql/native/**' - - 'packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php' - - 'packages/php-ext-wp-mysql-parser/**' - pull_request: - paths: - - '.github/workflows/parser-delegation-perf.yml' - - 'packages/mysql-on-sqlite/src/mysql/native/**' - - 'packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php' - - 'packages/php-ext-wp-mysql-parser/**' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - perf: - name: Parser delegation benchmark - runs-on: ubuntu-latest - timeout-minutes: 25 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.2' - coverage: none - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - - - name: Install native build dependencies - run: | - sudo apt-get update - sudo apt-get install -y libclang-dev - echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV" - LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)" - echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV" - - - name: Install Composer dependencies (root) - uses: ramsey/composer-install@v3 - with: - ignore-cache: "yes" - composer-options: "--optimize-autoloader" - - - name: Install Composer dependencies (mysql-on-sqlite) - uses: ramsey/composer-install@v3 - with: - working-directory: packages/mysql-on-sqlite - ignore-cache: "yes" - composer-options: "--optimize-autoloader" - - - name: Download MySQL test query corpus - working-directory: packages/mysql-on-sqlite - run: bash tests/tools/mysql-download-tests.sh || true - - - name: Build parser extension (release) - run: cargo build --release - working-directory: packages/php-ext-wp-mysql-parser - - - name: Locate built extension - run: | - EXT="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so" - test -f "$EXT" || { echo "Extension not built at $EXT"; exit 1; } - echo "NATIVE_EXT=$EXT" >> "$GITHUB_ENV" - - - name: Benchmark — this PR (trait delegation) - working-directory: packages/mysql-on-sqlite - run: | - for i in 1 2 3; do - echo "=== run $i ===" - php -d extension="$NATIVE_EXT" tests/tools/run-parser-benchmark.php - done | tee "$GITHUB_WORKSPACE/this-pr.txt" - - - name: Check out baseline (PR base, direct extends) - run: | - git fetch --no-tags --depth=1 origin codex/native-lazy-ast-facade - git worktree add ../baseline FETCH_HEAD - - - name: Install Composer dependencies (baseline) - uses: ramsey/composer-install@v3 - with: - working-directory: ../baseline/packages/mysql-on-sqlite - ignore-cache: "yes" - composer-options: "--optimize-autoloader" - - - name: Build baseline parser extension (release) - run: cargo build --release - working-directory: ../baseline/packages/php-ext-wp-mysql-parser - - - name: Stage corpus into baseline - run: | - mkdir -p ../baseline/packages/mysql-on-sqlite/tests/mysql/data - cp packages/mysql-on-sqlite/tests/mysql/data/*.csv \ - ../baseline/packages/mysql-on-sqlite/tests/mysql/data/ 2>/dev/null || true - - - name: Benchmark — baseline (direct extends) - working-directory: ../baseline/packages/mysql-on-sqlite - run: | - BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)" - for i in 1 2 3; do - echo "=== run $i ===" - php -d extension="$BASE_EXT" tests/tools/run-parser-benchmark.php - done | tee "$GITHUB_WORKSPACE/baseline.txt" - - - name: Summarize - if: always() - run: | - { - echo '### Parser delegation perf' - echo - echo '#### This PR (`use WP_MySQL_Native_Parser_Impl;`)' - echo '```' - cat "$GITHUB_WORKSPACE/this-pr.txt" 2>/dev/null || echo 'no output' - echo '```' - echo - echo '#### Baseline (`extends WP_MySQL_Native_Parser`)' - echo '```' - cat "$GITHUB_WORKSPACE/baseline.txt" 2>/dev/null || echo 'no output' - echo '```' - } >> "$GITHUB_STEP_SUMMARY" - - - name: Upload raw output - if: always() - uses: actions/upload-artifact@v4 - with: - name: parser-delegation-perf-${{ github.run_id }} - path: | - this-pr.txt - baseline.txt - if-no-files-found: warn diff --git a/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php deleted file mode 100644 index 3927d5c0..00000000 --- a/packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php +++ /dev/null @@ -1,186 +0,0 @@ -native_token_stream() - : $lexer->remaining_tokens(); - $parser = new WP_MySQL_Parser( $grammar, $tokens ); - $ast = $parser->parse(); - if ( null === $ast ) { - ++$failures; - continue; - } - ++$total; - - switch ( $mode ) { - case 'no-walk': - break; - - case 'walk': - $descendants = $ast->get_descendants(); - $walked += count( $descendants ); - - $first = $ast->get_first_child_node(); - if ( null !== $first ) { - $again = $ast->get_first_child_node(); - if ( $first !== $again ) { - $identity_ok = false; - } - } - break; - - case 'rewalk': - // Repeated full-tree walks. After the first pass every wrapper - // the cache returns is a hit; without the cache, every pass - // re-allocates wrappers for the entire tree from scratch. - for ( $r = 0; $r < $repeat; $r++ ) { - $descendants = $ast->get_descendants(); - $walked += count( $descendants ); - } - break; - - case 'reread': - // Repeated top-level child reads. Models analysis passes that - // keep poking at the root of the tree. - for ( $r = 0; $r < $repeat; $r++ ) { - $child = $ast->get_first_child_node(); - if ( null !== $child ) { - ++$walked; - // Identity must hold across repeated reads. - if ( $r > 0 && $child !== $prev ) { - $identity_ok = false; - } - $prev = $child; - } - } - break; - - case 'subtree': - // Walk descendants once, then for each descendant re-read its - // first child N times. Models translator/rewriter passes that - // re-enter previously visited subtrees. - $descendants = $ast->get_descendants(); - foreach ( $descendants as $d ) { - if ( ! $d instanceof WP_Parser_Node ) { - continue; - } - for ( $r = 0; $r < $repeat; $r++ ) { - $child = $d->get_first_child_node(); - if ( null !== $child ) { - ++$walked; - } - } - } - break; - - default: - fwrite( STDERR, "Unknown mode: $mode\n" ); - exit( 2 ); - } - } catch ( Throwable $e ) { - ++$failures; - } -} - -$duration = microtime( true ) - $start; -$peak_mb = memory_get_peak_usage( true ) / 1024 / 1024; -$native = class_exists( 'WP_MySQL_Native_Parser', false ) ? 'native' : 'php'; - -printf( - "path=%s mode=%s repeat=%d parsed=%d walked_nodes=%d failures=%d duration=%.4fs qps=%d peak_mem=%.1fMB identity_ok=%s\n", - $native, - $mode, - $repeat, - $total, - $walked, - $failures, - $duration, - $total > 0 ? (int) ( $total / $duration ) : 0, - $peak_mb, - $identity_ok ? 'true' : 'FALSE' -); - -if ( ! $identity_ok ) { - fwrite( STDERR, "Identity check failed: accessor returned different instances on repeat read.\n" ); - exit( 1 ); -}