From b70a9ff5f51b11feb35354a261684a301bda15fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 27 May 2026 11:39:12 +0100 Subject: [PATCH 01/11] Check Playground web runtime compatibility (#417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What? Adds a browser-runtime compatibility check for the `wp_mysql_parser` WASM side modules and wires it into both WASM workflows: - `wasm-spike.yml` - `publish-wasm-extension-artifact.yml` The check compares each side module's `env` function imports against the matching Playground web PHP.wasm runtime exports. It runs for PHP 8.0 through 8.5 so the published manifest cannot claim browser support for a PHP version that will crash during extension startup. Also updates the native extension README/demo URL to use the live `blueprint.json` location and documents PHP 8.0-8.5 browser-runtime coverage instead of the temporary PHP 8.3 pin. ## Why? The previous Node-based smoke test was insufficient: PHP 8.4 loaded in the Node/CLI runtime but crashed in the browser runtime because the web runtime did not export Zend symbols imported by the extension side module. This PR makes that class of failure visible in CI before publishing a WASM manifest. ## Dependency This depends on WordPress/wordpress-playground#3690 and updated Playground web PHP.wasm artifacts. Current Playground web builds are known to miss required exports for PHP 8.0, 8.1, 8.4, and 8.5; the new CI check is expected to pass once those runtime exports ship. ## Testing - `node --check packages/php-ext-wp-mysql-parser/wasm-spike/check-playground-web-compat.mjs` - `git diff --check` - Verified locally with the Playground checkout: - PHP 8.3 currently passes. - PHP 8.4 currently fails with missing `zend_declare_class_constant_ex` and `zend_register_internal_class_ex`, matching the browser crash root cause. --------- Co-authored-by: Jan Jakeš Co-authored-by: Chloe Pomegranate Co-authored-by: Claude Opus 4.6 Co-authored-by: Francesco Bigiarini Co-authored-by: Wojtek Naruniec Co-authored-by: wpfuse <113634078+wp-fuse@users.noreply.github.com> Co-authored-by: Ashish Kumar Co-authored-by: Jon Surrell Co-authored-by: John Blackbourn Co-authored-by: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .claude/settings.json | 22 + .devcontainer/devcontainer.json | 13 + .editorconfig | 3 - .github/workflows/cs.yml | 7 + .github/workflows/end-to-end-tests.yml | 6 + .../mysql-parser-extension-tests.yml | 189 + .github/workflows/mysql-proxy-tests.yml | 10 +- .github/workflows/phpunit-tests-run.yml | 49 +- .github/workflows/phpunit-tests.yml | 7 +- .../publish-wasm-extension-artifact.yml | 291 + .github/workflows/release-publish.yml | 143 + .github/workflows/release-wporg.yml | 71 + .github/workflows/verify-version.yml | 35 +- .github/workflows/wasm-spike.yml | 192 + .github/workflows/wp-tests-end-to-end.yml | 6 + ...wp-tests-phpunit-native-extension-setup.sh | 211 + .github/workflows/wp-tests-phpunit-run.js | 31 +- .github/workflows/wp-tests-phpunit.yml | 40 + .gitignore | 4 + AGENTS.md | 202 + CLAUDE.md | 1 + README.md | 124 + bin/build-sqlite-plugin-zip.sh | 41 + bin/prepare-release.sh | 149 + bin/verify-release-metadata.sh | 52 + composer.json | 11 +- grammar-tools/MySQLParser.g4 | 6 +- grammar-tools/convert-grammar.php | 6 +- packages/mysql-on-sqlite/composer.json | 10 + .../mysql-on-sqlite/phpunit.xml.dist | 6 +- packages/mysql-on-sqlite/src/load.php | 45 + .../src}/mysql/class-wp-mysql-lexer.php | 14 +- .../src}/mysql/class-wp-mysql-parser.php | 11 + .../src}/mysql/class-wp-mysql-token.php | 0 .../src/mysql/mysql-grammar.php | 4 + .../src/mysql/native/class-wp-mysql-lexer.php | 3 + .../class-wp-mysql-native-parser-node.php | 179 + .../mysql/native/class-wp-mysql-parser.php | 14 + .../src/mysql/native/mysql-rust-bridge.php | 22 + .../trait-wp-mysql-native-parser-impl.php | 55 + .../src}/parser/class-wp-parser-grammar.php | 1 + .../src}/parser/class-wp-parser-node.php | 2 +- .../src}/parser/class-wp-parser-token.php | 0 .../src}/parser/class-wp-parser.php | 0 .../mysql-on-sqlite/src/php-polyfills.php | 22 +- .../sqlite}/class-wp-pdo-mysql-on-sqlite.php | 571 +- .../sqlite}/class-wp-pdo-proxy-statement.php | 4 +- .../sqlite}/class-wp-sqlite-configurator.php | 0 .../sqlite}/class-wp-sqlite-connection.php | 0 .../class-wp-sqlite-driver-exception.php | 0 .../src/sqlite}/class-wp-sqlite-driver.php | 4 +- ...s-wp-sqlite-information-schema-builder.php | 0 ...wp-sqlite-information-schema-exception.php | 0 ...qlite-information-schema-reconstructor.php | 66 + ...s-wp-sqlite-pdo-user-defined-functions.php | 169 +- .../mysql-on-sqlite/src/version.php | 2 +- .../WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php | 114 + .../WP_SQLite_Driver_Concurrency_Tests.php | 136 + .../WP_SQLite_Driver_Metadata_Tests.php | 223 +- .../tests}/WP_SQLite_Driver_Query_Tests.php | 0 .../tests}/WP_SQLite_Driver_Tests.php | 1042 +++- .../WP_SQLite_Driver_Translation_Tests.php | 117 +- ...Information_Schema_Reconstructor_Tests.php | 82 + .../mysql-on-sqlite/tests}/bootstrap.php | 62 +- .../tests}/mysql/WP_MySQL_Lexer_Tests.php | 109 + .../WP_MySQL_Server_Suite_Lexer_Tests.php | 0 .../WP_MySQL_Server_Suite_Parser_Tests.php | 2 +- .../mysql/data/mysql-server-tests-queries.csv | 0 ...P_MySQL_Native_Parser_Node_Cycle_Tests.php | 267 + ...ySQL_Native_Parser_Node_Identity_Tests.php | 142 + .../WP_MySQL_Parser_Instanceof_Tests.php | 39 + .../tests}/parser/WP_Parser_Node_Tests.php | 2 +- .../mysql-on-sqlite/tests}/tools/.gitignore | 0 .../mysql-on-sqlite/tests}/tools/dump-ast.php | 16 +- .../tests}/tools/dump-sqlite-query.php | 2 +- .../tests}/tools/mysql-download-tests.sh | 0 .../tests}/tools/mysql-extract-queries.php | 0 .../tests/tools/run-lexer-benchmark.php | 73 + .../tools/run-native-extension-benchmark.php | 113 + .../tests/tools/run-parser-benchmark.php | 116 + .../tools/verify-native-parser-extension.php | 101 + .../tests}/wp-sqlite-schema.php | 0 .../{wp-mysql-proxy => mysql-proxy}/README.md | 0 .../bin/wp-mysql-proxy.php | 2 - .../composer.json | 5 +- .../phpunit.xml | 0 .../src/Adapter/class-adapter.php | 0 .../src/Adapter/class-sqlite-adapter.php | 0 .../src/class-logger.php | 0 .../src/class-mysql-protocol.php | 0 .../src/class-mysql-proxy.php | 0 .../src/class-mysql-result.php | 0 .../src/class-mysql-session.php | 0 .../src/exceptions.php | 0 .../tests/WP_MySQL_Proxy_CLI_Test.php | 0 .../tests/WP_MySQL_Proxy_MySQLi_Test.php | 0 .../tests/WP_MySQL_Proxy_PDO_Test.php | 2 +- .../tests/WP_MySQL_Proxy_Test.php | 0 .../tests/bootstrap/bootstrap.php | 0 .../tests/bootstrap/mysql-server-process.php | 0 .../tests/bootstrap/run-server.php | 0 packages/php-ext-wp-mysql-parser/.gitignore | 1 + packages/php-ext-wp-mysql-parser/Cargo.lock | 1689 ++++++ packages/php-ext-wp-mysql-parser/Cargo.toml | 17 + packages/php-ext-wp-mysql-parser/README.md | 104 + .../src/lexer_constants.rs | 5274 +++++++++++++++++ packages/php-ext-wp-mysql-parser/src/lib.rs | 2086 +++++++ .../tools/generate-lexer-constants.php | 229 + .../wasm-spike/.gitignore | 2 + .../wasm-spike/Dockerfile.rust | 81 + .../wasm-spike/RESULT.md | 138 + .../wasm-spike/build-in-docker-rust.sh | 330 ++ .../check-playground-web-compat.mjs | 105 + .../wasm-spike/probe-host-cargo-wasm.sh | 26 + .../wasm-spike/run-spike.mjs | 103 + .../wasm-spike/shim/config.m4 | 21 + .../wasm-spike/shim/wp_mysql_parser_shim.c | 16 + .../wasm-spike/write-extension-manifest.mjs | 61 + .../LICENSE | 339 ++ .../activate.php | 0 .../admin-notices.php | 0 .../admin-page.php | 3 +- .../composer.json | 15 + .../constants.php | 5 - .../db.copy | 5 +- .../deactivate.php | 0 .../health-check.php | 0 .../integrations}/query-monitor/boot.php | 6 +- .../integrations/query-monitor/qm3.php | 0 .../integrations/query-monitor/qm4.php | 107 + .../query-monitor/query-monitor-sqlite.js | 95 + .../load.php | 5 +- .../readme.txt | 125 +- .../wp-includes/database | 1 + .../sqlite/class-wp-sqlite-crosscheck-db.php | 0 .../sqlite/class-wp-sqlite-db.php | 149 +- .../wp-includes}/sqlite/db.php | 29 +- .../wp-includes}/sqlite/install-functions.php | 23 +- phpcs.xml.dist | 132 +- tests/WP_SQLite_Metadata_Tests.php | 368 -- ...QLite_PDO_User_Defined_Functions_Tests.php | 28 - tests/WP_SQLite_Query_RewriterTests.php | 81 - tests/WP_SQLite_Query_Tests.php | 573 -- tests/WP_SQLite_Translator_Tests.php | 3560 ----------- tests/e2e/specs/query-monitor-plugin.test.js | 164 +- tests/tools/run-lexer-benchmark.php | 39 - tests/tools/run-parser-benchmark.php | 82 - wp-includes/mysql/mysql-grammar.php | 4 - wp-includes/sqlite/class-wp-sqlite-lexer.php | 2575 -------- .../sqlite/class-wp-sqlite-query-rewriter.php | 343 -- wp-includes/sqlite/class-wp-sqlite-token.php | 327 - .../sqlite/class-wp-sqlite-translator.php | 4508 -------------- wp-pdo-mysql-on-sqlite.php | 24 +- wp-setup.sh | 19 +- 154 files changed, 16794 insertions(+), 13116 deletions(-) create mode 100644 .claude/settings.json create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/workflows/mysql-parser-extension-tests.yml create mode 100644 .github/workflows/publish-wasm-extension-artifact.yml create mode 100644 .github/workflows/release-publish.yml create mode 100644 .github/workflows/release-wporg.yml create mode 100644 .github/workflows/wasm-spike.yml create mode 100644 .github/workflows/wp-tests-phpunit-native-extension-setup.sh create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100755 bin/build-sqlite-plugin-zip.sh create mode 100755 bin/prepare-release.sh create mode 100755 bin/verify-release-metadata.sh create mode 100644 packages/mysql-on-sqlite/composer.json rename phpunit.xml.dist => packages/mysql-on-sqlite/phpunit.xml.dist (84%) create mode 100644 packages/mysql-on-sqlite/src/load.php rename {wp-includes => packages/mysql-on-sqlite/src}/mysql/class-wp-mysql-lexer.php (99%) rename {wp-includes => packages/mysql-on-sqlite/src}/mysql/class-wp-mysql-parser.php (83%) rename {wp-includes => packages/mysql-on-sqlite/src}/mysql/class-wp-mysql-token.php (100%) create mode 100644 packages/mysql-on-sqlite/src/mysql/mysql-grammar.php create mode 100644 packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-lexer.php create mode 100644 packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-native-parser-node.php create mode 100644 packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-parser.php create mode 100644 packages/mysql-on-sqlite/src/mysql/native/mysql-rust-bridge.php create mode 100644 packages/mysql-on-sqlite/src/mysql/native/trait-wp-mysql-native-parser-impl.php rename {wp-includes => packages/mysql-on-sqlite/src}/parser/class-wp-parser-grammar.php (99%) rename {wp-includes => packages/mysql-on-sqlite/src}/parser/class-wp-parser-node.php (99%) rename {wp-includes => packages/mysql-on-sqlite/src}/parser/class-wp-parser-token.php (100%) rename {wp-includes => packages/mysql-on-sqlite/src}/parser/class-wp-parser.php (100%) rename php-polyfills.php => packages/mysql-on-sqlite/src/php-polyfills.php (67%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-pdo-mysql-on-sqlite.php (92%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-pdo-proxy-statement.php (99%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-sqlite-configurator.php (100%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-sqlite-connection.php (100%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-sqlite-driver-exception.php (100%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-sqlite-driver.php (99%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-sqlite-information-schema-builder.php (100%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-sqlite-information-schema-exception.php (100%) rename {wp-includes/sqlite-ast => packages/mysql-on-sqlite/src/sqlite}/class-wp-sqlite-information-schema-reconstructor.php (90%) rename {wp-includes => packages/mysql-on-sqlite/src}/sqlite/class-wp-sqlite-pdo-user-defined-functions.php (80%) rename version.php => packages/mysql-on-sqlite/src/version.php (69%) rename {tests => packages/mysql-on-sqlite/tests}/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php (78%) create mode 100644 packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Concurrency_Tests.php rename {tests => packages/mysql-on-sqlite/tests}/WP_SQLite_Driver_Metadata_Tests.php (88%) rename {tests => packages/mysql-on-sqlite/tests}/WP_SQLite_Driver_Query_Tests.php (100%) rename {tests => packages/mysql-on-sqlite/tests}/WP_SQLite_Driver_Tests.php (90%) rename {tests => packages/mysql-on-sqlite/tests}/WP_SQLite_Driver_Translation_Tests.php (98%) rename {tests => packages/mysql-on-sqlite/tests}/WP_SQLite_Information_Schema_Reconstructor_Tests.php (82%) rename {tests => packages/mysql-on-sqlite/tests}/bootstrap.php (57%) rename {tests => packages/mysql-on-sqlite/tests}/mysql/WP_MySQL_Lexer_Tests.php (73%) rename {tests => packages/mysql-on-sqlite/tests}/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php (100%) rename {tests => packages/mysql-on-sqlite/tests}/mysql/WP_MySQL_Server_Suite_Parser_Tests.php (97%) rename {tests => packages/mysql-on-sqlite/tests}/mysql/data/mysql-server-tests-queries.csv (100%) create mode 100644 packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Cycle_Tests.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/mysql/native/WP_MySQL_Parser_Instanceof_Tests.php rename {tests => packages/mysql-on-sqlite/tests}/parser/WP_Parser_Node_Tests.php (98%) rename {tests => packages/mysql-on-sqlite/tests}/tools/.gitignore (100%) rename {tests => packages/mysql-on-sqlite/tests}/tools/dump-ast.php (55%) rename {tests => packages/mysql-on-sqlite/tests}/tools/dump-sqlite-query.php (91%) rename {tests => packages/mysql-on-sqlite/tests}/tools/mysql-download-tests.sh (100%) rename {tests => packages/mysql-on-sqlite/tests}/tools/mysql-extract-queries.php (100%) create mode 100644 packages/mysql-on-sqlite/tests/tools/run-lexer-benchmark.php create mode 100644 packages/mysql-on-sqlite/tests/tools/run-native-extension-benchmark.php create mode 100644 packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php create mode 100644 packages/mysql-on-sqlite/tests/tools/verify-native-parser-extension.php rename {tests => packages/mysql-on-sqlite/tests}/wp-sqlite-schema.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/README.md (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/bin/wp-mysql-proxy.php (97%) rename packages/{wp-mysql-proxy => mysql-proxy}/composer.json (74%) rename packages/{wp-mysql-proxy => mysql-proxy}/phpunit.xml (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/src/Adapter/class-adapter.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/src/Adapter/class-sqlite-adapter.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/src/class-logger.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/src/class-mysql-protocol.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/src/class-mysql-proxy.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/src/class-mysql-result.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/src/class-mysql-session.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/src/exceptions.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/tests/WP_MySQL_Proxy_CLI_Test.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/tests/WP_MySQL_Proxy_MySQLi_Test.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/tests/WP_MySQL_Proxy_PDO_Test.php (95%) rename packages/{wp-mysql-proxy => mysql-proxy}/tests/WP_MySQL_Proxy_Test.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/tests/bootstrap/bootstrap.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/tests/bootstrap/mysql-server-process.php (100%) rename packages/{wp-mysql-proxy => mysql-proxy}/tests/bootstrap/run-server.php (100%) create mode 100644 packages/php-ext-wp-mysql-parser/.gitignore create mode 100644 packages/php-ext-wp-mysql-parser/Cargo.lock create mode 100644 packages/php-ext-wp-mysql-parser/Cargo.toml create mode 100644 packages/php-ext-wp-mysql-parser/README.md create mode 100644 packages/php-ext-wp-mysql-parser/src/lexer_constants.rs create mode 100644 packages/php-ext-wp-mysql-parser/src/lib.rs create mode 100644 packages/php-ext-wp-mysql-parser/tools/generate-lexer-constants.php create mode 100644 packages/php-ext-wp-mysql-parser/wasm-spike/.gitignore create mode 100644 packages/php-ext-wp-mysql-parser/wasm-spike/Dockerfile.rust create mode 100644 packages/php-ext-wp-mysql-parser/wasm-spike/RESULT.md create mode 100755 packages/php-ext-wp-mysql-parser/wasm-spike/build-in-docker-rust.sh create mode 100755 packages/php-ext-wp-mysql-parser/wasm-spike/check-playground-web-compat.mjs create mode 100755 packages/php-ext-wp-mysql-parser/wasm-spike/probe-host-cargo-wasm.sh create mode 100644 packages/php-ext-wp-mysql-parser/wasm-spike/run-spike.mjs create mode 100644 packages/php-ext-wp-mysql-parser/wasm-spike/shim/config.m4 create mode 100644 packages/php-ext-wp-mysql-parser/wasm-spike/shim/wp_mysql_parser_shim.c create mode 100644 packages/php-ext-wp-mysql-parser/wasm-spike/write-extension-manifest.mjs create mode 100644 packages/plugin-sqlite-database-integration/LICENSE rename activate.php => packages/plugin-sqlite-database-integration/activate.php (100%) rename admin-notices.php => packages/plugin-sqlite-database-integration/admin-notices.php (100%) rename admin-page.php => packages/plugin-sqlite-database-integration/admin-page.php (97%) create mode 100644 packages/plugin-sqlite-database-integration/composer.json rename constants.php => packages/plugin-sqlite-database-integration/constants.php (85%) rename db.copy => packages/plugin-sqlite-database-integration/db.copy (93%) rename deactivate.php => packages/plugin-sqlite-database-integration/deactivate.php (100%) rename health-check.php => packages/plugin-sqlite-database-integration/health-check.php (100%) rename {integrations => packages/plugin-sqlite-database-integration/integrations}/query-monitor/boot.php (95%) rename integrations/query-monitor/plugin.php => packages/plugin-sqlite-database-integration/integrations/query-monitor/qm3.php (100%) create mode 100644 packages/plugin-sqlite-database-integration/integrations/query-monitor/qm4.php create mode 100644 packages/plugin-sqlite-database-integration/integrations/query-monitor/query-monitor-sqlite.js rename load.php => packages/plugin-sqlite-database-integration/load.php (87%) rename readme.txt => packages/plugin-sqlite-database-integration/readme.txt (55%) create mode 120000 packages/plugin-sqlite-database-integration/wp-includes/database rename {wp-includes => packages/plugin-sqlite-database-integration/wp-includes}/sqlite/class-wp-sqlite-crosscheck-db.php (100%) rename {wp-includes => packages/plugin-sqlite-database-integration/wp-includes}/sqlite/class-wp-sqlite-db.php (83%) rename {wp-includes => packages/plugin-sqlite-database-integration/wp-includes}/sqlite/db.php (60%) rename {wp-includes => packages/plugin-sqlite-database-integration/wp-includes}/sqlite/install-functions.php (92%) delete mode 100644 tests/WP_SQLite_Metadata_Tests.php delete mode 100644 tests/WP_SQLite_PDO_User_Defined_Functions_Tests.php delete mode 100644 tests/WP_SQLite_Query_RewriterTests.php delete mode 100644 tests/WP_SQLite_Query_Tests.php delete mode 100644 tests/WP_SQLite_Translator_Tests.php delete mode 100644 tests/tools/run-lexer-benchmark.php delete mode 100644 tests/tools/run-parser-benchmark.php delete mode 100644 wp-includes/mysql/mysql-grammar.php delete mode 100644 wp-includes/sqlite/class-wp-sqlite-lexer.php delete mode 100644 wp-includes/sqlite/class-wp-sqlite-query-rewriter.php delete mode 100644 wp-includes/sqlite/class-wp-sqlite-token.php delete mode 100644 wp-includes/sqlite/class-wp-sqlite-translator.php diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..ab30017e --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "permissions": { + "allow": [ + "Bash(composer install *)", + "Bash(composer update *)", + "Bash(composer run *)", + "Bash(composer check-cs *)", + "Bash(composer fix-cs *)", + "Bash(composer test *)", + "Bash(composer wp-*)", + "Bash(vendor/bin/phpunit *)", + "Bash(vendor/bin/phpcs *)", + "Bash(vendor/bin/phpcbf *)", + "Bash(vendor/bin/parallel-lint *)", + "Bash(npm --prefix wordpress *)", + "Bash(npm --prefix tests/e2e *)", + "WebFetch(domain:developer.wordpress.org)", + "WebFetch(domain:sqlite.org)" + ] + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..9db40ecd --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,13 @@ +{ + "name": "SQLite Database Integration", + "image": "mcr.microsoft.com/devcontainers/php:8.4", + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "20" + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "moby": false + } + }, + "postCreateCommand": "composer install" +} diff --git a/.editorconfig b/.editorconfig index c6f3cf49..a541e47e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -19,6 +19,3 @@ indent_size = 2 [*.md] trim_trailing_whitespace = false - -[{*.txt,wp-config-sample.php}] -end_of_line = crlf diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml index 3dc0cd15..64fa997d 100644 --- a/.github/workflows/cs.yml +++ b/.github/workflows/cs.yml @@ -21,6 +21,10 @@ on: # Allow manually triggering the workflow. workflow_dispatch: +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. @@ -31,6 +35,9 @@ jobs: checkcs: name: 'Check code style' runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read # Required to clone the repo. steps: - name: Checkout code diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index 64cf4b12..7be965a7 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -6,11 +6,17 @@ on: - main pull_request: +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + jobs: test: name: End-to-end Tests runs-on: ubuntu-latest timeout-minutes: 20 + permissions: + contents: read # Required to clone the repo. steps: - name: Checkout repository diff --git a/.github/workflows/mysql-parser-extension-tests.yml b/.github/workflows/mysql-parser-extension-tests.yml new file mode 100644 index 00000000..45425bb8 --- /dev/null +++ b/.github/workflows/mysql-parser-extension-tests.yml @@ -0,0 +1,189 @@ +name: MySQL Parser Extension Tests + +on: + push: + branches: + - trunk + 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: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + extension-tests: + name: PHP ${{ matrix.php }} / ${{ matrix.coverage }} / ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read # Required to clone the repo. + strategy: + fail-fast: false + matrix: + 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: + php-version: ${{ matrix.php }} + 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 + 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 + 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: Verify native parser extension + if: matrix.native + run: php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" tests/tools/verify-native-parser-extension.php + working-directory: packages/mysql-on-sqlite + + - 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/.github/workflows/mysql-proxy-tests.yml b/.github/workflows/mysql-proxy-tests.yml index c3808cf1..e24c36f3 100644 --- a/.github/workflows/mysql-proxy-tests.yml +++ b/.github/workflows/mysql-proxy-tests.yml @@ -6,11 +6,17 @@ on: - main pull_request: +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + jobs: test: name: MySQL Proxy Tests runs-on: ubuntu-latest timeout-minutes: 20 + permissions: + contents: read # Required to clone the repo. steps: - name: Checkout repository @@ -26,8 +32,8 @@ jobs: with: ignore-cache: "yes" composer-options: "--optimize-autoloader" - working-directory: packages/wp-mysql-proxy + working-directory: packages/mysql-proxy - name: Run MySQL Proxy tests run: composer run test - working-directory: packages/wp-mysql-proxy + working-directory: packages/mysql-proxy diff --git a/.github/workflows/phpunit-tests-run.yml b/.github/workflows/phpunit-tests-run.yml index 43c38bc9..2eec8ee2 100644 --- a/.github/workflows/phpunit-tests-run.yml +++ b/.github/workflows/phpunit-tests-run.yml @@ -12,11 +12,6 @@ on: description: 'The version of PHP to use, in the format of X.Y' required: true type: 'string' - phpunit-config: - description: 'The PHPUnit configuration file to use' - required: false - type: 'string' - default: 'phpunit.xml.dist' sqlite: description: 'SQLite version to install (e.g., 3.24.0). Leave empty for latest version.' required: false @@ -24,13 +19,18 @@ on: default: 'latest' env: LOCAL_PHP: ${{ inputs.php }}-fpm - PHPUNIT_CONFIG: ${{ inputs.phpunit-config }} + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} jobs: phpunit-tests: name: ${{ inputs.os }} runs-on: ${{ inputs.os }} timeout-minutes: 20 + permissions: + contents: read # Required to clone the repo. steps: - name: Checkout repository @@ -44,8 +44,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) @@ -72,11 +95,19 @@ jobs: exit 1 fi - - name: Install Composer dependencies + - 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: Run PHPUnit tests - run: php ./vendor/bin/phpunit -c ./phpunit.xml.dist + run: php ./vendor/bin/phpunit -c ./phpunit.xml.dist + working-directory: packages/mysql-on-sqlite diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index 9bfa33c7..23293087 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -6,12 +6,16 @@ on: - main pull_request: +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + jobs: test: name: PHP ${{ matrix.php }} / SQLite ${{ matrix.sqlite || 'latest' }} uses: ./.github/workflows/phpunit-tests-run.yml permissions: - contents: read + contents: read # Required to clone the repo. secrets: inherit strategy: fail-fast: false @@ -43,4 +47,3 @@ jobs: os: ${{ matrix.os }} php: ${{ matrix.php }} sqlite: ${{ matrix.sqlite || 'latest' }} - phpunit-config: ${{ 'phpunit.xml.dist' }} diff --git a/.github/workflows/publish-wasm-extension-artifact.yml b/.github/workflows/publish-wasm-extension-artifact.yml new file mode 100644 index 00000000..75f655c1 --- /dev/null +++ b/.github/workflows/publish-wasm-extension-artifact.yml @@ -0,0 +1,291 @@ +name: Publish WASM extension artifact + +on: + push: + branches: + - trunk + paths: + - 'packages/php-ext-wp-mysql-parser/**' + - '.github/workflows/publish-wasm-extension-artifact.yml' + workflow_dispatch: + inputs: + playground-ref: + description: 'wordpress-playground branch/tag/SHA to build against' + required: false + default: 'trunk' + retention-days: + description: 'Actions artifact retention in days' + required: false + default: '30' + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + base-image: + name: Build shared Playground base image + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read # Required to clone the wordpress-playground repo. + + steps: + - name: Check out wordpress-playground + uses: actions/checkout@v4 + with: + repository: WordPress/wordpress-playground + ref: ${{ github.event.inputs.playground-ref || 'trunk' }} + path: wordpress-playground + sparse-checkout: | + packages/php-wasm/compile/base-image + + - name: Build Playground base image + run: | + docker build \ + -f wordpress-playground/packages/php-wasm/compile/base-image/Dockerfile \ + --tag playground-php-wasm:base \ + wordpress-playground/packages/php-wasm/compile/base-image + + - name: Save Playground base image + run: docker save --output "${RUNNER_TEMP}/playground-php-wasm-base.tar" playground-php-wasm:base + + - name: Upload Playground base image + uses: actions/upload-artifact@v4 + with: + name: playground-php-wasm-base-image + path: ${{ runner.temp }}/playground-php-wasm-base.tar + if-no-files-found: error + retention-days: 1 + + build: + name: Build wp_mysql_parser.so (PHP ${{ matrix.php }}) + needs: base-image + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read # Required to clone the repo. + strategy: + fail-fast: false + max-parallel: 6 + matrix: + # The Rust WASM path uses ext-php-rs 0.15, which depends on PHP 8 + # Zend APIs. PHP 7.4 cannot be added here by extending the matrix. + php: ['8.5', '8.4', '8.3', '8.2', '8.1', '8.0'] + + steps: + - name: Check out sqlite-database-integration + uses: actions/checkout@v4 + with: + path: sqlite-database-integration + + - name: Check out wordpress-playground + uses: actions/checkout@v4 + with: + repository: WordPress/wordpress-playground + ref: ${{ github.event.inputs.playground-ref || 'trunk' }} + path: wordpress-playground + sparse-checkout-cone-mode: false + sparse-checkout: | + /package.json + /package-lock.json + /nx.json + /tsconfig.base.json + /packages/meta/ + /packages/nx-extensions/ + /packages/php-wasm/cli-util/ + /packages/php-wasm/compile-extension/ + /packages/php-wasm/compile/ + /packages/php-wasm/fs-journal/ + /packages/php-wasm/logger/ + /packages/php-wasm/node/ + /packages/php-wasm/node-builds/8-0/ + /packages/php-wasm/node-builds/8-1/ + /packages/php-wasm/node-builds/8-2/ + /packages/php-wasm/node-builds/8-3/ + /packages/php-wasm/node-builds/8-4/ + /packages/php-wasm/node-builds/8-5/ + /packages/php-wasm/web-builds/8-0/ + /packages/php-wasm/web-builds/8-1/ + /packages/php-wasm/web-builds/8-2/ + /packages/php-wasm/web-builds/8-3/ + /packages/php-wasm/web-builds/8-4/ + /packages/php-wasm/web-builds/8-5/ + /packages/php-wasm/progress/ + /packages/php-wasm/scopes/ + /packages/php-wasm/stream-compression/ + /packages/php-wasm/universal/ + /packages/php-wasm/util/ + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: wordpress-playground/package-lock.json + + - name: Download Playground base image + uses: actions/download-artifact@v4 + with: + name: playground-php-wasm-base-image + path: ${{ runner.temp }}/playground-base-image + + - name: Load Playground base image + run: docker load --input "${RUNNER_TEMP}/playground-base-image/playground-php-wasm-base.tar" + + - name: Install Playground deps + working-directory: wordpress-playground + run: npm ci --ignore-scripts + + - name: Build wp_mysql_parser side module + working-directory: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike + env: + PHP_VERSION: ${{ matrix.php }} + ASYNC_MODE: jspi + COMPILE_EXTENSION_PACKAGE: '@php-wasm/compile-extension@3.1.27' + PLAYGROUND_REPO: ${{ github.workspace }}/wordpress-playground + SKIP_BASE_IMAGE_BUILD: '1' + run: bash build-in-docker-rust.sh + + - name: Verify side module exists + working-directory: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike + run: | + test -f dist/wp_mysql_parser-php${{ matrix.php }}-jspi.so + ls -lh dist/wp_mysql_parser-php${{ matrix.php }}-jspi.so + + - name: Check Playground browser runtime compatibility + working-directory: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike + env: + PLAYGROUND_REPO: ${{ github.workspace }}/wordpress-playground + PHP_VERSION: ${{ matrix.php }} + ASYNC_MODE: jspi + run: node check-playground-web-compat.mjs + + - name: Upload side module + uses: actions/upload-artifact@v4 + with: + name: wp_mysql_parser-php${{ matrix.php }}-jspi-so + path: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike/dist/wp_mysql_parser-php${{ matrix.php }}-jspi.so + if-no-files-found: error + retention-days: ${{ github.event.inputs.retention-days || '30' }} + + package: + name: Package extension manifest + runs-on: ubuntu-latest + needs: build + timeout-minutes: 10 + permissions: + contents: write # Required to push the built extension to the gh-pages branch. + + steps: + - name: Check out sqlite-database-integration + uses: actions/checkout@v4 + + - name: Download side modules + uses: actions/download-artifact@v4 + with: + pattern: wp_mysql_parser-php*-jspi-so + path: build/wp_mysql_parser-wasm-extension + merge-multiple: true + + - name: Write Playground extension manifest + env: + ASYNC_MODE: jspi + EXTENSION_VERSION: ${{ github.sha }} + run: | + node packages/php-ext-wp-mysql-parser/wasm-spike/write-extension-manifest.mjs \ + build/wp_mysql_parser-wasm-extension \ + 8.5 8.4 8.3 8.2 8.1 8.0 + + - name: Verify published manifest shape + run: | + node <<'NODE' + const fs = require('node:fs'); + const path = require('node:path'); + + const root = 'build/wp_mysql_parser-wasm-extension'; + const manifest = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8')); + if (manifest.name !== 'wp_mysql_parser') { + throw new Error(`Unexpected extension name: ${manifest.name}`); + } + if (manifest.mode !== 'php-extension') { + throw new Error(`Unexpected manifest mode: ${manifest.mode}`); + } + if (!Array.isArray(manifest.artifacts) || manifest.artifacts.length !== 6) { + throw new Error('Expected six PHP 8.x artifacts.'); + } + for (const artifact of manifest.artifacts) { + if (!artifact.phpVersion || !artifact.sourcePath) { + throw new Error(`Invalid artifact entry: ${JSON.stringify(artifact)}`); + } + if ('file' in artifact || 'sha256' in artifact) { + throw new Error(`Manifest uses the retired pre-PR-3580 fields: ${JSON.stringify(artifact)}`); + } + const artifactPath = path.join(root, artifact.sourcePath); + if (!fs.existsSync(artifactPath)) { + throw new Error(`Manifest references missing artifact: ${artifactPath}`); + } + } + NODE + + - name: Write checksums + working-directory: build/wp_mysql_parser-wasm-extension + run: sha256sum *.so > SHA256SUMS + + - name: Upload packaged extension + uses: actions/upload-artifact@v4 + with: + name: wp_mysql_parser-wasm-extension + path: build/wp_mysql_parser-wasm-extension/ + if-no-files-found: error + retention-days: ${{ github.event.inputs.retention-days || '30' }} + + - name: Publish static extension bundle to gh-pages + env: + BUNDLE_DIR: build/wp_mysql_parser-wasm-extension + EXTENSION_PATH: wp_mysql_parser-wasm-extension + GITHUB_TOKEN: ${{ github.token }} + SITE_BRANCH: gh-pages + VERSION_PATH: ${{ github.sha }} + run: | + set -euo pipefail + + pages_dir="$(mktemp -d)" + git init "$pages_dir" + git -C "$pages_dir" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + auth_header="$(printf 'x-access-token:%s' "${GITHUB_TOKEN}" | base64 -w 0)" + git -C "$pages_dir" config http.https://github.com/.extraheader "AUTHORIZATION: basic ${auth_header}" + git -C "$pages_dir" config user.name "github-actions[bot]" + git -C "$pages_dir" config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + if git -C "$pages_dir" ls-remote --exit-code --heads origin "$SITE_BRANCH" >/dev/null 2>&1; then + git -C "$pages_dir" fetch --depth=1 origin "$SITE_BRANCH" + git -C "$pages_dir" checkout -B "$SITE_BRANCH" FETCH_HEAD + else + git -C "$pages_dir" checkout --orphan "$SITE_BRANCH" + fi + + mkdir -p "$pages_dir/$EXTENSION_PATH" + rm -rf "$pages_dir/$EXTENSION_PATH/$VERSION_PATH" "$pages_dir/$EXTENSION_PATH/latest" + mkdir -p "$pages_dir/$EXTENSION_PATH/$VERSION_PATH" "$pages_dir/$EXTENSION_PATH/latest" + cp -R "$BUNDLE_DIR"/. "$pages_dir/$EXTENSION_PATH/$VERSION_PATH/" + cp -R "$BUNDLE_DIR"/. "$pages_dir/$EXTENSION_PATH/latest/" + touch "$pages_dir/.nojekyll" + + git -C "$pages_dir" add .nojekyll "$EXTENSION_PATH" + if git -C "$pages_dir" diff --cached --quiet; then + echo "No gh-pages changes to publish." + exit 0 + fi + + git -C "$pages_dir" commit -m "Publish wp_mysql_parser WASM extension ${VERSION_PATH}" + git -C "$pages_dir" push origin "HEAD:$SITE_BRANCH" + + owner="${GITHUB_REPOSITORY_OWNER,,}" + repo="${GITHUB_REPOSITORY#*/}" + echo "Published latest manifest: https://${owner}.github.io/${repo}/${EXTENSION_PATH}/latest/manifest.json" + echo "Published immutable manifest: https://${owner}.github.io/${repo}/${EXTENSION_PATH}/${VERSION_PATH}/manifest.json" diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 00000000..7df54551 --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,143 @@ +name: Publish release + +on: + pull_request: + types: [closed] + branches: [trunk] + +concurrency: + group: release-publish + cancel-in-progress: false + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + publish-release: + name: Build plugin and create GitHub release + if: >- + github.repository == 'WordPress/sqlite-database-integration' + && github.event.pull_request.merged == true + && startsWith(github.event.pull_request.head.ref, 'release/') + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write # Required to clone the repo, create the GitHub release, and delete the release branch. + pull-requests: read # Required for gh api graphql queries that read pull request author data. + outputs: + tag: ${{ steps.version.outputs.tag }} + prerelease: ${{ steps.version.outputs.prerelease }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract version from branch name + id: version + env: + BRANCH: ${{ github.event.pull_request.head.ref }} + run: | + # Extract version from branch name (release/v2.3.0 → 2.3.0). + VERSION="${BRANCH#release/v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=v$VERSION" >> $GITHUB_OUTPUT + + # Versions with a hyphen (e.g., 2.3.0-beta.1) are prereleases. + if [[ "$VERSION" == *-* ]]; then + echo "prerelease=true" >> $GITHUB_OUTPUT + else + echo "prerelease=false" >> $GITHUB_OUTPUT + fi + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + + - name: Build plugin zip + run: composer run build-sqlite-plugin-zip + + - name: Extract changelog from readme.txt + id: changelog + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + README="packages/plugin-sqlite-database-integration/readme.txt" + + # Extract the changelog entry for this version. + CHANGELOG=$(awk -v version="$VERSION" ' + $0 == "= " version " =" { found = 1; next } + found && /^= / { exit } + found { print } + ' "$README" | sed -e '/./,$!d' -e :a -e '/^\s*$/{ $d; N; ba; }') # Trim leading/trailing blank lines. + + { + echo "body<> $GITHUB_OUTPUT + + - name: Get contributors from changelog PRs + id: contributors + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CHANGELOG: ${{ steps.changelog.outputs.body }} + run: | + # Extract PR numbers from changelog links like [#123](...). + PR_NUMBERS=$(echo "$CHANGELOG" | grep -oE '\[#[0-9]+\]' | tr -d '[]#' | sort -u) + + CONTRIBUTORS="" + if [ -n "$PR_NUMBERS" ]; then + # Build a single GraphQL query to fetch all PR authors. + QUERY='{ repository(owner: "${{ github.repository_owner }}", name: "${{ github.event.repository.name }}") {' + while IFS= read -r PR; do + [ -n "$PR" ] && QUERY="$QUERY pr$PR: pullRequest(number: $PR) { author { login } }" + done <<< "$PR_NUMBERS" + QUERY="$QUERY } }" + + CONTRIBUTORS=$(gh api graphql -f query="$QUERY" \ + --jq '[.data.repository[].author.login] | unique | map("@" + .) | join(", ")') + fi + echo "list=$CONTRIBUTORS" >> $GITHUB_OUTPUT + + - name: Create GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.version.outputs.tag }} + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + BODY: ${{ steps.changelog.outputs.body }} + CONTRIBUTORS: ${{ steps.contributors.outputs.list }} + run: | + PRERELEASE_FLAG="" + if [[ "${{ steps.version.outputs.prerelease }}" == "true" ]]; then + PRERELEASE_FLAG="--prerelease" + fi + + NOTES="$(printf '## Changelog\n\n%s' "$BODY")" + if [ -n "$CONTRIBUTORS" ]; then + NOTES="$(printf '%s\n\n## Contributors\n\nThe following contributors merged PRs in this release:\n\n%s' "$NOTES" "$CONTRIBUTORS")" + fi + + gh release create "$TAG" \ + --target "$MERGE_SHA" \ + --title "$TAG" \ + --notes "$NOTES" \ + $PRERELEASE_FLAG \ + "build/plugin-sqlite-database-integration.zip" + + - name: Delete release branch + env: + BRANCH: ${{ github.event.pull_request.head.ref }} + run: git push origin --delete "$BRANCH" 2>/dev/null || true + + deploy-wporg: + name: Deploy to WordPress.org + needs: publish-release + if: needs.publish-release.outputs.prerelease == 'false' + uses: ./.github/workflows/release-wporg.yml + permissions: + contents: read # Required to clone the repo and download the release asset. + with: + tag: ${{ needs.publish-release.outputs.tag }} + secrets: inherit diff --git a/.github/workflows/release-wporg.yml b/.github/workflows/release-wporg.yml new file mode 100644 index 00000000..a44ee372 --- /dev/null +++ b/.github/workflows/release-wporg.yml @@ -0,0 +1,71 @@ +name: Deploy to WordPress.org + +on: + workflow_call: + inputs: + tag: + required: true + type: string + workflow_dispatch: + inputs: + tag: + description: 'Tag to deploy (e.g., v2.2.21)' + required: true + type: string + +concurrency: + group: release-wporg + cancel-in-progress: false + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + deploy: + name: Deploy plugin to WordPress.org + if: github.repository == 'WordPress/sqlite-database-integration' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read # Required to clone the repo and download the release asset via gh. + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + sparse-checkout: bin/ + sparse-checkout-cone-mode: false + + - name: Resolve tag + id: resolve + run: | + TAG="${{ inputs.tag }}" + # Ensure the tag has a "v" prefix. + [[ "$TAG" == v* ]] || TAG="v$TAG" + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "version=${TAG#v}" >> $GITHUB_OUTPUT + + - name: Download plugin zip from release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.resolve.outputs.tag }} + run: | + gh release download "$TAG" \ + --repo "${{ github.repository }}" \ + --pattern "plugin-sqlite-database-integration.zip" \ + --dir . + unzip plugin-sqlite-database-integration.zip + + - name: Verify release metadata + run: | + bash bin/verify-release-metadata.sh plugin-sqlite-database-integration "${{ steps.resolve.outputs.version }}" + + - name: Deploy to WordPress.org + uses: 10up/action-wordpress-plugin-deploy@2.3.0 + env: + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} + VERSION: ${{ steps.resolve.outputs.version }} + SLUG: sqlite-database-integration + BUILD_DIR: plugin-sqlite-database-integration diff --git a/.github/workflows/verify-version.yml b/.github/workflows/verify-version.yml index 9bc9a617..dbc504ce 100644 --- a/.github/workflows/verify-version.yml +++ b/.github/workflows/verify-version.yml @@ -1,35 +1,24 @@ -name: Verify plugin version +name: Verify release metadata on: push: branches: - - main + - trunk pull_request: +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + jobs: verify-version: - name: Assert the WordPress plugin header declares the same version as the SQLITE_DRIVER_VERSION constant + name: Assert release metadata is internally consistent runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read # Required to clone the repo. steps: - uses: actions/checkout@v4 - - name: Extract version from "load.php" - id: load_version - run: | - VERSION=$(grep "Version:" load.php | sed "s/.*Version: \([^ ]*\).*/\1/") - echo "load_version=$VERSION" >> $GITHUB_OUTPUT - - - name: Extract version from "version.php" - id: const_version - run: | - VERSION=$(php -r "require 'version.php'; echo SQLITE_DRIVER_VERSION;") - echo "const_version=$VERSION" >> $GITHUB_OUTPUT - - - name: Compare versions - run: | - if [ "${{ steps.load_version.outputs.load_version }}" != "${{ steps.const_version.outputs.const_version }}" ]; then - echo "Version mismatch detected!" - echo " load.php version: ${{ steps.load_version.outputs.load_version }}" - echo " version.php constant: ${{ steps.const_version.outputs.const_version }}" - exit 1 - fi + - name: Verify release metadata + run: bash bin/verify-release-metadata.sh packages/plugin-sqlite-database-integration diff --git a/.github/workflows/wasm-spike.yml b/.github/workflows/wasm-spike.yml new file mode 100644 index 00000000..1fee946c --- /dev/null +++ b/.github/workflows/wasm-spike.yml @@ -0,0 +1,192 @@ +name: WASM extension build + +on: + push: + branches: + - main + paths: + - 'packages/php-ext-wp-mysql-parser/**' + - '.github/workflows/wasm-spike.yml' + pull_request: + paths: + - 'packages/php-ext-wp-mysql-parser/**' + - '.github/workflows/wasm-spike.yml' + workflow_dispatch: + inputs: + playground-ref: + description: 'wordpress-playground branch/tag/SHA to build against' + required: false + default: 'trunk' + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + base-image: + name: Build shared Playground base image + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read # Required to clone the wordpress-playground repo. + + steps: + - name: Check out wordpress-playground + uses: actions/checkout@v4 + with: + repository: WordPress/wordpress-playground + ref: ${{ github.event.inputs.playground-ref || 'trunk' }} + path: wordpress-playground + sparse-checkout: | + packages/php-wasm/compile/base-image + + - name: Build Playground base image + run: | + docker build \ + -f wordpress-playground/packages/php-wasm/compile/base-image/Dockerfile \ + --tag playground-php-wasm:base \ + wordpress-playground/packages/php-wasm/compile/base-image + + - name: Save Playground base image + run: docker save --output "${RUNNER_TEMP}/playground-php-wasm-base.tar" playground-php-wasm:base + + - name: Upload Playground base image + uses: actions/upload-artifact@v4 + with: + name: playground-php-wasm-base-image + path: ${{ runner.temp }}/playground-php-wasm-base.tar + if-no-files-found: error + retention-days: 1 + + build-and-load: + name: Build wp_mysql_parser.so and load it in Playground (PHP ${{ matrix.php }}) + needs: base-image + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read # Required to clone the repo. + strategy: + fail-fast: false + max-parallel: 6 + matrix: + # The Rust WASM path uses ext-php-rs 0.15, which depends on PHP 8 + # Zend APIs. PHP 7.4 cannot be added here by extending the matrix. + php: ['8.5', '8.4', '8.3', '8.2', '8.1', '8.0'] + async-mode: ['jspi'] + + steps: + - name: Check out sqlite-database-integration + uses: actions/checkout@v4 + with: + path: sqlite-database-integration + + - name: Check out wordpress-playground + uses: actions/checkout@v4 + with: + repository: WordPress/wordpress-playground + ref: ${{ github.event.inputs.playground-ref || 'trunk' }} + path: wordpress-playground + sparse-checkout-cone-mode: false + sparse-checkout: | + /package.json + /package-lock.json + /nx.json + /tsconfig.base.json + /packages/meta/ + /packages/nx-extensions/ + /packages/php-wasm/cli-util/ + /packages/php-wasm/compile-extension/ + /packages/php-wasm/compile/ + /packages/php-wasm/fs-journal/ + /packages/php-wasm/logger/ + /packages/php-wasm/node/ + /packages/php-wasm/node-builds/8-0/ + /packages/php-wasm/node-builds/8-1/ + /packages/php-wasm/node-builds/8-2/ + /packages/php-wasm/node-builds/8-3/ + /packages/php-wasm/node-builds/8-4/ + /packages/php-wasm/node-builds/8-5/ + /packages/php-wasm/web-builds/8-0/ + /packages/php-wasm/web-builds/8-1/ + /packages/php-wasm/web-builds/8-2/ + /packages/php-wasm/web-builds/8-3/ + /packages/php-wasm/web-builds/8-4/ + /packages/php-wasm/web-builds/8-5/ + /packages/php-wasm/progress/ + /packages/php-wasm/scopes/ + /packages/php-wasm/stream-compression/ + /packages/php-wasm/universal/ + /packages/php-wasm/util/ + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: wordpress-playground/package-lock.json + + - name: Download Playground base image + uses: actions/download-artifact@v4 + with: + name: playground-php-wasm-base-image + path: ${{ runner.temp }}/playground-base-image + + - name: Load Playground base image + run: docker load --input "${RUNNER_TEMP}/playground-base-image/playground-php-wasm-base.tar" + + - name: Install Playground deps + working-directory: wordpress-playground + run: npm ci --ignore-scripts + + - name: Build wp_mysql_parser side module + working-directory: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike + env: + PHP_VERSION: ${{ matrix.php }} + ASYNC_MODE: ${{ matrix.async-mode }} + COMPILE_EXTENSION_PACKAGE: '@php-wasm/compile-extension@3.1.27' + PLAYGROUND_REPO: ${{ github.workspace }}/wordpress-playground + SKIP_BASE_IMAGE_BUILD: '1' + run: bash build-in-docker-rust.sh + + - name: Verify build artifacts exist + working-directory: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike + run: | + test -f dist/wp_mysql_parser-php${{ matrix.php }}-${{ matrix.async-mode }}.so + test -f dist/manifest.json + test -f dist/libwp_mysql_parser.a + ls -lh dist/ + + - name: Verify manifest references the built side module + working-directory: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike + run: | + SOURCE_PATH=$(node -e "console.log(require('./dist/manifest.json').artifacts[0].sourcePath)") + EXPECTED="wp_mysql_parser-php${{ matrix.php }}-${{ matrix.async-mode }}.so" + echo "manifest sourcePath: $SOURCE_PATH" + echo "expected artifact: $EXPECTED" + test "$SOURCE_PATH" = "$EXPECTED" + test -f "dist/$SOURCE_PATH" + node -e "const artifact = require('./dist/manifest.json').artifacts[0]; if ('file' in artifact || 'sha256' in artifact) { throw new Error('manifest uses retired artifact fields'); }" + + - name: Check Playground browser runtime compatibility + working-directory: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike + env: + PLAYGROUND_REPO: ${{ github.workspace }}/wordpress-playground + PHP_VERSION: ${{ matrix.php }} + ASYNC_MODE: ${{ matrix.async-mode }} + run: node check-playground-web-compat.mjs + + - name: Load the extension in Playground and parse a query + working-directory: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike + env: + PLAYGROUND_REPO: ${{ github.workspace }}/wordpress-playground + PHP_VERSION: ${{ matrix.php }} + run: node run-spike.mjs + + - name: Upload artifacts on success + if: success() + uses: actions/upload-artifact@v4 + with: + name: wp_mysql_parser-php${{ matrix.php }}-${{ matrix.async-mode }} + path: sqlite-database-integration/packages/php-ext-wp-mysql-parser/wasm-spike/dist/ + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/wp-tests-end-to-end.yml b/.github/workflows/wp-tests-end-to-end.yml index 2b32a5a0..7b3637e4 100644 --- a/.github/workflows/wp-tests-end-to-end.yml +++ b/.github/workflows/wp-tests-end-to-end.yml @@ -6,11 +6,17 @@ on: - main pull_request: +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + jobs: test: name: WordPress End-to-end Tests runs-on: ubuntu-latest timeout-minutes: 20 + permissions: + contents: read # Required to clone the repo. steps: - name: Checkout repository 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..943b06c5 --- /dev/null +++ b/.github/workflows/wp-tests-phpunit-native-extension-setup.sh @@ -0,0 +1,211 @@ +#!/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/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" + +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' ); + +const serviceIndex = lines.findIndex( line => line === ` ${ service }:` ); +if ( serviceIndex === -1 ) { + throw new Error( `Service ${ service } not found in ${ file }.` ); +} + +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; + } +} + +if ( volumesIndex === -1 ) { + throw new Error( `Service ${ service } has no volumes list in ${ file }.` ); +} + +let insertAt = volumesIndex + 1; +while ( insertAt < serviceEnd && /^\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 --release +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/php-ext-wp-mysql-parser/target/release/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" + +cat > "$WP_DIR/native-verify-extension.php" <<'EOF' +hasProperty( 'native' ) ) { + wp_sqlite_native_parser_verification_fail( $message ); + } + + $native_property = $parser_reflection->getProperty( 'native' ); + $native_property->setAccessible( true ); + if ( ! ( $native_property->getValue( $parser ) instanceof WP_MySQL_Native_Parser ) ) { + wp_sqlite_native_parser_verification_fail( $message ); + } +} + +$lexer = new WP_MySQL_Lexer( 'SELECT 1' ); +if ( ! ( $lexer instanceof WP_MySQL_Native_Lexer ) ) { + wp_sqlite_native_parser_verification_fail( 'Native lexer is not available in the WordPress PHP test container.' ); +} + +$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 ); +wp_sqlite_assert_native_parser_delegate( $parser, 'WordPress PHP test container did not select the native parser delegate.' ); + +$parser_ast = $parser->parse(); +if ( ! ( $parser_ast instanceof WP_MySQL_Native_Parser_Node ) ) { + wp_sqlite_native_parser_verification_fail( 'Native parser did not produce a native-backed AST in the WordPress PHP test container.' ); +} + +$driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); +$parser = $driver->create_parser( 'SELECT 1' ); +wp_sqlite_assert_native_parser_delegate( $parser, 'WordPress PHP test container SQLite driver did not create a native parser delegate.' ); +$parser->next_query(); +$ast = $parser->get_query_ast(); + +if ( ! ( $ast instanceof WP_MySQL_Native_Parser_Node ) ) { + wp_sqlite_native_parser_verification_fail( 'WordPress PHP test container did not select the native-backed AST.' ); +} + +$reflection = new ReflectionObject( $ast ); +if ( $reflection->hasProperty( 'native_ast' ) || $reflection->hasProperty( 'native_node_index' ) ) { + wp_sqlite_native_parser_verification_fail( 'Native wrapper still stores Rust AST handle properties.' ); +} + +$first = $ast->get_first_child_node(); +if ( ! ( $first instanceof WP_MySQL_Native_Parser_Node ) ) { + wp_sqlite_native_parser_verification_fail( 'Native wrapper did not return a native-backed child node.' ); +} + +if ( $first !== $ast->get_first_child_node() ) { + wp_sqlite_native_parser_verification_fail( 'Native wrapper identity is not stable across reads.' ); +} + +$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 ) ) { + wp_sqlite_native_parser_verification_fail( 'Materialized native wrapper was lost from the parent cache.' ); +} +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 extension 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/.github/workflows/wp-tests-phpunit-run.js b/.github/workflows/wp-tests-phpunit-run.js index 88502fc4..ae4f5f6a 100644 --- a/.github/workflows/wp-tests-phpunit-run.js +++ b/.github/workflows/wp-tests-phpunit-run.js @@ -2,14 +2,15 @@ * Wrap the "composer run wp-tests-php" command to process tests * that are expected to error and fail at the moment. * - * This makes sure that the CI job passes, while explicitly tracking - * the issues that need to be addressed. Ideally, over time this script - * will become obsolete when all errors and failures are resolved. + * Unexpected errors/failures still fail the workflow. Expected failures that + * stop happening are reported so this allowlist can be reduced over time. */ 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', @@ -58,9 +59,6 @@ const expectedFailures = [ 'Tests_DB_Charset::test_strip_invalid_text with data set #32', 'Tests_DB_Charset::test_strip_invalid_text with data set #33', 'Tests_DB_Charset::test_strip_invalid_text with data set #34', - 'Tests_DB_Charset::test_strip_invalid_text with data set #35', - 'Tests_DB_Charset::test_strip_invalid_text with data set #36', - 'Tests_DB_Charset::test_strip_invalid_text with data set #37', 'Tests_DB_Charset::test_strip_invalid_text with data set #39', 'Tests_DB_Charset::test_strip_invalid_text with data set #40', 'Tests_DB_Charset::test_strip_invalid_text with data set #41', @@ -93,10 +91,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 d2746fdc..810b77b8 100644 --- a/.github/workflows/wp-tests-phpunit.yml +++ b/.github/workflows/wp-tests-phpunit.yml @@ -6,11 +6,17 @@ on: - main pull_request: +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + jobs: test: name: WordPress PHPUnit Tests runs-on: ubuntu-latest timeout-minutes: 20 + permissions: + contents: read # Required to clone the repo. steps: - name: Checkout repository @@ -30,3 +36,37 @@ 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 + permissions: + contents: read # Required to clone the repo. + + 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 + env: + WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION: '1' + run: node .github/workflows/wp-tests-phpunit-run.js + + - name: Stop Docker containers + if: always() + run: composer run wp-test-clean diff --git a/.gitignore b/.gitignore index 0de6379d..b75ec524 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,8 @@ composer.lock ._.DS_Store .DS_Store ._* +/build /wordpress +/.claude/settings.local.json +/.adversarial-loop/ +/packages/php-ext-wp-mysql-parser/target/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..eb9f2d08 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,202 @@ + + +# AGENTS.md - SQLite Database Integration + +## Project overview +This project implements SQLite database support for MySQL-based projects. + +It is a monorepo that includes the following components: +- **MySQL lexer** — A fast MySQL lexer with multi-version support. +- **MySQL parser** — An exhaustive MySQL parser with multi-version support. +- **SQLite driver** — A MySQL emulation layer on top of SQLite with a PDO-compatible API. +- **MySQL proxy** — A MySQL binary protocol implementation to support MySQL-based projects beyond PHP. +- **WordPress plugin** — A plugin that adds SQLite support to WordPress. +- **Test suites** — A set of extensive test suites to cover MySQL syntax and functionality. + +The monorepo packages are placed under the `packages` directory. + +The WordPress plugin links the SQLite driver using a symlink. The build script +replaces the symlink with a copy of the driver for release. + +The codebase is pure PHP with zero dependencies. It supports PHP 7.2 through 8.5, +MySQL syntax from version 5.7 onward, and requires SQLite 3.37.0 or newer +(with legacy mode down to 3.27.0). + +## Commands +The codebase is written in PHP and Composer is used to manage the project. +The following commands are useful for development and testing: + +```bash +composer install # Install dependencies +composer run check-cs # Check coding standards (PHPCS) +composer run fix-cs # Auto-fix coding standards (PHPCBF) +composer run build-sqlite-plugin-zip # Build the plugin zip +composer run prepare-release # Prepare a new release + +# SQLite driver tests (under packages/mysql-on-sqlite) +cd packages/mysql-on-sqlite +composer run test # Run unit tests +composer run test tests/SomeTest.php # Run specific unit test file +composer run test -- --filter testName # Run specific unit test class/method + +# SQLite Database Integration plugin E2E tests +composer run test-e2e # Run E2E tests (Playwright via WP env) + +# WordPress tests +composer run wp-setup # Set up WordPress with SQLite for tests +composer run wp-run # Run a WordPress repository command +composer run wp-test-start # Start WordPress environment (Docker) +composer run wp-test-php # Run WordPress PHPUnit tests +composer run wp-test-e2e # Run WordPress E2E tests (Playwright) +composer run wp-test-clean # Clean up WordPress environment (Docker and DB) +``` + +## Release workflow +Release is streamlined with a local preparation script and GitHub Actions: + +1. **Run the release preparation script locally.** + ```bash + composer run prepare-release + ``` + The script will: + - Bump version numbers and generate a changelog from merged PRs. + - Create a `release/` branch with a preparation commit. + - Push the branch and create a PR. + +2. **Review the PR.** + Edit the changelog or push additional changes to the release branch. + +3. **Mark as ready and merge the PR.** + The `release-publish` workflow will automatically: + - Build the plugin ZIP. + - Create and publish a GitHub release with the ZIP attached. + - Deploy the release to WordPress.org. + +## Architecture +The project consists of multiple components providing different APIs that funnel +into the SQLite driver to support diverse use cases both inside and outside the +PHP ecosystem. + +### Component overview +The following diagrams show how different types of applications can be supported +using components from this project. + +**PHP applications** are supported through a PDO\MySQL-compatible API: +``` +PHP applications, Adminer, phpMyAdmin + ↓ PDO\MySQL API +SQLite driver + ↓ PDO\SQLite +SQLite +``` + +**WordPress** projects are powered by a `wpdb` compatible drop-in: +``` +WordPress + plugins, WordPress Playground, WordPress Studio, wp-env + ↓ wpdb +wpdb drop-in + ↓ PDO\MySQL API +SQLite driver + ↓ PDO\SQLite +SQLite +``` + +**Other applications** can be run using the MySQL proxy: +``` +MySQL CLI, Desktop clients + ↓ MySQL binary protocol v10 +MySQL proxy + ↓ PDO\MySQL API +SQLite driver + ↓ PDO\SQLite +SQLite +``` + +### Query processing pipeline +The following diagram illustrates how a MySQL query is processed and emulated: + +``` +MySQL query + ↓ string +Lexer + ↓ tokens +Parser + ↓ AST +Translation & Emulation ← INFORMATION_SCHEMA emulation + ↓ SQL +SQLite +``` + +## Principles +This project implements sophisticated emulation and complex APIs. Any changes and +new features must be carefully designed and planned, and their implementation +must fit into the project architecture. + +When working on changes to the project: +- **Analyze** the existing code and its architecture. +- **Design** changes in accordance with the existing architecture, if possible. +- **Modify** or extend the architecture with consideration when appropriate. +- **Plan** the implementation carefully so that the changes align with the project. +- **Implement** the changes following the planned design and architecture. +- **Test** all newly added logic using a test suite that is appropriate. +- **Verify** implemented changes. Review their architecture and its suitability. +- **Adjust** the implemented changes if needed to improve the implementation. +- **Simplify** the implemented changes when possible to keep them lean. + +Always aim to implement changes and solve problems fully and properly. Don't use +shortcuts and hacks. Never silence errors, linters, or tests to simplify the job. + +## Security +Security is critical to this project. The implementation must prevent all vulnerabilities +that could lead to data compromise or corruption. This includes: +- **Quoting and escaping:** Always use correct escaping and quoting that's appropriate + in a given context. Always correctly prevent SQL injection and other vulnerabilities. +- **Encoding:** Be diligent about encodings and the nuances between MySQL and SQLite. +- **Data integrity:** Always preserve data integrity to avoid data loss or corruption. + +Always scrutinize implemented logic for security vulnerabilities and verify any +assumptions. Never take shortcuts. + +## Compatibility +This project must support a range of PHP and SQLite versions, and it must evolve +the public APIs responsibly, following semantic versioning practices. + +In particular: +- **Public APIs:** It's possible to evolve the public API, but this must always be + surfaced to the developer so versioning decisions can be made. +- **PDO API:** The SQLite driver must follow PDO\MySQL API as closely as possible. +- **MySQL binary protocol:** The MySQL proxy must follow the MySQL binary protocol + as closely as possible. +- **PHP version support:** All PHP versions starting from **PHP 7.2** must be supported. + It is possible to use PHP version checks when needed. +- **SQLite version support:** All SQLite versions starting from **SQLite 3.37.0** must be + supported. Older versions (down to 3.27.0) have limited compatibility and require setting + `WP_SQLITE_UNSAFE_ENABLE_UNSUPPORTED_VERSIONS`. + +## Coding conventions +Follow these conventions when writing code in this project: +- **Coding style:** Use WordPress Coding Standards via PHPCS (`phpcs.xml.dist`). +- **Function ordering:** First caller, then callee. When function A calls function B, write first A, then B. +- **Method ordering:** First public, then protected, then private. Respect Function ordering as well. + +## Git +When creating commits, branches, and pull requests, use clear, concise, and +human-readable prose in plain English. + +### Commits +- Make commits readable for humans, not machines. +- Make subject of a commit message short but clear. +- Start with a verb, use present-tense, imperative form. +- Explain details in a commit body below, if needed. +- Include links in the body if the change relates to external sources, issues, PRs, or tickets. + +### Pull requests +- When creating a pull request, always follow the repository PR template. +- Pull request title must be brief and accurate. +- Pull request description must be clear, comprehensible, and well organized. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..15928b57 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ + + +# SQLite database integration +This project implements SQLite database support for MySQL-based projects. + +It is a monorepo that includes the following components: +- **MySQL lexer** — A fast MySQL lexer with multi-version support. +- **MySQL parser** — An exhaustive MySQL parser with multi-version support. +- **SQLite driver** — A MySQL emulation layer on top of SQLite with a PDO-compatible API. +- **MySQL proxy** — A MySQL binary protocol implementation to support MySQL-based projects beyond PHP. +- **WordPress plugin** — A plugin that adds SQLite support to WordPress. +- **Test suites** — A set of extensive test suites to cover MySQL syntax and functionality. + +The monorepo packages are placed under the `packages` directory. + +The WordPress plugin links the SQLite driver using a symlink. The build script +replaces the symlink with a copy of the driver for release. + +The codebase is pure PHP with zero dependencies. It supports PHP 7.2 through 8.5, +MySQL syntax from version 5.7 onward, and requires SQLite 3.37.0 or newer +(with legacy mode down to 3.27.0). + +## Quick start +The codebase is written in PHP and Composer is used to manage the project. +The following commands are useful for development and testing: + +```bash +composer install # Install dependencies +composer run check-cs # Check coding standards (PHPCS) +composer run fix-cs # Auto-fix coding standards (PHPCBF) +composer run build-sqlite-plugin-zip # Build the plugin zip +composer run prepare-release # Prepare a new release + +# SQLite driver tests (under packages/mysql-on-sqlite) +cd packages/mysql-on-sqlite +composer run test # Run unit tests +composer run test tests/SomeTest.php # Run specific unit test file +composer run test -- --filter testName # Run specific unit test class/method + +# SQLite Database Integration plugin E2E tests +composer run test-e2e # Run E2E tests (Playwright via WP env) + +# WordPress tests +composer run wp-setup # Set up WordPress with SQLite for tests +composer run wp-run # Run a WordPress repository command +composer run wp-test-start # Start WordPress environment (Docker) +composer run wp-test-php # Run WordPress PHPUnit tests +composer run wp-test-e2e # Run WordPress E2E tests (Playwright) +composer run wp-test-clean # Clean up WordPress environment (Docker and DB) +``` + +## Optional: Native MySQL Parser Extension + +The default code path is pure PHP. For environments that can load PHP extensions, the optional `wp_mysql_parser` extension accelerates the MySQL lexer/parser used by the SQLite driver. + +- [Published WASM release list, manifest links, Playground links, and native extension overview](https://wordpress.github.io/sqlite-database-integration/) +- [Build, load, and benchmark docs](packages/php-ext-wp-mysql-parser/README.md) + +Latest local measurement (Apple Silicon macOS, PHP 8.4.5 CLI, 2026-05-26): the native lexer path processed the MySQL test corpus at ~343k QPS versus ~72k QPS for pure PHP (~4.80x), and the native parser path processed it at ~108k QPS versus ~7k QPS for pure PHP (~15.45x). + +## Release workflow +Release is streamlined with a local preparation script and GitHub Actions: + +1. **Run the release preparation script locally.** + ```bash + composer run prepare-release + ``` + The script will: + - Bump version numbers and generate a changelog from merged PRs. + - Create a `release/` branch with a preparation commit. + - Push the branch and create a PR. + +2. **Review the PR.** + Edit the changelog or push additional changes to the release branch. + +3. **Mark as ready and merge the PR.** + The `release-publish` workflow will automatically: + - Build the plugin ZIP. + - Create and publish a GitHub release with the ZIP attached. + - Deploy the release to WordPress.org. + +## Architecture +The project consists of multiple components providing different APIs that funnel +into the SQLite driver to support diverse use cases both inside and outside the +PHP ecosystem. + +### Component overview +The following diagrams show how different types of applications can be supported +using components from this project: + +``` +┌──────────────────────┐ +│ PHP applications │ +│ Adminer, phpMyAdmin │──────────────────────────┐ +└──────────────────────┘ │ + │ +┌──────────────────────┐ wpdb API │ PDO\MySQL API PDO\SQLite +│ WordPress + plugins │ │ ╔══════════════╗ │ │ ╔═══════════════╗ │ ┌────────┐ +│ WordPress Playground │───┴──→║ wpdb drop-in ║───┼───┴──→║ SQLite driver ║───┴──→│ SQLite │ +│ Studio, wp-env │ ╚══════════════╝ │ ╚═══════════════╝ └────────┘ +└──────────────────────┘ │ + MySQL binary protocol │ +┌──────────────────────┐ │ ╔══════════════╗ │ +│ MySQL CLI │───┴──→║ MySQL proxy ║───┘ +│ Desktop clients │ ╚══════════════╝ +└──────────────────────┘ +``` + +### Query processing pipeline +The following diagram illustrates how a MySQL query is processed and emulated: + +``` + string tokens AST ╔═════════════╗ SQL +┌─────────────┐ │ ╔═══════╗ │ ╔════════╗ │ ║ Translation ║ │ ┌────────┐ +│ MySQL query │──┴─→║ Lexer ║──┴─→║ Parser ║──┴─→║ & ║──┴─→│ SQLite │ +└─────────────┘ ╚═══════╝ ╚════════╝ ║ Emulation ║ └────────┘ + ╚═════════════╝ +``` diff --git a/bin/build-sqlite-plugin-zip.sh b/bin/build-sqlite-plugin-zip.sh new file mode 100755 index 00000000..fcb80e7e --- /dev/null +++ b/bin/build-sqlite-plugin-zip.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +## +# Build the SQLite Database Integration plugin zip. +# +# This script copies the plugin package into ./build/plugin-sqlite-database-integration/, +# resolves the driver symlink, removes dev-only files, and creates a zip archive. +## + +set -e + +DIR="$(cd "$(dirname "$0")/.." && pwd)" +BUILD_DIR="$DIR/build" +PLUGIN_DIR="$BUILD_DIR/plugin-sqlite-database-integration" +ZIP_FILE="$BUILD_DIR/plugin-sqlite-database-integration.zip" + +# Clean previous build. +rm -rf "$PLUGIN_DIR" +rm -f "$ZIP_FILE" +mkdir -p "$BUILD_DIR" + +# Copy the plugin package. +cp -R "$DIR/packages/plugin-sqlite-database-integration" "$PLUGIN_DIR" + +# Resolve the database symlink — replace it with a real copy of the driver. +rm "$PLUGIN_DIR/wp-includes/database" +cp -R "$DIR/packages/mysql-on-sqlite/src" "$PLUGIN_DIR/wp-includes/database" + +# Remove dev-only files. +rm -rf "$PLUGIN_DIR/composer.json" +rm -rf "$PLUGIN_DIR/vendor" +rm -rf "$PLUGIN_DIR/node_modules" + +# Verify release metadata in the built plugin. +bash "$DIR/bin/verify-release-metadata.sh" "$PLUGIN_DIR" + +# Create the zip archive. +cd "$BUILD_DIR" +zip -r "$ZIP_FILE" "$(basename "$PLUGIN_DIR")/" -x "*.DS_Store" + +echo "Built: $ZIP_FILE" diff --git a/bin/prepare-release.sh b/bin/prepare-release.sh new file mode 100755 index 00000000..5170291d --- /dev/null +++ b/bin/prepare-release.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# +# Prepare a new release of the SQLite Database Integration plugin. +# +# Usage: +# ./bin/prepare-release.sh +# +# This script: +# 1. Verifies prerequisites (clean tree, trunk branch, up to date). +# 2. Creates a release branch and bumps version numbers. +# 3. Generates a changelog from merged PRs since the last release. +# 4. Commits the changes and creates a pull request. + +set -euo pipefail +cd "$(dirname "$0")/.." + +fail() { + printf '\033[0;31mError: %s\033[0m\n' "$1" >&2 + exit 1 +} + +REPO_URL="https://github.com/WordPress/sqlite-database-integration" +VERSION_PHP="packages/mysql-on-sqlite/src/version.php" +LOAD_PHP="packages/plugin-sqlite-database-integration/load.php" +README_TXT="packages/plugin-sqlite-database-integration/readme.txt" +CURRENT_VERSION="$(sed -n "s/.*SQLITE_DRIVER_VERSION', '\(.*\)'.*/\1/p" "$VERSION_PHP")" + +# 1. VALIDATE ARGUMENTS +NEW_VERSION="${1:-}" +NEW_VERSION="${NEW_VERSION#v}" + +if [ -z "$NEW_VERSION" ]; then + echo "Usage: $0 " + echo "" + echo "Current version: $CURRENT_VERSION" + exit 1 +fi + +if ! [[ "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; then + fail "Invalid version format: $NEW_VERSION" +fi + +PRERELEASE=false +[[ "$NEW_VERSION" == *-* ]] && PRERELEASE=true + +# 2. VERIFY PREREQUISITES +command -v git >/dev/null 2>&1 || fail "git is not installed." +command -v gh >/dev/null 2>&1 || fail "gh CLI is not installed." + +[ -z "$(git status --porcelain)" ] || fail "Working tree is not clean." + +BRANCH="$(git rev-parse --abbrev-ref HEAD)" +[ "$BRANCH" = "trunk" ] || fail "Not on trunk branch (current: $BRANCH)." + +git pull --ff-only origin trunk --quiet || fail "trunk is not up to date with origin/trunk." + +# 3. GENERATE CHANGELOG +LATEST_TAG="v$CURRENT_VERSION" + +echo "Generating changelog from merged PRs since $LATEST_TAG..." +CHANGELOG="" + +if git rev-parse "$LATEST_TAG" >/dev/null 2>&1; then + TAG_TIMESTAMP="$(git log -1 --format='%aI' "$LATEST_TAG")" + + CHANGELOG="$(gh pr list \ + --state merged \ + --base trunk \ + --search "merged:>$TAG_TIMESTAMP" \ + --limit 100 \ + --json number,title,mergedAt \ + --jq "sort_by(.mergedAt) | reverse | .[] + | \"* \\(.title) ([#\\(.number)]($REPO_URL/pull/\\(.number)))\"")" +fi + +if [ -z "$CHANGELOG" ]; then + CHANGELOG="* (no changes listed)" +fi + +# 4. PREPARE RELEASE BRANCH +RELEASE_BRANCH="release/v$NEW_VERSION" +echo "Creating branch $RELEASE_BRANCH..." +git checkout -q -b "$RELEASE_BRANCH" + +echo "Bumping version numbers..." +sed -i.bak "s/define( 'SQLITE_DRIVER_VERSION', '.*' );/define( 'SQLITE_DRIVER_VERSION', '$NEW_VERSION' );/" "$VERSION_PHP" +sed -i.bak "s/^ \* Version: .*/ * Version: $NEW_VERSION/" "$LOAD_PHP" +sed -i.bak "s/^Stable tag:[[:space:]]*.*/Stable tag: $NEW_VERSION/" "$README_TXT" + +echo "Updating changelog..." +if ! grep -Fxq '== Changelog ==' "$README_TXT"; then + printf '\n== Changelog ==\n' >> "$README_TXT" +fi +TMPFILE="$(mktemp)" +trap 'rm -f "$TMPFILE"' EXIT +printf '\n= %s =\n\n%s\n' "$NEW_VERSION" "$CHANGELOG" > "$TMPFILE" +sed -i.bak "/^== Changelog ==$/r $TMPFILE" "$README_TXT" + +rm -f "$VERSION_PHP.bak" "$LOAD_PHP.bak" "$README_TXT.bak" + +# 5. CREATE A PULL REQUEST +echo "Committing changes..." +git add "$VERSION_PHP" "$LOAD_PHP" "$README_TXT" +git commit -q -m "Prepare release $NEW_VERSION" + +echo "Pushing to origin..." +PUSH_OUTPUT="$(git push -q origin "$RELEASE_BRANCH" 2>&1)" \ + || { echo "$PUSH_OUTPUT" >&2; fail "Failed to push $RELEASE_BRANCH."; } + +if [ "$PRERELEASE" = true ]; then + MERGE_NOTE="Merging will automatically build the plugin ZIP and create a [GitHub release]($REPO_URL/releases). + +> [!NOTE] +> This is a **pre-release**. It will not be deployed to [WordPress.org](https://wordpress.org/plugins/sqlite-database-integration/)." +else + MERGE_NOTE="Merging will automatically build the plugin ZIP, create a [GitHub release]($REPO_URL/releases), and deploy to [WordPress.org](https://wordpress.org/plugins/sqlite-database-integration/)." +fi + +echo "Creating pull request..." +PR_URL="$(gh pr create \ + --base trunk \ + --head "$RELEASE_BRANCH" \ + --title "Release $NEW_VERSION" \ + --assignee @me \ + --reviewer "$(gh api user --jq '.login')" \ + --body "$(cat < # Check version consistency. +# verify-release-metadata.sh # Also verify changelog entry. + +set -euo pipefail + +fail() { + echo "::error::$1" + exit 1 +} + +PLUGIN_DIR="${1:?Usage: verify-release-metadata.sh []}" +EXPECTED_VERSION="${2:-}" + +# Extract versions from the three source-of-truth files. +PLUGIN_VERSION="$(sed -n 's/^ \* Version: \(.*\)$/\1/p' "$PLUGIN_DIR/load.php" | head -n 1 | tr -d '\r')" +STABLE_TAG="$(sed -n 's/^Stable tag:[[:space:]]*//p' "$PLUGIN_DIR/readme.txt" | head -n 1 | tr -d '\r')" +CONSTANT_VERSION="$(php -r "require '$PLUGIN_DIR/wp-includes/database/version.php'; echo SQLITE_DRIVER_VERSION;")" + +[ -n "$PLUGIN_VERSION" ] || fail "Could not extract the plugin version from load.php." +[ -n "$STABLE_TAG" ] || fail "Could not extract the Stable tag from readme.txt." +[ -n "$CONSTANT_VERSION" ] || fail "Could not extract SQLITE_DRIVER_VERSION from version.php." + +# All three must agree. +[ "$PLUGIN_VERSION" = "$CONSTANT_VERSION" ] || fail "Version mismatch: load.php=$PLUGIN_VERSION, version.php=$CONSTANT_VERSION." +[ "$PLUGIN_VERSION" = "$STABLE_TAG" ] || fail "Version mismatch: load.php=$PLUGIN_VERSION, readme.txt Stable tag=$STABLE_TAG." + +# When an expected version is given, also verify it matches and that +# the changelog contains a non-empty entry for that version. +if [ -n "$EXPECTED_VERSION" ]; then + [ "$PLUGIN_VERSION" = "$EXPECTED_VERSION" ] || fail "Version mismatch: expected $EXPECTED_VERSION, found $PLUGIN_VERSION." + + grep -Fxq '== Changelog ==' "$PLUGIN_DIR/readme.txt" \ + || fail "readme.txt is missing a == Changelog == section." + + if ! awk -v version="$EXPECTED_VERSION" ' + BEGIN { in_changelog = 0; in_entry = 0; has_content = 0 } + $0 == "== Changelog ==" { in_changelog = 1; next } + in_changelog && $0 == "= " version " =" { in_entry = 1; next } + in_entry && $0 ~ /^= / { exit has_content ? 0 : 1 } + in_entry && $0 !~ /^[[:space:]]*$/ { has_content = 1 } + END { if (!in_changelog || !in_entry || !has_content) exit 1 } + ' "$PLUGIN_DIR/readme.txt"; then + fail "readme.txt must contain a changelog entry with content for version $EXPECTED_VERSION." + fi +fi + +echo "Verified release metadata for version $PLUGIN_VERSION." diff --git a/composer.json b/composer.json index 370c1d1e..689b44ed 100644 --- a/composer.json +++ b/composer.json @@ -18,9 +18,7 @@ "squizlabs/php_codesniffer": "^3.7", "wp-coding-standards/wpcs": "^3.1", "phpcompatibility/phpcompatibility-wp": "*", - "php-parallel-lint/php-parallel-lint": "^1.3", - "yoast/phpunit-polyfills": "2.0.0", - "phpunit/phpunit": "8.5.38" + "php-parallel-lint/php-parallel-lint": "^1.3" }, "config": { "allow-plugins": { @@ -36,8 +34,11 @@ "fix-cs": [ "@php ./vendor/bin/phpcbf" ], - "test": [ - "phpunit" + "build-sqlite-plugin-zip": [ + "./bin/build-sqlite-plugin-zip.sh" + ], + "prepare-release": [ + "./bin/prepare-release.sh" ], "test-e2e": [ "@wp-test-ensure-env @no_additional_args", diff --git a/grammar-tools/MySQLParser.g4 b/grammar-tools/MySQLParser.g4 index 40c114d8..8c1b0b25 100644 --- a/grammar-tools/MySQLParser.g4 +++ b/grammar-tools/MySQLParser.g4 @@ -2961,13 +2961,13 @@ bitExpr: simpleExpr %bitExpr_rr*; /* * @CHANGED: - * Factored left recursion. + * Factored left recursion and introduced "simpleExprBody" for easier processing. */ simpleExpr: %simpleExpr_collate (CONCAT_PIPES_SYMBOL %simpleExpr_collate)*; -%simpleExpr_collate: %simpleExpr_factored (COLLATE_SYMBOL textOrIdentifier)?; +%simpleExpr_collate: simpleExprBody (COLLATE_SYMBOL textOrIdentifier)?; -%simpleExpr_factored: +simpleExprBody: literal # simpleExprLiteral | sumExpr # simpleExprSum | variable (equal expr)? # simpleExprVariable diff --git a/grammar-tools/convert-grammar.php b/grammar-tools/convert-grammar.php index 56e7c478..6e01682a 100644 --- a/grammar-tools/convert-grammar.php +++ b/grammar-tools/convert-grammar.php @@ -10,10 +10,10 @@ * @TODO Migrate the current regex-based solution to a proper grammar parser. */ -require_once __DIR__ . '/../wp-includes/parser/class-wp-parser-grammar.php'; -require_once __DIR__ . '/../wp-includes/mysql/class-wp-mysql-lexer.php'; +require_once __DIR__ . '/../packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php'; +require_once __DIR__ . '/../packages/mysql-on-sqlite/src/mysql/class-wp-mysql-lexer.php'; -const GRAMMAR_FILE = __DIR__ . '/../wp-includes/mysql/mysql-grammar.php'; +const GRAMMAR_FILE = __DIR__ . '/../packages/mysql-on-sqlite/src/mysql/mysql-grammar.php'; // Convert the original MySQLParser.g4 grammar to a JSON format. // The grammar is also flattened and expanded to an ebnf-to-json-like format. diff --git a/packages/mysql-on-sqlite/composer.json b/packages/mysql-on-sqlite/composer.json new file mode 100644 index 00000000..9d2b148f --- /dev/null +++ b/packages/mysql-on-sqlite/composer.json @@ -0,0 +1,10 @@ +{ + "name": "wordpress/mysql-on-sqlite", + "type": "library", + "scripts": { + "test": "phpunit" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + } +} diff --git a/phpunit.xml.dist b/packages/mysql-on-sqlite/phpunit.xml.dist similarity index 84% rename from phpunit.xml.dist rename to packages/mysql-on-sqlite/phpunit.xml.dist index 2340405a..a41280c8 100644 --- a/phpunit.xml.dist +++ b/packages/mysql-on-sqlite/phpunit.xml.dist @@ -11,11 +11,13 @@ convertNoticesToExceptions="true" convertDeprecationsToExceptions="true" > + + + - tests/ - + tests/ tests/tools diff --git a/packages/mysql-on-sqlite/src/load.php b/packages/mysql-on-sqlite/src/load.php new file mode 100644 index 00000000..62387a2e --- /dev/null +++ b/packages/mysql-on-sqlite/src/load.php @@ -0,0 +1,45 @@ +sql[ $this->bytes_already_read ]; @@ -2842,6 +2840,11 @@ private function read_quoted_text(): ?int { while ( true ) { $at += strcspn( $this->sql, $quote, $at ); + // Unclosed string - unexpected EOF. + if ( ( $this->sql[ $at ] ?? null ) !== $quote ) { + return null; // Invalid input. + } + /* * By default, quotes can be escaped with a "\". * When NO_BACKSLASH_ESCAPES SQL mode is active, the "\" treated as @@ -2852,18 +2855,13 @@ private function read_quoted_text(): ?int { * "\\\" is an escaped backslash and an escape sequence, and so on. */ if ( ! $no_backslash_escapes ) { - for ($i = 0; '\\' === $this->sql[ $at - $i - 1 ]; $i += 1); + for ( $i = 0; ( $at - $i - 1 ) >= 0 && '\\' === $this->sql[ $at - $i - 1 ]; $i += 1 ); if ( 1 === $i % 2 ) { $at += 1; continue; } } - // Unclosed string - unexpected EOF. - if ( ( $this->sql[ $at ] ?? null ) !== $quote ) { - return null; // Invalid input. - } - // Check if the quote is doubled. if ( ( $this->sql[ $at + 1 ] ?? null ) === $quote ) { $at += 2; diff --git a/wp-includes/mysql/class-wp-mysql-parser.php b/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php similarity index 83% rename from wp-includes/mysql/class-wp-mysql-parser.php rename to packages/mysql-on-sqlite/src/mysql/class-wp-mysql-parser.php index f291064e..69282b9c 100644 --- a/wp-includes/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/wp-includes/mysql/class-wp-mysql-token.php b/packages/mysql-on-sqlite/src/mysql/class-wp-mysql-token.php similarity index 100% rename from wp-includes/mysql/class-wp-mysql-token.php rename to packages/mysql-on-sqlite/src/mysql/class-wp-mysql-token.php diff --git a/packages/mysql-on-sqlite/src/mysql/mysql-grammar.php b/packages/mysql-on-sqlite/src/mysql/mysql-grammar.php new file mode 100644 index 00000000..e68646fb --- /dev/null +++ b/packages/mysql-on-sqlite/src/mysql/mysql-grammar.php @@ -0,0 +1,4 @@ +2000,'rules_names'=>['query','%f1','%f2','%f3','simpleStatement','%f4','%f5','%f6','%f7','alterStatement','%f8','%f9','%f10','alterInstance','%f11','%f12','%f13','%f14','%f15','%f16','%f17','%f18','%f19','%f20','%f21','%f22','%f23','%f24','alterDatabase','%f25','%f26','%f27','%f28','%f29','%f30','%f31','%f32','%f33','%f34','alterEvent','%f35','%f36','%f37','%f38','%f39','%f40','%f41','%f42','%f43','%f44','alterLogfileGroup','%f45','alterLogfileGroupOptions','%f46','%f47','alterLogfileGroupOption','alterServer','%f48','%f49','%f50','alterTable','%f51','%f52','%f53','%f54','alterTableActions','%f55','%f56','%f57','alterCommandList','%f58','%f59','alterCommandsModifierList','%f60','standaloneAlterCommands','%f61','%f62','%f63','%f64','%f65','%f66','%f67','alterPartition','%f68','%f69','%f70','%f71','%f72','alterList','%f73','%f74','%f75','alterCommandsModifier','%f76','%f77','%f78','%f79','alterListItem','%f80','%f81','%f82','%f83','%f84','%f85','%f86','%f87','%f88','%f89','%f90','%f91','%f92','%f93','%f94','%f95','%f96','%f97','%f98','%f99','%f100','%f101','%f102','place','restrict','%f103','%f104','alterOrderList','%f105','%f106','alterAlgorithmOption','%f107','alterLockOption','%f108','%f109','%f110','indexLockAndAlgorithm','withValidation','%f111','%f112','removePartitioning','allOrPartitionNameList','alterTablespace','%f113','%f114','%f115','%f116','%f117','%f118','%f119','%f120','%f121','%f122','%f123','%f124','alterUndoTablespace','%f125','%f126','undoTableSpaceOptions','%f127','undoTableSpaceOption','%f128','alterTablespaceOptions','%f129','alterTablespaceOption','%f130','changeTablespaceOption','%f131','%f132','alterView','%f133','viewTail','%f134','viewSelect','%f135','viewCheckOption','%f136','createStatement','%f137','%f138','%f139','%f140','%f141','%f142','createDatabase','createDatabaseOption','%f143','%f144','createTable','%f145','%f146','%f147','%f148','%f149','%f150','%f151','tableElementList','%f152','tableElement','%f153','%f154','duplicateAsQueryExpression','%f155','queryExpressionOrParens','%f156','createRoutine','%f157','%f158','%f159','createProcedure','%f160','%f161','%f162','%f163','%f164','%f165','createFunction','%f166','%f167','%f168','%f169','%f170','%f171','createUdf','%f172','routineCreateOption','%f173','routineAlterOptions','routineOption','%f174','%f175','createIndex','%f176','%f177','%f178','%f179','%f180','%f181','%f182','%f183','%f184','%f185','%f186','indexNameAndType','%f187','%f188','createIndexTarget','%f189','createLogfileGroup','%f190','%f191','logfileGroupOptions','%f192','logfileGroupOption','createServer','%f193','serverOptions','%f194','serverOption','%f195','%f196','createTablespace','%f197','createUndoTablespace','tsDataFileName','%f198','%f199','%f200','tsDataFile','%f201','tablespaceOptions','%f202','tablespaceOption','%f203','%f204','%f205','tsOptionInitialSize','tsOptionUndoRedoBufferSize','%f206','tsOptionAutoextendSize','tsOptionMaxSize','tsOptionExtentSize','tsOptionNodegroup','%f207','tsOptionEngine','tsOptionEngineAttribute','tsOptionWait','%f208','tsOptionComment','tsOptionFileblockSize','tsOptionEncryption','%f209','createView','viewReplaceOrAlgorithm','viewAlgorithm','%f210','viewSuid','%f211','%f212','createTrigger','%f213','%f214','%f215','%f216','triggerFollowsPrecedesClause','%f217','%f218','%f219','%f220','%f221','createEvent','%f222','%f223','%f224','%f225','%f226','createRole','%f227','createSpatialReference','srsAttribute','dropStatement','%f228','%f229','%f230','%f231','%f232','dropDatabase','dropEvent','dropFunction','dropProcedure','dropIndex','%f233','dropLogfileGroup','%f234','%f235','%f236','dropLogfileGroupOption','dropServer','%f237','dropTable','%f238','%f239','%f240','dropTableSpace','%f241','%f242','%f243','dropTrigger','%f244','dropView','%f245','dropRole','dropSpatialReference','dropUndoTablespace','%f246','renameTableStatement','%f247','%f248','renamePair','%f249','truncateTableStatement','importStatement','%f250','callStatement','%f251','%f252','%f253','%f254','deleteStatement','%f255','%f256','%f257','%f258','%f259','%f260','%f261','%f262','%f263','%f264','%f265','partitionDelete','%f266','deleteStatementOption','doStatement','%f267','%f268','%f269','handlerStatement','%f270','%f271','%f272','%f273','handlerReadOrScan','%f274','%f275','%f276','%f277','%f278','%f279','%f280','%f281','%f282','insertStatement','%f283','%f284','%f285','%f286','%f287','%f288','%f289','insertLockOption','%f290','insertFromConstructor','%f291','%f292','%f293','fields','%f294','insertValues','%f295','insertQueryExpression','%f296','%f297','valueList','%f298','%f299','values','%f300','%f301','%f302','valuesReference','insertUpdateList','%f303','%f304','%f305','%f306','%f307','%f308','%f309','loadStatement','%f310','%f311','dataOrXml','xmlRowsIdentifiedBy','%f312','%f313','%f314','loadDataFileTail','%f315','%f316','%f317','%f318','loadDataFileTargetList','%f319','fieldOrVariableList','%f320','%f321','%f322','%f323','replaceStatement','%f324','%f325','%f326','selectStatement','selectStatementWithInto','%f327','%f328','queryExpression','%f329','%f330','%f331','%f332','%f333','%f334','queryExpressionBody','%f335','%f336','%f337','%f338','%f339','queryTerm','%f340','%f341','%f342','%f343','queryExpressionParens','queryPrimary','%f344','%f345','%f346','%f347','%f348','%f349','%f350','%f351','querySpecification','%f352','%f353','subquery','querySpecOption','limitClause','simpleLimitClause','%f354','limitOptions','%f355','%f356','limitOption','%f357','intoClause','%f358','%f359','%f360','%f361','%f362','%f363','procedureAnalyseClause','%f364','%f365','%f366','havingClause','%f367','windowClause','%f368','windowDefinition','windowSpec','%f369','%f370','%f371','%f372','%f373','windowSpecDetails','%f374','%f375','%f376','%f377','windowFrameClause','windowFrameUnits','windowFrameExtent','windowFrameStart','windowFrameBetween','windowFrameBound','windowFrameExclusion','%f378','%f379','%f380','withClause','%f381','commonTableExpression','%f382','groupByClause','olapOption','%f383','orderClause','direction','fromClause','%f384','%f385','tableReferenceList','%f386','%f387','tableValueConstructor','%f388','explicitTable','rowValueExplicit','selectOption','%f389','%f390','%f391','lockingClauseList','%f392','%f393','lockingClause','%f394','%f395','%f396','%f397','lockStrengh','%f398','lockedRowAction','%f399','selectItemList','%f400','%f401','%f402','selectItem','selectAlias','%f403','whereClause','%f404','tableReference','%f405','%f406','%f407','escapedTableReference','%f408','joinedTable','%f409','%f410','%f411','%f412','naturalJoinType','%f413','%f414','innerJoinType','%f415','outerJoinType','%f416','tableFactor','%f417','%f418','singleTable','singleTableParens','%f419','%f420','derivedTable','%f421','%f422','%f423','tableReferenceListParens','%f424','tableFunction','%f425','columnsClause','%f426','%f427','%f428','%f429','jtColumn','%f430','%f431','%f432','%f433','onEmptyOrError','onEmpty','onError','jtOnResponse','setOperationOption','%f434','tableAlias','%f435','%f436','%f437','indexHintList','%f438','%f439','indexHint','indexHintType','keyOrIndex','%f440','constraintKeyType','indexHintClause','%f441','%f442','indexList','%f443','indexListElement','%f444','%f445','updateStatement','%f446','%f447','transactionOrLockingStatement','%f448','%f449','%f450','%f451','transactionStatement','%f452','%f453','%f454','beginWork','%f455','transactionCharacteristicList','%f456','transactionCharacteristic','%f457','%f458','savepointStatement','%f459','%f460','%f461','%f462','%f463','%f464','%f465','lockStatement','%f466','%f467','%f468','%f469','%f470','lockItem','lockOption','xaStatement','%f471','%f472','%f473','%f474','%f475','%f476','%f477','%f478','%f479','%f480','xaConvert','%f481','%f482','%f483','%f484','xid','%f485','%f486','%f487','%f488','replicationStatement','%f489','%f490','%f491','%f492','%f493','%f494','%f495','%f496','%f497','%f498','%f499','resetOption','%f500','masterResetOptions','%f501','%f502','%f503','%f504','replicationLoad','%f505','changeMaster','%f506','changeMasterOptions','%f507','masterOption','privilegeCheckDef','tablePrimaryKeyCheckDef','masterTlsCiphersuitesDef','masterFileDef','%f508','serverIdList','%f509','%f510','%f511','%f512','%f513','changeReplication','%f514','%f515','%f516','%f517','changeReplicationSourceOptions','%f518','replicationSourceOption','%f519','%f520','%f521','%f522','%f523','%f524','%f525','%f526','%f527','%f528','%f529','%f530','%f531','%f532','%f533','%f534','%f535','%f536','%f537','%f538','%f539','%f540','%f541','%f542','%f543','%f544','%f545','%f546','%f547','%f548','%f549','filterDefinition','%f550','filterDbList','%f551','%f552','filterTableList','%f553','%f554','filterStringList','%f555','filterWildDbTableString','%f556','filterDbPairList','%f557','%f558','%f559','slave','%f560','%f561','slaveUntilOptions','%f562','%f563','%f564','%f565','%f566','slaveConnectionOptions','%f567','%f568','%f569','%f570','%f571','%f572','%f573','%f574','%f575','%f576','slaveThreadOptions','%f577','slaveThreadOption','groupReplication','%f578','preparedStatement','%f579','%f580','%f581','executeStatement','%f582','%f583','executeVarList','%f584','cloneStatement','%f585','%f586','%f587','%f588','%f589','%f590','%f591','dataDirSSL','ssl','accountManagementStatement','%f592','%f593','%f594','alterUser','%f595','%f596','alterUserTail','%f597','%f598','%f599','%f600','%f601','%f602','userFunction','createUser','%f603','%f604','createUserTail','%f605','%f606','%f607','%f608','%f609','%f610','%f611','%f612','defaultRoleClause','%f613','%f614','%f615','requireClause','%f616','%f617','%f618','connectOptions','%f619','accountLockPasswordExpireOptions','%f620','%f621','%f622','%f623','%f624','%f625','%f626','%f627','%f628','%f629','%f630','dropUser','%f631','%f632','grant','%f633','%f634','%f635','%f636','%f637','%f638','%f639','%f640','%f641','%f642','%f643','%f644','grantTargetList','%f645','%f646','grantOptions','%f647','%f648','%f649','exceptRoleList','withRoles','%f650','%f651','%f652','grantAs','versionedRequireClause','%f653','%f654','renameUser','%f655','%f656','%f657','revoke','%f658','%f659','%f660','%f661','%f662','%f663','%f664','%f665','%f666','%f667','%f668','onTypeTo','%f669','%f670','%f671','%f672','aclType','%f673','roleOrPrivilegesList','%f674','%f675','%f676','roleOrPrivilege','%f677','%f678','%f679','%f680','%f681','%f682','%f683','%f684','%f685','%f686','%f687','grantIdentifier','%f688','%f689','%f690','%f691','requireList','%f692','%f693','requireListElement','grantOption','%f694','setRole','%f695','%f696','%f697','%f698','roleList','%f699','%f700','role','%f701','%f702','%f703','tableAdministrationStatement','%f704','%f705','%f706','%f707','%f708','%f709','%f710','%f711','%f712','%f713','histogram','%f714','%f715','checkOption','%f716','repairType','%f717','%f718','installUninstallStatment','%f719','%f720','installOptionType','%f721','installSetValue','%f722','%f723','installSetValueList','%f724','setStatement','%f725','startOptionValueList','%f726','%f727','%f728','%f729','%f730','%f731','%f732','%f733','%f734','%f735','%f736','transactionCharacteristics','%f737','%f738','transactionAccessMode','%f739','isolationLevel','%f740','%f741','%f742','optionValueListContinued','%f743','optionValueNoOptionType','%f744','%f745','optionValue','%f746','setSystemVariable','startOptionValueListFollowingOptionType','optionValueFollowingOptionType','setExprOrDefault','%f747','%f748','%f749','showStatement','%f750','%f751','%f752','%f753','%f754','%f755','%f756','%f757','%f758','%f759','%f760','%f761','%f762','%f763','%f764','%f765','%f766','%f767','%f768','%f769','%f770','%f771','%f772','%f773','%f774','%f775','%f776','%f777','%f778','%f779','%f780','%f781','%f782','%f783','showCommandType','%f784','nonBlocking','%f785','%f786','fromOrIn','inDb','profileType','%f787','%f788','otherAdministrativeStatement','%f789','%f790','%f791','%f792','%f793','%f794','keyCacheListOrParts','%f795','keyCacheList','%f796','%f797','assignToKeycache','assignToKeycachePartition','%f798','cacheKeyList','keyUsageElement','%f799','keyUsageList','%f800','%f801','flushOption','%f802','%f803','%f804','logType','%f805','flushTables','%f806','%f807','%f808','flushTablesOptions','%f809','%f810','preloadTail','%f811','%f812','preloadList','%f813','%f814','preloadKeys','%f815','adminPartition','resourceGroupManagement','%f816','%f817','%f818','createResourceGroup','%f819','%f820','resourceGroupVcpuList','%f821','%f822','vcpuNumOrRange','%f823','resourceGroupPriority','resourceGroupEnableDisable','%f824','alterResourceGroup','%f825','setResourceGroup','%f826','%f827','threadIdList','%f828','dropResourceGroup','utilityStatement','%f829','%f830','describeStatement','%f831','%f832','%f833','explainStatement','%f834','%f835','%f836','%f837','%f838','%f839','%f840','explainableStatement','%f841','%f842','%f843','helpCommand','useCommand','restartServer','%f844','expr','%f845','%f846','%f847','%f848','%f849','%f850','%f851','%f852','%f853','%f854','boolPri','%f855','%f856','compOp','%f857','predicate','%f858','%f859','%f860','%f861','predicateOperations','%f862','%f863','%f864','bitExpr','%f865','%f866','%f867','%f868','%f869','%f870','simpleExpr','%f871','%f872','%f873','%f874','%f875','%f876','%f877','%f878','%f879','%f880','%f881','%f882','simpleExprBody','%f883','%f884','%f885','%f886','%f887','%f888','%f889','%f890','%f891','arrayCast','%f892','jsonOperator','%f893','%f894','%f895','%f896','%f897','%f898','%f899','%f900','%f901','%f902','%f903','%f904','%f905','%f906','%f907','%f908','sumExpr','%f909','%f910','%f911','%f912','%f913','%f914','%f915','%f916','%f917','%f918','%f919','%f920','%f921','%f922','%f923','%f924','%f925','%f926','%f927','%f928','%f929','%f930','%f931','%f932','%f933','%f934','%f935','%f936','groupingOperation','%f937','%f938','%f939','windowFunctionCall','%f940','%f941','%f942','%f943','%f944','windowingClause','%f945','%f946','leadLagInfo','%f947','%f948','nullTreatment','%f949','%f950','jsonFunction','inSumExpr','identListArg','%f951','identList','%f952','%f953','fulltextOptions','%f954','%f955','%f956','%f957','%f958','%f959','%f960','%f961','runtimeFunctionCall','%f962','%f963','%f964','%f965','%f966','%f967','%f968','%f969','%f970','%f971','%f972','%f973','%f974','%f975','%f976','%f977','%f978','%f979','%f980','%f981','geometryFunction','%f982','%f983','timeFunctionParameters','fractionalPrecision','%f984','weightStringLevels','%f985','%f986','%f987','%f988','weightStringLevelListItem','%f989','%f990','%f991','dateTimeTtype','trimFunction','%f992','%f993','%f994','substringFunction','%f995','%f996','%f997','%f998','%f999','%f1000','functionCall','%f1001','udfExprList','%f1002','udfExpr','variable','userVariable','%f1003','%f1004','systemVariable','internalVariableName','%f1005','%f1006','%f1007','whenExpression','thenExpression','elseExpression','%f1008','%f1009','%f1010','%f1011','castType','%f1012','%f1013','%f1014','%f1015','%f1016','exprList','%f1017','charset','notRule','not2Rule','interval','%f1018','intervalTimeStamp','exprListWithParentheses','exprWithParentheses','simpleExprWithParentheses','%f1019','orderList','%f1020','orderExpression','%f1021','groupList','%f1022','groupingExpression','channel','%f1023','compoundStatement','returnStatement','ifStatement','%f1024','ifBody','%f1025','thenStatement','%f1026','compoundStatementList','%f1027','%f1028','%f1029','caseStatement','%f1030','elseStatement','%f1031','labeledBlock','unlabeledBlock','label','%f1032','%f1033','beginEndBlock','labeledControl','unlabeledControl','loopBlock','whileDoBlock','repeatUntilBlock','%f1034','spDeclarations','%f1035','spDeclaration','%f1036','variableDeclaration','%f1037','conditionDeclaration','spCondition','%f1038','sqlstate','%f1039','handlerDeclaration','%f1040','%f1041','handlerCondition','cursorDeclaration','iterateStatement','leaveStatement','%f1042','getDiagnostics','%f1043','%f1044','%f1045','%f1046','%f1047','%f1048','%f1049','signalAllowedExpr','statementInformationItem','%f1050','%f1051','conditionInformationItem','%f1052','%f1053','signalInformationItemName','%f1054','signalStatement','%f1055','%f1056','%f1057','%f1058','%f1059','%f1060','resignalStatement','%f1061','%f1062','%f1063','%f1064','signalInformationItem','cursorOpen','cursorClose','%f1065','cursorFetch','%f1066','%f1067','%f1068','%f1069','schedule','%f1070','%f1071','columnDefinition','checkOrReferences','%f1072','checkConstraint','constraintEnforcement','%f1073','tableConstraintDef','%f1074','%f1075','%f1076','%f1077','%f1078','%f1079','%f1080','constraintName','fieldDefinition','%f1081','%f1082','%f1083','%f1084','%f1085','%f1086','%f1087','%f1088','%f1089','%f1090','%f1091','%f1092','%f1093','columnAttribute','%f1094','%f1095','%f1096','%f1097','%f1098','%f1099','%f1100','columnFormat','storageMedia','gcolAttribute','%f1101','%f1102','%f1103','references','%f1104','%f1105','%f1106','%f1107','%f1108','%f1109','%f1110','deleteOption','%f1111','%f1112','keyList','%f1113','keyPart','%f1114','keyListWithExpression','%f1115','keyPartOrExpression','keyListVariants','%f1116','%f1117','indexType','%f1118','indexOption','commonIndexOption','%f1119','visibility','indexTypeClause','%f1120','fulltextIndexOption','spatialIndexOption','dataTypeDefinition','%f1121','%f1122','%f1123','%f1124','dataType','%f1125','%f1126','%f1127','%f1128','%f1129','%f1130','%f1131','%f1132','%f1133','%f1134','%f1135','nchar','realType','fieldLength','%f1136','%f1137','fieldOptions','%f1138','%f1139','charsetWithOptBinary','%f1140','ascii','unicode','wsNumCodepoints','typeDatetimePrecision','charsetName','%f1141','collationName','%f1142','%f1143','%f1144','createTableOptions','%f1145','%f1146','createTableOptionsSpaceSeparated','%f1147','createTableOption','%f1148','%f1149','%f1150','%f1151','%f1152','%f1153','%f1154','%f1155','%f1156','%f1157','%f1158','%f1159','%f1160','%f1161','%f1162','ternaryOption','%f1163','defaultCollation','defaultEncryption','defaultCharset','%f1164','%f1165','%f1166','partitionClause','%f1167','%f1168','%f1169','%f1170','partitionTypeDef','%f1171','%f1172','%f1173','subPartitions','%f1174','%f1175','partitionKeyAlgorithm','%f1176','%f1177','partitionDefinitions','%f1178','%f1179','%f1180','%f1181','partitionDefinition','%f1182','%f1183','%f1184','%f1185','%f1186','%f1187','partitionValuesIn','%f1188','partitionOption','%f1189','%f1190','subpartitionDefinition','%f1191','partitionValueItemListParen','%f1192','partitionValueItem','definerClause','ifExists','ifNotExists','%f1193','procedureParameter','%f1194','functionParameter','collate','typeWithOptCollate','schemaIdentifierPair','%f1195','viewRefList','%f1196','%f1197','updateList','%f1198','updateElement','%f1199','charsetClause','%f1200','fieldsClause','%f1201','fieldTerm','%f1202','linesClause','lineTerm','%f1203','%f1204','userList','%f1205','%f1206','createUserList','%f1207','%f1208','alterUserList','%f1209','%f1210','createUserEntry','%f1211','%f1212','%f1213','%f1214','%f1215','%f1216','%f1217','%f1218','%f1219','%f1220','%f1221','alterUserEntry','%f1222','%f1223','%f1224','%f1225','%f1226','%f1227','%f1228','%f1229','%f1230','%f1231','%f1232','retainCurrentPassword','discardOldPassword','replacePassword','%f1233','userIdentifierOrText','%f1234','%f1235','user','likeClause','likeOrWhere','onlineOption','noWriteToBinLog','usePartition','%f1236','fieldIdentifier','%f1237','%f1238','%f1239','columnInternalRef','%f1240','columnInternalRefList','%f1241','columnRef','insertIdentifier','indexName','indexRef','%f1242','tableWild','%f1243','schemaName','schemaRef','procedureName','procedureRef','functionName','functionRef','triggerName','triggerRef','viewName','%f1244','viewRef','%f1245','tablespaceName','tablespaceRef','logfileGroupName','logfileGroupRef','eventName','eventRef','udfName','serverName','serverRef','engineRef','tableName','%f1246','filterTableRef','%f1247','tableRefWithWildcard','%f1248','%f1249','%f1250','tableRef','%f1251','%f1252','tableRefList','%f1253','%f1254','tableAliasRefList','%f1255','parameterName','labelIdentifier','labelRef','roleIdentifier','roleRef','pluginRef','componentRef','resourceGroupRef','windowName','pureIdentifier','%f1256','%f1257','identifier','%f1258','identifierList','%f1259','identifierListWithParentheses','qualifiedIdentifier','%f1260','simpleIdentifier','%f1261','%f1262','dotIdentifier','ulong_number','real_ulong_number','ulonglong_number','real_ulonglong_number','%f1263','%f1264','literal','%f1265','signedLiteral','%f1266','stringList','%f1267','textStringLiteral','%f1268','textString','textStringHash','%f1269','%f1270','textLiteral','%f1271','textStringNoLinebreak','%f1272','textStringLiteralList','%f1273','numLiteral','boolLiteral','nullLiteral','temporalLiteral','floatOptions','standardFloatOptions','precision','textOrIdentifier','lValueIdentifier','roleIdentifierOrText','sizeNumber','parentheses','equal','optionType','varIdentType','setVarIdentType','identifierKeyword','%f1274','%f1275','%f1276','%f1277','%f1278','identifierKeywordsAmbiguous1RolesAndLabels','identifierKeywordsAmbiguous2Labels','labelKeyword','%f1279','%f1280','%f1281','identifierKeywordsAmbiguous3Roles','identifierKeywordsUnambiguous','%f1282','%f1283','%f1284','roleKeyword','%f1285','%f1286','%f1287','lValueKeyword','identifierKeywordsAmbiguous4SystemVariables','roleOrIdentifierKeyword','%f1288','%f1289','%f1290','roleOrLabelKeyword','%f1291','%f1292','%f1293','%f1294','%f1295','%f1296','%f1297'],'grammar'=>[[[-1],[2001,2003]],[[2004],[2668]],[[-1],[0]],[[755,2002],[-1]],[[2009],[2175],[2318],[2353],[2358],[2005],[2361],[2366],[2381],[2385],[2400],[2437],[2457],[2461],[2656],[2659],[2712],[2829],[2006],[2848],[2991],[3010],[3020],[3057],[2007],[3102],[3168],[2008],[3489],[3496]],[[2359]],[[2838]],[[3145]],[[3472]],[[11,2012]],[[2153]],[[2225],[0]],[[2060],[2028],[422,3783,2011],[206,3785,2011],[2167],[2039],[2140],[2010],[2050],[2056],[2013]],[[2026]],[[33]],[[844],[2014]],[[2017],[0]],[[373,480,383,165]],[[451,845,2016]],[[2020],[0]],[[373,480,383,165]],[[451,845,200,57,3830,2019]],[[156],[140]],[[2022,844,846]],[[451,847]],[[482,2015,316,265],[2018],[2021],[2023],[2024]],[[244,2025]],[[3781],[0]],[[109,2027,2031]],[[615,112,139,357]],[[2183,2030],[2183]],[[2030],[2029]],[[3690],[0]],[[2040],[0]],[[2042],[0]],[[2043],[0]],[[2046],[0]],[[2047],[0]],[[2048],[0]],[[2032,170,3797,2033,2034,2035,2036,2037,2038]],[[383,490,3510]],[[371],[0]],[[383,79,2041,418]],[[453,590,3830]],[[2045],[0]],[[383,514]],[[156],[140,2044]],[[75,3859]],[[147,3425]],[[2052],[0]],[[288,217,3795,4,603,3859,2049]],[[2054,2051],[2054],[0]],[[2055,2051]],[[750],[0]],[[2053,2055]],[[2274],[2282],[2284]],[[503,3800,2254]],[[3761],[0]],[[2062],[0]],[[2065],[0]],[[2057,2058,574,3810,2059]],[[232]],[[2061]],[[2066],[0]],[[2067],[0]],[[2063,2074],[2069,2064],[3653],[2138]],[[2072,750]],[[3653],[2138]],[[2070],[0]],[[2072,2068],[2088]],[[750,2088]],[[2073,2071],[2073],[0]],[[2092,2071]],[[750,2092]],[[141,572],[234,572],[2082],[2076]],[[722],[723]],[[2075]],[[3762],[0]],[[3005,2078],[3005],[0]],[[3007,2079],[3007],[0]],[[2084],[0]],[[2135],[0]],[[4,405,2077,2083],[148,405,3832],[438,405,2077,2139],[388,405,2077,2139,2077],[14,405,2077,2139],[62,405,2139,2078],[455,405,2077,2139,2079],[67,405,2077,3842],[597,405,2139],[454,405,2077,2080],[172,405,3830,645,574,3810,2081],[2085],[2086]],[[3668],[404,3842]],[[3832,248,3668]],[[141,405,2139,572]],[[234,405,2139,572]],[[2091,2087],[2091],[0]],[[2089,2087]],[[2097],[3627]],[[2097],[2092],[3627]],[[750,2090]],[[2128],[2130],[2135]],[[72],[0]],[[2121],[0]],[[2116],[0]],[[3697],[0]],[[4,2093,2099],[4,3519],[55,2093,3765,3830,3528,2094],[348,2093,3765,3528,2094],[148,2107],[140,263],[156,263],[11,2093,3765,2111],[2112],[2113],[2114],[2115],[453,2095,3802],[2117],[94,590,3406,2119,2096],[198],[393,45,2125],[2120]],[[3514],[0]],[[3830,3528,2098,2094],[753,2194,748]],[[3765]],[[3765],[0]],[[2101]],[[2100],[2102]],[[62,3830]],[[86,3830]],[[2122],[0]],[[2093,3765,2106],[199,265,2103],[420,265],[2645,3776],[2104],[2105]],[[3413]],[[2108],[3849]],[[506,3582]],[[506,128,2109],[148,128],[2110]],[[11,236,3776,3582]],[[11,62,3830,3517]],[[11,86,3830,3517]],[[453,72,3765,590,3830]],[[590],[17]],[[453,2645,3776,590,3775]],[[128]],[[2118],[3618]],[[615,403]],[[6,3830],[191]],[[471],[49]],[[2551],[0]],[[2126,2124],[2126],[0]],[[3837,2123,2124]],[[750,3837,2123]],[[763],[0]],[[9,2127,2129]],[[128],[3830]],[[287,2127,2131]],[[128],[3830]],[[2130],[0]],[[2128],[0]],[[2128,2132],[2130,2133]],[[2137]],[[645],[646]],[[2136,625]],[[452,403]],[[10],[3832]],[[572,3793,2151]],[[4],[148]],[[2143,2142],[2143],[0]],[[2053,2164]],[[2145],[0]],[[2164,2142]],[[434],[436]],[[55,111,3859,2144],[2146],[371,1]],[[2147]],[[2160]],[[2160],[0]],[[2141,111,3859,2150],[2148],[453,590,3830],[2149]],[[2156],[0]],[[605,572,3793,506,2154,2152]],[[724],[725]],[[2157,2155],[2157],[0]],[[2158,2155]],[[2053,2158]],[[2282]],[[2161,2159],[2161],[0]],[[2162,2159]],[[2053,2162]],[[238,2127,3875],[2277],[2278],[2282],[2163],[2284],[2288]],[[2283]],[[238,2127,3875],[2277],[2278]],[[2292],[0]],[[2294],[0]],[[2165,2032,2166,636,3790,2169]],[[3771],[0]],[[2168,17,2171]],[[2173],[0]],[[2201,2170]],[[2174],[0]],[[645,2172,62,391]],[[50],[284]],[[97,2179]],[[2314]],[[2316]],[[2261]],[[2182],[2186],[2214],[2207],[2221],[2246],[2290],[2297],[2229],[2252],[2259],[2308],[2176],[2177],[2178]],[[3692],[0]],[[2183,2181],[2183],[0]],[[109,2180,3780,2181]],[[3649],[3647],[2184]],[[3648]],[[577],[0]],[[2185,574,2180,3802,2192]],[[2188],[0]],[[753,2194,748]],[[3624],[0]],[[3653],[0]],[[2199],[0]],[[275,3810],[753,275,3810,748],[2187,2189,2190,2191]],[[2195,2193],[2195],[0]],[[2196,2193]],[[750,2196]],[[3513],[3519]],[[2200],[0]],[[17],[0]],[[2197,2198,2201]],[[458],[232]],[[2465],[2483]],[[755],[0]],[[97,2204,2202,-1]],[[2207],[2214],[2221]],[[2212],[0]],[[2223,2206],[2223],[0]],[[2032,422,2209,3782,753,2205,748,2206,3425]],[[2180]],[[2208]],[[2211,2210],[2211],[0]],[[750,3694]],[[3694,2210]],[[2219],[0]],[[2032,206,2216,3784,753,2213,748,474,3698,2206,3425]],[[2180]],[[2215]],[[2218,2217],[2218],[0]],[[750,3696]],[[3696,2217]],[[8],[0]],[[2220,206,3798,474,2222,520,3859]],[[556],[249],[437],[126]],[[2226],[2041,137]],[[2223,2224],[2223]],[[2224]],[[75,3859],[267,537],[373,537],[90,537],[433,537,112],[347,537,112],[537,496,2227]],[[130],[250]],[[2134],[0]],[[2057,2238,2228]],[[3583],[0]],[[3775,2230]],[[2241],[0]],[[2231],[2232]],[[609],[0]],[[3579,2235],[3579],[0]],[[3585,2236],[3585],[0]],[[3586,2237],[3586],[0]],[[2234,236,2233,2244,2235],[205,236,3775,2244,2236],[523,236,3775,2244,2237]],[[3775],[0]],[[2243],[0]],[[2239,2240]],[[621],[599]],[[2242,3577]],[[383,3810,3574]],[[2249],[0]],[[288,217,3794,4,2247,3859,2245]],[[603],[440]],[[2250,2248],[2250],[0]],[[2251,2248]],[[2053,2251]],[[2274],[2275],[2280],[2282],[2284],[2286]],[[503,3799,199,112,648,3872,2254]],[[2255,2253],[2255],[0]],[[390,753,2256,2253,748]],[[750,2256]],[[224,3859],[109,3859],[618,3859],[406,3859],[519,3859],[398,3859],[413,3841]],[[2260],[0]],[[2268],[0]],[[572,3792,2262,2257,2258]],[[620,288,217,3795]],[[605,572,3792,4,2266,2152]],[[2265],[4,2266]],[[2264],[0]],[[4,2266]],[[2263]],[[111,3859]],[[2269,2267],[2269],[0]],[[2270,2267]],[[2053,2270]],[[2274],[2277],[2278],[2279],[2280],[2282],[2271],[2284],[2286],[2272],[2273]],[[2283]],[[2287]],[[2288]],[[238,2127,3875]],[[2276,2127,3875]],[[604],[441]],[[23,2127,3875]],[[324,2127,3875]],[[181,2127,3875]],[[368,2127,3842]],[[553],[0]],[[2281,163,2127,3801]],[[848,2127,3853]],[[2285]],[[638],[374]],[[75,2127,3859]],[[189,2127,3875]],[[158,2127,3853]],[[2291],[0]],[[2289,2032,2166,636,3788,2169]],[[394,458,2165],[2292]],[[9,763,2293]],[[602],[335],[578]],[[537,496,2295]],[[130],[250]],[[2302],[0]],[[2032,594,2299,3786,2300,2301,383,3810,200,153,487,2296,3425]],[[2180]],[[2298]],[[28],[6]],[[242],[614],[133]],[[2304]],[[197],[415]],[[2303,3872]],[[2309],[0]],[[2312],[0]],[[2313],[0]],[[2032,170,2180,3796,383,490,3510,2305,2306,2307,147,3425]],[[383,79,2041,418]],[[2311],[0]],[[383,514]],[[156],[140,2310]],[[75,3859]],[[659,2180,2984]],[[2317,2315],[2317],[0]],[[394,458,523,718,710,3844,2315],[523,718,710,2180,3844,2315]],[[357,580,3861],[715,580,3861],[717,3861,230,45,3844],[716,580,3861]],[[148,2322]],[[2349]],[[2350]],[[2351]],[[2324],[2325],[2326],[2327],[2328],[2330],[2335],[2337],[2341],[2345],[2347],[2319],[2320],[2321]],[[3691],[0]],[[109,2323,3781]],[[170,2323,3797]],[[206,2323,3785]],[[422,2323,3783]],[[2057,236,3776,383,3810,2228]],[[2333],[0]],[[288,217,3795,2329]],[[2332,2331],[2332],[0]],[[2053,2334]],[[2334,2331]],[[2284],[2282]],[[503,2323,3800]],[[2339],[0]],[[2185,2338,2323,3813,2336]],[[574],[571]],[[471],[49]],[[2344],[0]],[[572,3793,2340]],[[2343,2342],[2343],[0]],[[2053,2334]],[[2334,2342]],[[594,2323,3787]],[[2348],[0]],[[636,2323,3701,2346]],[[471],[49]],[[659,2323,2984]],[[523,718,710,2323,3844]],[[605,572,3793,2152]],[[2355,2352],[2355],[0]],[[453,2354,2356,2352]],[[574],[571]],[[750,2356]],[[3810,590,3802]],[[574],[0]],[[597,2357,3810]],[[234,574,203,3863]],[[2363],[0]],[[48,3783,2360]],[[3404],[0]],[[753,2362,748]],[[2368],[0]],[[2380,2365],[2380],[0]],[[2364,133,2365,2377]],[[2543]],[[2367]],[[2636]],[[2371],[0]],[[2369]],[[2585],[0]],[[2378],[0]],[[2550],[0]],[[2499],[0]],[[3816,621,2555,2372],[3810,2370,2373,2372,2374,2375]],[[203,2376],[3816,203,2555,2372]],[[2379]],[[405,753,3832,748]],[[431],[295],[431],[232]],[[147,2384]],[[2578]],[[3404]],[[2382],[2383]],[[219,2389]],[[2498],[0]],[[66],[435,2390,2372,2386]],[[2636],[0]],[[3810,387,2388],[3830,2387]],[[2391],[3830,2394]],[[191],[367]],[[191],[367],[419],[268]],[[763],[769],[765],[768],[764]],[[2392],[2393,753,2424,748]],[[2408],[0]],[[232],[0]],[[248],[0]],[[3763],[0]],[[2429],[0]],[[242,2395,2396,2397,3810,2398,2407,2399]],[[2428]],[[2403],[0]],[[2401]],[[2428]],[[2406],[0]],[[2404]],[[2410,2402],[506,3704,2405],[2418]],[[295],[131],[223]],[[2412],[0]],[[2409,2416]],[[2414],[0]],[[753,2411,748]],[[2415,2413],[2415],[0]],[[3774,2413]],[[750,3774]],[[2417,2421]],[[626],[627]],[[2201],[753,2411,748,2201]],[[2424],[0]],[[2422,2420],[2422],[0]],[[753,2419,748,2420]],[[750,753,2419,748]],[[2427,2423],[2427],[0]],[[2425,2423]],[[3191],[128]],[[3191],[128]],[[750,2426]],[[17,3830,2168]],[[383,151,265,614,3704]],[[2438],[0]],[[284],[0]],[[2439],[0]],[[3708],[0]],[[2441],[0]],[[3710],[0]],[[3714],[0]],[[281,2440,2430,2431,237,3859,2432,248,574,3810,2398,2433,2434,2435,2436,2445]],[[295],[82]],[[458],[232]],[[112],[653]],[[484,230,45,3855]],[[2447],[0]],[[2450],[0]],[[2448],[0]],[[2442,2443,2444]],[[278],[484]],[[232,787,2446]],[[506,3704]],[[2452],[0]],[[753,2449,748]],[[2455,2451],[2455],[0]],[[2453,2451]],[[3773],[3383]],[[3773],[3383]],[[750,2454]],[[2458],[0]],[[458,2456,2397,3810,2398,2459]],[[295],[131]],[[2410],[506,3704],[2418]],[[2566],[0]],[[2465,2460],[2462]],[[753,2462,748],[2465,2506,2460],[2465,2566,2506]],[[2467],[0]],[[2470],[0]],[[2463,2468,2464]],[[2543]],[[2466]],[[2472,2374,2386],[2483,2374,2386]],[[2513]],[[2469]],[[2476,2471],[2476],[0]],[[2478,2471]],[[663]],[[608],[2473]],[[2634],[0]],[[2474,2475,2478]],[[2482,2477],[2482],[0]],[[2479,2477]],[[2484],[2483]],[[2484],[2483]],[[811,2475,2480]],[[2481]],[[753,2465,2460,748]],[[2493],[2485],[2486]],[[2558]],[[2560]],[[2562,2487],[2562],[0]],[[2506],[0]],[[2552],[0]],[[2547],[0]],[[2517],[0]],[[2495],[0]],[[497,2487,2578,2488,2489,2372,2490,2491,2492]],[[2519]],[[2494]],[[2483]],[[10],[143],[555],[223],[536],[531],[532],[534]],[[276,2501]],[[276,2504]],[[2503],[0]],[[2504,2500]],[[750],[381]],[[2502,2504]],[[3830],[2505]],[[754],[791],[788],[787]],[[248,2511]],[[3872],[3383]],[[3872],[3383]],[[2510,2509],[2510],[0]],[[750,2508]],[[396,3853,2433,2435,2436],[150,3853],[2507,2509]],[[2516],[0]],[[422,13,753,2512,748]],[[2515],[0]],[[750,787]],[[787,2514]],[[221,3191]],[[2520,2518],[2520],[0]],[[699,2521,2518]],[[750,2521]],[[3826,17,2522]],[[753,2528,748]],[[2533],[0]],[[2529],[0]],[[2530],[0]],[[3826],[0]],[[2531],[0]],[[405,45,3416,2374,2523],[2524,2550,2523],[2525,2374,2533],[2526,2527,2374,2523]],[[405,45,3416]],[[405,45,3416]],[[405,45,3416]],[[2539],[0]],[[2534,2535,2532]],[[484],[432],[683]],[[2536],[2537]],[[698,693],[3843,693],[754,693],[247,3191,3409,693],[101,487]],[[30,2538,15,2538]],[[2536],[698,682],[3843,682],[754,682],[247,3191,3409,682]],[[680,2540]],[[101,487],[217],[697],[373,690]],[[665],[0]],[[2544,2542],[2544],[0]],[[645,2541,2545,2542]],[[750,2545]],[[3830,2168,17,2496]],[[2548],[0]],[[217,45,3416,2546]],[[645,481],[2549]],[[645,99]],[[393,45,3416]],[[18],[134]],[[203,2553]],[[149],[2555]],[[2556,2554],[2556],[0]],[[2587,2554]],[[750,2587]],[[2559,2557],[2559],[0]],[[626,2561,2557]],[[750,2561]],[[574,3810]],[[487,753,2419,748]],[[2497],[535],[2563],[2564]],[[533]],[[325,763,3842]],[[2569,2565],[2569]],[[2565]],[[2571],[0]],[[2573],[0]],[[200,2574,2567,2568],[287,251,508,346]],[[668,3816]],[[2570]],[[2576]],[[2572]],[[614],[2575]],[[508]],[[669,670],[671]],[[2580,2577],[2580],[0]],[[2579,2577]],[[2582],[775]],[[750,2582]],[[2583],[0]],[[3778],[3191,2581]],[[2198,2584]],[[3830],[3853]],[[643,3191]],[[2593,2586],[2593],[0]],[[2590,2586]],[[3830]],[[2588],[732]],[[2605],[752,2589,2591,747]],[[2605,2586]],[[2594],[0]],[[2601,2587,2592],[2603,2587,2595],[2598,2605]],[[383,3191],[621,3834]],[[383,3191],[621,3834]],[[239],[0]],[[395],[0]],[[359,2596,261],[359,2599,2597,261]],[[272],[478]],[[2602],[0]],[[2600,261],[555]],[[239],[98]],[[2604,2597,261]],[[272],[478]],[[2608],[2609],[2612],[2616],[2606]],[[2618]],[[2640],[0]],[[3810,2398,2388,2607]],[[753,2610,748]],[[2608],[2609]],[[2614],[0]],[[2496,2388,2611],[2615]],[[3771]],[[2613]],[[726,2496,2388,2168]],[[753,2617,748]],[[2555],[2616]],[[701,753,3191,750,3853,2620,748,2388]],[[2621,2619],[2621],[0]],[[71,753,2625,2619,748]],[[750,2625]],[[2627],[0]],[[174],[0]],[[2630],[0]],[[3830,200,703],[3830,3592,2622,2623,704,3853,2624],[702,704,3853,2620]],[[3697]],[[2626]],[[2632],[0]],[[2631],[0]],[[2631,2628],[2632,2629]],[[2633,383,700]],[[2633,383,165]],[[165],[376],[128,3853]],[[143],[10]],[[2638],[0]],[[2635,3830]],[[763]],[[17],[2637]],[[2643,2639],[2643]],[[2639]],[[2648],[0]],[[2651],[0]],[[2644,2645,2641,753,2651,748],[620,2645,2641,753,2642,748]],[[198],[232]],[[265],[236]],[[2645],[0]],[[420,265],[609,2646]],[[200,2649]],[[261],[393,45],[217,45]],[[2652,2650],[2652],[0]],[[2653,2650]],[[750,2653]],[[3830],[420]],[[2658],[0]],[[295],[0]],[[2654,614,2655,2396,2555,506,3704,2372,2374,2375]],[[2543]],[[2657]],[[2664],[2675],[2683],[2691]],[[2670],[0]],[[647],[0]],[[2666],[0]],[[2667],[0]],[[543,592,2660],[77,2661,2662,2663]],[[373],[0]],[[15,2665,54]],[[2665,450]],[[29,2661]],[[2671,2669],[2671],[0]],[[2672,2669]],[[750,2672]],[[645,85,517],[2674]],[[649],[386]],[[435,2673]],[[489,3830],[480,2661,2681],[450,489,3830]],[[2677],[0]],[[15,2665,54]],[[2679],[0]],[[2665,450]],[[489],[0]],[[590,2680,3830],[2676,2678]],[[2685,2682],[2685],[0]],[[287,2684,2689,2682],[2686],[611,2688]],[[571],[574]],[[750,2689]],[[287,244,200,27]],[[244]],[[571],[574],[2687]],[[3810,2388,2690]],[[435,2431],[2655,649]],[[651,2701]],[[543],[29]],[[2694],[0]],[[261],[472]],[[2696],[0]],[[200,340]],[[2698],[0]],[[566,2695]],[[2700],[0]],[[384,407]],[[2692,2707,2693],[159,2707,2697],[417,2707],[77,2707,2699],[480,2707],[439,2702]],[[2705],[0]],[[2704],[0]],[[94,652]],[[2703]],[[2710],[0]],[[3855,2706]],[[2709],[0]],[[750,3841]],[[750,3855,2708]],[[2715,2711],[2715],[0]],[[428,2713,289,2714],[2733],[468,2724,2711],[2718],[2804],[2719],[2731],[2720]],[[32],[316]],[[590,3859],[28,3191]],[[750,2724]],[[2717],[0]],[[2323,3387]],[[468,658,2716]],[[2749]],[[2827]],[[2726],[0]],[[10],[0]],[[3423],[0]],[[316,2721],[2725],[514,2722,2723]],[[430,47]],[[2730]],[[3842]],[[3844]],[[2727],[2728]],[[590,2729]],[[281,2732,203,316]],[[112],[574,3810]],[[55,316,590,2735,2723]],[[2736,2734],[2736],[0]],[[2737,2734]],[[750,2737]],[[300,763,3861],[729,763,3861],[297,763,3861],[318,763,3861],[303,763,3861],[304,763,3841],[298,763,3841],[305,763,3841],[299,763,3841],[314,763,3841],[308,763,3861],[307,763,3861],[317,763,3861],[309,763,3861],[738,763,2740],[310,763,3861],[313,763,3861],[315,763,3841],[311,763,3859],[312,763,3861],[712,763,3861],[713,763,3841],[319,763,3841],[233,763,2743],[735,763,3853],[736,763,3841],[296,763,3841],[737,763,2738],[739,763,3841],[742,763,2739],[2741]],[[3755],[376]],[[743],[383],[744]],[[3861],[376]],[[301,763,3861],[302,763,3843],[447,763,3861],[448,763,3841]],[[2746],[0]],[[753,2742,748]],[[2745,2744],[2745],[0]],[[750,3841]],[[3841,2744]],[[2750,2747],[2750],[0]],[[2752],[0]],[[55,459,522,590,2754,2723],[55,459,190,2788,2747,2748]],[[750,2788]],[[3423]],[[2751]],[[2755,2753],[2755],[0]],[[2756,2753]],[[750,2756]],[[2757,763,3861],[2758,763,3861],[2759,763,3861],[2760,763,3861],[2761,763,3841],[2762,763,3861],[2763,763,3843],[2764,763,3841],[2765,763,3841],[2766,763,3841],[2767,763,3841],[817,763,3841],[2768,763,3841],[2769,763,3853],[2770,763,3841],[2771,763,3841],[2772,763,3861],[2773,763,3861],[2774,763,3861],[2775,763,3859],[2776,763,3861],[2777,763,3861],[2778,763,3861],[2779,763,3841],[2780,763,3861],[2781,763,2740],[2782,763,3861],[2783,763,3841],[729,763,3861],[233,763,2743],[841,763,3841],[737,763,2738],[739,763,3841],[742,763,2739],[842,763,3841],[447,763,3861],[448,763,3841]],[[814],[297]],[[820],[300]],[[838],[318]],[[823],[303]],[[824],[304]],[[821],[301]],[[822],[302]],[[813],[296]],[[819],[319]],[[816],[298]],[[826],[305]],[[818],[299]],[[815],[735]],[[839],[736]],[[827],[314]],[[828],[308]],[[829],[307]],[[830],[309]],[[832],[311]],[[833],[312]],[[834],[313]],[[831],[310]],[[835],[315]],[[837],[317]],[[836],[738]],[[825],[712]],[[840],[713]],[[2790],[0]],[[2793],[0]],[[2796],[0]],[[2800],[0]],[[460,763,753,2784,748],[461,763,753,2784,748],[462,763,753,2785,748],[463,763,753,2785,748],[464,763,753,2786,748],[465,763,753,2786,748],[466,763,753,2787,748]],[[2791,2789],[2791],[0]],[[3781,2789]],[[750,3781]],[[2794,2792],[2794],[0]],[[3804,2792]],[[750,3804]],[[2797,2795],[2797],[0]],[[2798,2795]],[[750,2798]],[[3861]],[[2801,2799],[2801],[0]],[[3699,2799]],[[750,3699]],[[2824],[0]],[[2805],[0]],[[543,514,2802,2803,2813,2723],[552,514,2802,2723]],[[613,2807]],[[2812,2806],[2812],[0]],[[2811,2806]],[[530],[528]],[[2808,763,3855]],[[529]],[[2741],[2809],[2810]],[[750,2741]],[[2822],[0]],[[2815],[0]],[[618,763,3855]],[[2817],[0]],[[406,763,3855]],[[2819],[0]],[[129,763,3855]],[[2821],[0]],[[409,763,3855]],[[2814,2816,2818,2820]],[[2825,2823],[2825],[0]],[[2826,2823]],[[750,2826]],[[449],[538]],[[2828,210]],[[543],[552]],[[417,3830,203,2830],[2833],[2831,417,3830]],[[3859],[3383]],[[123],[148]],[[2834],[0]],[[173,3830,2832]],[[621,2836]],[[2837,2835],[2837],[0]],[[3383,2835]],[[750,3383]],[[677,2844]],[[2840],[0]],[[200,459]],[[2846],[0]],[[244,203,3758,749,3841,230,45,3853,2841]],[[3877],[0]],[[284,112,139,2843,3853],[676,2839],[2842]],[[2847],[0]],[[2847],[112,139,2843,3853,2845]],[[467,2665,539]],[[2849],[2863],[2897],[2900],[2929],[2933],[2850]],[[2852]],[[2979]],[[2854],[0]],[[11,618,2851,2855]],[[3691]],[[2853]],[[2858],[2861,2866]],[[2862],[3758]],[[10],[369],[2984]],[[2856,128,659,2857]],[[3724]],[[3721]],[[2859],[2860]],[[618,3876]],[[97,618,2865,3721,2875,2866]],[[3692]],[[2864],[0]],[[2874],[0]],[[75],[812]],[[2867,3855]],[[2870],[0]],[[2868]],[[2879],[0]],[[2883],[0]],[[2885,2873],[2885],[0]],[[2871,2872,2873,2869]],[[2878],[0]],[[2877],[0]],[[128,659,2984]],[[2876]],[[467,2881]],[[539],[650],[369]],[[2973],[2880]],[[2884,2882],[2884]],[[645,2882]],[[322,3841],[327,3841],[321,3841],[328,3841]],[[2,2886],[406,2894],[740,2895],[741,3842]],[[287],[611]],[[2888],[0]],[[247,3842,122],[365],[128]],[[3842],[128]],[[3842,122],[128]],[[2892],[0]],[[128],[719]],[[467,101,2891]],[[177,2887],[705,2889],[706,247,2890],[2893]],[[3842],[698]],[[2899],[0]],[[148,618,2896,3718]],[[3691]],[[2898]],[[215,2912]],[[2902],[0]],[[645,660,391]],[[2952,590,3718,2901]],[[421],[0]],[[2952],[10,2904]],[[2907],[0]],[[645,215,391]],[[2950],[0]],[[2926],[0]],[[2916],[0]],[[2925],[0]],[[2903],[2905,383,2908,2968,590,2913,2909,2910,2911],[427,383,3758,590,2913,2906]],[[2914],[2915]],[[3721]],[[3718]],[[2918],[2919]],[[2977,2917],[2977]],[[645,2917]],[[645,215,391]],[[663,2984]],[[645,659,2923]],[[2920],[0]],[[2984],[10,2922],[369],[128]],[[2921],[0]],[[17,618,2924]],[[2927]],[[2879]],[[2930,2928],[2930],[0]],[[453,618,3758,590,3758,2928]],[[750,3758,590,3758]],[[2935],[0]],[[2944],[0]],[[477,2931,2942,2932]],[[3691]],[[2934]],[[2952,203,3718]],[[2945],[0]],[[2939],[0]],[[2937,203,3718]],[[383,2908,2968,2938]],[[2940],[750,215,391,203,3718]],[[2936],[2952,2945,203,3718],[10,2904,2941],[427,383,3758,203,3718]],[[232,610,618]],[[2943]],[[2946],[2949]],[[383,2908,2968]],[[2948],[0]],[[383,2908,2968]],[[2947]],[[574],[206],[422]],[[2953,2951],[2953],[0]],[[2956,2951]],[[750,2956]],[[2965],[0]],[[483],[0]],[[2959],[2960,2168],[2962],[2963],[215,391],[509,110],[97,2954],[287,571],[459,2966],[509,636],[11,2955]],[[792],[746,3872]],[[3874,2957],[3874,2168]],[[2958]],[[497],[242],[614],[443]],[[97],[148]],[[2961,659]],[[133],[616],[236],[148],[173],[451],[510],[423],[188],[427],[565],[170],[594]],[[483],[572],[618],[636]],[[577,571],[2964]],[[65],[514]],[[2969],[0]],[[775,2967],[3781,751,2971],[3781],[3810]],[[751,775]],[[3810]],[[775],[2970]],[[2975,2972],[2975],[0]],[[2976,2972]],[[15],[0]],[[2974,2976]],[[63,3855],[259,3855],[559,3855]],[[215,391],[322,3841],[327,3841],[321,3841],[328,3841]],[[2982],[0]],[[506,659,2984],[506,659,2980],[506,128,659,2981,590,2984],[506,659,10,2978]],[[369],[128]],[[2984],[369],[10]],[[663,2984]],[[2985,2983],[2985],[0]],[[2987,2983]],[[750,2987]],[[2988],[0]],[[3874,2986]],[[746,3872],[792]],[[2994],[0]],[[2997],[0]],[[14,2077,2992,3813,2989],[62,2995,3813,2078],[61,2996,3813,2990],[388,2077,2998,3813],[455,2077,2999,3813,2079]],[[574],[571]],[[3002]],[[2993]],[[574],[571]],[[574],[571]],[[431],[180]],[[574],[571]],[[574],[571]],[[3003],[0]],[[3004],[0]],[[614,674,383,3832,3000,3001],[148,674,383,3832]],[[645,787,675]],[[621,112,3853]],[[200,615],[3006]],[[431],[184],[333],[180],[56]],[[431],[180],[619]],[[3011],[0]],[[3012,3009],[3012],[0]],[[245,410,3830,520,3853],[245,664,3863,3008],[607,410,3823],[607,664,3824,3009]],[[506,3018]],[[750,3824]],[[214],[658]],[[3013],[0]],[[3014,3387,3877,3016]],[[383],[3191]],[[3019,3017],[3019],[0]],[[3015,3017]],[[750,3015]],[[506,3022]],[[3023],[0]],[[592,3034],[406,3021,3877,3028],[3031],[3045,3043],[3878,3051]],[[200,3758]],[[382,753,3855,748]],[[406,753,3855,748]],[[3753],[0]],[[3751],[0]],[[3855,3026,3027],[3855,3026,3027],[3024],[3025]],[[3030],[0]],[[200,3758]],[[406,3029,590,734,3026,3027]],[[3035],[0]],[[3036],[0]],[[3037,3032],[3039,3033]],[[750,3039]],[[750,3037]],[[435,3038]],[[649],[386]],[[258,274,3041]],[[76],[601]],[[456,435],[435,3040],[500]],[[3044,3042],[3044],[0]],[[3042]],[[750,3048]],[[3387,3877,3053],[3708],[3383,3877,3191],[3050,3877,3053],[356,3047]],[[128]],[[3877,3191],[3618,2096],[3046]],[[3878,3387,3877,3053],[3045]],[[3880],[0]],[[745,3049,3387]],[[3052,3043],[592,3034]],[[3387,3877,3053]],[[3191],[3054],[3056]],[[128],[383],[10],[32]],[[487],[710]],[[3055]],[[509,3091]],[[22]],[[3801],[10]],[[547],[354],[289]],[[203],[251]],[[32],[316]],[[225],[547,3094,2723]],[[33],[446]],[[3066],[0]],[[251,3855]],[[3068],[0]],[[203,3843]],[[180]],[[3071],[0]],[[3069]],[[236],[235],[263]],[[639],[166]],[[3075,3074],[3075],[0]],[[750,3099]],[[3077],[0]],[[3099,3074]],[[3079],[0]],[[200,430,787]],[[547],[631]],[[93]],[[3083],[0]],[[200,3758]],[[618,3758]],[[109,2180,3781],[170,3797],[206,3785],[422,3783],[574,3810],[594,3787],[636,3790],[3084]],[[3760],[0]],[[3092],[0]],[[3098],[0]],[[204],[0]],[[3878],[0]],[[3058],[110,3086],[3087,571,3088,3086],[3089,593,3088,3086],[169,3088,3086],[574,547,3088,3086],[387,571,3088,3086],[408],[163,3059,3060],[3087,71,3061,3810,3088,3086],[3062,289],[514,3063],[3064,169,3065,3067,2386,2723],[3070,3072,3097,3810,3088,2372],[2281,162],[95,753,775,748,3073],[639,2386],[166,2386],[426],[425,3076,3078,2386],[3090,3080,3086],[3089,424],[3406,3086],[70,3086],[3081],[421],[216,200,3758,621,3718],[216,3082],[316,547],[97,3085],[422,547,3086],[206,547,3086],[422,68,3783],[206,68,3785]],[[204],[3093]],[[180,3089]],[[3096],[0]],[[370],[0]],[[3095]],[[203],[251]],[[3097,3830]],[[40,255],[91,568],[400,185],[3100]],[[10],[96],[256],[334],[522],[567]],[[3107],[0]],[[33,3859],[47,236,3109,251,3103],[196,2077,3106],[266,3101,3191],[281,236,248,47,3136],[3108]],[[3830],[128]],[[3105,3104],[3105],[0]],[[750,3123]],[[3129],[3123,3104]],[[84],[430]],[[510]],[[3111],[3115]],[[3112,3110],[3112],[0]],[[3114,3110]],[[750,3114]],[[3117],[0]],[[3810,3113]],[[3810,405,753,2139,748,3113]],[[3120],[0]],[[2645,753,3116,748]],[[3830],[420]],[[3121,3119],[3121],[0]],[[3118,3119]],[[750,3118]],[[3127],[0]],[[3124],[3122,289],[445,289,2723],[3125],[3126]],[[136],[225],[421],[547],[617]],[[430,47]],[[389]],[[32],[163],[165],[208],[515]],[[3132],[0]],[[3130,3128]],[[571],[574]],[[3133],[0]],[[645,435,287],[3813,3131]],[[3134],[645,435,287]],[[200,179]],[[3137],[0]],[[3810,3144,3113,3135],[3139]],[[232,270]],[[3140,3138],[3140],[0]],[[3142,3138]],[[750,3142]],[[3143],[0]],[[3810,3113,3141]],[[232,270]],[[405,753,2139,748]],[[3149],[3160],[3162],[3167]],[[3152],[0]],[[3157],[0]],[[3158],[0]],[[97,709,217,3830,599,2843,3150,3146,3147,3148]],[[618],[710]],[[3153,3151],[3153],[0]],[[711,2843,3155,3151]],[[2053,3155]],[[3156],[0]],[[787,3154]],[[773,787]],[[708,2843,787]],[[156],[140]],[[198],[0]],[[11,709,217,3825,3146,3147,3148,3159]],[[3163],[0]],[[506,709,217,3830,3161]],[[200,3165]],[[3166,3164],[3166],[0]],[[3842,3164]],[[2053,3842]],[[148,709,217,3825,3159]],[[3175],[3171],[3187],[3188],[3169]],[[3189]],[[3173],[0]],[[3172,3810,3170]],[[178],[135],[134]],[[3855],[3773]],[[3182],[0]],[[3176,3174,3183]],[[178],[135],[134]],[[180]],[[404]],[[201,763,3872]],[[14,201,763,3872]],[[14]],[[3177],[3178],[3179],[3180],[3181]],[[2461],[3185],[3186]],[[2366],[2400],[2457],[2656]],[[3184]],[[200,84,3842]],[[222,3872]],[[620,3830]],[[714]],[[3198,3190],[3198],[0]],[[3193,3190]],[[3196],[0]],[[3202,3192],[3197]],[[596],[183],[610]],[[3407],[0]],[[257,3195,3194]],[[371,3191]],[[3199,3191],[654,3191],[3200,3191]],[[15],[770]],[[394],[772]],[[3203,3201],[3203],[0]],[[3207,3201]],[[257,3195,376],[3205,3204,2496],[3205,3207]],[[10],[16]],[[763],[777],[764],[765],[768],[769],[776]],[[3210],[0]],[[3216,3206]],[[668],[0]],[[733,3208,3414]],[[3195,3212],[3209],[521,275,3216]],[[3214],[0]],[[251,3213],[30,3216,15,3207],[275,3223,3211],[444,3216]],[[2496],[753,3404,748]],[[168,3223]],[[3217,3215],[3217],[0]],[[3223,3215]],[[760,3216],[3218,3216],[3219,3216],[3220,247,3191,3409],[3221,3216],[757,3216],[759,3216]],[[775],[762],[774],[145],[349]],[[778],[773]],[[778],[773]],[[779],[780]],[[3224,3222],[3224],[0]],[[3226,3222]],[[761,3226]],[[3227],[0]],[[3236,3225]],[[69,3872]],[[3237],[0]],[[487],[0]],[[3320],[0]],[[3246],[0]],[[3191],[0]],[[3245,3233],[3245]],[[3393],[0]],[[3248],[0]],[[3847],[3265],[3382,3228],[754],[3238],[3239],[3240,3223],[3408,3223],[3229,753,3404,748],[2623,2496],[752,3830,3191,747],[320,3315,7,753,3216,3230,748],[32,3223],[3244],[52,753,3191,17,3398,3231,748],[51,3232,3233,3234,159],[94,753,3191,750,3398,748],[94,753,3191,621,3618,748],[128,753,3837,748],[626,753,3837,748],[247,3191,3409,778,3191],[3377],[3329],[3773,3235]],[[3877,3191]],[[3294]],[[3298]],[[778],[773],[758]],[[247],[0]],[[3617],[0]],[[52,753,3191,21,586,843,3241,3853,17,113,3242,748]],[[3243]],[[3391,3392]],[[3247]],[[731]],[[3249],[3250]],[[766,3853]],[[767,3853]],[[143],[0]],[[3267],[0]],[[3270],[0]],[[3273],[0]],[[3276],[0]],[[3278],[0]],[[3280],[0]],[[3282],[0]],[[3284],[0]],[[3286],[0]],[[3288],[0]],[[3290],[0]],[[3291],[0]],[[3293],[0]],[[26,753,3251,3314,748,3252],[3268,753,3314,748,3253],[3271],[95,753,2722,775,748,3254],[95,753,3274,748,3255],[345,753,3251,3314,748,3256],[326,753,3251,3314,748,3257],[551,753,3314,748,3258],[632,753,3314,748,3259],[548,753,3314,748,3260],[635,753,3314,748,3261],[564,753,3251,3314,748,3262],[218,753,3251,3404,2374,3263,748,3264]],[[3304]],[[3266]],[[35],[36],[38]],[[3304]],[[3269]],[[3313]],[[3304]],[[3272]],[[2722,775],[3314],[143,3404]],[[3304]],[[3275]],[[3304]],[[3277]],[[3304]],[[3279]],[[3304]],[[3281]],[[3304]],[[3283]],[[3304]],[[3285]],[[3304]],[[3287]],[[3304]],[[3289]],[[499,3855]],[[3304]],[[3292]],[[672,753,3404,748]],[[3307],[0]],[[3310],[0]],[[3303],[0]],[[3299,3876,3304],[688,3414,3304],[3300,753,3191,3295,748,3296,3304],[3301,3413,3296,3304],[687,753,3191,750,3223,748,3297,3296,3304]],[[696],[694],[679],[678],[692]],[[686],[684]],[[681],[685]],[[191],[268]],[[203,3302]],[[691,3305]],[[3826],[2522]],[[3309],[0]],[[750,3308,3306]],[[3843],[754],[3830],[3383]],[[750,3191]],[[3311,689]],[[695],[232]],[[3304],[0]],[[667,753,3314,748,3312],[666,753,3314,750,3314,748,3312]],[[2722,3191]],[[3317],[753,3317,748]],[[3318,3316],[3318],[0]],[[3837,3316]],[[750,3837]],[[3321],[0]],[[251,41,346],[251,359,267,346,3319],[645,430,176]],[[645,430,176]],[[3330],[0]],[[3876],[0]],[[3331,3324],[3331]],[[3335],[0]],[[3353],[0]],[[3340],[0]],[[3343],[0]],[[60,753,3404,3322,748],[105,3323],[116,3413],[122,3413],[229,3413],[242,753,3191,750,3191,750,3191,750,3191,748],[247,753,3191,3324,748],[3334],[272,753,3191,750,3191,748],[343,3413],[350,3413],[478,753,3191,750,3191,748],[495,3413],[586,3413],[583,753,3191,3325,748],[3366],[618,3876],[626,3413],[656,3413],[3336,753,3191,750,3337,748],[100,3323],[108,3326],[3338,753,3191,750,247,3191,3409,748],[182,753,3409,203,3191,748],[213,753,3365,750,3191,748],[372,3326],[414,753,3216,251,3191,748],[3370],[569,3326],[3339,753,3411,750,3191,750,3191,748],[622,3323],[624,3326],[623,3326],[19,3413],[58,3413],[67,3412],[70,3413],[109,3876],[231,753,3191,750,3191,750,3191,748],[201,753,3191,750,3191,3327,748],[337,3413],[349,753,3191,750,3191,748],[3341],[3342],[429,3413],[457,753,3191,750,3191,748],[458,753,3191,750,3191,750,3191,748],[476,3413],[485,3876],[597,753,3191,750,3191,748],[640,753,3191,3328,748],[641,753,3191,17,60,748],[641,753,3191,3349,748],[3350]],[[621,3618]],[[750,3191]],[[3333],[0]],[[851,3398]],[[850,753,3223,750,3859,3332,2624,748]],[[750,3191]],[[5],[558]],[[3191],[247,3191,3409]],[[114],[115]],[[584],[585]],[[750,3191]],[[382,753,3859,748]],[[406,3413]],[[750,3191]],[[3345],[0]],[[17,60,3616]],[[3356]],[[3348],[0]],[[3346]],[[17,32,3616],[3344,3347],[750,3841,750,3841,750,3841]],[[3351],[211,753,2362,748],[279,3412],[351,3412],[352,3412],[353,3412],[411,753,3191,750,3191,748],[412,3412]],[[90,753,3191,750,3191,748]],[[3354],[0]],[[753,3352,748]],[[3355]],[[787]],[[274,3359]],[[3358,3357],[3358],[0]],[[750,3361]],[[3842,773,3842],[3361,3357]],[[3364],[0]],[[3842,3360]],[[18],[134]],[[476],[0]],[[3362,3363],[476]],[[116],[586],[113],[583]],[[595,753,3369,748]],[[3368],[0]],[[203,3191]],[[3191,3367],[269,3232,203,3191],[591,3232,203,3191],[43,3232,203,3191]],[[563,753,3191,3375,748]],[[3372],[0]],[[750,3191]],[[3374],[0]],[[200,3191]],[[750,3191,3371],[203,3191,3373]],[[3379],[0]],[[3827,753,3376,748],[3835,753,2362,748]],[[3380,3378],[3380],[0]],[[3381,3378]],[[750,3381]],[[3191,2581]],[[3383],[3386]],[[746,3872],[792]],[[3879],[0]],[[3840],[0]],[[745,3384,3872,3385]],[[3390],[128,3840]],[[3830,3385]],[[3873,3385]],[[3388],[3389]],[[642,3191]],[[582,3191]],[[154,3191]],[[3606],[0]],[[3612],[0]],[[249],[0]],[[3869],[0]],[[32,3394],[60,3394,3395],[3604,3394],[512,3396],[612,3396],[116],[586,3242],[113,3242],[126,3397],[656],[3399],[3400],[3402]],[[262]],[[3605]],[[3870],[0]],[[195,3401]],[[3405,3403],[3405],[0]],[[3191,3403]],[[750,3191]],[[60,506],[58]],[[371],[800]],[[771],[800]],[[3411],[3410]],[[494],[341],[342],[226],[228],[227],[119],[121],[120],[118],[655]],[[337],[495],[343],[229],[122],[640],[350],[429],[656]],[[753,3404,748]],[[753,3191,748]],[[753,3223,748]],[[3417,3415],[3417],[0]],[[3418,3415]],[[750,3418]],[[3191,2123]],[[3421,3419],[3421],[0]],[[3422,3419]],[[750,3422]],[[3191]],[[3424]],[[200,57,3861]],[[2004],[3426],[3427],[3437],[3441],[3442],[3447],[3448],[3470],[3469],[3502],[3505],[3503]],[[475,3191]],[[231,3429,159,231]],[[3430],[0]],[[3191,3431,3428]],[[155,3429],[154,3433]],[[582,3433]],[[3434,3432],[3434]],[[3432]],[[3425,755]],[[3438,3435],[3438]],[[3439],[0]],[[51,3232,3435,3436,159,51]],[[3391,3431]],[[154,3433]],[[3820],[0]],[[3443,3446,3440]],[[3446]],[[3819,749]],[[3453],[0]],[[3433],[0]],[[29,3444,3445,159]],[[3443,3448,3440]],[[3449],[3450],[3451]],[[294,3433,159,294]],[[644,3191,147,3433,159,644]],[[457,3433,613,3191,159,457]],[[3454,3452],[3454]],[[3452]],[[3455,755]],[[3457],[3459],[3464],[3468]],[[3458],[0]],[[127,3832,3592,2096,3456]],[[128,3191]],[[127,3830,83,200,3460]],[[3841],[3462]],[[627],[0]],[[526,3461,3859]],[[3466,3463],[3466],[0]],[[127,3465,219,200,3467,3463,3425]],[[92],[175],[605]],[[750,3467]],[[3460],[3830],[527],[3407,202],[525]],[[127,3830,106,200,2461]],[[260,3820]],[[271,3820]],[[3474],[0]],[[207,3471,138,3479]],[[540]],[[101],[3473]],[[3476,3475],[3476],[0]],[[750,3481]],[[3478,3477],[3478],[0]],[[750,3484]],[[3481,3475],[83,3480,3484,3477]],[[3847],[3382],[3837]],[[3482,763,3483]],[[3382],[3830]],[[377],[485]],[[3485,763,3486]],[[3382],[3830]],[[3487],[473]],[[64],[557],[87],[89],[88],[53],[492],[576],[73],[107],[336],[355]],[[3493],[0]],[[511,3490,3488]],[[3830],[3462]],[[3492,3491],[3492],[0]],[[750,3501]],[[506,3501,3491]],[[3497],[0]],[[3500],[0]],[[469,3494,3495]],[[3830],[3462]],[[3499,3498],[3499],[0]],[[750,3501]],[[506,3501,3498]],[[3487,763,3480]],[[387,3830]],[[66,3830]],[[3507],[0]],[[186,3504,3830,248,3832]],[[367],[0]],[[3506,203]],[[3511],[0]],[[3512],[0]],[[21,3191],[171,3191,3409,3508,3509]],[[542,3191]],[[160,3191]],[[3765,3528,2098]],[[3515],[3556]],[[3516]],[[62,3413]],[[2041,730]],[[3527],[0]],[[3520,2232,3574,2235],[205,2646,2239,3574,2236],[523,2646,2239,3574,2237],[3518,3525]],[[265],[236]],[[420,265],[609,2646]],[[3517]],[[3524],[0]],[[3522]],[[3521,2232,3574,2235],[199,265,2239,3567,3556],[3516,3523]],[[3830],[0]],[[86,3526]],[[3592,3539]],[[3530],[0]],[[209,12]],[[3532],[0]],[[637],[554]],[[3542,3533],[3542],[0]],[[3533]],[[3552,3535],[3552],[0]],[[3535]],[[3534],[3536]],[[2096,3529,17,3413,3531,3537]],[[3538],[3533]],[[420],[0]],[[265],[0]],[[2041,3867],[3543],[128,3545],[3546],[383,614,372,3326],[24],[501,128,627],[3540,265],[609,3541],[75,3859],[3697],[74,3550],[553,3551],[3547],[3548],[3549]],[[371,720]],[[3413]],[[3849],[372,3326],[3544]],[[3582]],[[707,3844]],[[3518,3516]],[[3517]],[[192],[152],[128]],[[142],[334],[128]],[[609,3541],[75,3855],[3195,376],[3540,265]],[[3834],[0]],[[3558],[0]],[[3563],[0]],[[443,3810,3553,3554,3555]],[[204],[402],[513]],[[320,3557]],[[3560],[0]],[[383,133,3564]],[[3562],[0]],[[383,614,3564]],[[383,614,3564,3559],[383,133,3564,3561]],[[3565],[506,3867],[373,3],[506,128]],[[471],[49]],[[3568,3566],[3568],[0]],[[753,3569,3566,748]],[[750,3569]],[[3830,3394,2123]],[[3572,3570],[3572],[0]],[[753,3573,3570,748]],[[750,3573]],[[3569],[3413,2123]],[[3575],[3576]],[[3571]],[[3567]],[[3578]],[[44],[488],[220]],[[3580],[3583]],[[264,2127,3841],[75,3859],[3581]],[[3582]],[[662],[661]],[[3584,3577]],[[621],[599]],[[3580],[645,401,3830]],[[3580]],[[3592,-1]],[[3609],[0]],[[3871],[0]],[[32],[0]],[[3601],[0]],[[3593,3394,3588],[3595,3589,3588],[3596,3397,3588],[37,3394],[3597],[3598,3606,3395],[60,3394,3395],[32,3394],[3599,3606,3590],[3604,3394,3590],[628,3606],[656,3394,3588],[116],[586,3242],[583,3242],[113,3242],[587],[39,3394],[3600],[293,628],[293,3591,3395],[589,3395],[580,3394,3395],[332,3395],[291,3395],[164,3851,3395],[506,3851,3395],[501],[3602],[3603]],[[249],[588],[516],[331],[31]],[[416],[0]],[[437],[146,3594]],[[195],[126],[378],[192]],[[42],[41]],[[60,633],[629]],[[358,629],[379],[361,629],[358,60,633],[361,633]],[[330],[290]],[[60,633],[629]],[[262]],[[212],[211],[411],[352],[279],[351],[412],[353]],[[361],[358,60]],[[437],[146,3594]],[[753,3607,748]],[[3844],[783]],[[3610,3608],[3610]],[[3608]],[[512],[612],[657]],[[3613],[0]],[[3614],[3615],[46],[3406,3618,3590],[32,3611]],[[3406,3618]],[[19,3590],[32,19]],[[606,3590],[32,606]],[[753,3842,748]],[[753,787,748]],[[3872],[32],[3619]],[[128]],[[3872],[3621],[3622]],[[128]],[[32]],[[3625,3623],[3625],[0]],[[3629,3623]],[[2053,3629]],[[3629,3626],[3629]],[[3626]],[[3813],[0]],[[163,2127,3801],[3631],[323,2127,3843],[344,2127,3843],[25,2127,3841],[406,2127,3853],[75,2127,3853],[3632],[3633],[24,2127,3843],[399,2127,3645],[3634,2127,3645],[3635,2127,3841],[132,2127,3841],[486,2127,3636],[608,2127,753,3628,748],[3649],[3647],[243,2127,3637],[112,139,2127,3855],[236,139,2127,3855],[572,3639,3830],[553,3640],[84,2127,3855],[264,2127,3841],[3641],[3642],[3643],[3644]],[[376],[3872]],[[721,2843,3630]],[[81,2127,3855]],[[158,2127,3855]],[[544],[545],[546]],[[61],[575]],[[128],[152],[192],[80],[442],[78]],[[373],[191],[268]],[[2127]],[[3638],[0]],[[142],[334]],[[543,592]],[[848,2127,3855]],[[849,2127,3855]],[[2277]],[[3841],[128]],[[128],[0]],[[3646,69,2127,3620]],[[3646,158,2127,3853]],[[3646,3406,2127,3618]],[[3654],[0]],[[3662],[0]],[[3668],[0]],[[405,45,3658,3650,3651,3652]],[[404,3842]],[[277],[0]],[[3665],[0]],[[3832],[0]],[[3655,265,3656,753,3657,748],[3655,220,753,3216,748],[3659,3660]],[[432],[280]],[[753,3216,748],[71,753,3657,748]],[[3664],[0]],[[561,45,3655,3663,3661]],[[220,753,3216,748],[265,3656,3834]],[[560,3842]],[[3666]],[[9,763,3842]],[[3669,3667],[3669],[0]],[[753,3673,3667,748]],[[750,3673]],[[3675],[0]],[[3682,3671],[3682],[0]],[[3678],[0]],[[405,3830,3670,3671,3672]],[[3687],[329]],[[626,273,581,3674],[626,251,3680]],[[3677,3676],[3677],[0]],[[750,3685]],[[753,3685,3676,748]],[[3681,3679],[3681],[0]],[[3687],[753,3687,3679,748]],[[750,3687]],[[572,2127,3830],[2281,163,2127,3801],[368,2127,3842],[3683,2127,3842],[3684,139,2127,3859],[75,2127,3859]],[[323],[344]],[[112],[236]],[[561,3872,3671]],[[3688,3686],[3688],[0]],[[753,3689,3686,748]],[[750,3689]],[[3216],[329]],[[130,763,3758]],[[231,174]],[[231,3407,174]],[[3695],[0]],[[3693,3696]],[[251],[397],[240]],[[3818,3698]],[[69,3620]],[[3592,2096]],[[753,3781,750,3781,748]],[[3702,3700],[3702],[0]],[[3790,3700]],[[750,3790]],[[3705,3703],[3705],[0]],[[3706,3703]],[[750,3706]],[[3773,3877,3707]],[[3191],[128]],[[3406,3618]],[[3712,3709],[3712]],[[71,3709]],[[392],[0]],[[579,45,3855],[3711,157,45,3855],[167,45,3855]],[[3715,3713],[3715]],[[278,3713]],[[3716,45,3855]],[[579],[541]],[[3719,3717],[3719],[0]],[[3758,3717]],[[750,3758]],[[3722,3720],[3722],[0]],[[3727,3720]],[[750,3727]],[[3725,3723],[3725],[0]],[[3739,3723]],[[750,3739]],[[3738],[0]],[[3758,3726]],[[406]],[[3730],[0]],[[3728]],[[45,3855]],[[3733],[0]],[[17,3856],[3731]],[[3735],[0]],[[645,3872]],[[3734,45,734,406]],[[45,3729,3855],[645,3872,3732],[3736]],[[230,3737]],[[3740,3750]],[[2862],[3758]],[[3742],[0]],[[645,3872]],[[734,406]],[[3743],[3855]],[[3746],[0]],[[17,3856,3027]],[[3748],[0]],[[3741,45,3744,3026,3027],[645,3872,3745]],[[3752]],[[230,3747],[3749],[0]],[[727,101,406]],[[141,728,406]],[[458,3855]],[[3757],[0]],[[3872,3754]],[[3872],[0]],[[746,3756],[792]],[[3755],[105,3323]],[[275,3853]],[[3759],[2585]],[[385],[380]],[[284],[375]],[[3764]],[[405,3834]],[[3767],[3768]],[[3840],[3835,3385]],[[3766]],[[3830]],[[3830]],[[3772,3770],[3772],[0]],[[753,3769,3770,748]],[[750,3769]],[[3765]],[[3773],[3778]],[[3830]],[[3765]],[[3779],[0]],[[3830,751,3777,775]],[[3830,751]],[[3830]],[[3830]],[[3835]],[[3835]],[[3835]],[[3835]],[[3835]],[[3835]],[[3835],[3789]],[[3840]],[[3835],[3791]],[[3840]],[[3830]],[[3830]],[[3830]],[[3830]],[[3835]],[[3835]],[[3830]],[[3872]],[[3872]],[[3872]],[[3835],[3803]],[[3840]],[[3781,3840]],[[3809],[0]],[[3830,3805]],[[3808],[0]],[[751,775]],[[751,775],[3840,3807]],[[3835],[3811]],[[3840]],[[3814,3812],[3814],[0]],[[3810,3812]],[[750,3810]],[[3817,3815],[3817],[0]],[[3806,3815]],[[750,3806]],[[3830]],[[3827],[3889]],[[3819]],[[3827],[3898]],[[3821]],[[3830]],[[3853]],[[3830]],[[3830]],[[3828],[3829]],[[793],[781]],[[784]],[[3827],[3881]],[[3833,3831],[3833],[0]],[[3830,3831]],[[750,3830]],[[753,3832,748]],[[3830,3385]],[[3838],[0]],[[3830,3836],[3839]],[[3840,3385]],[[3840,3840]],[[751,3830]],[[787],[786],[788],[791],[783],[785]],[[787],[786],[788],[791]],[[787],[788],[791],[783],[785]],[[787],[3845],[791],[788]],[[786]],[[794],[0]],[[3859],[3865],[3868],[3867],[3866],[3846,3848]],[[786],[782]],[[3847],[778,3841],[773,3841]],[[3852,3850],[3852],[0]],[[753,3855,3850,748]],[[750,3855]],[[790],[3854]],[[784]],[[3853],[786],[782]],[[3853],[3857]],[[786]],[[3853,3858],[3853],[0]],[[3860,3858]],[[3846,3853],[789]],[[3853]],[[3864,3862],[3864],[0]],[[3853,3862]],[[750,3853]],[[787],[788],[791],[783],[785]],[[596],[183]],[[376],[801]],[[116,3853],[586,3853],[583,3853]],[[3606],[3871]],[[3606]],[[753,787,750,787,748]],[[3830],[3853]],[[3827],[3902]],[[3821],[3853]],[[3844],[3827]],[[753,748]],[[763],[756]],[[658],[673],[214],[284],[502]],[[214,751],[284,751],[502,751]],[[658,751],[673,751],[214,751],[284,751],[502,751]],[[3885],[3886]],[[510]],[[714]],[[3889],[3904],[173],[3882],[3883]],[[3884]],[[3894],[3887],[3888],[3893],[3903]],[[173],[714],[510]],[[19],[29],[46],[47],[58],[61],[677],[75],[77],[90],[123],[147],[159],[196],[197],[219],[222],[234],[245],[267],[373],[415],[417],[455],[468],[480],[489],[512],[514],[543],[552],[597],[606],[607],[651]],[[3891],[3892]],[[3908],[170],[188],[369],[423],[427],[451],[459],[709],[565]],[[3890]],[[3894],[3893],[3903]],[[170],[188],[369],[423],[427],[451],[459],[709],[565]],[[3895],[3897]],[[3],[2],[724],[5],[660],[6],[7],[8],[9],[12],[16],[21],[812],[23],[24],[25],[26],[27],[33],[37],[40],[41],[42],[44],[675],[50],[53],[54],[56],[57],[63],[64],[65],[66],[67],[68],[70],[71],[74],[73],[76],[78],[79],[664],[80],[81],[82],[84],[85],[87],[88],[89],[91],[96],[101],[107],[111],[112],[113],[116],[122],[129],[130],[715],[132],[716],[138],[139],[140],[141],[142],[150],[151],[152],[156],[158],[160],[730],[162],[163],[848],[164],[166],[165],[168],[169],[171],[172],[680],[176],[177],[179],[180],[181],[184],[185],[189],[190],[191],[192],[682],[201],[202],[204],[208],[211],[212],[213],[713],[840],[216],[210],[841],[220],[674],[705],[225],[224],[229],[230],[233],[725],[235],[238],[844],[243],[244],[661],[250],[255],[256],[258],[259],[262],[264],[847],[268],[270],[273],[274],[279],[280],[670],[286],[288],[289],[296],[735],[298],[299],[319],[300],[729],[301],[302],[303],[304],[712],[305],[306],[307],[308],[309],[310],[312],[311],[313],[314],[316],[738],[317],[318],[736],[321],[322],[323],[324],[327],[328],[333],[334],[335],[336],[337],[340],[343],[344],[346],[348],[350],[351],[352],[353],[354],[355],[356],[357],[358],[361],[363],[702],[365],[366],[367],[368],[671],[374],[689],[377],[379],[381],[732],[728],[384],[386],[387],[719],[390],[703],[717],[690],[398],[399],[400],[401],[402],[403],[404],[406],[704],[407],[408],[409],[410],[411],[412],[413],[693],[418],[419],[421],[737],[424],[426],[425],[429],[430],[431],[434],[438],[439],[441],[846],[442],[718],[445],[446],[447],[448],[449],[452],[842],[454],[456],[460],[462],[461],[463],[466],[464],[465],[617],[695],[470],[472],[727],[473],[851],[474],[706],[476],[659],[481],[482],[483],[485],[486],[488],[490],[492],[721],[849],[722],[720],[723],[495],[496],[500],[501],[503],[508],[513],[669],[515],[517],[519],[520],[521],[813],[814],[815],[817],[816],[818],[819],[820],[821],[822],[823],[824],[825],[826],[829],[828],[830],[831],[833],[832],[834],[827],[835],[522],[836],[837],[838],[839],[528],[529],[530],[532],[535],[538],[707],[540],[542],[544],[545],[546],[547],[553],[556],[557],[558],[559],[560],[561],[566],[567],[568],[571],[572],[575],[576],[577],[578],[580],[581],[708],[697],[584],[585],[583],[586],[845],[592],[593],[598],[599],[698],[601],[602],[603],[604],[610],[613],[615],[618],[619],[625],[627],[631],[711],[636],[662],[638],[639],[640],[641],[646],[647],[648],[650],[652],[653],[656],[843]],[[731],[741],[735],[738],[736],[733],[744],[740],[737],[734],[739],[742],[743],[583],[586]],[[3896]],[[3900],[3901]],[[3908],[3904]],[[3899]],[[3894],[3888],[3903]],[[3894],[3887],[3888],[3893]],[[214],[284],[658],[673],[502]],[[3905],[3906],[3907]],[[2],[19],[12],[27],[29],[46],[47],[58],[61],[677],[66],[75],[77],[90],[123],[147],[159],[196],[197],[201],[210],[219],[222],[224],[245],[661],[267],[373],[387],[390],[398],[401],[413],[415],[417],[452],[455],[468],[470],[659],[480],[489],[720],[721],[722],[723],[496],[503],[512],[519],[514],[520],[543],[552],[597],[606],[607],[615],[662],[648],[651]],[[510]],[[234]],[[3909],[3910],[3912],[3914],[3915]],[[3],[724],[5],[6],[7],[8],[9],[13],[16],[21],[22],[24],[23],[25],[26],[33],[37],[40],[42],[41],[44],[675],[50],[53],[54],[56],[57],[63],[65],[64],[67],[68],[70],[73],[74],[71],[76],[78],[79],[664],[80],[81],[82],[84],[85],[87],[89],[88],[91],[93],[96],[101],[107],[112],[111],[113],[116],[122],[129],[130],[132],[136],[716],[138],[139],[140],[141],[142],[150],[151],[152],[158],[160],[164],[163],[162],[165],[166],[168],[169],[171],[680],[176],[179],[180],[181],[185],[184],[682],[202],[156],[204],[189],[190],[191],[192],[208],[212],[211],[213],[216],[214],[220],[674],[705],[225],[229],[230],[233],[250],[235],[238],[244],[725],[255],[256],[258],[259],[243],[262],[850],[264],[268],[270],[273],[274],[279],[280],[284],[670],[286],[288],[289],[323],[316],[319],[300],[304],[301],[302],[318],[303],[712],[306],[298],[305],[299],[314],[308],[307],[317],[309],[310],[311],[312],[313],[296],[321],[322],[325],[324],[327],[328],[333],[334],[335],[336],[337],[340],[343],[344],[348],[346],[350],[351],[352],[353],[354],[355],[357],[356],[358],[361],[363],[702],[365],[367],[366],[374],[368],[689],[671],[377],[379],[381],[728],[382],[384],[719],[703],[717],[690],[399],[400],[402],[403],[404],[406],[704],[407],[409],[410],[408],[411],[412],[693],[418],[419],[708],[421],[424],[425],[426],[429],[430],[431],[434],[438],[439],[441],[440],[442],[445],[446],[447],[448],[449],[676],[454],[456],[460],[461],[462],[463],[464],[465],[466],[617],[695],[472],[727],[473],[474],[706],[476],[481],[482],[483],[485],[486],[488],[490],[492],[495],[501],[500],[502],[508],[513],[669],[515],[517],[521],[522],[528],[529],[530],[533],[532],[535],[538],[707],[540],[542],[544],[545],[546],[547],[553],[556],[557],[558],[559],[561],[560],[565],[566],[567],[568],[576],[571],[575],[572],[577],[578],[580],[581],[697],[592],[593],[583],[584],[585],[586],[598],[599],[600],[698],[601],[602],[604],[603],[610],[613],[618],[619],[631],[711],[636],[627],[639],[638],[640],[647],[641],[650],[652],[653],[656]],[[510]],[[99],[234],[206],[484],[487]],[[3911]],[[172],[177],[386],[565],[625],[646]],[[3913]],[[660]]]]; diff --git a/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-lexer.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-lexer.php new file mode 100644 index 00000000..def8ca3f --- /dev/null +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-lexer.php @@ -0,0 +1,3 @@ +materialize_native_children(); + parent::append_child( $node ); + } + + /** @inheritDoc */ + public function merge_fragment( $node ) { + $this->materialize_native_children(); + if ( $node instanceof self ) { + $node->materialize_native_children(); + } + parent::merge_fragment( $node ); + } + + /** @inheritDoc */ + public function has_child(): bool { + if ( $this->was_mutated ) { + return parent::has_child(); + } + return wp_sqlite_mysql_native_ast_has_child( $this ); + } + + /** @inheritDoc */ + 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, $rule_name ); + } + + /** @inheritDoc */ + 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, $token_id ); + } + + /** @inheritDoc */ + public function get_first_child() { + if ( $this->was_mutated ) { + return parent::get_first_child(); + } + return wp_sqlite_mysql_native_ast_get_first_child( $this ); + } + + /** @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 ); + } + return wp_sqlite_mysql_native_ast_get_first_child_node( $this, $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 ); + } + return wp_sqlite_mysql_native_ast_get_first_child_token( $this, $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 ); + } + return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this, $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 ); + } + return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this, $token_id ); + } + + /** @inheritDoc */ + public function get_children(): array { + if ( $this->was_mutated ) { + return parent::get_children(); + } + return wp_sqlite_mysql_native_ast_get_children( $this ); + } + + /** @inheritDoc */ + 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, $rule_name ); + } + + /** @inheritDoc */ + 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, $token_id ); + } + + /** @inheritDoc */ + public function get_descendants(): array { + if ( $this->was_mutated ) { + return parent::get_descendants(); + } + return wp_sqlite_mysql_native_ast_get_descendants( $this ); + } + + /** @inheritDoc */ + 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, $rule_name ); + } + + /** @inheritDoc */ + 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, $token_id ); + } + + /** @inheritDoc */ + public function get_start(): int { + if ( $this->was_mutated ) { + return parent::get_start(); + } + return wp_sqlite_mysql_native_ast_get_start( $this ); + } + + /** @inheritDoc */ + public function get_length(): int { + if ( $this->was_mutated ) { + return parent::get_length(); + } + return wp_sqlite_mysql_native_ast_get_length( $this ); + } + + private function materialize_native_children(): void { + if ( $this->was_mutated ) { + return; + } + + $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/src/mysql/native/class-wp-mysql-parser.php b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-parser.php new file mode 100644 index 00000000..76244ad7 --- /dev/null +++ b/packages/mysql-on-sqlite/src/mysql/native/class-wp-mysql-parser.php @@ -0,0 +1,14 @@ + + */ +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, + ); +} diff --git a/packages/mysql-on-sqlite/src/mysql/native/trait-wp-mysql-native-parser-impl.php b/packages/mysql-on-sqlite/src/mysql/native/trait-wp-mysql-native-parser-impl.php new file mode 100644 index 00000000..c53e96e5 --- /dev/null +++ b/packages/mysql-on-sqlite/src/mysql/native/trait-wp-mysql-native-parser-impl.php @@ -0,0 +1,55 @@ +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/wp-includes/parser/class-wp-parser-grammar.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php similarity index 99% rename from wp-includes/parser/class-wp-parser-grammar.php rename to packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php index 8c17b458..9bf30b97 100644 --- a/wp-includes/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/wp-includes/parser/class-wp-parser-node.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser-node.php similarity index 99% rename from wp-includes/parser/class-wp-parser-node.php rename to packages/mysql-on-sqlite/src/parser/class-wp-parser-node.php index e2d67018..b61f38d5 100644 --- a/wp-includes/parser/class-wp-parser-node.php +++ b/packages/mysql-on-sqlite/src/parser/class-wp-parser-node.php @@ -15,7 +15,7 @@ class WP_Parser_Node { */ public $rule_id; public $rule_name; - private $children = array(); + protected $children = array(); public function __construct( $rule_id, $rule_name ) { $this->rule_id = $rule_id; diff --git a/wp-includes/parser/class-wp-parser-token.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser-token.php similarity index 100% rename from wp-includes/parser/class-wp-parser-token.php rename to packages/mysql-on-sqlite/src/parser/class-wp-parser-token.php diff --git a/wp-includes/parser/class-wp-parser.php b/packages/mysql-on-sqlite/src/parser/class-wp-parser.php similarity index 100% rename from wp-includes/parser/class-wp-parser.php rename to packages/mysql-on-sqlite/src/parser/class-wp-parser.php diff --git a/php-polyfills.php b/packages/mysql-on-sqlite/src/php-polyfills.php similarity index 67% rename from php-polyfills.php rename to packages/mysql-on-sqlite/src/php-polyfills.php index 89d6d1a7..b3ab8ed6 100644 --- a/php-polyfills.php +++ b/packages/mysql-on-sqlite/src/php-polyfills.php @@ -1,6 +1,10 @@ connection->query( 'PRAGMA foreign_keys = ON' ); // Register SQLite functions. - WP_SQLite_PDO_User_Defined_Functions::register_for( $this->connection->get_pdo() ); + $this->user_defined_functions = WP_SQLite_PDO_User_Defined_Functions::register_for( $this->connection->get_pdo() ); // Load MySQL grammar. if ( null === self::$mysql_grammar ) { @@ -883,12 +897,34 @@ public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_ar null === $child_node || 'beginWork' === $child_node->rule_name || $child_node->has_child_node( 'transactionOrLockingStatement' ) + || $child_node->has_child_node( 'selectStatement' ) ) { $wrap_in_transaction = false; } else { $wrap_in_transaction = true; } + /* + * Detect read-only statements before opening the wrapper transaction. + * + * [GRAMMAR] + * simpleStatement: selectStatement | showStatement | utilityStatement | ... + */ + if ( null !== $child_node && $child_node->has_child_node() ) { + $statement_node = $child_node->get_first_child_node(); + if ( + 'selectStatement' === $statement_node->rule_name + || 'showStatement' === $statement_node->rule_name + ) { + $this->is_readonly = true; + } elseif ( 'utilityStatement' === $statement_node->rule_name ) { + $utility_subnode = $statement_node->get_first_child_node(); + if ( null !== $utility_subnode && 'describeStatement' === $utility_subnode->rule_name ) { + $this->is_readonly = true; + } + } + } + if ( $wrap_in_transaction ) { $this->begin_wrapper_transaction(); } @@ -1136,8 +1172,26 @@ public function create_parser( string $query ): WP_MySQL_Parser { 80038, $this->active_sql_modes ); - $tokens = $lexer->remaining_tokens(); - return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + $tokens = $lexer instanceof WP_MySQL_Native_Lexer + ? $lexer->native_token_stream() + : $lexer->remaining_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; } /** @@ -1366,7 +1420,6 @@ private function execute_mysql_query( WP_Parser_Node $node ): void { $this->execute_transaction_or_locking_statement( $node ); break; case 'selectStatement': - $this->is_readonly = true; $this->execute_select_statement( $node ); break; case 'insertStatement': @@ -1444,14 +1497,12 @@ private function execute_mysql_query( WP_Parser_Node $node ): void { $this->execute_set_statement( $node ); break; case 'showStatement': - $this->is_readonly = true; $this->execute_show_statement( $node ); break; case 'utilityStatement': $subtree = $node->get_first_child_node(); switch ( $subtree->rule_name ) { case 'describeStatement': - $this->is_readonly = true; $this->execute_describe_statement( $subtree ); break; case 'useCommand': @@ -1835,6 +1886,11 @@ private function execute_insert_or_replace_statement( WP_Parser_Node $node ): vo $is_node = $child instanceof WP_Parser_Node; if ( $child instanceof WP_Parser_Node && 'tableRef' === $child->rule_name ) { + // MySQL supports INSERT without the INTO keyword; SQLite requires it. + if ( ! $node->has_child_token( WP_MySQL_Lexer::INTO_SYMBOL ) ) { + $parts[] = 'INTO'; + } + $database = $this->get_database_name( $child ); if ( 'information_schema' === strtolower( $database ) ) { throw $this->new_access_denied_to_information_schema_exception(); @@ -2284,6 +2340,38 @@ private function execute_delete_statement( WP_Parser_Node $node ): void { throw $this->new_access_denied_to_information_schema_exception(); } + /* + * SQLite doesn't support DELETE with ORDER BY/LIMIT. + * We need to use a subquery to emulate this behavior. + * + * For instance, the following query: + * DELETE FROM t WHERE c = 2 LIMIT 1; + * Will be rewritten to: + * DELETE FROM t WHERE rowid IN ( SELECT rowid FROM t WHERE c = 2 LIMIT 1 ); + */ + $has_order = $node->has_child_node( 'orderClause' ); + $has_limit = $node->has_child_node( 'simpleLimitClause' ); + if ( $has_order || $has_limit ) { + $where_subquery = 'SELECT rowid FROM ' . $this->translate_sequence( + array( + $table_ref, + $node->get_first_child_node( 'tableAlias' ), + $node->get_first_child_node( 'whereClause' ), + $node->get_first_child_node( 'orderClause' ), + $node->get_first_child_node( 'simpleLimitClause' ), + ) + ); + + $query = sprintf( + 'DELETE FROM %s WHERE rowid IN ( %s )', + $this->translate( $table_ref ), + $where_subquery + ); + + $this->last_result_statement = $this->execute_sqlite_query( $query ); + return; + } + $query = $this->translate( $node ); $this->last_result_statement = $this->execute_sqlite_query( $query ); } @@ -2354,6 +2442,9 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void { foreach ( $constraint_queries as $query ) { $this->execute_sqlite_query( $query ); } + + // Apply AUTO_INCREMENT = N table option, if any. + $this->apply_auto_increment_table_option( $table_is_temporary, $table_name, $node ); } /** @@ -2427,8 +2518,18 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void { } } - $this->information_schema_builder->record_alter_table( $node ); - $this->recreate_table_from_information_schema( $table_is_temporary, $table_name, $column_map ); + /* + * Skip the expensive table rebuild when the statement only carries + * table options (e.g. ALTER TABLE t AUTO_INCREMENT = N). These don't + * change the schema, so the recreate would be a pointless full copy. + */ + if ( count( $node->get_descendant_nodes( 'alterListItem' ) ) > 0 ) { + $this->information_schema_builder->record_alter_table( $node ); + $this->recreate_table_from_information_schema( $table_is_temporary, $table_name, $column_map ); + } + + // Apply AUTO_INCREMENT = N table option, if any. + $this->apply_auto_increment_table_option( $table_is_temporary, $table_name, $node ); // @TODO: Consider using a "fast path" for ALTER TABLE statements that // consist only of operations that SQLite's ALTER TABLE supports. @@ -2934,41 +3035,64 @@ private function execute_show_table_status_statement( WP_Parser_Node $node ): vo // LIKE and WHERE clauses. $like_or_where = $node->get_first_child_node( 'likeOrWhere' ); if ( null !== $like_or_where ) { - $condition = $this->translate_show_like_or_where_condition( $like_or_where, 'table_name' ); + $condition = $this->translate_show_like_or_where_condition( $like_or_where, 'Name' ); } - // Fetch table information. - $tables_tables = $this->information_schema_builder->get_table_name( - false, // SHOW TABLE STATUS lists only non-temporary tables. - 'tables' + // SHOW TABLE STATUS lists only non-temporary tables. + $tables_table = $this->information_schema_builder->get_table_name( false, 'tables' ); + $columns_table = $this->information_schema_builder->get_table_name( false, 'columns' ); + + // Compose a subquery to compute auto-increment values. + $has_sequence_table = (bool) $this->execute_sqlite_query( + "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'" + )->fetchColumn(); + + $auto_increment_subquery = sprintf( + "( + SELECT COALESCE(s.seq + 1, 1) + FROM %s AS c + %s + WHERE c.extra = 'auto_increment' + AND c.table_schema = t.table_schema + AND c.table_name = t.table_name + )", + $this->quote_sqlite_identifier( $columns_table ), + $has_sequence_table + ? 'LEFT JOIN main.sqlite_sequence AS s ON s.name = c.table_name' + : 'LEFT JOIN (SELECT 0 AS seq) AS s' ); - $query = sprintf( - 'SELECT - table_name AS `Name`, - engine AS `Engine`, - version AS `Version`, - row_format AS `Row_format`, - table_rows AS `Rows`, - avg_row_length AS `Avg_row_length`, - data_length AS `Data_length`, - max_data_length AS `Max_data_length`, - index_length AS `Index_length`, - data_free AS `Data_free`, - auto_increment AS `Auto_increment`, - create_time AS `Create_time`, - update_time AS `Update_time`, - check_time AS `Check_time`, - table_collation AS `Collation`, - checksum AS `Checksum`, - create_options AS `Create_options`, - table_comment AS `Comment` - FROM %s - WHERE table_schema = ? %s - ORDER BY table_name', - $this->quote_sqlite_identifier( $tables_tables ), + + $query = sprintf( + 'SELECT * FROM ( + SELECT + table_name AS `Name`, + engine AS `Engine`, + version AS `Version`, + row_format AS `Row_format`, + table_rows AS `Rows`, + avg_row_length AS `Avg_row_length`, + data_length AS `Data_length`, + max_data_length AS `Max_data_length`, + index_length AS `Index_length`, + data_free AS `Data_free`, + %s AS `Auto_increment`, + create_time AS `Create_time`, + update_time AS `Update_time`, + check_time AS `Check_time`, + table_collation AS `Collation`, + checksum AS `Checksum`, + create_options AS `Create_options`, + table_comment AS `Comment` + FROM %s AS t + WHERE table_schema = ? + ) + WHERE 1 %s + ORDER BY `Name`', + $auto_increment_subquery, + $this->quote_sqlite_identifier( $tables_table ), $condition ?? '' ); - $params = array( + $params = array( $this->get_saved_db_name( $database ), ); @@ -3686,8 +3810,8 @@ private function translate( $node ): ?string { return null; } return $this->translate_sequence( $node->get_children() ); - case 'simpleExpr': - return $this->translate_simple_expr( $node ); + case 'simpleExprBody': + return $this->translate_simple_expr_body( $node ); case 'predicateOperations': $token = $node->get_first_child_token(); if ( WP_MySQL_Lexer::LIKE_SYMBOL === $token->id ) { @@ -3785,6 +3909,8 @@ private function translate( $node ): ?string { return 'TEXT'; case WP_MySQL_Lexer::SIGNED_SYMBOL: case WP_MySQL_Lexer::UNSIGNED_SYMBOL: + // @TODO: Emulate UNSIGNED semantics. MySQL wraps negative + // values, but SQLite has no unsigned integer type. return 'INTEGER'; case WP_MySQL_Lexer::DECIMAL_SYMBOL: case WP_MySQL_Lexer::FLOAT_SYMBOL: @@ -3866,9 +3992,9 @@ private function translate_token( WP_MySQL_Token $token ): ?string { return 'AUTOINCREMENT'; case WP_MySQL_Lexer::BINARY_SYMBOL: /* - * There is no "BINARY expr" equivalent in SQLite. We look for the - * keyword from a higher level to respect it in particular cases - * (REGEXP, LIKE, etc.) and then remove it from the output here. + * "BINARY expr" is translated in "translate_simple_expr_body()". + * Returning null here is a safety net for any unhandled context + * where a bare BINARY token would otherwise leak into the output. */ return null; case WP_MySQL_Lexer::SQL_CALC_FOUND_ROWS_SYMBOL: @@ -4199,13 +4325,13 @@ private function translate_query_specification( WP_Parser_Node $node ): string { } /** - * Translate a MySQL simple expression to SQLite. + * Translate a MySQL simple expression body to SQLite. * - * @param WP_Parser_Node $node The "simpleExpr" AST node. + * @param WP_Parser_Node $node The "simpleExprBody" AST node. * @return string The translated value. * @throws WP_SQLite_Driver_Exception When the translation fails. */ - private function translate_simple_expr( WP_Parser_Node $node ): string { + private function translate_simple_expr_body( WP_Parser_Node $node ): string { $token = $node->get_first_child_token(); // Translate "VALUES(col)" to "excluded.col" in ON DUPLICATE KEY UPDATE. @@ -4216,9 +4342,70 @@ private function translate_simple_expr( WP_Parser_Node $node ): string { ); } + /* + * Translate "BINARY expr" to "expr COLLATE BINARY". + * + * The MySQL BINARY operator enforces byte-by-byte string comparison. + * In SQLite, COLLATE BINARY is equivalent in comparison contexts. + */ + if ( null !== $token && WP_MySQL_Lexer::BINARY_SYMBOL === $token->id ) { + $expr = $node->get_first_child_node( 'simpleExpr' ); + return sprintf( '%s COLLATE BINARY', $this->translate( $expr ) ); + } + + // Translate "CAST(expr AS type)" to its SQLite equivalent. + if ( null !== $token && WP_MySQL_Lexer::CAST_SYMBOL === $token->id ) { + $expr = $node->get_first_child_node( 'expr' ); + $cast_type = $node->get_first_child_node( 'castType' ); + return $this->translate_cast_expr( $expr, $cast_type ); + } + + /** + * Translate MySQL CONVERT() expression. + * + * MySQL supports two forms of CONVERT(): + * 1. CONVERT(expr, type): Equivalent to CAST(expr AS type). + * 2. CONVERT(expr USING charset): Converts the character set. + */ + if ( null !== $token && WP_MySQL_Lexer::CONVERT_SYMBOL === $token->id ) { + $expr = $node->get_first_child_node( 'expr' ); + $cast_type = $node->get_first_child_node( 'castType' ); + + if ( null !== $cast_type ) { + // CONVERT(expr, type): Translate to cast expression. + // TODO: Emulate UNSIGNED cast. SQLite has no unsigned integer type. + return $this->translate_cast_expr( $expr, $cast_type ); + } else { + // CONVERT(expr USING charset): Keep "expr" as is (no SQLite support). + // TODO: Consider rejecting UTF-8-incompatible charasets. + return $this->translate( $expr ); + } + } + return $this->translate_sequence( $node->get_children() ); } + /** + * Translate a MySQL CAST expression to SQLite. + * + * Shared by the CAST(expr AS type) and CONVERT(expr, type) forms. + * + * @param WP_Parser_Node $expr The "expr" AST node. + * @param WP_Parser_Node $cast_type The "castType" AST node. + * @return string The translated SQLite expression. + */ + private function translate_cast_expr( WP_Parser_Node $expr, WP_Parser_Node $cast_type ): string { + /* + * Translate "CAST(expr AS BINARY)" to "CAST(expr AS TEXT) COLLATE BINARY". + * Emitting "CAST(expr AS BLOB)" would break equality against TEXT values + * due to SQLite's storage-class ordering (BLOB > TEXT). + */ + if ( $cast_type->has_child_token( WP_MySQL_Lexer::BINARY_SYMBOL ) ) { + return sprintf( 'CAST(%s AS TEXT) COLLATE BINARY', $this->translate( $expr ) ); + } + return sprintf( 'CAST(%s AS %s)', $this->translate( $expr ), $this->translate( $cast_type ) ); + } + /** * Translate a MySQL LIKE expression to SQLite. * @@ -4366,6 +4553,25 @@ private function translate_function_call( WP_Parser_Node $node ): string { } switch ( $name ) { + case 'RAND': + /* + * Unseeded RAND() compiles to a fast native SQLite expression. + * In MySQL, unseeded RAND() uses the thread-level random state, + * independent of any RAND(N) seeding, so we don't need PHP UDF. + * + * We map SQLite's RANDOM() to a float in [0, 1) by masking to + * 53 bits (IEEE 754 double mantissa width) and dividing by 2^53. + * This avoids the edge case where masking to 63 bits and dividing + * by 2^63 could round to 1.0 due to loss of precision in double. + * + * Seeded RAND(N) falls through to the UDF, which implements + * MySQL's deterministic LCG (Linear Congruential Generator). + * The UDF also handles RAND(NULL) as RAND(0), matching MySQL. + */ + if ( 0 === count( $args ) ) { + return '((RANDOM() & ((1 << 53) - 1)) / ((1 << 53) * 1.0))'; + } + return $this->translate_sequence( $node->get_children() ); case 'DATE_FORMAT': list ( $date, $mysql_format ) = $args; @@ -4494,14 +4700,30 @@ private function translate_datetime_literal( string $value ): string { * In the future, let's update WordPress to do its own date validation * and stop relying on this MySQL feature, */ - if ( 1 === preg_match( '/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/', $value, $matches ) ) { + if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}:\d{2})$/', $value, $matches ) ) { /* * Calling strtotime("0000-00-00 00:00:00") in 32-bit environments triggers * an "out of integer range" warning – let's avoid that call for the popular * case of "zero" dates. */ if ( '0000-00-00 00:00:00' !== $value && false === strtotime( $value ) ) { - $value = '0000-00-00 00:00:00'; + /* + * Check for dates with zero month/day parts (e.g. '2020-00-15 00:00:00'). + * + * When the NO_ZERO_IN_DATE SQL mode is not active, MySQL accepts dates + * where the year is nonzero but the month or day is zero. We must + * preserve these values so that cast_value_for_saving() can handle + * them correctly at the column level. + * + * See: https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_in_date + */ + $has_zero_in_date = ( + ( '00' === $matches[2] || '00' === $matches[3] ) && + '0000' !== $matches[1] + ); + if ( ! $has_zero_in_date || $this->is_sql_mode_active( 'NO_ZERO_IN_DATE' ) ) { + $value = '0000-00-00 00:00:00'; + } } } return $value; @@ -4561,11 +4783,27 @@ public function translate_select_item( WP_Parser_Node $node ): string { * * For example, for "SELECT 'abc'", the resulting column name is "abc" * in MySQL, but would be "'abc'" in SQLite if an alias was not used. + * + * Descend the AST until we reach a textStringLiteral. If at any level + * we don't have a single child node, bail out; it's not a bare literal. */ - $text_string_literal = $node->get_first_descendant_node( 'textStringLiteral' ); - $is_text_string_literal = $text_string_literal && $item === $this->translate( $text_string_literal ); - if ( $is_text_string_literal ) { - $alias = $text_string_literal->get_first_child_token()->get_value(); + $current = $node; + while ( 'textStringLiteral' !== $current->rule_name ) { + $children = $current->get_children(); + if ( 1 !== count( $children ) || ! $children[0] instanceof WP_Parser_Node ) { + break; + } + $current = $children[0]; + } + if ( 'textStringLiteral' === $current->rule_name ) { + $alias = $current->get_first_child_token()->get_value(); + + // When the literal value contains a NULL byte, MySQL truncates the + // resulting identifier at the position of the first one of them. + $fist_null_byte_pos = strpos( $alias, "\0" ); + if ( false !== $fist_null_byte_pos ) { + $alias = substr( $alias, 0, $fist_null_byte_pos ); + } return sprintf( '%s AS %s', $item, $this->quote_sqlite_identifier( $alias ) ); } @@ -4626,7 +4864,7 @@ public function translate_table_ref( WP_Parser_Node $node ): string { $table_name = $this->unquote_sqlite_identifier( $this->translate( $table ) ); // When the table reference targets an information schema table, - // we need to inject the configured database name dynamically. + // we need to inject some additional values dynamically. if ( ( null === $schema_name && 'information_schema' === $this->db_name ) || ( null !== $schema_name && 'information_schema' === strtolower( $schema_name ) ) @@ -4672,14 +4910,40 @@ public function translate_table_ref( WP_Parser_Node $node ): string { $expanded_list = array(); foreach ( $columns as $column ) { $quoted_column = $this->quote_sqlite_identifier( $column ); - if ( isset( $information_schema_db_column_map[ strtoupper( $column ) ] ) ) { + if ( isset( $information_schema_db_column_map[ $column ] ) ) { + // Replace the database name with the configured database name. $expanded_list[] = sprintf( "CASE WHEN %s = 'information_schema' THEN %s ELSE %s END AS %s", $quoted_column, $quoted_column, $this->quote_sqlite_value( $this->main_db_name ), - strtoupper( $quoted_column ) + $quoted_column ); + } elseif ( 'tables' === $table_name && 'AUTO_INCREMENT' === $column ) { + // Inject the auto-increment values. + $columns_table = $this->information_schema_builder->get_table_name( false, 'columns' ); + $has_sequence_table = (bool) $this->execute_sqlite_query( + "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'" + )->fetchColumn(); + + $auto_increment_subquery = sprintf( + "( + SELECT COALESCE(s.seq + 1, 1) + FROM %s AS c + %s + WHERE c.extra = 'auto_increment' + AND c.table_schema = %s.table_schema + AND c.table_name = %s.table_name + )", + $this->quote_sqlite_identifier( $columns_table ), + $has_sequence_table + ? 'LEFT JOIN main.sqlite_sequence AS s ON s.name = c.table_name' + : 'LEFT JOIN (SELECT 0 AS seq) AS s', + $this->quote_sqlite_identifier( $table_name ), + $this->quote_sqlite_identifier( $table_name ) + ); + + $expanded_list[] = sprintf( '%s AS %s', $auto_increment_subquery, $quoted_column ); } else { $expanded_list[] = $quoted_column; } @@ -4796,6 +5060,83 @@ private function recreate_table_from_information_schema( // @TODO: Triggers and views. } + /** + * Apply the AUTO_INCREMENT table option from a CREATE TABLE or ALTER TABLE + * statement by adjusting the row in SQLite's "sqlite_sequence" table. + * + * @param bool $table_is_temporary Whether the table is temporary. + * @param string $table_name The table name. + * @param WP_Parser_Node $node The "createStatement" or "alterStatement" AST node. + */ + private function apply_auto_increment_table_option( + bool $table_is_temporary, + string $table_name, + WP_Parser_Node $node + ): void { + // Find the last AUTO_INCREMENT = N option (MySQL uses the last one). + $value = null; + foreach ( $node->get_descendant_nodes( 'createTableOption' ) as $option ) { + if ( ! $option->has_child_token( WP_MySQL_Lexer::AUTO_INCREMENT_SYMBOL ) ) { + continue; + } + $number_node = $option->get_first_child_node( 'ulonglong_number' ); + if ( null === $number_node ) { + continue; + } + $value = (int) $number_node->get_first_descendant_token()->get_value(); + } + if ( null === $value ) { + return; + } + + // Find the AUTO_INCREMENT column. + $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' ); + $auto_column = $this->execute_sqlite_query( + sprintf( + "SELECT column_name FROM %s + WHERE table_schema = ? + AND table_name = ? + AND extra = 'auto_increment'", + $this->quote_sqlite_identifier( $columns_table ) + ), + array( $this->get_saved_db_name(), $table_name ) + )->fetchColumn(); + if ( false === $auto_column ) { + return; + } + + /* + * Prepare an expression for the sequence value. + * 1. Use N - 1. MySQL stores the next value, SQLite the last one. + * 2. Clamp to MAX(col) like MySQL (we can't go below existing values). + * + * The value is inlined as an integer literal because PDO binds PHP ints + * as TEXT, and SQLite's type affinity ranks TEXT above INTEGER in MAX(). + */ + $schema = $table_is_temporary ? 'temp' : 'main'; + $seq_expr = sprintf( + 'MAX(%d, COALESCE((SELECT MAX(%s) FROM %s), 0))', + $value - 1, + $this->quote_sqlite_identifier( $auto_column ), + $this->quote_sqlite_identifier( $table_name ) + ); + + // Update the value in the "sqlite_sequence" table. + $updated = $this->execute_sqlite_query( + sprintf( 'UPDATE %s.sqlite_sequence SET seq = %s WHERE name = ?', $schema, $seq_expr ), + array( $table_name ) + )->rowCount(); + + // If the sequence value does not exist yet, insert a new row. + // SQLite reports matched (not affected) rows, so the 0 check is safe. + if ( 0 === $updated ) { + $this->execute_sqlite_query( + sprintf( 'INSERT INTO %s.sqlite_sequence (name, seq) VALUES (?, %s)', $schema, $seq_expr ), + array( $table_name ) + ); + } + } + /** * Translate a MySQL SHOW LIKE ... or SHOW WHERE ... condition to SQLite. * @@ -5061,9 +5402,24 @@ function ( $column ) use ( $is_strict_mode, $insert_map ) { $fragment .= null === $default ? 'NULL' : $this->quote_sqlite_value( $default ); } else { // When a column value is included, we need to apply type casting. - $position = array_search( $column['COLUMN_NAME'], $insert_list, true ); - $identifier = $this->quote_sqlite_identifier( $select_list[ $position ] ); - $value = $this->cast_value_for_saving( $column['DATA_TYPE'], $identifier ); + $position = array_search( $column['COLUMN_NAME'], $insert_list, true ); + $identifier = $this->quote_sqlite_identifier( $select_list[ $position ] ); + $value = $this->cast_value_for_saving( $column['DATA_TYPE'], $identifier ); + $is_auto_increment = str_contains( $column['EXTRA'], 'auto_increment' ); + + /* + * In MySQL, inserting 0 into an AUTO_INCREMENT column increments + * the sequence, unless the NO_AUTO_VALUE_ON_ZERO SQL mode is set. + * + * In SQLite, we need to rewrite 0 to NULL to advance the sequence. + * The value is cast to INTEGER before the comparison, because + * SQLite treats values of different types as unequal (0 != '0'). + * + * See: https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_auto_value_on_zero + */ + if ( $is_auto_increment && ! $this->is_sql_mode_active( 'NO_AUTO_VALUE_ON_ZERO' ) ) { + $value = sprintf( 'NULLIF(CAST(%s AS INTEGER), 0)', $value ); + } /* * In MySQL non-STRICT mode, when inserting from a SELECT query: @@ -5071,9 +5427,17 @@ function ( $column ) use ( $is_strict_mode, $insert_map ) { * When a column is declared as NOT NULL, inserting a NULL value * saves an IMPLICIT DEFAULT value instead. This behavior only * applies to the INSERT ... SELECT syntax (not VALUES or SET). + * + * AUTO_INCREMENT columns are excluded. A NULL value advances + * the sequence regardless of the column's nullability. */ $is_insert_from_select = 'insertQueryExpression' === $node->rule_name; - if ( ! $is_strict_mode && $is_insert_from_select && 'NO' === $column['IS_NULLABLE'] ) { + if ( + ! $is_strict_mode + && ! $is_auto_increment + && $is_insert_from_select + && 'NO' === $column['IS_NULLABLE'] + ) { $implicit_default = self::DATA_TYPE_IMPLICIT_DEFAULT_MAP[ $column['DATA_TYPE'] ] ?? null; if ( null !== $implicit_default ) { $value = sprintf( 'COALESCE(%s, %s)', $value, $this->quote_sqlite_value( $implicit_default ) ); @@ -5322,12 +5686,12 @@ private function store_last_column_meta_from_statement( PDOStatement $stmt ): vo private function unnest_parenthesized_expression( WP_Parser_Node $node ): WP_Parser_Node { $children = $node->get_children(); - // Descend the "expr -> boolPri -> predicate -> bitExpr -> simpleExpr" tree, - // when on each level we have only a single child node (expression nesting). + // Descend the "expr -> boolPri -> predicate -> bitExpr -> simpleExpr" -> "simpleExprBody" + // tree, when on each level we have only a single child node (expression nesting). if ( 1 === count( $children ) && $children[0] instanceof WP_Parser_Node - && in_array( $children[0]->rule_name, array( 'expr', 'boolPri', 'predicate', 'bitExpr', 'simpleExpr' ), true ) + && in_array( $children[0]->rule_name, array( 'expr', 'boolPri', 'predicate', 'bitExpr', 'simpleExpr', 'simpleExprBody' ), true ) ) { $unnested = $this->unnest_parenthesized_expression( $children[0] ); return $unnested === $children[0] ? $node : $unnested; @@ -5653,16 +6017,46 @@ private function cast_value_for_saving( ? 'NULL' : $this->quote_sqlite_value( $implicit_default ); } - return sprintf( + + /* + * Build the CASE expression for date/time validation. + * + * SQLite's DATE()/DATETIME() functions return NULL for zero + * dates, so the CASE includes explicit checks controlled by + * the NO_ZERO_DATE and NO_ZERO_IN_DATE SQL modes. + * + * In MySQL, the behavior of zero dates depends on these modes: + * + * NO_ZERO_DATE (see https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_date): + * - Disabled: '0000-00-00' is permitted and produces no warning. + * - Enabled without strict mode: '0000-00-00' is permitted but produces a warning. + * - Enabled with strict mode: '0000-00-00' is not permitted and produces an error. + * + * NO_ZERO_IN_DATE (see https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_in_date): + * - Disabled: dates with zero month/day parts (e.g. '2020-00-15') are permitted. + * - Enabled without strict mode: zero-part dates produce a warning and are stored as '0000-00-00'. + * - Enabled with strict mode: zero-part dates produce an error. + */ + return strtr( "CASE - WHEN %s IS NULL THEN NULL - WHEN %s > '0' THEN %s - ELSE %s + WHEN {value} IS NULL THEN NULL + WHEN {value} IN ('0000-00-00', '0000-00-00 00:00:00') AND NOT {reject_zero_date} THEN {zero_date_value} + WHEN SUBSTR({value}, 1, 4) != '0000' AND (SUBSTR({value}, 6, 2) = '00' OR SUBSTR({value}, 9, 2) = '00') AND NOT {reject_zero_in_date} THEN {value} + WHEN {function_call} > '0' THEN {function_call} + ELSE {fallback} END", - $translated_value, - $function_call, - $function_call, - $fallback + array( + '{value}' => $translated_value, + '{reject_zero_date}' => ( + $this->is_sql_mode_active( 'NO_ZERO_DATE' ) && $is_strict_mode + ) ? 1 : 0, + '{zero_date_value}' => 'date' === $mysql_data_type + ? "'0000-00-00'" + : "'0000-00-00 00:00:00'", + '{reject_zero_in_date}' => $this->is_sql_mode_active( 'NO_ZERO_IN_DATE' ) ? 1 : 0, + '{function_call}' => $function_call, + '{fallback}' => $fallback, + ) ); default: /* @@ -5886,7 +6280,7 @@ private function get_sqlite_create_table_statement( if ( 'INTEGER' === $type && 'PRI' === $column['COLUMN_KEY'] - && 'auto_increment' !== $column['EXTRA'] + && ! str_contains( $column['EXTRA'], 'auto_increment' ) && count( $grouped_constraints['PRIMARY'] ) === 1 ) { $type = 'INT'; @@ -5903,7 +6297,7 @@ private function get_sqlite_create_table_statement( if ( 'NO' === $column['IS_NULLABLE'] ) { $query .= ' NOT NULL'; } - if ( 'auto_increment' === $column['EXTRA'] ) { + if ( str_contains( $column['EXTRA'], 'auto_increment' ) ) { $has_autoincrement = true; $query .= ' PRIMARY KEY AUTOINCREMENT'; } @@ -6202,7 +6596,8 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str )->fetchAll( PDO::FETCH_ASSOC ); // 6. Generate CREATE TABLE statement columns. - $rows = array(); + $rows = array(); + $has_auto_increment = false; foreach ( $column_info as $column ) { $sql = ' '; $sql .= $this->quote_mysql_identifier( $column['COLUMN_NAME'] ); @@ -6213,8 +6608,9 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str // Nullable "timestamp" columns dump NULL explicitly. $sql .= ' NULL'; } - if ( 'auto_increment' === $column['EXTRA'] ) { - $sql .= ' AUTO_INCREMENT'; + if ( str_contains( $column['EXTRA'], 'auto_increment' ) ) { + $has_auto_increment = true; + $sql .= ' AUTO_INCREMENT'; } // Handle DEFAULT CURRENT_TIMESTAMP. This works only with timestamp @@ -6353,6 +6749,28 @@ function ( $column ) { $sql .= implode( ",\n", $rows ); $sql .= "\n)"; $sql .= sprintf( ' ENGINE=%s', $table_info['ENGINE'] ); + + // Add "AUTO_INCREMENT=N" if a sequence exists and has been advanced. + if ( $has_auto_increment ) { + try { + $seq = (int) $this->execute_sqlite_query( + sprintf( + 'SELECT seq FROM %s.sqlite_sequence WHERE name = ?', + $table_is_temporary ? 'temp' : 'main' + ), + array( $table_name ) + )->fetchColumn(); + } catch ( PDOException $e ) { + if ( ! str_contains( $e->getMessage(), 'no such table' ) ) { + throw $e; + } + $seq = 0; + } + if ( $seq > 0 ) { + $sql .= sprintf( ' AUTO_INCREMENT=%d', $seq + 1 ); + } + } + $sql .= sprintf( ' DEFAULT CHARSET=%s', $charset ); $sql .= sprintf( ' COLLATE=%s', $collation ); if ( '' !== $table_info['TABLE_COMMENT'] ) { @@ -6527,6 +6945,7 @@ private function flush(): void { $this->last_column_meta = array(); $this->is_readonly = false; $this->wrapper_transaction_type = null; + $this->user_defined_functions->flush(); } /** diff --git a/wp-includes/sqlite-ast/class-wp-pdo-proxy-statement.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-proxy-statement.php similarity index 99% rename from wp-includes/sqlite-ast/class-wp-pdo-proxy-statement.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-proxy-statement.php index c5efd2b4..f56340d7 100644 --- a/wp-includes/sqlite-ast/class-wp-pdo-proxy-statement.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-proxy-statement.php @@ -196,7 +196,7 @@ public function fetch( */ #[ReturnTypeWillChange] public function fetchColumn( $column = 0 ) { - throw new RuntimeException( 'Not implemented' ); + return $this->statement->fetchColumn( $column ); } /** @@ -208,7 +208,7 @@ public function fetchColumn( $column = 0 ) { */ #[ReturnTypeWillChange] public function fetchObject( $class = 'stdClass', $constructorArgs = array() ) { - throw new RuntimeException( 'Not implemented' ); + return $this->statement->fetchObject( $class, $constructorArgs ); } /** diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-configurator.php similarity index 100% rename from wp-includes/sqlite-ast/class-wp-sqlite-configurator.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-configurator.php diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-connection.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-connection.php similarity index 100% rename from wp-includes/sqlite-ast/class-wp-sqlite-connection.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-connection.php diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-driver-exception.php similarity index 100% rename from wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-driver-exception.php diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-driver.php similarity index 99% rename from wp-includes/sqlite-ast/class-wp-sqlite-driver.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-driver.php index f33af1c5..04ec0630 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-driver.php @@ -161,10 +161,8 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo if ( $stmt->columnCount() > 0 ) { $this->last_result = $stmt->fetchAll( $fetch_mode ); - } elseif ( $stmt->rowCount() > 0 ) { - $this->last_result = $stmt->rowCount(); } else { - $this->last_result = null; + $this->last_result = $stmt->rowCount(); } return $this->last_result; } diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-builder.php similarity index 100% rename from wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-builder.php diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-exception.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-exception.php similarity index 100% rename from wp-includes/sqlite-ast/class-wp-sqlite-information-schema-exception.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-exception.php diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-reconstructor.php similarity index 90% rename from wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-reconstructor.php index ff91e0ec..103bb8c1 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-reconstructor.php @@ -623,6 +623,72 @@ private function get_cached_mysql_data_type( string $table_name, string $column_ return null; } + /** + * Check whether the stored type value is a valid MySQL column type. + * + * Some older versions of the legacy SQLite driver might have stored + * invalid MySQL column types in some scenarios: + * + * 1. Before https://github.com/WordPress/sqlite-database-integration/pull/126, + * the legacy SQLite driver incorrectly stored MySQL column types + * for columns with multiple type arguments. + * + * E.g., a column definition like "col_name decimal(26, 8)" would + * be stored with invalid type "decimal(26,". + * + * 2. Before https://github.com/WordPress/sqlite-database-integration/commit/b5a9fbaed4d0d843f792aaa959e3d00f193ff1ee + * (see also https://github.com/Automattic/sqlite-database-integration/pull/2), + * the legacy SQLite driver incorrectly recognized indexes on columns + * with type keywords as additional table column definitions. + * + * E.g., an index definition like "KEY timestamp (timestamp)" would + * be stored as column "KEY" with invalid type "timestamp(timestamp)". + * + * To address these issues, we need to check whether the stored type looks + * like a valid MySQL column type definition. + */ + $open_par_index = strpos( $mysql_type, '(' ); + $close_par_index = strpos( $mysql_type, ')' ); + if ( false !== $open_par_index ) { + $end = false !== $close_par_index ? $close_par_index : strlen( $mysql_type ); + $parts = explode( '(', substr( $mysql_type, 0, $end ) ); + $type = strtolower( trim( $parts[0] ) ); + $args = array(); + foreach ( explode( ',', $parts[1] ) as $arg ) { + $args[] = strtolower( trim( $arg ) ); + } + + // WooCommerce uses decimal(26,8), decimal(19,4), and decimal(3,2) + // column types, so we can can fix the invalid column definitions. + $looks_like_wc_table = str_contains( $table_name, 'wc_' ) || str_contains( $table_name, 'woocommerce_' ); + $is_invalid_decimal = 'decimal' === $type && count( $args ) === 2 && '' === $args[1]; + if ( $looks_like_wc_table && $is_invalid_decimal ) { + if ( '26' === $args[0] ) { + // Fix "decimal(26,". + return 'decimal(26,8)'; + } elseif ( '19' === $args[0] ) { + // Fix "decimal(19,". + return 'decimal(19,4)'; + } elseif ( '3' === $args[0] ) { + // Fix "decimal(3,". + return 'decimal(3,2)'; + } + } + + // Only numeric arguments are allowed for MySQL column types. + // This handles the incorrectly stored index definition case. + foreach ( $args as $arg ) { + if ( ! is_numeric( $arg ) ) { + return null; + } + } + + // If there is no closing parenthesis, the type is invalid. + if ( false === $close_par_index ) { + return null; + } + } + // Normalize index type for backward compatibility. Some older versions // of the SQLite driver stored index types with a " KEY" suffix, e.g., // "PRIMARY KEY" or "UNIQUE KEY". More recent versions omit the suffix. diff --git a/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-pdo-user-defined-functions.php similarity index 80% rename from wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php rename to packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-pdo-user-defined-functions.php index 5959868a..7abd5add 100644 --- a/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-pdo-user-defined-functions.php @@ -80,6 +80,8 @@ public static function register_for( $pdo ): self { 'ucase' => 'ucase', 'lcase' => 'lcase', 'unhex' => 'unhex', + 'from_base64' => 'from_base64', + 'to_base64' => 'to_base64', 'inet_ntoa' => 'inet_ntoa', 'inet_aton' => 'inet_aton', 'datediff' => 'datediff', @@ -93,6 +95,39 @@ public static function register_for( $pdo ): self { '_helper_like_to_glob_pattern' => '_helper_like_to_glob_pattern', ); + /** + * First element of the RAND(N) LCG state (the value the output is derived from). + * + * @var int|null + */ + private $rand_seed1 = null; + + /** + * Second element of the RAND(N) LCG state (the paired value used in the recurrence). + * + * @var int|null + */ + private $rand_seed2 = null; + + /** + * Last seed value passed to RAND(N) in the current statement. + * + * Used to detect whether the rand sequence is advancing with the same seed + * (e.g. "SELECT RAND(3) FROM t"), or reseeding (starting a new sequence). + * + * @var int|null + */ + private $rand_last_seed = null; + + /** + * Clear any per-statement state held by the UDFs. + */ + public function flush(): void { + $this->rand_seed1 = null; + $this->rand_seed2 = null; + $this->rand_last_seed = null; + } + /** * A helper function to throw an error from SQLite expressions. * @@ -165,19 +200,74 @@ public function md5( $field ) { } /** - * Method to emulate MySQL RAND() function. + * Method to emulate MySQL's seeded RAND(N) function. * - * SQLite does have a random generator, but it is called RANDOM() and returns random - * number between -9223372036854775808 and +9223372036854775807. So we substitute it - * with PHP random generator. + * Implements MySQL's deterministic LCG (Linear Congruential Generator), + * producing bit-exact output for a given seed. * - * This function uses mt_rand() which is four times faster than rand() and returns - * the random number between 0 and 1. + * Known divergences from MySQL: * - * @return int + * 1. In MySQL, RAND(N) behaves differently depending on whether the seed + * is constant expression or varies per invocation: + * - Constant seed (e.g. "SELECT RAND(3) FROM t"): + * LCG is initialized once per statement and advanced for each row. + * - Non-constant seed (e.g. "SELECT RAND(col) FROM t"): + * LCG is initialized for every row with its seed value. + * + * A SQLite UDF cannot tell whether the seed expression is constant, so + * we just compare the seed against its last value. This diverges from + * MySQL in rare cases, and we can consider improving it in the future. + * + * 2. The LCG state is shared across call sites in the same query, so + * "SELECT RAND(1), RAND(1)" yields different results here than in MySQL. + * This is a rare edge case that we can consider improving in the future. + * + * Unseeded RAND() never reaches this function. The AST driver translates it + * directly to a more efficient SQLite-native expression. + * + * @param int|float|string|null $seed Seed value. + * + * @return float A value in [0, 1). */ - public function rand() { - return mt_rand( 0, 1 ); + public function rand( $seed ) { + // Requires 64-bit PHP. Seed * 0x10000001 can exceed PHP_INT_MAX on 32-bit. + $max_value = 0x3FFFFFFF; + + if ( null === $seed ) { + // MySQL treats NULL seed as 0. + $seed = 0; + } elseif ( ! is_int( $seed ) ) { + /* + * MySQL rounds float values and numeric strings take the same path. + * Reduce the value to a 32-bit range using "fmod" to avoid firing + * the "out-of-range float to int" cast deprecation on PHP 8.1+. + */ + $seed = (int) fmod( round( (float) $seed, 0, PHP_ROUND_HALF_EVEN ), 0x100000000 ); + } + + // Initialize MySQL's internal 30-bit seeds. + if ( $seed !== $this->rand_last_seed ) { + /* + * MySQL casts to uint32, and the intermediate results wrap at 32-bit + * unsigned boundaries. We emulate this with & 0xFFFFFFFF masks. + */ + $seed_u32 = $seed & 0xFFFFFFFF; + $this->rand_seed1 = ( ( $seed_u32 * 0x10001 + 55555555 ) & 0xFFFFFFFF ) % $max_value; + $this->rand_seed2 = ( ( $seed_u32 * 0x10000001 ) & 0xFFFFFFFF ) % $max_value; + $this->rand_last_seed = $seed; + } + + /* + * MySQL's LCG recurrence: + * seed1 = (seed1 * 3 + seed2) % max_value + * seed2 = (seed1 + seed2 + 33) % max_value + * + * Note that seed1 is updated first and the new value is used for seed2. + */ + $this->rand_seed1 = ( $this->rand_seed1 * 3 + $this->rand_seed2 ) % $max_value; + $this->rand_seed2 = ( $this->rand_seed1 + $this->rand_seed2 + 33 ) % $max_value; + + return (float) $this->rand_seed1 / (float) $max_value; } /** @@ -236,6 +326,14 @@ public function dateformat( $date, $format ) { * @return string Representing the number of the month between 1 and 12. */ public function month( $field ) { + /* + * MySQL returns 0 for MONTH('0000-00-00') and for dates with + * zero month parts like '2020-00-15'. PHP's strtotime() can't + * parse these, so we extract the month directly from the string. + */ + if ( preg_match( '/^\d{4}-(\d{2})/', $field, $matches ) ) { + return intval( $matches[1] ); + } /* * From https://www.php.net/manual/en/datetime.format.php: * @@ -253,6 +351,13 @@ public function month( $field ) { * @return string Representing the number of the year. */ public function year( $field ) { + /* + * MySQL returns 0 for YEAR('0000-00-00'). PHP's strtotime() + * can't parse zero dates, so we extract the year directly. + */ + if ( preg_match( '/^(\d{4})-\d{2}/', $field, $matches ) ) { + return intval( $matches[1] ); + } /* * From https://www.php.net/manual/en/datetime.format.php: * @@ -269,6 +374,14 @@ public function year( $field ) { * @return string Representing the number of the day of the month from 1 and 31. */ public function day( $field ) { + /* + * MySQL returns 0 for DAY('0000-00-00') and for dates with + * zero day parts like '2020-01-00'. PHP's strtotime() can't + * parse these, so we extract the day directly from the string. + */ + if ( preg_match( '/^\d{4}-\d{2}-(\d{2})/', $field, $matches ) ) { + return intval( $matches[1] ); + } /* * From https://www.php.net/manual/en/datetime.format.php: * @@ -667,6 +780,44 @@ public function unhex( $number ) { return pack( 'H*', $number ); } + /** + * Method to emulate MySQL FROM_BASE64() function. + * + * Takes a base64-encoded string and returns the decoded result as a binary + * string. Returns NULL if the argument is NULL or is not a valid base64 string. + * + * @param string|null $str The base64-encoded string. + * + * @return string|null Decoded binary string, or NULL. + */ + public function from_base64( $str ) { + if ( null === $str ) { + return null; + } + $decoded = base64_decode( $str, true ); + if ( false === $decoded ) { + return null; + } + return $decoded; + } + + /** + * Method to emulate MySQL TO_BASE64() function. + * + * Takes a string and returns a base64-encoded result. + * Returns NULL if the argument is NULL. + * + * @param string|null $str The string to encode. + * + * @return string|null Base64-encoded string, or NULL. + */ + public function to_base64( $str ) { + if ( null === $str ) { + return null; + } + return base64_encode( $str ); + } + /** * Method to emulate MySQL INET_NTOA() function. * diff --git a/version.php b/packages/mysql-on-sqlite/src/version.php similarity index 69% rename from version.php rename to packages/mysql-on-sqlite/src/version.php index 3fac28ba..918d9735 100644 --- a/version.php +++ b/packages/mysql-on-sqlite/src/version.php @@ -5,4 +5,4 @@ * * This constant needs to be updated on plugin release! */ -define( 'SQLITE_DRIVER_VERSION', '2.2.16' ); +define( 'SQLITE_DRIVER_VERSION', '3.0.0-rc.3' ); diff --git a/tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php b/packages/mysql-on-sqlite/tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php similarity index 78% rename from tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php rename to packages/mysql-on-sqlite/tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php index d9b474fb..9ac8a301 100644 --- a/tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php @@ -2,6 +2,20 @@ use PHPUnit\Framework\TestCase; +// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound +class FetchObjectTestClass { + public $col1; + public $col2; + public $col3; + public $arg1; + public $arg2; + + public function __construct( $arg1 = null, $arg2 = null ) { + $this->arg1 = $arg1; + $this->arg2 = $arg2; + } +} + class WP_PDO_MySQL_On_SQLite_PDO_API_Tests extends TestCase { /** @var WP_PDO_MySQL_On_SQLite */ private $driver; @@ -324,6 +338,106 @@ public function test_fetch( $query, $mode, $expected ): void { } } + public function test_fetch_column(): void { + $query = " + SELECT 1, 'abc', true + UNION ALL + SELECT 2, 'xyz', false + UNION ALL + SELECT 3, null, null + "; + + // Fetch first column (default). + $stmt = $this->driver->query( $query ); + $this->assertSame( '1', $stmt->fetchColumn() ); + $this->assertSame( '2', $stmt->fetchColumn() ); + $this->assertSame( '3', $stmt->fetchColumn() ); + $this->assertFalse( $stmt->fetchColumn() ); + + // Fetch second column. + $stmt = $this->driver->query( $query ); + $this->assertSame( 'abc', $stmt->fetchColumn( 1 ) ); + $this->assertSame( 'xyz', $stmt->fetchColumn( 1 ) ); + $this->assertNull( $stmt->fetchColumn( 1 ) ); + $this->assertFalse( $stmt->fetchColumn( 1 ) ); + + // Fetch third column. + $stmt = $this->driver->query( $query ); + $this->assertSame( '1', $stmt->fetchColumn( 2 ) ); + $this->assertSame( '0', $stmt->fetchColumn( 2 ) ); + $this->assertNull( $stmt->fetchColumn( 2 ) ); + $this->assertFalse( $stmt->fetchColumn( 2 ) ); + + // Fetch different columns across rows. + $stmt = $this->driver->query( $query ); + $this->assertSame( '1', $stmt->fetchColumn( 0 ) ); + $this->assertSame( 'xyz', $stmt->fetchColumn( 1 ) ); + $this->assertNull( $stmt->fetchColumn( 2 ) ); + $this->assertFalse( $stmt->fetchColumn() ); + } + + public function test_fetch_column_invalid_index(): void { + $stmt = $this->driver->query( "SELECT 1, 'abc', true" ); + + if ( PHP_VERSION_ID < 80000 ) { + $this->expectException( PDOException::class ); + $this->expectExceptionMessage( 'Invalid column index' ); + } else { + $this->expectException( ValueError::class ); + $this->expectExceptionMessage( 'Invalid column index' ); + } + $stmt->fetchColumn( 3 ); + } + + public function test_fetch_column_negative_index(): void { + $stmt = $this->driver->query( "SELECT 1, 'abc', true" ); + + if ( PHP_VERSION_ID < 80000 ) { + $this->expectException( PDOException::class ); + $this->expectExceptionMessage( 'Invalid column index' ); + } else { + $this->expectException( ValueError::class ); + $this->expectExceptionMessage( 'Column index must be greater than or equal to 0' ); + } + $stmt->fetchColumn( -1 ); + } + + public function test_fetch_obj(): void { + // No arguments (stdClass). + $stmt = $this->driver->query( "SELECT 1, 'abc', true" ); + $this->assertEquals( + (object) array( + 1 => '1', + 'abc' => 'abc', + 'true' => true, + ), + $stmt->fetchObject() + ); + $this->assertFalse( $stmt->fetchObject() ); + + // Custom class. + $stmt = $this->driver->query( "SELECT 1 AS col1, 'abc' AS col2, true AS col3" ); + $result = $stmt->fetchObject( FetchObjectTestClass::class ); + $this->assertInstanceOf( FetchObjectTestClass::class, $result ); + $this->assertSame( '1', $result->col1 ); + $this->assertSame( 'abc', $result->col2 ); + $this->assertSame( '1', $result->col3 ); + $this->assertNull( $result->arg1 ); + $this->assertNull( $result->arg2 ); + $this->assertFalse( $stmt->fetchObject( FetchObjectTestClass::class ) ); + + // Custom class with constructor arguments. + $stmt = $this->driver->query( "SELECT 1 AS col1, 'abc' AS col2, true AS col3" ); + $result = $stmt->fetchObject( FetchObjectTestClass::class, array( 'val1', 'val2' ) ); + $this->assertInstanceOf( FetchObjectTestClass::class, $result ); + $this->assertSame( '1', $result->col1 ); + $this->assertSame( 'abc', $result->col2 ); + $this->assertSame( '1', $result->col3 ); + $this->assertSame( 'val1', $result->arg1 ); + $this->assertSame( 'val2', $result->arg2 ); + $this->assertFalse( $stmt->fetchObject( FetchObjectTestClass::class, array( 'val1', 'val2' ) ) ); + } + public function test_attr_default_fetch_mode(): void { $this->driver->setAttribute( PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_NUM ); $result = $this->driver->query( "SELECT 'a', 'b', 'c'" ); diff --git a/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Concurrency_Tests.php b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Concurrency_Tests.php new file mode 100644 index 00000000..adad1c0b --- /dev/null +++ b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Concurrency_Tests.php @@ -0,0 +1,136 @@ +db_path = tempnam( sys_get_temp_dir(), 'wp_sqlite_' ); + unlink( $this->db_path ); // Remove so SQLite creates a fresh database. + } + + public function tearDown(): void { + foreach ( array( + $this->db_path, + $this->db_path . '-wal', + $this->db_path . '-shm', + $this->db_path . '-journal', + ) as $path ) { + if ( is_string( $path ) && file_exists( $path ) ) { + unlink( $path ); + } + } + $this->db_path = null; + } + + public function testSelectQueryIsNotWrappedInTransaction(): void { + $driver = $this->create_in_memory_driver(); + $driver->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' ); + + $driver->query( 'SELECT * FROM t' ); + + $this->assertStringStartsNotWith( 'BEGIN', $driver->get_last_sqlite_queries()[0]['sql'] ); + } + + public function testShowQueryOpensReadOnlyTransaction(): void { + $driver = $this->create_in_memory_driver(); + $driver->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' ); + + $driver->query( 'SHOW TABLES' ); + + $this->assertSame( 'BEGIN', $driver->get_last_sqlite_queries()[0]['sql'] ); + } + + public function testDescribeQueryOpensReadOnlyTransaction(): void { + $driver = $this->create_in_memory_driver(); + $driver->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' ); + + $driver->query( 'DESCRIBE t' ); + + $this->assertSame( 'BEGIN', $driver->get_last_sqlite_queries()[0]['sql'] ); + } + + /** + * @dataProvider provideWriteStatements + */ + public function testWriteQueryOpensWriteTransaction( string $query ): void { + $driver = $this->create_in_memory_driver(); + $driver->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' ); + $driver->query( "INSERT INTO t VALUES (1, 'Alice')" ); + + $driver->query( $query ); + + $this->assertSame( 'BEGIN IMMEDIATE', $driver->get_last_sqlite_queries()[0]['sql'] ); + } + + public function provideWriteStatements(): array { + return array( + 'INSERT' => array( "INSERT INTO t VALUES (2, 'Bob')" ), + 'UPDATE' => array( "UPDATE t SET name = 'Carol' WHERE id = 1" ), + 'DELETE' => array( 'DELETE FROM t WHERE id = 1' ), + 'REPLACE' => array( "REPLACE INTO t VALUES (1, 'Dan')" ), + 'CREATE TABLE' => array( 'CREATE TABLE u (id INT)' ), + 'ALTER TABLE' => array( 'ALTER TABLE t ADD COLUMN x INT' ), + 'DROP TABLE' => array( 'DROP TABLE t' ), + 'TRUNCATE TABLE' => array( 'TRUNCATE TABLE t' ), + ); + } + + public function testSelectQuerySucceedsWhileAnotherConnectionHoldsWriteLock(): void { + $this->assertReadOnlyQuerySucceedsUnderWriteLock( 'SELECT * FROM t' ); + } + + public function testShowQuerySucceedsWhileAnotherConnectionHoldsWriteLock(): void { + $this->assertReadOnlyQuerySucceedsUnderWriteLock( 'SHOW TABLES' ); + } + + public function testDescribeQuerySucceedsWhileAnotherConnectionHoldsWriteLock(): void { + $this->assertReadOnlyQuerySucceedsUnderWriteLock( 'DESCRIBE t' ); + } + + private function assertReadOnlyQuerySucceedsUnderWriteLock( string $query ): void { + // Connection A: set up the database. + $conn_a = new WP_SQLite_Connection( array( 'path' => $this->db_path ) ); + $driver_a = new WP_SQLite_Driver( $conn_a, 'wp' ); + $driver_a->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' ); + $driver_a->query( "INSERT INTO t VALUES (1, 'Alice')" ); + + // Simulate another PHP process holding a write transaction. + $conn_a->get_pdo()->exec( 'BEGIN IMMEDIATE' ); + + try { + // Connection B with zero timeout — any lock conflict fails immediately. + $conn_b = new WP_SQLite_Connection( + array( + 'path' => $this->db_path, + 'timeout' => 0, + ) + ); + $driver_b = new WP_SQLite_Driver( $conn_b, 'wp' ); + $conn_b->get_pdo()->setAttribute( PDO::ATTR_TIMEOUT, 0 ); + + $result = $driver_b->query( $query ); + + $this->assertIsArray( $result ); + $this->assertNotEmpty( $result ); + } finally { + $conn_a->get_pdo()->exec( 'ROLLBACK' ); + } + } + + private function create_in_memory_driver(): WP_SQLite_Driver { + $pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class; + $pdo = new $pdo_class( 'sqlite::memory:' ); + $connection = new WP_SQLite_Connection( array( 'pdo' => $pdo ) ); + return new WP_SQLite_Driver( $connection, 'wp' ); + } +} diff --git a/tests/WP_SQLite_Driver_Metadata_Tests.php b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Metadata_Tests.php similarity index 88% rename from tests/WP_SQLite_Driver_Metadata_Tests.php rename to packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Metadata_Tests.php index 6be0a542..6b6f4b7f 100644 --- a/tests/WP_SQLite_Driver_Metadata_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Metadata_Tests.php @@ -628,6 +628,227 @@ public function testShowTableStatus() { ); } + public function testInformationSchemaTablesAutoIncrement(): void { + // A non-AUTO_INCREMENT table reports NULL. + $this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' ); + $result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 'plain'" ); + $this->assertSame( null, $result[0]->AUTO_INCREMENT ); + + // A fresh AUTO_INCREMENT table reports 1. + $this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + $result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 't'" ); + $this->assertSame( '1', $result[0]->AUTO_INCREMENT ); + + // After inserts, the sequence advances. + $this->assertQuery( "INSERT INTO t (name) VALUES ('a'), ('b'), ('c')" ); + $result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 't'" ); + $this->assertSame( '4', $result[0]->AUTO_INCREMENT ); + + // DELETE preserves the counter. + $this->assertQuery( 'DELETE FROM t' ); + $result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 't'" ); + $this->assertSame( '4', $result[0]->AUTO_INCREMENT ); + + // TRUNCATE resets the counter. + $this->assertQuery( 'TRUNCATE TABLE t' ); + $result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 't'" ); + $this->assertSame( '1', $result[0]->AUTO_INCREMENT ); + } + + public function testInformationSchemaTablesFilterByAutoIncrement(): void { + $this->assertQuery( 'CREATE TABLE low (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + $this->assertQuery( 'CREATE TABLE high (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + $this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' ); + $this->assertQuery( "INSERT INTO low (name) VALUES ('a')" ); + $this->assertQuery( "INSERT INTO high (name) VALUES ('a'), ('b'), ('c'), ('d'), ('e')" ); + + // > 3 + $result = $this->assertQuery( + 'SELECT TABLE_NAME FROM information_schema.tables WHERE `AUTO_INCREMENT` > 3' + ); + $this->assertCount( 1, $result ); + $this->assertSame( 'high', $result[0]->TABLE_NAME ); + + // IS NULL + $result = $this->assertQuery( + 'SELECT TABLE_NAME FROM information_schema.tables WHERE `AUTO_INCREMENT` IS NULL' + ); + $this->assertCount( 1, $result ); + $this->assertSame( 'plain', $result[0]->TABLE_NAME ); + } + + public function testShowTableStatusAutoIncrement(): void { + // A non-AUTO_INCREMENT table reports NULL. + $this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 'plain'" ); + $this->assertSame( null, $result[0]->Auto_increment ); + + // A fresh AUTO_INCREMENT table reports 1. + $this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '1', $result[0]->Auto_increment ); + + // After inserts, the sequence advances. + $this->assertQuery( "INSERT INTO t (name) VALUES ('a'), ('b'), ('c')" ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '4', $result[0]->Auto_increment ); + + // DELETE preserves the counter. + $this->assertQuery( 'DELETE FROM t' ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '4', $result[0]->Auto_increment ); + + // TRUNCATE resets the counter. + $this->assertQuery( 'TRUNCATE TABLE t' ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '1', $result[0]->Auto_increment ); + } + + public function testShowTableStatusFilterByAutoIncrement(): void { + $this->assertQuery( 'CREATE TABLE low (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + $this->assertQuery( 'CREATE TABLE high (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + $this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' ); + $this->assertQuery( "INSERT INTO low (name) VALUES ('a')" ); + $this->assertQuery( "INSERT INTO high (name) VALUES ('a'), ('b'), ('c'), ('d'), ('e')" ); + + // > 3 + $result = $this->assertQuery( 'SHOW TABLE STATUS WHERE `Auto_increment` > 3' ); + $this->assertCount( 1, $result ); + $this->assertSame( 'high', $result[0]->Name ); + + // IS NULL + $result = $this->assertQuery( 'SHOW TABLE STATUS WHERE `Auto_increment` IS NULL' ); + $this->assertCount( 1, $result ); + $this->assertSame( 'plain', $result[0]->Name ); + } + + public function testShowCreateTableAutoIncrement(): void { + // No AUTO_INCREMENT=N on a non-AUTO_INCREMENT table. + $this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' ); + $result = $this->assertQuery( 'SHOW CREATE TABLE plain' ); + $this->assertStringNotContainsString( 'AUTO_INCREMENT=', $result[0]->{'Create Table'} ); + + // No AUTO_INCREMENT=N on a fresh AUTO_INCREMENT table (sequence not advanced). + $this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertStringNotContainsString( 'AUTO_INCREMENT=', $result[0]->{'Create Table'} ); + + // After inserts, the sequence advances. + $this->assertQuery( "INSERT INTO t (name) VALUES ('a'), ('b'), ('c')" ); + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertStringContainsString( 'AUTO_INCREMENT=4', $result[0]->{'Create Table'} ); + + // DELETE preserves the counter. + $this->assertQuery( 'DELETE FROM t' ); + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertStringContainsString( 'AUTO_INCREMENT=4', $result[0]->{'Create Table'} ); + + // TRUNCATE resets the counter. + $this->assertQuery( 'TRUNCATE TABLE t' ); + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertStringNotContainsString( 'AUTO_INCREMENT=', $result[0]->{'Create Table'} ); + } + + + public function testCreateTableSetAutoIncrement(): void { + $this->assertQuery( + 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT) AUTO_INCREMENT=100' + ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '100', $result[0]->Auto_increment ); + + // INSERT advances the sequence. + $this->assertQuery( "INSERT INTO t (name) VALUES ('a')" ); + $this->assertSame( '100', $this->assertQuery( 'SELECT id FROM t' )[0]->id ); + + // SHOW TABLE STATUS + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '101', $result[0]->Auto_increment ); + + // SHOW CREATE TABLE + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertStringContainsString( 'AUTO_INCREMENT=101', $result[0]->{'Create Table'} ); + + // Without an AUTO_INCREMENT column, the option is ignored. + $this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT) AUTO_INCREMENT=500' ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 'plain'" ); + $this->assertSame( null, $result[0]->Auto_increment ); + } + + public function testAlterTableSetAutoIncrement(): void { + $this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + + // Set the sequence value on an empty table. + $this->assertQuery( 'ALTER TABLE t AUTO_INCREMENT = 50' ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '50', $result[0]->Auto_increment ); + + // INSERT advances the sequence. + $this->assertQuery( "INSERT INTO t (name) VALUES ('a')" ); + $this->assertSame( '50', $this->assertQuery( 'SELECT id FROM t' )[0]->id ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '51', $result[0]->Auto_increment ); + + // Update the sequence value. + $this->assertQuery( 'ALTER TABLE t AUTO_INCREMENT = 200' ); + + // SHOW TABLE STATUS + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '200', $result[0]->Auto_increment ); + + // SHOW CREATE TABLE + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertStringContainsString( 'AUTO_INCREMENT=200', $result[0]->{'Create Table'} ); + + // Lowering the counter at or below MAX(id) clamps to MAX(id) + 1. + $this->assertQuery( 'ALTER TABLE t AUTO_INCREMENT = 1' ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '51', $result[0]->Auto_increment ); + + // Without an AUTO_INCREMENT column, the option is ignored. + $this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT) AUTO_INCREMENT=500' ); + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 'plain'" ); + $this->assertSame( null, $result[0]->Auto_increment ); + } + + public function testTemporaryTableAutoIncrement(): void { + // Persistent and temporary tables must keep independent sequences. + + // Persistent table (next sequence value is 3). + $this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' ); + $this->assertQuery( "INSERT INTO t (name) VALUES ('a'), ('b')" ); + + // Temporary table with the same name (seeded to start at 500). + $this->assertQuery( + 'CREATE TEMPORARY TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT) AUTO_INCREMENT=500' + ); + + // SHOW TABLE STATUS lists only persistent tables. + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertCount( 1, $result ); + $this->assertSame( '3', $result[0]->Auto_increment ); + + // INFORMATION_SCHEMA.TABLES lists only persistent tables. + $result = $this->assertQuery( + "SELECT `AUTO_INCREMENT` FROM information_schema.tables WHERE table_name = 't'" + ); + $this->assertCount( 1, $result ); + $this->assertSame( '3', $result[0]->AUTO_INCREMENT ); + + // Unqualified statements target the temporary table. + $this->assertQuery( "INSERT INTO t (name) VALUES ('x')" ); + $result = $this->assertQuery( "SELECT id FROM t WHERE name = 'x'" ); + $this->assertSame( '500', $result[0]->id ); + + $this->assertQuery( 'ALTER TABLE t AUTO_INCREMENT = 1000' ); + $this->assertQuery( "INSERT INTO t (name) VALUES ('y')" ); + $this->assertSame( '1000', $this->assertQuery( "SELECT id FROM t WHERE name = 'y'" )[0]->id ); + + // The persistent table's sequence remains unaffected. + $result = $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" ); + $this->assertSame( '3', $result[0]->Auto_increment ); + } + public function testShowFullColumns(): void { $this->assertQuery( "CREATE TABLE t (id INT COMMENT 'Comment ID', name TEXT COMMENT 'Comment Name')" ); $result = $this->assertQuery( 'SHOW FULL COLUMNS FROM t' ); @@ -719,7 +940,7 @@ public function testTruncateTable() { 'TRUNCATE TABLE wp_comments;' ); $actual = $this->engine->get_query_results(); - $this->assertNull( $actual ); + $this->assertSame( 0, $actual ); $this->assertTableEmpty( 'wp_comments', true ); } diff --git a/tests/WP_SQLite_Driver_Query_Tests.php b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Query_Tests.php similarity index 100% rename from tests/WP_SQLite_Driver_Query_Tests.php rename to packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Query_Tests.php diff --git a/tests/WP_SQLite_Driver_Tests.php b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Tests.php similarity index 90% rename from tests/WP_SQLite_Driver_Tests.php rename to packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Tests.php index 95a478db..18a0424c 100644 --- a/tests/WP_SQLite_Driver_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Tests.php @@ -222,16 +222,119 @@ public function testUpdateWithoutWhereButWithLimit() { $this->assertEquals( '2003-05-27 10:08:48', $result2[0]->option_value ); } - public function testCastAsBinary() { + public function testDeleteWithLimit() { + $this->assertQuery( + "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 00:00:45')" + ); + $this->assertQuery( + "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2003-05-28 00:00:45')" + ); + $this->assertQuery( + "INSERT INTO _dates (option_name, option_value) VALUES ('third', '2003-05-29 00:00:45')" + ); + + // LIMIT only: only one row is removed even though WHERE matches every row. + $result = $this->assertQuery( "DELETE FROM _dates WHERE option_name LIKE '%' LIMIT 1" ); + $this->assertSame( 1, $result ); + + $rows = $this->engine->query( 'SELECT option_name FROM _dates ORDER BY option_name' ); + $this->assertCount( 2, $rows ); + + // ORDER BY + LIMIT: deletes the lexicographically-first remaining row. + $result = $this->assertQuery( 'DELETE FROM _dates ORDER BY option_name ASC LIMIT 1' ); + $this->assertSame( 1, $result ); + + $rows = $this->engine->query( 'SELECT option_name FROM _dates' ); + $this->assertCount( 1, $rows ); + $this->assertSame( 'third', $rows[0]->option_name ); + } + + public function testDeleteWithoutWhereButWithLimit() { + $this->assertQuery( + "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48')" + ); + $this->assertQuery( + "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2003-05-27 10:08:48')" + ); + + $result = $this->assertQuery( 'DELETE FROM _dates LIMIT 1' ); + $this->assertSame( 1, $result ); + + $rows = $this->engine->query( 'SELECT option_name FROM _dates' ); + $this->assertCount( 1, $rows ); + } + + public function testDeleteWithAliasAndLimit() { + $this->assertQuery( + "INSERT INTO _dates (option_name, option_value) VALUES ('a', '2003-05-27 00:00:45')" + ); $this->assertQuery( - // Use a confusing alias to make sure it replaces only the correct token - "SELECT CAST('ABC' AS BINARY) as `binary`;" + "INSERT INTO _dates (option_name, option_value) VALUES ('b', '2003-05-28 00:00:45')" ); + + $result = $this->assertQuery( "DELETE FROM _dates AS d WHERE d.option_name = 'a' LIMIT 1" ); + $this->assertSame( 1, $result ); + + $rows = $this->engine->query( 'SELECT option_name FROM _dates' ); + $this->assertCount( 1, $rows ); + $this->assertSame( 'b', $rows[0]->option_name ); + } + + public function testCastAsBinary() { + // Use a confusing alias to make sure it replaces only the correct token + $this->assertQuery( "SELECT CAST('ABC' AS BINARY) as `binary`" ); $results = $this->engine->get_query_results(); $this->assertCount( 1, $results ); $this->assertEquals( 'ABC', $results[0]->binary ); } + public function testBinaryOperator() { + // Use a NOCASE column so plain "=" is case-insensitive. This makes the + // BINARY operator's effect (forcing byte-by-byte comparison) visible. + $this->assertQuery( 'CREATE TABLE t (name TEXT COLLATE NOCASE NOT NULL)' ); + $this->assertQuery( "INSERT INTO t (name) VALUES ('abc'), ('ABC')" ); + + // Sanity: with NOCASE, plain "=" matches both rows. + $result = $this->assertQuery( "SELECT name FROM t WHERE name = 'abc' ORDER BY name" ); + $this->assertCount( 2, $result ); + + // "= BINARY 'x'" forces byte-by-byte equality. + $result = $this->assertQuery( "SELECT name FROM t WHERE name = BINARY 'abc'" ); + $this->assertCount( 1, $result ); + $this->assertEquals( 'abc', $result[0]->name ); + + // "BINARY x = 'X'" is symmetric. + $result = $this->assertQuery( "SELECT name FROM t WHERE BINARY name = 'ABC'" ); + $this->assertCount( 1, $result ); + $this->assertEquals( 'ABC', $result[0]->name ); + + // "ORDER BY BINARY" sorts by byte value ('ABC' before 'abc'). + $result = $this->assertQuery( 'SELECT name FROM t ORDER BY BINARY name' ); + $this->assertEquals( 'ABC', $result[0]->name ); + $this->assertEquals( 'abc', $result[1]->name ); + + // CAST(expr AS BINARY) = expr + $result = $this->assertQuery( "SELECT CAST('abc' AS BINARY) = 'abc' AS r" ); + $this->assertEquals( 1, $result[0]->r ); + + // CONVERT(expr, BINARY) = expr + $result = $this->assertQuery( "SELECT CONVERT('abc', BINARY) = 'abc' AS r" ); + $this->assertEquals( 1, $result[0]->r ); + + // The "information_schema.TABLES.TABLE_NAME" column uses COLLATE NOCASE. + // Let's verify that the BINARY override works for this scenario as well. + $this->assertQuery( 'CREATE TABLE CaseSensitive (id INT)' ); + + $result = $this->assertQuery( "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME = 'casesensitive'" ); + $this->assertCount( 1, $result ); + + $result = $this->assertQuery( "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME = BINARY 'casesensitive'" ); + $this->assertCount( 0, $result ); + + $result = $this->assertQuery( "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME = BINARY 'CaseSensitive'" ); + $this->assertCount( 1, $result ); + } + public function testSelectFromDual() { $result = $this->assertQuery( 'SELECT 1 as output FROM DUAL' @@ -889,7 +992,7 @@ public function testShowTableStatusWhere() { ); $this->assertQuery( - "SHOW TABLE STATUS WHERE SUBSTR(table_name, 11, 1) = '1'" + "SHOW TABLE STATUS WHERE SUBSTR(Name, 11, 1) = '1'" ); $this->assertCount( 1, @@ -920,7 +1023,7 @@ public function testCreateTable() { KEY user_email (user_email) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci" ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $this->assertQuery( 'DESCRIBE wptests_users;' ); $results = $this->engine->get_query_results(); @@ -1018,7 +1121,7 @@ public function testCreateTableWithTrailingComma() { PRIMARY KEY (ID) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); } public function testCreateTableSpatialIndex() { @@ -1028,7 +1131,7 @@ public function testCreateTableSpatialIndex() { UNIQUE KEY (ID) )' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); } public function testCreateTableWithMultiValueColumnTypeModifiers() { @@ -1041,7 +1144,7 @@ enum_column ENUM('a', 'b', 'c') NOT NULL DEFAULT 'a', PRIMARY KEY (ID) )" ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $this->assertQuery( 'DESCRIBE wptests_users;' ); $results = $this->engine->get_query_results(); @@ -1090,10 +1193,10 @@ public function testAlterTableAddAndDropColumn() { name varchar(20) NOT NULL default '' );" ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD COLUMN `column` int;' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $this->assertQuery( 'DESCRIBE _tmp_table;' ); $results = $this->engine->get_query_results(); @@ -1120,7 +1223,7 @@ public function testAlterTableAddAndDropColumn() { ); $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD `column2` int;' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $this->assertQuery( 'DESCRIBE _tmp_table;' ); $results = $this->engine->get_query_results(); @@ -1155,7 +1258,7 @@ public function testAlterTableAddAndDropColumn() { ); $result = $this->assertQuery( 'ALTER TABLE _tmp_table DROP COLUMN `column`;' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $this->assertQuery( 'DESCRIBE _tmp_table;' ); $results = $this->engine->get_query_results(); @@ -1182,7 +1285,7 @@ public function testAlterTableAddAndDropColumn() { ); $result = $this->assertQuery( 'ALTER TABLE _tmp_table DROP `column2`;' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $this->assertQuery( 'DESCRIBE _tmp_table;' ); $results = $this->engine->get_query_results(); @@ -1209,7 +1312,7 @@ public function testAlterTableAddNotNullVarcharColumn() { ); $result = $this->assertQuery( "ALTER TABLE _tmp_table ADD COLUMN `column` VARCHAR(20) NOT NULL DEFAULT 'foo';" ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $this->assertQuery( 'DESCRIBE _tmp_table;' ); $results = $this->engine->get_query_results(); @@ -1809,7 +1912,7 @@ public function testAlterTableAddIndex() { ); $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD INDEX name (name);' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Verify that the index was created in the information schema. $this->assertQuery( 'SHOW INDEX FROM _tmp_table;' ); @@ -1862,7 +1965,7 @@ public function testAlterTableAddUniqueIndex() { ); $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD UNIQUE INDEX name (name(20));' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Verify that the index was created in the information schema. $this->assertQuery( 'SHOW INDEX FROM _tmp_table;' ); @@ -1915,7 +2018,7 @@ public function testAlterTableAddFulltextIndex() { ); $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD FULLTEXT INDEX name (name);' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Verify that the index was created in the information schema. $this->assertQuery( 'SHOW INDEX FROM _tmp_table;' ); @@ -2038,7 +2141,7 @@ public function testAlterTableModifyColumn() { // Rename the "name" field to "firstname": $result = $this->engine->query( "ALTER TABLE _tmp_table CHANGE column name firstname varchar(50) NOT NULL default 'mark';" ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Confirm the original data is still there: $result = $this->engine->query( 'SELECT * FROM _tmp_table;' ); @@ -2108,7 +2211,7 @@ public function testAlterTableModifyColumnWithSkippedColumnKeyword() { // Rename the "name" field to "firstname": $result = $this->engine->query( "ALTER TABLE _tmp_table CHANGE name firstname varchar(50) NOT NULL default 'mark';" ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Confirm the original data is still there: $result = $this->engine->query( 'SELECT * FROM _tmp_table;' ); @@ -2149,12 +2252,12 @@ public function testAlterTableModifyColumnWithHyphens() { `foo-bar` varchar(255) DEFAULT NULL )' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $result = $this->assertQuery( 'ALTER TABLE wptests_dbdelta_test2 CHANGE COLUMN `foo-bar` `foo-bar` text DEFAULT NULL' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $result = $this->assertQuery( 'DESCRIBE wptests_dbdelta_test2;' ); $this->assertNotFalse( $result ); @@ -2183,19 +2286,19 @@ public function testAlterTableModifyColumnComplexChange() { PRIMARY KEY (ID, name) );" ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Add a unique index $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD UNIQUE INDEX "test_unique_composite" (name, lastname);' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Add a regular index $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD INDEX "test_regular" (lastname);' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Confirm the table is well-behaved so far: @@ -2236,10 +2339,10 @@ public function testAlterTableModifyColumnComplexChange() { // Now – let's change a few columns: $result = $this->engine->query( 'ALTER TABLE _tmp_table CHANGE COLUMN name firstname varchar(20)' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $result = $this->engine->query( 'ALTER TABLE _tmp_table CHANGE COLUMN date_as_string datetime datetime NOT NULL' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); // Finally, let's confirm our data is intact and the table is still well-behaved: $result = $this->engine->query( 'SELECT * FROM _tmp_table ORDER BY ID;' ); @@ -2283,7 +2386,7 @@ public function testCaseInsensitiveUniqueIndex() { UNIQUE KEY last (lastname) );" ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $result1 = $this->engine->query( "INSERT INTO _tmp_table (name, lastname) VALUES ('first', 'last');" ); $this->assertEquals( 1, $result1 ); @@ -2370,6 +2473,378 @@ public function testTruncatesInvalidDates() { $this->assertEquals( '0000-00-00 00:00:00', $results[1]->option_value ); } + /** + * Test NO_ZERO_DATE SQL mode behavior. + * + * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_date): + * + * "The NO_ZERO_DATE mode affects whether the server permits '0000-00-00' as a valid date. + * Its effect also depends on whether strict SQL mode is enabled. + * - If this mode is not enabled, '0000-00-00' is permitted and inserts produce no warning. + * - If this mode is enabled, '0000-00-00' is permitted but produces a warning. + * - If this mode and strict mode are both enabled, '0000-00-00' is not permitted + * and inserts produce an error, unless IGNORE is also given." + */ + public function testZeroDateAcceptedWhenNoZeroDateModeIsOff() { + // With NO_ZERO_DATE disabled, '0000-00-00 00:00:00' should be accepted. + $this->assertQuery( "SET sql_mode = 'STRICT_TRANS_TABLES'" ); + + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('0000-00-00 00:00:00');" ); + + $this->assertQuery( 'SELECT * FROM _dates;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( '0000-00-00 00:00:00', $results[0]->option_value ); + } + + /** + * Test that zero dates are rejected in strict mode when NO_ZERO_DATE is active. + * + * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_date): + * + * "If this mode and strict mode are both enabled, '0000-00-00' is not + * permitted and inserts produce an error." + */ + public function testZeroDateRejectedWhenNoZeroDateAndStrictModeAreOn() { + // Default modes include both NO_ZERO_DATE and STRICT_TRANS_TABLES. + $this->assertQueryError( + "INSERT INTO _dates (option_value) VALUES ('0000-00-00 00:00:00');", + "Incorrect datetime value: '0000-00-00 00:00:00'" + ); + } + + /** + * Test that zero dates are accepted (with warning) when NO_ZERO_DATE is on + * but strict mode is off. + * + * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_date): + * + * "If this mode is enabled, '0000-00-00' is permitted but produces a warning." + */ + public function testZeroDateAcceptedWhenNoZeroDateOnButStrictModeOff() { + $this->assertQuery( "SET sql_mode = 'NO_ZERO_DATE'" ); + + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('0000-00-00 00:00:00');" ); + + $this->assertQuery( 'SELECT * FROM _dates;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( '0000-00-00 00:00:00', $results[0]->option_value ); + } + + /** + * Test that zero dates work with the DATE column type too. + */ + public function testZeroDateAcceptedForDateColumn() { + $this->assertQuery( "SET sql_mode = 'STRICT_TRANS_TABLES'" ); + + $this->assertQuery( + 'CREATE TABLE _date_test ( + ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, + col_date DATE NOT NULL + );' + ); + + $this->assertQuery( "INSERT INTO _date_test (col_date) VALUES ('0000-00-00');" ); + + $this->assertQuery( 'SELECT * FROM _date_test;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( '0000-00-00', $results[0]->col_date ); + } + + /** + * Test NO_ZERO_IN_DATE SQL mode behavior. + * + * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_in_date): + * + * "The NO_ZERO_IN_DATE mode affects whether the server permits dates in + * which the year part is nonzero but the month or day part is 0. (This + * mode affects dates such as '2010-00-01' or '2010-01-00', but not + * '0000-00-00'. To control whether the server permits '0000-00-00', + * use the NO_ZERO_DATE mode.) + * - If this mode is not enabled, dates with zero parts are permitted + * and inserts produce no warning. + * - If this mode is enabled, dates with zero parts are inserted as + * '0000-00-00' and produce a warning. + * - If this mode and strict mode are both enabled, dates with zero parts + * are not permitted and inserts produce an error." + */ + public function testZeroInDateAcceptedWhenNoZeroInDateModeIsOff() { + // Disable NO_ZERO_IN_DATE but keep strict mode on. + $this->assertQuery( "SET sql_mode = 'STRICT_TRANS_TABLES'" ); + + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2020-00-15 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2020-01-00 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2020-00-00 00:00:00');" ); + + $this->assertQuery( 'SELECT * FROM _dates;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 3, $results ); + $this->assertEquals( '2020-00-15 00:00:00', $results[0]->option_value ); + $this->assertEquals( '2020-01-00 00:00:00', $results[1]->option_value ); + $this->assertEquals( '2020-00-00 00:00:00', $results[2]->option_value ); + } + + /** + * Test that dates with zero parts are rejected in strict mode when + * NO_ZERO_IN_DATE is active. + */ + public function testZeroInDateRejectedWhenNoZeroInDateAndStrictModeAreOn() { + // Default modes include both NO_ZERO_IN_DATE and STRICT_TRANS_TABLES. + $this->assertQueryError( + "INSERT INTO _dates (option_value) VALUES ('2020-00-15 00:00:00');", + "Incorrect datetime value: '2020-00-15 00:00:00'" + ); + } + + /** + * Test that dates with zero parts get stored as '0000-00-00 00:00:00' + * when NO_ZERO_IN_DATE is on but strict mode is off. + * + * MySQL reference (https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_no_zero_in_date): + * + * "If this mode is enabled, dates with zero parts are inserted as + * '0000-00-00' and produce a warning." + */ + public function testZeroInDateBecomesZeroDateWhenNoZeroInDateOnButStrictOff() { + $this->assertQuery( "SET sql_mode = 'NO_ZERO_IN_DATE'" ); + + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2020-00-15 00:00:00');" ); + + $this->assertQuery( 'SELECT * FROM _dates;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( '0000-00-00 00:00:00', $results[0]->option_value ); + } + + /** + * Test that all modes disabled allows both zero dates and zero-in-dates. + */ + public function testBothZeroDateModesDisabledAcceptsAll() { + $this->assertQuery( "SET sql_mode = ''" ); + + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('0000-00-00 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2020-00-15 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2020-01-00 00:00:00');" ); + + $this->assertQuery( 'SELECT * FROM _dates;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 3, $results ); + $this->assertEquals( '0000-00-00 00:00:00', $results[0]->option_value ); + $this->assertEquals( '2020-00-15 00:00:00', $results[1]->option_value ); + $this->assertEquals( '2020-01-00 00:00:00', $results[2]->option_value ); + } + + /** + * Test that valid dates still work correctly regardless of zero date modes. + */ + public function testValidDatesWorkWithZeroDateModes() { + // Default modes (NO_ZERO_DATE + STRICT_TRANS_TABLES). + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2022-01-15 14:30:00');" ); + + $this->assertQuery( 'SELECT * FROM _dates;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( '2022-01-15 14:30:00', $results[0]->option_value ); + } + + /** + * Test zero date handling in UPDATE statements. + */ + public function testZeroDateInUpdate() { + $this->assertQuery( "SET sql_mode = 'STRICT_TRANS_TABLES'" ); + + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2022-01-15 14:30:00');" ); + $this->assertQuery( "UPDATE _dates SET option_value = '0000-00-00 00:00:00';" ); + + $this->assertQuery( 'SELECT * FROM _dates;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( '0000-00-00 00:00:00', $results[0]->option_value ); + } + + /** + * Test that zero dates are rejected in UPDATE when NO_ZERO_DATE and strict mode are on. + */ + public function testZeroDateInUpdateRejectedWhenNoZeroDateAndStrictModeAreOn() { + // Default modes include both NO_ZERO_DATE and STRICT_TRANS_TABLES. + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2022-01-15 14:30:00');" ); + $this->assertQueryError( + "UPDATE _dates SET option_value = '0000-00-00 00:00:00';", + "Incorrect datetime value: '0000-00-00 00:00:00'" + ); + } + + /** + * Test that dates with zero parts are rejected in UPDATE when + * NO_ZERO_IN_DATE and strict mode are on. + */ + public function testZeroInDateInUpdateRejectedWhenNoZeroInDateAndStrictModeAreOn() { + // Default modes include both NO_ZERO_IN_DATE and STRICT_TRANS_TABLES. + $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2022-01-15 14:30:00');" ); + $this->assertQueryError( + "UPDATE _dates SET option_value = '2020-00-15 00:00:00';", + "Incorrect datetime value: '2020-00-15 00:00:00'" + ); + } + + /** + * Test that stored zero dates can be selected and compared. + * + * In MySQL, zero dates are regular values for reads — they can appear in + * WHERE, ORDER BY, and comparisons regardless of the current SQL mode. + */ + public function testSelectZeroDatesComparison() { + $this->assertQuery( "SET sql_mode = 'STRICT_TRANS_TABLES'" ); + + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('zero', '0000-00-00 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('real', '2022-01-15 14:30:00');" ); + + // Zero dates compare as less than real dates. + $this->assertQuery( "SELECT option_name FROM _dates WHERE option_value < '2000-01-01 00:00:00' ORDER BY option_value;" ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( 'zero', $results[0]->option_name ); + + // Equality match on zero date. + $this->assertQuery( "SELECT option_name FROM _dates WHERE option_value = '0000-00-00 00:00:00';" ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( 'zero', $results[0]->option_name ); + } + + /** + * Test ORDER BY with a mix of zero and non-zero dates. + */ + public function testSelectZeroDatesOrderBy() { + $this->assertQuery( "SET sql_mode = 'STRICT_TRANS_TABLES'" ); + + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('b', '2022-06-01 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('a', '0000-00-00 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('c', '2023-01-01 00:00:00');" ); + + $this->assertQuery( 'SELECT option_name FROM _dates ORDER BY option_value ASC;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 3, $results ); + $this->assertEquals( 'a', $results[0]->option_name ); + $this->assertEquals( 'b', $results[1]->option_name ); + $this->assertEquals( 'c', $results[2]->option_name ); + } + + /** + * Test that zero-in-dates stored in the database can be read back + * and filtered in SELECT statements. + */ + public function testSelectZeroInDates() { + $this->assertQuery( "SET sql_mode = 'STRICT_TRANS_TABLES'" ); + + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('zero-month', '2020-00-15 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('zero-day', '2020-01-00 00:00:00');" ); + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('normal', '2020-01-15 00:00:00');" ); + + // All three rows are readable. + $this->assertQuery( 'SELECT option_name, option_value FROM _dates ORDER BY option_value ASC;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 3, $results ); + $this->assertEquals( '2020-00-15 00:00:00', $results[0]->option_value ); + $this->assertEquals( '2020-01-00 00:00:00', $results[1]->option_value ); + $this->assertEquals( '2020-01-15 00:00:00', $results[2]->option_value ); + + // Filtering by a zero-in-date value works. + $this->assertQuery( "SELECT option_name FROM _dates WHERE option_value = '2020-00-15 00:00:00';" ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( 'zero-month', $results[0]->option_name ); + } + + /** + * Test date functions on zero dates — YEAR(), MONTH(), DAY() all return 0. + */ + public function testDateFunctionsOnZeroDates() { + $this->assertQuery( "SET sql_mode = 'STRICT_TRANS_TABLES'" ); + + $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('zero', '0000-00-00 00:00:00');" ); + + $this->assertQuery( 'SELECT YEAR(option_value) as y, MONTH(option_value) as m, DAY(option_value) as d FROM _dates;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( 0, $results[0]->y ); + $this->assertEquals( 0, $results[0]->m ); + $this->assertEquals( 0, $results[0]->d ); + } + + public function testDefaultSqlModeDoesNotIncludeNoAutoValueOnZero() { + $this->assertQuery( 'SELECT @@sql_mode AS mode;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertStringNotContainsString( 'NO_AUTO_VALUE_ON_ZERO', strtoupper( $results[0]->mode ) ); + } + + public function testAutoIncrementZeroAdvancesSequenceByDefault() { + // Default SQL modes do not include NO_AUTO_VALUE_ON_ZERO. + // Values like 0 and '0' should behave like NULL and advance the sequence. + $this->assertQuery( + "INSERT INTO _options (ID, option_name, option_value) VALUES (0, 'a', '1');" + ); + $this->assertQuery( + "INSERT INTO _options (ID, option_name, option_value) VALUES ('0', 'b', '2');" + ); + $this->assertQuery( + "INSERT INTO _options (ID, option_name, option_value) VALUES (NULL, 'c', '3');" + ); + + $this->assertQuery( 'SELECT ID, option_name FROM _options ORDER BY ID;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 3, $results ); + $this->assertEquals( 1, $results[0]->ID ); + $this->assertEquals( 'a', $results[0]->option_name ); + $this->assertEquals( 2, $results[1]->ID ); + $this->assertEquals( 'b', $results[1]->option_name ); + $this->assertEquals( 3, $results[2]->ID ); + $this->assertEquals( 'c', $results[2]->option_name ); + } + + public function testAutoIncrementZeroAdvancesSequenceForAllInsertShapes() { + // INSERT ... SET + $this->assertQuery( "INSERT INTO _options SET ID = 0, option_name = 'set', option_value = '1';" ); + + // INSERT ... SELECT + $this->assertQuery( "INSERT INTO _options (ID, option_name, option_value) SELECT 0, 'select', '2';" ); + + // REPLACE ... VALUES + $this->assertQuery( "REPLACE INTO _options (ID, option_name, option_value) VALUES ('0', 'replace', '3');" ); + + $this->assertQuery( 'SELECT ID, option_name FROM _options ORDER BY ID;' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 3, $results ); + $this->assertEquals( 1, $results[0]->ID ); + $this->assertEquals( 2, $results[1]->ID ); + $this->assertEquals( 3, $results[2]->ID ); + } + + public function testNoAutoValueOnZeroSqlMode() { + $this->assertQuery( "SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'" ); + + // Literal 0 and '0' are stored as-is. Only NULL generates a value. + $this->assertQuery( + "INSERT INTO _options (ID, option_name, option_value) VALUES (0, 'a', '1');" + ); + + $this->assertQuery( "SELECT ID FROM _options WHERE option_name = 'a';" ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( 0, $results[0]->ID ); + + $this->assertQuery( + "INSERT INTO _options (ID, option_name, option_value) VALUES (NULL, 'b', '2');" + ); + $this->assertQuery( "SELECT ID FROM _options WHERE option_name = 'b';" ); + $results = $this->engine->get_query_results(); + $this->assertCount( 1, $results ); + $this->assertEquals( 1, $results[0]->ID ); + } + public function testCaseInsensitiveSelect() { $this->assertQuery( "CREATE TABLE _tmp_table ( @@ -3342,6 +3817,31 @@ public function testTranslatesComplexDelete() { ); } + public function testDeleteReturnsZeroAffectedRowsWhenNoMatchingRows() { + $this->assertQuery( 'CREATE TABLE t (id INT)' ); + $this->assertQuery( 'INSERT INTO t (id) VALUES (1)' ); + + $result = $this->assertQuery( 'DELETE FROM t WHERE id = 1' ); + $this->assertSame( 1, $result ); + $this->assertSame( 1, $this->engine->get_last_return_value() ); + + $result = $this->assertQuery( 'DELETE FROM t WHERE id = 1' ); + $this->assertSame( 0, $result ); + $this->assertSame( 0, $this->engine->get_last_return_value() ); + } + + public function testUpdateReturnsZeroAffectedRowsWhenNoMatchingRows() { + $this->assertQuery( 'CREATE TABLE t (id INT, val TEXT)' ); + $this->assertQuery( "INSERT INTO t (id, val) VALUES (1, 'a')" ); + + $result = $this->assertQuery( "UPDATE t SET val = 'b' WHERE id = 1" ); + $this->assertSame( 1, $result ); + + $result = $this->assertQuery( "UPDATE t SET val = 'c' WHERE id = 999" ); + $this->assertSame( 0, $result ); + $this->assertSame( 0, $this->engine->get_last_return_value() ); + } + public function testTranslatesDoubleAlterTable() { $result = $this->assertQuery( 'ALTER TABLE _options @@ -3350,7 +3850,7 @@ public function testTranslatesDoubleAlterTable() { ADD INDEX test_index2(option_name(140),option_value(51)) ' ); - $this->assertNull( $result ); + $this->assertSame( 0, $result ); $result = $this->assertQuery( 'SHOW INDEX FROM _options' @@ -3416,13 +3916,292 @@ public function testTranslatesUtf8Insert() { $this->assertQuery( 'DELETE FROM _options' ); } - public function testTranslatesRandom() { - $this->assertIsNumeric( - $this->sqlite->query( 'SELECT RAND() AS rand' )->fetchColumn() + public function testRandUnseededReturnsFloatInRange() { + for ( $i = 0; $i < 100; $i++ ) { + $this->assertQuery( 'SELECT RAND() AS r' ); + $results = $this->engine->get_query_results(); + $value = (float) $results[0]->r; + $this->assertGreaterThanOrEqual( 0.0, $value ); + $this->assertLessThan( 1.0, $value ); + } + } + + public function testRandSeededProducesDeterministicValues() { + // MySQL reference values for RAND(0), RAND(1), RAND(5). + $seeds_and_expected = array( + 0 => 0.15522042769494, + 1 => 0.40540353712198, + 5 => 0.40613597483014, ); + foreach ( $seeds_and_expected as $seed => $expected ) { + $this->assertQuery( "SELECT RAND($seed) AS r" ); + $results = $this->engine->get_query_results(); + $value = (float) $results[0]->r; + $this->assertEqualsWithDelta( $expected, $value, 1e-12, "RAND($seed) mismatch" ); + } + } + + public function testRandSeededStateIsResetBetweenStatements() { + // Each statement must restart the sequence from its own seed, even when + // an intervening statement used a different seed. This pins down the + // per-statement flush contract. + $this->assertQuery( 'SELECT RAND(3) AS r' ); + $first_of_3 = (float) $this->engine->get_query_results()[0]->r; + + $this->assertQuery( 'SELECT RAND(5) AS r' ); + $first_of_5 = (float) $this->engine->get_query_results()[0]->r; + + $this->assertQuery( 'SELECT RAND(3) AS r' ); + $first_of_3_again = (float) $this->engine->get_query_results()[0]->r; + + $this->assertSame( $first_of_3, $first_of_3_again ); + $this->assertNotSame( $first_of_3, $first_of_5 ); + } - $this->assertIsNumeric( - $this->sqlite->query( 'SELECT RAND(5) AS rand' )->fetchColumn() + public function testRandSeededAndUnseededInSameQueryAreIndependent() { + // In MySQL, unseeded RAND() uses the thread-level random state and is + // independent of RAND(N) seeding. Running the same combined query + // twice must reproduce the seeded column exactly, and the unseeded + // columns must vary between runs (probability of collision is ~2^-53 + // per pair, so a match would indicate a shared state bug, not luck). + $this->assertQuery( 'SELECT RAND(1) AS r1, RAND() AS r2, RAND() AS r3' ); + $run1 = $this->engine->get_query_results()[0]; + + $this->assertQuery( 'SELECT RAND(1) AS r1, RAND() AS r2, RAND() AS r3' ); + $run2 = $this->engine->get_query_results()[0]; + + // Seeded column is stable across runs. + $this->assertSame( (float) $run1->r1, (float) $run2->r1 ); + $this->assertEqualsWithDelta( 0.40540353712198, (float) $run1->r1, 1e-12 ); + + // Unseeded columns vary. + $this->assertTrue( + (float) $run1->r2 !== (float) $run2->r2 + || (float) $run1->r3 !== (float) $run2->r3, + 'Unseeded RAND() must vary between identical queries.' + ); + + // All unseeded values are in [0, 1). + foreach ( array( $run1->r2, $run1->r3, $run2->r2, $run2->r3 ) as $value ) { + $this->assertGreaterThanOrEqual( 0.0, (float) $value ); + $this->assertLessThan( 1.0, (float) $value ); + } + } + + public function testRandMultiRowReturnsDifferentValues() { + $this->assertQuery( "INSERT INTO _options (option_name, option_value) VALUES ('a', '1'), ('b', '2'), ('c', '3')" ); + $this->assertQuery( 'SELECT RAND() AS r FROM _options' ); + $results = $this->engine->get_query_results(); + $values = array_map( + function ( $row ) { + return $row->r; + }, + $results + ); + + // At least two distinct values among three rows. + $this->assertGreaterThan( 1, count( array_unique( $values ) ) ); + } + + public function testRandSeededMultiRowProducesDeterministicSequence() { + // Per the MySQL docs: with a constant seed, RAND(N) is initialized + // once per statement and advances on each row. Values below are + // from the MySQL 8.4 manual example "SELECT i, RAND(3) FROM t". + $this->assertQuery( "INSERT INTO _options (option_name, option_value) VALUES ('a', '1'), ('b', '2'), ('c', '3')" ); + $this->assertQuery( 'SELECT RAND(3) AS r FROM _options' ); + $results = $this->engine->get_query_results(); + $this->assertCount( 3, $results ); + $this->assertEqualsWithDelta( 0.90576975597606, (float) $results[0]->r, 1e-12 ); + $this->assertEqualsWithDelta( 0.37307905813035, (float) $results[1]->r, 1e-12 ); + $this->assertEqualsWithDelta( 0.14808605345719, (float) $results[2]->r, 1e-12 ); + + // A second identical query must restart the sequence from seed 3, + // not continue where the previous statement left off. + $this->assertQuery( 'SELECT RAND(3) AS r FROM _options' ); + $results = $this->engine->get_query_results(); + $this->assertEqualsWithDelta( 0.90576975597606, (float) $results[0]->r, 1e-12 ); + $this->assertEqualsWithDelta( 0.37307905813035, (float) $results[1]->r, 1e-12 ); + $this->assertEqualsWithDelta( 0.14808605345719, (float) $results[2]->r, 1e-12 ); + } + + public function testRandNonConstantSeedReinitializesPerRow() { + // Per the MySQL docs: with a non-constant seed (e.g. a column), the + // LCG is reinitialized for each row, so every row gets the first + // value of the sequence for its own seed. We verify this indirectly + // by comparing against RAND(seed) called from scalar subqueries. + $this->assertQuery( "INSERT INTO _options (option_name, option_value) VALUES ('a', '1'), ('b', '2'), ('c', '3')" ); + + $this->assertQuery( 'SELECT RAND(CAST(option_value AS SIGNED)) AS r FROM _options ORDER BY option_name' ); + $per_row = $this->engine->get_query_results(); + + $this->assertQuery( 'SELECT RAND(1) AS r' ); + $r1 = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(2) AS r' ); + $r2 = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(3) AS r' ); + $r3 = (float) $this->engine->get_query_results()[0]->r; + + $this->assertEqualsWithDelta( $r1, (float) $per_row[0]->r, 1e-12 ); + $this->assertEqualsWithDelta( $r2, (float) $per_row[1]->r, 1e-12 ); + $this->assertEqualsWithDelta( $r3, (float) $per_row[2]->r, 1e-12 ); + } + + public function testRandOrderBy() { + $this->assertQuery( + 'INSERT INTO _options (option_name, option_value) VALUES ' + . "('a', '1'), ('b', '2'), ('c', '3'), ('d', '4'), ('e', '5')" + ); + + // Unseeded `ORDER BY RAND() LIMIT N` — the common "random sample" pattern. + $this->assertQuery( 'SELECT option_name FROM _options ORDER BY RAND() LIMIT 2' ); + $rows = $this->engine->get_query_results(); + $this->assertCount( 2, $rows ); + foreach ( $rows as $row ) { + $this->assertContains( $row->option_name, array( 'a', 'b', 'c', 'd', 'e' ) ); + } + + // Seeded `ORDER BY RAND(N)` is a deterministic permutation: running the + // same query twice yields the same order, and every row appears once. + $extract = function ( $results ) { + return array_map( + function ( $row ) { + return $row->option_name; + }, + $results + ); + }; + $this->assertQuery( 'SELECT option_name FROM _options ORDER BY RAND(1)' ); + $first = $extract( $this->engine->get_query_results() ); + $this->assertQuery( 'SELECT option_name FROM _options ORDER BY RAND(1)' ); + $second = $extract( $this->engine->get_query_results() ); + $this->assertSame( $first, $second ); + $sorted = $first; + sort( $sorted ); + $this->assertSame( array( 'a', 'b', 'c', 'd', 'e' ), $sorted ); + } + + public function testRandNullIsEquivalentToZeroSeed() { + // In MySQL, RAND(NULL) is equivalent to RAND(0) because val_int() + // on a NULL argument returns 0. Verified against MySQL 9.6. + $this->assertQuery( 'SELECT RAND(NULL) AS r' ); + $this->assertEqualsWithDelta( + 0.15522042769493574, + (float) $this->engine->get_query_results()[0]->r, + 1e-12 + ); + + // The same rule applies when the seed expression evaluates to NULL + // at runtime rather than being a literal NULL. + $this->assertQuery( 'SELECT RAND(NULLIF(1, 1)) AS r' ); + $this->assertEqualsWithDelta( + 0.15522042769493574, + (float) $this->engine->get_query_results()[0]->r, + 1e-12 + ); + } + + public function testRandFloatSeedRoundsToNearestInteger() { + // MySQL's val_int() on DOUBLE rounds to nearest, so RAND(3.9) is + // equivalent to RAND(4) and RAND(3.1) to RAND(3). + $this->assertQuery( 'SELECT RAND(3.9) AS r' ); + $from_float = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(4) AS r' ); + $from_int = (float) $this->engine->get_query_results()[0]->r; + $this->assertSame( $from_int, $from_float ); + + $this->assertQuery( 'SELECT RAND(3.1) AS r' ); + $from_float = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(3) AS r' ); + $from_int = (float) $this->engine->get_query_results()[0]->r; + $this->assertSame( $from_int, $from_float ); + } + + public function testRandStringSeedIsCoercedLikeMySQL() { + // Numeric strings are coerced through float-then-round; non-numeric + // strings fall back to 0, matching MySQL's val_int() semantics. + $this->assertQuery( "SELECT RAND('5') AS r" ); + $from_string = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(5) AS r' ); + $from_int = (float) $this->engine->get_query_results()[0]->r; + $this->assertSame( $from_int, $from_string ); + + $this->assertQuery( "SELECT RAND('3.9') AS r" ); + $from_string = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(4) AS r' ); + $from_int = (float) $this->engine->get_query_results()[0]->r; + $this->assertSame( $from_int, $from_string ); + + $this->assertQuery( "SELECT RAND('abc') AS r" ); + $from_bad_string = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(0) AS r' ); + $from_zero = (float) $this->engine->get_query_results()[0]->r; + $this->assertSame( $from_zero, $from_bad_string ); + } + + public function testRandNegativeSeedIsDeterministicAndInRange() { + // Negative seeds fold into uint32 (matching MySQL's cast-to-uint32 + // before the LCG init). The exact value is defined by the LCG; this + // test pins determinism and range, not a reference constant. + $this->assertQuery( 'SELECT RAND(-1) AS r' ); + $v1 = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(-1) AS r' ); + $v2 = (float) $this->engine->get_query_results()[0]->r; + $this->assertSame( $v1, $v2 ); + $this->assertGreaterThanOrEqual( 0.0, $v1 ); + $this->assertLessThan( 1.0, $v1 ); + } + + public function testRandMultipleCallSitesShareLcgState() { + // Documented divergence from MySQL: each Item_func_rand in MySQL + // owns its own LCG state, so `SELECT RAND(1), RAND(1)` returns + // (v, v). This UDF keeps a single per-connection state, so the + // second call advances the shared stream and returns the next + // value. Pin the current behavior so any future change is explicit. + $this->assertQuery( 'SELECT RAND(1) AS a, RAND(1) AS b' ); + $row = $this->engine->get_query_results()[0]; + $this->assertEqualsWithDelta( 0.40540353712198, (float) $row->a, 1e-12 ); + $this->assertNotEquals( (float) $row->a, (float) $row->b ); + $this->assertGreaterThanOrEqual( 0.0, (float) $row->b ); + $this->assertLessThan( 1.0, (float) $row->b ); + } + + public function testRandSeededResetsAcrossStatementsInsideTransaction() { + // The per-statement flush contract must hold inside a transaction. + $this->assertQuery( 'BEGIN' ); + $this->assertQuery( 'SELECT RAND(1) AS r' ); + $a = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'SELECT RAND(1) AS r' ); + $b = (float) $this->engine->get_query_results()[0]->r; + $this->assertQuery( 'COMMIT' ); + $this->assertSame( $a, $b ); + $this->assertEqualsWithDelta( 0.40540353712198, $a, 1e-12 ); + } + + public function testRandInWhereClauseRuns() { + $this->assertQuery( "INSERT INTO _options (option_name, option_value) VALUES ('a', '1'), ('b', '2'), ('c', '3')" ); + // Sanity: a predicate that is always true / always false. + $this->assertQuery( 'SELECT COUNT(*) AS c FROM _options WHERE RAND() < 2' ); + $this->assertSame( 3, (int) $this->engine->get_query_results()[0]->c ); + $this->assertQuery( 'SELECT COUNT(*) AS c FROM _options WHERE RAND() > 2' ); + $this->assertSame( 0, (int) $this->engine->get_query_results()[0]->c ); + } + + public function testRandInUpdateAndInsert() { + $this->assertQuery( "INSERT INTO _options (option_name, option_value) VALUES ('a', '0')" ); + $this->assertQuery( "UPDATE _options SET option_value = RAND(1) WHERE option_name = 'a'" ); + $this->assertQuery( "SELECT option_value FROM _options WHERE option_name = 'a'" ); + $this->assertEqualsWithDelta( + 0.40540353712198, + (float) $this->engine->get_query_results()[0]->option_value, + 1e-12 + ); + + $this->assertQuery( "INSERT INTO _options (option_name, option_value) VALUES ('b', RAND(1))" ); + $this->assertQuery( "SELECT option_value FROM _options WHERE option_name = 'b'" ); + $this->assertEqualsWithDelta( + 0.40540353712198, + (float) $this->engine->get_query_results()[0]->option_value, + 1e-12 ); } @@ -4236,7 +5015,7 @@ public function testCompoundPrimaryKeyWithAutoincrement(): void { ' `id` int NOT NULL AUTO_INCREMENT,', ' `name` varchar(32) NOT NULL,', ' PRIMARY KEY (`id`, `name`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci', + ') ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci', ) ), $results[0]->{'Create Table'} @@ -6138,6 +6917,14 @@ public function testSelectColumnNames(): void { $this->assertQuery( 'CREATE TABLE t (id INT, name VARCHAR(255))' ); $this->assertQuery( 'INSERT INTO t (id, name) VALUES (1, "John"), (2, "Jane")' ); + // Literal with a NULL byte (no explicit alias). + $result = $this->assertQuery( "SELECT 'abc\0def'" ); + $this->assertSame( array( 'abc' ), array_keys( (array) $result[0] ) ); + + // Literal with a NULL byte (with an explicit alias). + $result = $this->assertQuery( "SELECT 'abc\0def' AS `col1`" ); + $this->assertSame( array( 'col1' ), array_keys( (array) $result[0] ) ); + // Columns (no explicit alias). $result = $this->assertQuery( 'SELECT id, name FROM t' ); $this->assertSame( array( 'id', 'name' ), array_keys( (array) $result[0] ) ); @@ -6471,6 +7258,31 @@ public function testTransactionSavepoints(): void { $this->assertSame( array(), (array) array_column( $result, 'id' ) ); } + public function testRowLeveLockingClauses() { + $this->assertQuery( 'CREATE TABLE t (name VARCHAR(255), value VARCHAR(255))' ); + $this->engine->query( "INSERT INTO t (name, value) VALUES ('test_lock', '123')" ); + + // FOR UPDATE + $res = $this->assertQuery( "SELECT value FROM t WHERE name = 'test_lock' FOR UPDATE" ); + $this->assertEquals( '123', $res[0]->value ); + + // FOR SHARE + $res = $this->assertQuery( "SELECT value FROM t WHERE name = 'test_lock' FOR SHARE" ); + $this->assertEquals( '123', $res[0]->value ); + + // LOCK IN SHARE MODE + $res = $this->assertQuery( "SELECT value FROM t WHERE name = 'test_lock' LOCK IN SHARE MODE" ); + $this->assertEquals( '123', $res[0]->value ); + + // SKIP LOCKED + $res = $this->assertQuery( "SELECT value FROM t WHERE name = 'test_lock' FOR UPDATE SKIP LOCKED" ); + $this->assertEquals( '123', $res[0]->value ); + + // NOWAIT + $res = $this->assertQuery( "SELECT value FROM t WHERE name = 'test_lock' FOR UPDATE NOWAIT" ); + $this->assertEquals( '123', $res[0]->value ); + } + public function testSelectOrderByAmbiguousColumnResolution(): void { $this->assertQuery( 'CREATE TABLE t1 (id INT, name TEXT)' ); $this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' ); @@ -8734,14 +9546,13 @@ public function testColumnInfoForExpressions(): void { 'mysqli:type' => 8, ), array( - // TODO: Fix custom "RAND()" function to behave like in MySQL. - 'native_type' => 'LONGLONG', // DOUBLE in MySQL. - 'pdo_type' => PDO::PARAM_INT, // PARAM_STR in MySQL. + 'native_type' => 'DOUBLE', + 'pdo_type' => PDO::PARAM_STR, 'flags' => array( 'not_null' ), 'table' => '', 'name' => 'col_expr_19', - 'len' => 21, // 23 in MySQL. - 'precision' => 0, // 31 in MySQL. + 'len' => 23, + 'precision' => 31, 'sqlite:decl_type' => '', // Additional MySQLi metadata. @@ -8750,7 +9561,7 @@ public function testColumnInfoForExpressions(): void { 'mysqli:db' => 'wp', 'mysqli:charsetnr' => 63, 'mysqli:flags' => 0, // 32769 in MySQL. - 'mysqli:type' => 8, // 5 in MySQL. + 'mysqli:type' => 5, ), array( 'native_type' => 'LONGLONG', @@ -9612,7 +10423,8 @@ public function testCastExpression(): void { 'expr_5' => 'abc', 'expr_6' => 'abc', // 'ab' In MySQL 'expr_7' => '-10', - 'expr_8' => '-10', // 18446744073709551606 in MySQL + // @TODO: Emulate UNSIGNED cast. MySQL returns 18446744073709551606 (2^64 - 10). + 'expr_8' => '-10', 'expr_9' => '2025-10-05 14:05:28', // 2025-10-05 in MySQL 'expr_10' => '2025-10-05 14:05:28', // 14:05:28 in MySQL 'expr_11' => '2025-10-05 14:05:28', @@ -9627,6 +10439,118 @@ public function testCastExpression(): void { ); } + public function testConvertExpression(): void { + // CONVERT(expr, type) should behave like CAST(expr AS type). + $result = $this->assertQuery( + "SELECT + CONVERT('abc', BINARY) AS expr_1, + CONVERT('abc', CHAR) AS expr_2, + CONVERT('-10', SIGNED) AS expr_3, + CONVERT('-10', UNSIGNED) AS expr_4, + CONVERT('123.456', DECIMAL) AS expr_5, + CONVERT('2025-10-05', DATE) AS expr_6 + " + ); + + $this->assertEquals( + array( + (object) array( + 'expr_1' => 'abc', + 'expr_2' => 'abc', + 'expr_3' => '-10', + // @TODO: Emulate UNSIGNED cast. MySQL returns 18446744073709551606 (2^64 - 10). + 'expr_4' => '-10', + 'expr_5' => '123.456', + 'expr_6' => '2025-10-05', + ), + ), + $result + ); + } + + public function testConvertUsingExpression(): void { + // CONVERT(expr USING charset) converts character set. + // In SQLite, all text is UTF-8 — the conversion is a no-op. + $result = $this->assertQuery( + "SELECT + CONVERT('Customer' USING utf8mb4) AS expr_1, + CONVERT('test' USING utf8) AS expr_2, + CONVERT('data' USING latin1) AS expr_3 + " + ); + + $this->assertEquals( + array( + (object) array( + 'expr_1' => 'Customer', + 'expr_2' => 'test', + 'expr_3' => 'data', + ), + ), + $result + ); + } + + public function testConvertUsingWithCollate(): void { + $result = $this->assertQuery( + "SELECT CONVERT('Customer' USING utf8mb4) COLLATE utf8mb4_bin AS val" + ); + + $this->assertEquals( + array( + (object) array( 'val' => 'Customer' ), + ), + $result + ); + } + + public function testConvertWithColumnReferences(): void { + $this->assertQuery( 'CREATE TABLE t (val VARCHAR(255), num VARCHAR(255))' ); + $this->assertQuery( "INSERT INTO t (val, num) VALUES ('hello', '-42')" ); + + $result = $this->assertQuery( + 'SELECT CONVERT(val, BINARY) AS v1, CONVERT(val USING utf8mb4) AS v2 + FROM t WHERE CONVERT(num, SIGNED) < 0 ORDER BY CONVERT(val USING utf8mb4)' + ); + $this->assertEquals( + array( + (object) array( + 'v1' => 'hello', + 'v2' => 'hello', + ), + ), + $result + ); + } + + public function testInsertWithoutInto(): void { + $this->assertQuery( 'CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(255))' ); + + // INSERT ... VALUES + $this->assertQuery( "INSERT t (id, name) VALUES (1, 'a')" ); + + // INSERT IGNORE + $this->assertQuery( "INSERT IGNORE t (id, name) VALUES (1, 'a')" ); + + // INSERT ... SET + $this->assertQuery( "INSERT t SET id = 2, name = 'b'" ); + + // INSERT IGNORE ... SET + $this->assertQuery( "INSERT IGNORE t SET id = 2, name = 'b'" ); + + // INSERT ... SELECT + $this->assertQuery( "INSERT t (id, name) SELECT 3, 'c'" ); + + // INSERT IGNORE ... SELECT + $this->assertQuery( "INSERT IGNORE t (id, name) SELECT 3, 'c'" ); + + $res = $this->assertQuery( 'SELECT * FROM t ORDER BY id' ); + $this->assertCount( 3, $res ); + $this->assertEquals( 'a', $res[0]->name ); + $this->assertEquals( 'b', $res[1]->name ); + $this->assertEquals( 'c', $res[2]->name ); + } + public function testInsertIntoSetSyntax(): void { $this->assertQuery( 'CREATE TABLE t ( @@ -11355,6 +12279,42 @@ public function testVersionFunction(): void { $this->assertSame( '8.0.38', $result[0]->{'VERSION()'} ); } + public function testFromBase64Function(): void { + // Basic decoding. + $result = $this->assertQuery( "SELECT FROM_BASE64('SGVsbG8gV29ybGQ=') AS decoded" ); + $this->assertSame( 'Hello World', $result[0]->decoded ); + + // Empty string. + $result = $this->assertQuery( "SELECT FROM_BASE64('') AS decoded" ); + $this->assertSame( '', $result[0]->decoded ); + + // NULL input returns NULL. + $result = $this->assertQuery( 'SELECT FROM_BASE64(NULL) AS decoded' ); + $this->assertNull( $result[0]->decoded ); + + // Binary data round-trip. + $result = $this->assertQuery( "SELECT FROM_BASE64(TO_BASE64('binary\\0data')) AS decoded" ); + $this->assertSame( "binary\0data", $result[0]->decoded ); + } + + public function testToBase64Function(): void { + // Basic encoding. + $result = $this->assertQuery( "SELECT TO_BASE64('Hello World') AS encoded" ); + $this->assertSame( 'SGVsbG8gV29ybGQ=', $result[0]->encoded ); + + // Empty string. + $result = $this->assertQuery( "SELECT TO_BASE64('') AS encoded" ); + $this->assertSame( '', $result[0]->encoded ); + + // NULL input returns NULL. + $result = $this->assertQuery( 'SELECT TO_BASE64(NULL) AS encoded' ); + $this->assertNull( $result[0]->encoded ); + + // Round-trip: TO_BASE64(FROM_BASE64(x)) = x. + $result = $this->assertQuery( "SELECT TO_BASE64(FROM_BASE64('dGVzdA==')) AS encoded" ); + $this->assertSame( 'dGVzdA==', $result[0]->encoded ); + } + public function testSubstringFunction(): void { $result = $this->assertQuery( "SELECT SUBSTRING('abcdef', 1, 3) AS s" ); $this->assertSame( 'abc', $result[0]->s ); diff --git a/tests/WP_SQLite_Driver_Translation_Tests.php b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Translation_Tests.php similarity index 98% rename from tests/WP_SQLite_Driver_Translation_Tests.php rename to packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Translation_Tests.php index eeaa9596..119ec3ac 100644 --- a/tests/WP_SQLite_Driver_Translation_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Translation_Tests.php @@ -3,7 +3,7 @@ use PHPUnit\Framework\TestCase; class WP_SQLite_Driver_Translation_Tests extends TestCase { - const GRAMMAR_PATH = __DIR__ . '/../wp-includes/mysql/mysql-grammar.php'; + const GRAMMAR_PATH = __DIR__ . '/../src/mysql/mysql-grammar.php'; /** * @var WP_Parser_Grammar @@ -101,6 +101,91 @@ public function testSelect(): void { ); } + public function testConvert(): void { + // CONVERT(expr, type) → CAST(expr AS type) + $this->assertQuery( + "SELECT CAST('abc' AS TEXT) COLLATE BINARY AS `CONVERT('abc', BINARY)`", + "SELECT CONVERT('abc', BINARY)" + ); + + $this->assertQuery( + "SELECT CAST('abc' AS TEXT) AS `CONVERT('abc', CHAR)`", + "SELECT CONVERT('abc', CHAR)" + ); + + $this->assertQuery( + "SELECT CAST('-10' AS INTEGER) AS `CONVERT('-10', SIGNED)`", + "SELECT CONVERT('-10', SIGNED)" + ); + + // CONVERT(expr USING charset) → expr + $this->assertQuery( + "SELECT 'Customer' AS `CONVERT('Customer' USING utf8mb4)`", + "SELECT CONVERT('Customer' USING utf8mb4)" + ); + + $this->assertQuery( + "SELECT 'test' AS `CONVERT('test' USING utf8)`", + "SELECT CONVERT('test' USING utf8)" + ); + + // CONVERT(expr USING charset) COLLATE collation → expr COLLATE collation + $this->assertQuery( + "SELECT 'Customer' COLLATE `utf8mb4_bin` AS `CONVERT('Customer' USING utf8mb4) COLLATE utf8mb4_bin`", + "SELECT CONVERT('Customer' USING utf8mb4) COLLATE utf8mb4_bin" + ); + } + + public function testBinary(): void { + // "BINARY expr" on the left side of comparison + $this->assertQuery( + 'SELECT `a` COLLATE BINARY = `b` AS `BINARY a = b` FROM `t`', + 'SELECT BINARY a = b FROM t' + ); + + // "BINARY expr" on the right side of comparison + $this->assertQuery( + 'SELECT `a` = `b` COLLATE BINARY AS `a = BINARY b` FROM `t`', + 'SELECT a = BINARY b FROM t' + ); + + // "BINARY literal" + $this->assertQuery( + "SELECT 'abc' COLLATE BINARY AS `BINARY 'abc'`", + "SELECT BINARY 'abc'" + ); + + // "BINARY expr" in ORDER BY + $this->assertQuery( + 'SELECT `a` FROM `t` ORDER BY `a` COLLATE BINARY', + 'SELECT a FROM t ORDER BY BINARY a' + ); + + // "BINARY expr" in GROUP BY + $this->assertQuery( + 'SELECT `a` FROM `t` GROUP BY `a` COLLATE BINARY', + 'SELECT a FROM t GROUP BY BINARY a' + ); + + // "BINARY expr" wrapping a parenthesized expression + $this->assertQuery( + "SELECT ( `a` || `b` ) COLLATE BINARY = 'x' AS `BINARY (a || b) = 'x'` FROM `t`", + "SELECT BINARY (a || b) = 'x' FROM t" + ); + + // "CAST(expr AS BINARY)" → "CAST(expr AS TEXT) COLLATE BINARY" + $this->assertQuery( + "SELECT CAST('abc' AS TEXT) COLLATE BINARY AS `CAST('abc' AS BINARY)`", + "SELECT CAST('abc' AS BINARY)" + ); + + // "CAST(expr AS BINARY) = expr" → "CAST(expr AS TEXT) COLLATE BINARY = expr" + $this->assertQuery( + "SELECT CAST('abc' AS TEXT) COLLATE BINARY = 'abc' AS `CAST('abc' AS BINARY) = 'abc'`", + "SELECT CAST('abc' AS BINARY) = 'abc'" + ); + } + public function testInsert(): void { $this->driver->query( 'CREATE TABLE t (c INT, c1 INT, c2 INT)' ); $this->driver->query( 'CREATE TABLE t1 (c1 INT, c2 INT)' ); @@ -347,6 +432,36 @@ public function testDelete(): void { 'DELETE FROM `t` WHERE `c` = 1', 'DELETE FROM t WHERE c = 1' ); + + // DELETE with LIMIT. + $this->assertQuery( + 'DELETE FROM `t` WHERE rowid IN ( SELECT rowid FROM `t` LIMIT 1 )', + 'DELETE FROM t LIMIT 1' + ); + + // DELETE with WHERE and LIMIT. + $this->assertQuery( + 'DELETE FROM `t` WHERE rowid IN ( SELECT rowid FROM `t` WHERE `c` = 1 LIMIT 5 )', + 'DELETE FROM t WHERE c = 1 LIMIT 5' + ); + + // DELETE with ORDER BY and LIMIT. + $this->assertQuery( + 'DELETE FROM `t` WHERE rowid IN ( SELECT rowid FROM `t` ORDER BY `c` ASC LIMIT 1 )', + 'DELETE FROM t ORDER BY c ASC LIMIT 1' + ); + + // DELETE with WHERE, ORDER BY, and LIMIT. + $this->assertQuery( + 'DELETE FROM `t` WHERE rowid IN ( SELECT rowid FROM `t` WHERE `c` = 1 ORDER BY `c` ASC LIMIT 1 )', + 'DELETE FROM t WHERE c = 1 ORDER BY c ASC LIMIT 1' + ); + + // DELETE with a table alias and LIMIT. + $this->assertQuery( + 'DELETE FROM `t` WHERE rowid IN ( SELECT rowid FROM `t` AS `a` WHERE `a`.`c` = 1 LIMIT 1 )', + 'DELETE FROM t AS a WHERE a.c = 1 LIMIT 1' + ); } public function testCreateTable(): void { diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/packages/mysql-on-sqlite/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php similarity index 82% rename from tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php rename to packages/mysql-on-sqlite/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index cfc6840c..af14aecf 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -310,6 +310,88 @@ public function testDefaultValueEscaping(): void { ); } + public function testInvalidDataTypeCacheDataForDecimalDefinition(): void { + // Recreate the invalid database state before the following fix: + // https://github.com/WordPress/sqlite-database-integration/pull/126 + $connection = $this->engine->get_connection(); + $connection->query( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL ); + $connection->query( 'CREATE TABLE t ( dec_col REAL )' ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'decimal', 'decimal(26,')" ); + + // Ensure the information schema is reconstructed correctly. + $this->reconstructor->ensure_correct_information_schema(); + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertSame( + implode( + "\n", + array( + 'CREATE TABLE `t` (', + ' `dec_col` float DEFAULT NULL', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci', + ) + ), + $result[0]->{'Create Table'} + ); + } + + public function testInvalidDataTypeCacheDataForDecimalDefinitionOnWooCommerceTable(): void { + // Recreate the invalid database state before the following fix: + // https://github.com/WordPress/sqlite-database-integration/pull/126 + $connection = $this->engine->get_connection(); + $connection->query( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL ); + $connection->query( 'CREATE TABLE wc_testing_table ( col1 REAL, col2 REAL, col3 REAL, col4 REAL )' ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('wc_testing_table', 'col1', 'decimal(26,')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('wc_testing_table', 'col2', 'decimal(19,')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('wc_testing_table', 'col3', 'decimal(3,')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('wc_testing_table', 'col4', 'decimal(5,')" ); + + // Ensure the information schema is reconstructed correctly. + $this->reconstructor->ensure_correct_information_schema(); + $result = $this->assertQuery( 'SHOW CREATE TABLE wc_testing_table' ); + $this->assertSame( + implode( + "\n", + array( + 'CREATE TABLE `wc_testing_table` (', + ' `col1` decimal(26,8) DEFAULT NULL,', + ' `col2` decimal(19,4) DEFAULT NULL,', + ' `col3` decimal(3,2) DEFAULT NULL,', + ' `col4` float DEFAULT NULL', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci', + ) + ), + $result[0]->{'Create Table'} + ); + } + + public function testInvalidDataTypeCacheDataForIndexDefinition(): void { + $connection = $this->engine->get_connection(); + + // Recreate the invalid database state before the following fix: + // https://github.com/WordPress/sqlite-database-integration/commit/b5a9fbaed4d0d843f792aaa959e3d00f193ff1ee + // https://github.com/Automattic/sqlite-database-integration/pull/2 + $connection->query( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL ); + $connection->query( 'CREATE TABLE t ( `timestamp` TEXT, `KEY` TEXT)' ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'timestamp', 'datetime')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'KEY', 'timestamp(timestamp)')" ); + + // Ensure the information schema is reconstructed correctly. + $this->reconstructor->ensure_correct_information_schema(); + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertSame( + implode( + "\n", + array( + 'CREATE TABLE `t` (', + ' `timestamp` datetime DEFAULT NULL,', + ' `KEY` text DEFAULT NULL', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci', + ) + ), + $result[0]->{'Create Table'} + ); + } + private function assertQuery( $sql ) { $retval = $this->engine->query( $sql ); $this->assertNotFalse( $retval ); diff --git a/tests/bootstrap.php b/packages/mysql-on-sqlite/tests/bootstrap.php similarity index 57% rename from tests/bootstrap.php rename to packages/mysql-on-sqlite/tests/bootstrap.php index 6f97b26e..3afa3b9d 100644 --- a/tests/bootstrap.php +++ b/packages/mysql-on-sqlite/tests/bootstrap.php @@ -1,12 +1,7 @@ query( 'SELECT SQLITE_VERSION();' )->fetch()[0]; @@ -14,6 +9,10 @@ define( 'WP_SQLITE_UNSAFE_ENABLE_UNSUPPORTED_VERSIONS', true ); } +if ( '1' === getenv( 'WP_SQLITE_REQUIRE_NATIVE_PARSER_EXTENSION' ) ) { + require_once __DIR__ . '/tools/verify-native-parser-extension.php'; +} + // Configure the test environment. error_reporting( E_ALL ); define( 'FQDB', ':memory:' ); @@ -50,57 +49,6 @@ function apply_filters( $tag, $value, ...$args ) { } } -/** - * Polyfills for php 7 & 8 functions - */ - -if ( ! function_exists( 'str_starts_with' ) ) { - /** - * Check if a string starts with a specific substring. - * - * @param string $haystack The string to search in. - * @param string $needle The string to search for. - * - * @see https://www.php.net/manual/en/function.str-starts-with - * - * @return bool - */ - function str_starts_with( string $haystack, string $needle ) { - return empty( $needle ) || 0 === strpos( $haystack, $needle ); - } -} - -if ( ! function_exists( 'str_contains' ) ) { - /** - * Check if a string contains a specific substring. - * - * @param string $haystack The string to search in. - * @param string $needle The string to search for. - * - * @see https://www.php.net/manual/en/function.str-contains - * - * @return bool - */ - function str_contains( string $haystack, string $needle ) { - return empty( $needle ) || false !== strpos( $haystack, $needle ); - } -} - -if ( ! function_exists( 'str_ends_with' ) ) { - /** - * Check if a string ends with a specific substring. - * - * @param string $haystack The string to search in. - * @param string $needle The string to search for. - * - * @see https://www.php.net/manual/en/function.str-ends-with - * - * @return bool - */ - function str_ends_with( string $haystack, string $needle ) { - return empty( $needle ) || substr( $haystack, -strlen( $needle ) ) === $needle; - } -} if ( extension_loaded( 'mbstring' ) ) { if ( ! function_exists( 'mb_str_starts_with' ) ) { diff --git a/tests/mysql/WP_MySQL_Lexer_Tests.php b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Lexer_Tests.php similarity index 73% rename from tests/mysql/WP_MySQL_Lexer_Tests.php rename to packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Lexer_Tests.php index 574249d0..8f18cf17 100644 --- a/tests/mysql/WP_MySQL_Lexer_Tests.php +++ b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Lexer_Tests.php @@ -258,6 +258,115 @@ public function data_identifier_or_number(): array { ); } + /** + * Test that unclosed quoted strings with trailing backslashes do not + * cause out-of-bounds string access in read_quoted_text(). + * + * The backslash-counting loop walks backward from the position returned + * by strcspn(). When the closing quote is missing, strcspn() returns + * the remaining string length. If the last byte is a backslash, the + * loop treats the absent quote as escaped and advances past the end + * of the string. On the next iteration, the loop accesses an invalid + * string offset, triggering "Uninitialized string offset" warnings. + * + * @dataProvider data_unclosed_strings_with_backslashes + */ + public function test_unclosed_string_with_trailing_backslash( string $sql ): void { + set_error_handler( + function ( $severity, $message, $file, $line ) { + throw new \ErrorException( $message, 0, $severity, $file, $line ); + }, + E_WARNING | E_NOTICE + ); + + try { + $lexer = new WP_MySQL_Lexer( $sql ); + while ( $lexer->next_token() ) { + // Consume all tokens. + } + } finally { + restore_error_handler(); + } + + // If we reach here without an ErrorException, no OOB access occurred. + $this->assertNull( $lexer->get_token() ); + } + + public function data_unclosed_strings_with_backslashes(): array { + return array( + 'single-quoted trailing backslash' => array( "SELECT '\\" ), + 'double-quoted trailing backslash' => array( 'SELECT "\\' ), + 'even trailing backslashes' => array( "SELECT '\\\\" ), + 'odd trailing backslashes' => array( "SELECT '\\\\\\" ), + 'backslash-only single-quoted' => array( "'\\" ), + 'backslash-only double-quoted' => array( '"\\' ), + ); + } + + /** + * Regression: valid strings with escapes must still tokenize correctly. + * + * @dataProvider data_valid_escaped_strings + */ + public function test_valid_escaped_string( string $sql, int $expected_token_id ): void { + $lexer = new WP_MySQL_Lexer( $sql ); + $this->assertTrue( $lexer->next_token() ); + $this->assertSame( $expected_token_id, $lexer->get_token()->id ); + } + + public function data_valid_escaped_strings(): array { + return array( + 'escaped single quote' => array( "'it\\'s'", WP_MySQL_Lexer::SINGLE_QUOTED_TEXT ), + 'trailing escaped backslash' => array( "'path\\\\'", WP_MySQL_Lexer::SINGLE_QUOTED_TEXT ), + 'doubled single quote' => array( "'it''s'", WP_MySQL_Lexer::SINGLE_QUOTED_TEXT ), + 'empty single-quoted string' => array( "''", WP_MySQL_Lexer::SINGLE_QUOTED_TEXT ), + 'escaped double quote' => array( '"col\\"name"', WP_MySQL_Lexer::DOUBLE_QUOTED_TEXT ), + 'backtick identifier' => array( '`my_column`', WP_MySQL_Lexer::BACK_TICK_QUOTED_ID ), + ); + } + + /** + * Test that a chunk boundary splitting a quoted string with a trailing + * backslash does not cause an out-of-bounds string access. + * + * This simulates streaming SQL processing where a buffer boundary falls + * inside a string literal right after a backslash escape character. + */ + public function test_chunk_boundary_inside_escaped_string(): void { + set_error_handler( + function ( $severity, $message, $file, $line ) { + throw new \ErrorException( $message, 0, $severity, $file, $line ); + }, + E_WARNING | E_NOTICE + ); + + try { + // Build a SQL string where a backslash falls at the chunk boundary. + // The string content before the boundary is padded to place the + // backslash at exactly position $chunk_size - 1. + $chunk_size = 8192; + + // "SELECT '" = 8 bytes, so we need chunk_size - 8 - 1 bytes of + // padding before the trailing backslash to place '\' at the last + // byte of the chunk. + $padding = str_repeat( 'A', $chunk_size - 8 - 1 ); + $sql = "SELECT '" . $padding . '\\'; + + // The chunk is exactly $chunk_size bytes. The last byte is '\'. + // The lexer should handle this as an unclosed string without OOB. + $this->assertSame( $chunk_size, strlen( $sql ) ); + + $lexer = new WP_MySQL_Lexer( $sql ); + while ( $lexer->next_token() ) { + // Consume all tokens. + } + } finally { + restore_error_handler(); + } + + $this->assertNull( $lexer->get_token() ); + } + private function get_token_names( array $token_types ): array { return array_map( function ( $token_type ) { diff --git a/tests/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php similarity index 100% rename from tests/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php rename to packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php diff --git a/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php similarity index 97% rename from tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php rename to packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php index 27274f71..02769884 100644 --- a/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php +++ b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php @@ -7,7 +7,7 @@ */ class WP_MySQL_Server_Suite_Parser_Tests extends TestCase { const TEST_DATA_PATH = __DIR__ . '/data/mysql-server-tests-queries.csv'; - const GRAMMAR_PATH = __DIR__ . '/../../wp-includes/mysql/mysql-grammar.php'; + const GRAMMAR_PATH = __DIR__ . '/../../src/mysql/mysql-grammar.php'; /** * Some of the queries in the test suite are known to fail parsing. diff --git a/tests/mysql/data/mysql-server-tests-queries.csv b/packages/mysql-on-sqlite/tests/mysql/data/mysql-server-tests-queries.csv similarity index 100% rename from tests/mysql/data/mysql-server-tests-queries.csv rename to packages/mysql-on-sqlite/tests/mysql/data/mysql-server-tests-queries.csv 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 new file mode 100644 index 00000000..066fd38d --- /dev/null +++ b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Native_Parser_Node_Identity_Tests.php @@ -0,0 +1,142 @@ +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_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' ); + + $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 catches regressions + // where accessors hand back fresh wrappers and lose state written + // through a previously returned child. + $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_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' ); + + $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/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..d308d558 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/mysql/native/WP_MySQL_Parser_Instanceof_Tests.php @@ -0,0 +1,39 @@ +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 ); + } +} diff --git a/tests/parser/WP_Parser_Node_Tests.php b/packages/mysql-on-sqlite/tests/parser/WP_Parser_Node_Tests.php similarity index 98% rename from tests/parser/WP_Parser_Node_Tests.php rename to packages/mysql-on-sqlite/tests/parser/WP_Parser_Node_Tests.php index 16fa49d3..80fbcb06 100644 --- a/tests/parser/WP_Parser_Node_Tests.php +++ b/packages/mysql-on-sqlite/tests/parser/WP_Parser_Node_Tests.php @@ -1,6 +1,6 @@ ':memory:' ) ), diff --git a/tests/tools/mysql-download-tests.sh b/packages/mysql-on-sqlite/tests/tools/mysql-download-tests.sh similarity index 100% rename from tests/tools/mysql-download-tests.sh rename to packages/mysql-on-sqlite/tests/tools/mysql-download-tests.sh diff --git a/tests/tools/mysql-extract-queries.php b/packages/mysql-on-sqlite/tests/tools/mysql-extract-queries.php similarity index 100% rename from tests/tools/mysql-extract-queries.php rename to packages/mysql-on-sqlite/tests/tools/mysql-extract-queries.php diff --git a/packages/mysql-on-sqlite/tests/tools/run-lexer-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-lexer-benchmark.php new file mode 100644 index 00000000..87f1ec79 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/tools/run-lexer-benchmark.php @@ -0,0 +1,73 @@ += $limit ) { + break; + } +} + +// Run the lexer. +$processed = 0; +$start = microtime( true ); +for ( $i = 0; $i < count( $records ); $i += 1 ) { + $query = $records[ $i ][0]; + $lexer = new WP_MySQL_Lexer( $query ); + $tokens = $lexer->remaining_tokens(); + if ( count( $tokens ) === 0 ) { + throw new Exception( 'Failed to tokenize query: ' . $query ); + } + $processed += 1; +} +$duration = microtime( true ) - $start; +$qps = $processed / $duration; + +if ( $json ) { + echo json_encode( + array( + 'benchmark' => 'mysql-lexer', + 'implementation' => 'php', + 'queries' => $processed, + 'duration' => $duration, + 'qps' => $qps, + 'php_version' => PHP_VERSION, + ), + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ), "\n"; + exit; +} + +// Print the results. +printf( "\nTokenized %d queries in %.5fs @ %d QPS.\n", $processed, $duration, $qps ); diff --git a/packages/mysql-on-sqlite/tests/tools/run-native-extension-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-native-extension-benchmark.php new file mode 100644 index 00000000..e4e304a9 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/tools/run-native-extension-benchmark.php @@ -0,0 +1,113 @@ += $limit ) { + break; + } +} + +function benchmark_php_mysql_lexer( $queries ) { + $start = microtime( true ); + foreach ( $queries as $query ) { + $lexer = new WP_MySQL_Lexer( $query ); + $tokens = $lexer->remaining_tokens(); + if ( count( $tokens ) === 0 ) { + throw new Exception( 'Failed to tokenize query: ' . $query ); + } + } + $duration = microtime( true ) - $start; + return array( + 'available' => true, + 'duration' => $duration, + 'qps' => count( $queries ) / $duration, + ); +} + +function benchmark_native_mysql_lexer( $queries ) { + if ( ! class_exists( 'WP_MySQL_Native_Lexer', false ) ) { + return array( + 'available' => false, + 'reason' => 'The wp_mysql_parser extension is not loaded.', + ); + } + + $start = microtime( true ); + foreach ( $queries as $query ) { + $lexer = new WP_MySQL_Native_Lexer( $query ); + $tokens = $lexer->native_token_stream(); + if ( 0 === $tokens->count() ) { + throw new Exception( 'Failed to tokenize query with native lexer: ' . $query ); + } + } + $duration = microtime( true ) - $start; + return array( + 'available' => true, + 'duration' => $duration, + 'qps' => count( $queries ) / $duration, + ); +} + +$php = benchmark_php_mysql_lexer( $queries ); +$native = benchmark_native_mysql_lexer( $queries ); + +$result = array( + 'benchmark' => 'mysql-lexer-native-extension', + 'queries' => count( $queries ), + 'php_version' => PHP_VERSION, + 'extension_loaded' => extension_loaded( 'wp_mysql_parser' ), + 'pure_php' => $php, + 'native_extension' => $native, +); + +if ( ! empty( $native['available'] ) ) { + $result['speedup'] = $native['qps'] / $php['qps']; +} + +if ( $json ) { + echo json_encode( $result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), "\n"; + exit; +} + +printf( "Benchmarked %d MySQL queries on PHP %s.\n", $result['queries'], PHP_VERSION ); +printf( "Pure PHP lexer: %.5fs @ %d QPS\n", $php['duration'], $php['qps'] ); +if ( empty( $native['available'] ) ) { + printf( "Native extension: unavailable (%s)\n", $native['reason'] ); + exit; +} +printf( "Native extension: %.5fs @ %d QPS\n", $native['duration'], $native['qps'] ); +printf( "Speedup: %.2fx\n", $result['speedup'] ); diff --git a/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php new file mode 100644 index 00000000..6b77ea89 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php @@ -0,0 +1,116 @@ += $limit ) { + break; + } +} + +// Run the parser. +$failures = array(); +$exceptions = array(); +$processed = 0; +$start = microtime( true ); +foreach ( $queries as $query ) { + try { + $lexer = new WP_MySQL_Lexer( $query ); + $tokens = $lexer instanceof WP_MySQL_Native_Lexer + ? $lexer->native_token_stream() + : $lexer->remaining_tokens(); + if ( ( is_array( $tokens ) ? count( $tokens ) : $tokens->count() ) === 0 ) { + throw new Exception( 'Failed to tokenize query: ' . $query ); + } + + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + $ast = $parser->parse(); + if ( null === $ast ) { + $failures[] = $query; + } + } catch ( Exception $e ) { + $exceptions[] = $query; + } + + $processed += 1; + if ( ! $json && $processed > 0 && 0 === $processed % 1000 ) { + echo get_stats( $processed, count( $failures ), count( $exceptions ) ), "\n"; + } +} +$duration = microtime( true ) - $start; +$qps = $processed / $duration; + +if ( $json ) { + echo json_encode( + array( + 'benchmark' => 'mysql-parser', + 'implementation' => class_exists( 'WP_MySQL_Native_Parser', false ) ? 'native-extension' : 'php', + 'extension_loaded' => extension_loaded( 'wp_mysql_parser' ), + 'queries' => $processed, + 'duration' => $duration, + 'qps' => $qps, + 'failures' => count( $failures ), + 'exceptions' => count( $exceptions ), + 'php_version' => PHP_VERSION, + ), + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ), "\n"; + exit; +} + +echo get_stats( $processed, count( $failures ), count( $exceptions ) ), "\n"; + +// Print the results. +printf( "\nParsed %d queries in %.5fs @ %d QPS.\n", $processed, $duration, $qps ); diff --git a/packages/mysql-on-sqlite/tests/tools/verify-native-parser-extension.php b/packages/mysql-on-sqlite/tests/tools/verify-native-parser-extension.php new file mode 100644 index 00000000..84e99ba5 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/tools/verify-native-parser-extension.php @@ -0,0 +1,101 @@ +hasProperty( 'native' ) ) { + wp_sqlite_native_parser_verification_fail( $context ); + } + + $native_property = $reflection->getProperty( 'native' ); + $native_property->setAccessible( true ); + if ( ! ( $native_property->getValue( $parser ) instanceof WP_MySQL_Native_Parser ) ) { + wp_sqlite_native_parser_verification_fail( $context ); + } +} + +/** + * Run the native parser verification. + */ +function wp_sqlite_verify_native_parser_extension(): void { + if ( ! class_exists( 'WP_MySQL_Native_Lexer', false ) || ! class_exists( 'WP_MySQL_Native_Parser', false ) ) { + wp_sqlite_native_parser_verification_fail( 'Native MySQL parser extension is not loaded.' ); + } + + $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 ) ) { + wp_sqlite_native_parser_verification_fail( 'WP_MySQL_Lexer did not resolve to the native implementation.' ); + } + + $tokens = $lexer->native_token_stream(); + $rules = include __DIR__ . '/../../src/mysql/mysql-grammar.php'; + $grammar = new WP_Parser_Grammar( $rules ); + $parser = new WP_MySQL_Parser( $grammar, $tokens ); + wp_sqlite_assert_native_parser_delegate( + $parser, + 'WP_MySQL_Parser did not create a native parser delegate.' + ); + + $parser_ast = $parser->parse(); + if ( ! ( $parser_ast instanceof WP_MySQL_Native_Parser_Node ) || 'query' !== $parser_ast->rule_name ) { + wp_sqlite_native_parser_verification_fail( 'Native parser did not produce the expected query AST.' ); + } + + $driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' ); + $parser = $driver->create_parser( 'SELECT 1' ); + wp_sqlite_assert_native_parser_delegate( + $parser, + 'WP_PDO_MySQL_On_SQLite did not create a native parser delegate.' + ); + + $parser->next_query(); + $ast = $parser->get_query_ast(); + if ( ! ( $ast instanceof WP_MySQL_Native_Parser_Node ) ) { + wp_sqlite_native_parser_verification_fail( 'WP_PDO_MySQL_On_SQLite did not produce a native-backed AST.' ); + } + + $reflection = new ReflectionObject( $ast ); + if ( $reflection->hasProperty( 'native_ast' ) || $reflection->hasProperty( 'native_node_index' ) ) { + wp_sqlite_native_parser_verification_fail( 'Native wrapper still stores Rust AST handle properties.' ); + } + + $first = $ast->get_first_child_node(); + if ( ! ( $first instanceof WP_MySQL_Native_Parser_Node ) ) { + wp_sqlite_native_parser_verification_fail( 'Native wrapper did not return a native-backed child node.' ); + } + + if ( $first !== $ast->get_first_child_node() ) { + wp_sqlite_native_parser_verification_fail( 'Native wrapper identity is not stable across reads.' ); + } + + $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 ) ) { + wp_sqlite_native_parser_verification_fail( 'Materialized native wrapper was lost from the parent cache.' ); + } +} + +wp_sqlite_verify_native_parser_extension(); diff --git a/tests/wp-sqlite-schema.php b/packages/mysql-on-sqlite/tests/wp-sqlite-schema.php similarity index 100% rename from tests/wp-sqlite-schema.php rename to packages/mysql-on-sqlite/tests/wp-sqlite-schema.php diff --git a/packages/wp-mysql-proxy/README.md b/packages/mysql-proxy/README.md similarity index 100% rename from packages/wp-mysql-proxy/README.md rename to packages/mysql-proxy/README.md diff --git a/packages/wp-mysql-proxy/bin/wp-mysql-proxy.php b/packages/mysql-proxy/bin/wp-mysql-proxy.php similarity index 97% rename from packages/wp-mysql-proxy/bin/wp-mysql-proxy.php rename to packages/mysql-proxy/bin/wp-mysql-proxy.php index d23984a6..dfb652d7 100644 --- a/packages/wp-mysql-proxy/bin/wp-mysql-proxy.php +++ b/packages/mysql-proxy/bin/wp-mysql-proxy.php @@ -6,8 +6,6 @@ require_once __DIR__ . '/../vendor/autoload.php'; -define( 'WP_SQLITE_AST_DRIVER', true ); - // Process CLI arguments: $shortopts = 'h:d:p:l:'; $longopts = array( 'help', 'database:', 'port:', 'log-level:' ); diff --git a/packages/wp-mysql-proxy/composer.json b/packages/mysql-proxy/composer.json similarity index 74% rename from packages/wp-mysql-proxy/composer.json rename to packages/mysql-proxy/composer.json index 65079872..b231344d 100644 --- a/packages/wp-mysql-proxy/composer.json +++ b/packages/mysql-proxy/composer.json @@ -1,5 +1,5 @@ { - "name": "wordpress/wp-mysql-proxy", + "name": "wordpress/mysql-proxy", "type": "library", "bin": [ "bin/wp-mysql-proxy.php" @@ -14,9 +14,6 @@ "autoload": { "classmap": [ "src/" - ], - "files": [ - "../../php-polyfills.php" ] } } diff --git a/packages/wp-mysql-proxy/phpunit.xml b/packages/mysql-proxy/phpunit.xml similarity index 100% rename from packages/wp-mysql-proxy/phpunit.xml rename to packages/mysql-proxy/phpunit.xml diff --git a/packages/wp-mysql-proxy/src/Adapter/class-adapter.php b/packages/mysql-proxy/src/Adapter/class-adapter.php similarity index 100% rename from packages/wp-mysql-proxy/src/Adapter/class-adapter.php rename to packages/mysql-proxy/src/Adapter/class-adapter.php diff --git a/packages/wp-mysql-proxy/src/Adapter/class-sqlite-adapter.php b/packages/mysql-proxy/src/Adapter/class-sqlite-adapter.php similarity index 100% rename from packages/wp-mysql-proxy/src/Adapter/class-sqlite-adapter.php rename to packages/mysql-proxy/src/Adapter/class-sqlite-adapter.php diff --git a/packages/wp-mysql-proxy/src/class-logger.php b/packages/mysql-proxy/src/class-logger.php similarity index 100% rename from packages/wp-mysql-proxy/src/class-logger.php rename to packages/mysql-proxy/src/class-logger.php diff --git a/packages/wp-mysql-proxy/src/class-mysql-protocol.php b/packages/mysql-proxy/src/class-mysql-protocol.php similarity index 100% rename from packages/wp-mysql-proxy/src/class-mysql-protocol.php rename to packages/mysql-proxy/src/class-mysql-protocol.php diff --git a/packages/wp-mysql-proxy/src/class-mysql-proxy.php b/packages/mysql-proxy/src/class-mysql-proxy.php similarity index 100% rename from packages/wp-mysql-proxy/src/class-mysql-proxy.php rename to packages/mysql-proxy/src/class-mysql-proxy.php diff --git a/packages/wp-mysql-proxy/src/class-mysql-result.php b/packages/mysql-proxy/src/class-mysql-result.php similarity index 100% rename from packages/wp-mysql-proxy/src/class-mysql-result.php rename to packages/mysql-proxy/src/class-mysql-result.php diff --git a/packages/wp-mysql-proxy/src/class-mysql-session.php b/packages/mysql-proxy/src/class-mysql-session.php similarity index 100% rename from packages/wp-mysql-proxy/src/class-mysql-session.php rename to packages/mysql-proxy/src/class-mysql-session.php diff --git a/packages/wp-mysql-proxy/src/exceptions.php b/packages/mysql-proxy/src/exceptions.php similarity index 100% rename from packages/wp-mysql-proxy/src/exceptions.php rename to packages/mysql-proxy/src/exceptions.php diff --git a/packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_CLI_Test.php b/packages/mysql-proxy/tests/WP_MySQL_Proxy_CLI_Test.php similarity index 100% rename from packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_CLI_Test.php rename to packages/mysql-proxy/tests/WP_MySQL_Proxy_CLI_Test.php diff --git a/packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_MySQLi_Test.php b/packages/mysql-proxy/tests/WP_MySQL_Proxy_MySQLi_Test.php similarity index 100% rename from packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_MySQLi_Test.php rename to packages/mysql-proxy/tests/WP_MySQL_Proxy_MySQLi_Test.php diff --git a/packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_PDO_Test.php b/packages/mysql-proxy/tests/WP_MySQL_Proxy_PDO_Test.php similarity index 95% rename from packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_PDO_Test.php rename to packages/mysql-proxy/tests/WP_MySQL_Proxy_PDO_Test.php index 561121ab..cb9d6a8f 100644 --- a/packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_PDO_Test.php +++ b/packages/mysql-proxy/tests/WP_MySQL_Proxy_PDO_Test.php @@ -7,7 +7,7 @@ class WP_MySQL_Proxy_PDO_Test extends WP_MySQL_Proxy_Test { public function setUp(): void { parent::setUp(); - $pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class; + $pdo_class = PHP_VERSION_ID >= 80400 ? PDO\MySQL::class : PDO::class; $this->pdo = new $pdo_class( sprintf( 'mysql:host=127.0.0.1;port=%d', $this->port ), 'user', diff --git a/packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_Test.php b/packages/mysql-proxy/tests/WP_MySQL_Proxy_Test.php similarity index 100% rename from packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_Test.php rename to packages/mysql-proxy/tests/WP_MySQL_Proxy_Test.php diff --git a/packages/wp-mysql-proxy/tests/bootstrap/bootstrap.php b/packages/mysql-proxy/tests/bootstrap/bootstrap.php similarity index 100% rename from packages/wp-mysql-proxy/tests/bootstrap/bootstrap.php rename to packages/mysql-proxy/tests/bootstrap/bootstrap.php diff --git a/packages/wp-mysql-proxy/tests/bootstrap/mysql-server-process.php b/packages/mysql-proxy/tests/bootstrap/mysql-server-process.php similarity index 100% rename from packages/wp-mysql-proxy/tests/bootstrap/mysql-server-process.php rename to packages/mysql-proxy/tests/bootstrap/mysql-server-process.php diff --git a/packages/wp-mysql-proxy/tests/bootstrap/run-server.php b/packages/mysql-proxy/tests/bootstrap/run-server.php similarity index 100% rename from packages/wp-mysql-proxy/tests/bootstrap/run-server.php rename to packages/mysql-proxy/tests/bootstrap/run-server.php diff --git a/packages/php-ext-wp-mysql-parser/.gitignore b/packages/php-ext-wp-mysql-parser/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/packages/php-ext-wp-mysql-parser/Cargo.lock b/packages/php-ext-wp-mysql-parser/Cargo.lock new file mode 100644 index 00000000..baf9d849 --- /dev/null +++ b/packages/php-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/php-ext-wp-mysql-parser/Cargo.toml b/packages/php-ext-wp-mysql-parser/Cargo.toml new file mode 100644 index 00000000..6646c110 --- /dev/null +++ b/packages/php-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 = { version = "0.15.12", default-features = false, features = ["runtime"] } +libc = "0.2" +stacker = "0.1" + +[profile.release] +lto = "thin" +codegen-units = 1 diff --git a/packages/php-ext-wp-mysql-parser/README.md b/packages/php-ext-wp-mysql-parser/README.md new file mode 100644 index 00000000..2df0b54c --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/README.md @@ -0,0 +1,104 @@ +# WP MySQL Parser PHP Extension + +`wp_mysql_parser` is an optional native PHP extension for the SQLite Database Integration project. It provides native implementations of the MySQL lexer/parser path used by the SQLite driver while keeping the pure-PHP implementation as the portable fallback. + +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 pure-PHP implementations instead. + +## Published WASM build for Playground + +Published WASM builds are listed on this repository's GitHub Pages site, with manifest links and a “Run in Playground” link for each release: + + + +The current build is also available directly: + +- Manifest: +- Supported PHP versions: 8.0, 8.1, 8.2, 8.3, 8.4, and 8.5. + +Use this Playground URL to load the extension and open the demo/benchmark page. +The published manifest contains PHP 8.0 through 8.5 side modules, and CI checks +that every side module imports only symbols exported by the matching Playground +browser runtime. + + + +## Build the native extension locally + +Requirements: + +- Rust toolchain. +- PHP development headers and `php-config`. +- libclang, with `LIBCLANG_PATH` pointing at the directory containing the libclang shared library when auto-detection is not enough. + +```bash +( + cd packages/php-ext-wp-mysql-parser + PHP_CONFIG="$(command -v php-config)" \ + LIBCLANG_PATH=/path/to/libclang \ + cargo build --release +) +``` + +On macOS, if the linker reports unresolved Zend/PHP symbols, add dynamic lookup linker flags: + +```bash +( + cd packages/php-ext-wp-mysql-parser + RUSTFLAGS='-C link-arg=-undefined -C link-arg=dynamic_lookup' \ + PHP_CONFIG="$(command -v php-config)" \ + LIBCLANG_PATH=/path/to/libclang \ + cargo build --release +) +``` + +The resulting shared object is written under `target/release/` as `libwp_mysql_parser.so` on Linux or `libwp_mysql_parser.dylib` on macOS. + +From the repository root, load it for local verification or benchmarks: + +```bash +php -d extension=/absolute/path/to/libwp_mysql_parser.so -m | grep wp_mysql_parser +php -d extension=/absolute/path/to/libwp_mysql_parser.so packages/mysql-on-sqlite/tests/tools/verify-native-parser-extension.php +``` + +## Benchmarks + +Run the pure-PHP lexer/parser benchmarks: + +```bash +php packages/mysql-on-sqlite/tests/tools/run-lexer-benchmark.php --json +php packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php --json +``` + +Run the same parser benchmark with the native extension loaded: + +```bash +php -d extension=/absolute/path/to/libwp_mysql_parser.so packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php --json +``` + +Compare the pure-PHP lexer with the native extension lexer in one process: + +```bash +php -d extension=/absolute/path/to/libwp_mysql_parser.so packages/mysql-on-sqlite/tests/tools/run-native-extension-benchmark.php +php -d extension=/absolute/path/to/libwp_mysql_parser.so packages/mysql-on-sqlite/tests/tools/run-native-extension-benchmark.php --json +``` + +The GitHub Pages demo reads published benchmark data from: + + + +Latest local measurement (Apple Silicon macOS, PHP 8.4.5 CLI, 2026-05-26): + +| Benchmark | Implementation | Queries | QPS | Speedup | +| --- | --- | ---: | ---: | ---: | +| MySQL lexer | Pure PHP | 69,577 | 71,553 | — | +| MySQL lexer | Native extension | 69,577 | 343,124 | 4.80x | +| MySQL parser | Pure PHP | 69,577 | 7,015 | — | +| MySQL parser | Native extension | 69,577 | 108,354 | 15.45x | + +That file should be updated whenever a new extension build or benchmark environment is published. + +## PHP.wasm side-module build + +The experimental PHP.wasm side-module build lives in `wasm-spike/` and is verified in CI for PHP `8.0` through `8.5`. + +PHP `7.4` is not supported by this Rust WASM path. The build uses `ext-php-rs` `0.15`, which depends on PHP 8 Zend APIs and does not compile against PHP `7.4` headers. diff --git a/packages/php-ext-wp-mysql-parser/src/lexer_constants.rs b/packages/php-ext-wp-mysql-parser/src/lexer_constants.rs new file mode 100644 index 00000000..5a3d3a40 --- /dev/null +++ b/packages/php-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/php-ext-wp-mysql-parser/src/lib.rs b/packages/php-ext-wp-mysql-parser/src/lib.rs new file mode 100644 index 00000000..35f17fbd --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/src/lib.rs @@ -0,0 +1,2086 @@ +#![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}; +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, 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; +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; + +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); + +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) +} + +struct PhpClasses { + parser_token: &'static ClassEntry, + mysql_token: &'static ClassEntry, + native_parser_node: &'static ClassEntry, +} + +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"))?, + native_parser_node: ClassEntry::try_find("WP_MySQL_Native_Parser_Node") + .ok_or_else(|| php_error("Missing WP_MySQL_Native_Parser_Node class"))?, + }) +} + +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 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 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 { + 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_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 { + 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 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 + } + + 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 { + create_mysql_token( + &self.sql_zval, + token, + self.is_sql_mode_active(SQL_MODE_NO_BACKSLASH_ESCAPES), + ) + } + + 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) + } +} + +struct Grammar { + highest_terminal_id: i64, + 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) + } +} + +#[php_class] +#[php(name = "WP_MySQL_Native_Grammar")] +pub struct WpMySqlNativeGrammar { + grammar: Arc, +} + +enum ParserTokenSource { + Php(Vec), + Native { + sql_zval: Zval, + tokens: Vec, + no_backslash: bool, + }, +} + +impl ParserTokenSource { + fn create_php_token_with_classes(&self, index: usize, classes: &PhpClasses) -> 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_with_classes(sql_zval, token, *no_backslash, classes) + } + } + } + + 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, + descendant_count: usize, +} + +struct NativeAstArena { + grammar: Arc, + token_source: Arc, + nodes: Vec, + root: NativeAstRoot, +} + +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 { + 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; + 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; + } + 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); + descendant_count += 1; + } + } + } + + self.nodes.push(NativeAstNode { + rule_id, + children, + first_token, + last_token, + descendant_count, + }); + index + } + + 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(&classes) + } + + 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(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: &Rc, + index: usize, + classes: &PhpClasses, + ) -> PhpResult { + 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(); + + update_object_property( + &mut object, + classes.native_parser_node, + "rule_id", + node.rule_id, + )?; + update_object_property( + &mut object, + classes.native_parser_node, + "rule_name", + rule_name.to_owned(), + )?; + + register_native_ast_wrapper(object.as_ref(), self, index); + object.into_zval(false).map_err(php_error) + } +} + +#[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(()) +} + +#[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(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( + wrapper_zval: &Zval, + rule_name: Option, +) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + Ok(ast + .arena + .node(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( + wrapper_zval: &Zval, + token_id: Option, +) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + Ok(ast + .arena + .node(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(wrapper_zval: &Zval) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let classes = php_classes()?; + let Some(child) = ast.arena.node(node_index)?.children.first().copied() else { + return Ok(Zval::null()); + }; + ast.cached_child_zval(child, &classes) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_first_child_node( + wrapper_zval: &Zval, + rule_name: Option, +) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let classes = php_classes()?; + for child in &ast.arena.node(node_index)?.children { + if ast.arena.child_node_matches(*child, rule_name.as_deref()) { + return ast.cached_child_zval(*child, &classes); + } + } + Ok(Zval::null()) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_first_child_token( + wrapper_zval: &Zval, + token_id: Option, +) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let classes = php_classes()?; + for child in &ast.arena.node(node_index)?.children { + if ast.arena.child_token_matches(*child, token_id) { + return ast.cached_child_zval(*child, &classes); + } + } + Ok(Zval::null()) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_first_descendant_node( + wrapper_zval: &Zval, + rule_name: Option, +) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let classes = php_classes()?; + 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.cached_child_zval(child, &classes); + } + 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( + wrapper_zval: &Zval, + token_id: Option, +) -> PhpResult { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let classes = php_classes()?; + 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.cached_child_zval(child, &classes); + } + 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(wrapper_zval: &Zval) -> PhpResult> { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let classes = php_classes()?; + ast.arena + .node(node_index)? + .children + .iter() + .copied() + .map(|child| ast.cached_child_zval(child, &classes)) + .collect() +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_child_nodes( + wrapper_zval: &Zval, + rule_name: Option, +) -> PhpResult> { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let classes = php_classes()?; + ast.arena + .node(node_index)? + .children + .iter() + .copied() + .filter(|child| ast.arena.child_node_matches(*child, rule_name.as_deref())) + .map(|child| ast.cached_child_zval(child, &classes)) + .collect() +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_child_tokens( + wrapper_zval: &Zval, + token_id: Option, +) -> PhpResult> { + let (ast, node_index) = native_ast_from_wrapper(wrapper_zval)?; + let classes = php_classes()?; + ast.arena + .node(node_index)? + .children + .iter() + .copied() + .filter(|child| ast.arena.child_token_matches(*child, token_id)) + .map(|child| ast.cached_child_zval(child, &classes)) + .collect() +} + +#[php_function] +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(node_index)?; + let mut descendants = Vec::with_capacity(root.descendant_count); + let mut stack = ast.arena.descendant_stack(node_index)?; + while let Some(child) = stack.pop() { + 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); + } + } + } + Ok(descendants) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_descendant_nodes( + wrapper_zval: &Zval, + rule_name: Option, +) -> PhpResult> { + 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(node_index)?; + while let Some(child) = stack.pop() { + if ast.arena.child_node_matches(child, rule_name.as_deref()) { + 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); + } + } + } + Ok(descendants) +} + +#[php_function] +pub fn wp_sqlite_mysql_native_ast_get_descendant_tokens( + wrapper_zval: &Zval, + token_id: Option, +) -> PhpResult> { + 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(node_index)?; + while let Some(child) = stack.pop() { + if ast.arena.child_token_matches(child, token_id) { + 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); + } + } + } + Ok(descendants) +} + +#[php_function] +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"))?; + 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(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"))?; + 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: Arc, + token_ids: Vec, + position: usize, + current_ast: Option>, + current_php_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 (token_source, token_ids) = export_tokens(tokens)?; + + Ok(Self { + grammar, + token_source: Arc::new(token_source), + token_ids, + position: 0, + current_ast: None, + current_php_ast: None, + }) + } + + 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()?; + self.create_php_ast(ast) + }) + } + + pub fn next_query(&mut self) -> PhpResult { + stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || self.next_query_inner()) + } + + pub fn get_query_ast(&mut self) -> PhpResult { + 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(Rc::clone(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()), + } + } +} + +impl WpMySqlNativeParser { + 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_native_ast()?); + self.current_php_ast = None; + Ok(true) + } + + 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(NativeAstState::new(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(NativeParseMatch::No); + } + if rule_id == 0 { + return Ok(NativeParseMatch::Empty); + } + if self.token_ids[self.position] == rule_id { + let token_index = self.position; + self.position += 1; + return Ok(NativeParseMatch::Token(token_index)); + } + return Ok(NativeParseMatch::No); + } + + 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(NativeParseMatch::No); + }; + if rule.branches.is_empty() { + 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(NativeParseMatch::No); + } + } + + let starting_position = self.position; + 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(arena, subrule_id)? { + NativeParseMatch::No => { + branch_matches = false; + break; + } + NativeParseMatch::Empty => {} + NativeParseMatch::Token(token_index) => { + children.push(NativeAstChild::Token(token_index)); + } + NativeParseMatch::Node(node_index) => { + children.push(NativeAstChild::Node(node_index)); + } + NativeParseMatch::Fragment(fragment_children) => { + children.extend(fragment_children); + } + } + } + + if branch_matches + && grammar.select_statement_rule_id == Some(rule_id) + && self + .token_ids + .get(self.position) + .is_some_and(|token_id| *token_id == lex::INTO_SYMBOL) + { + branch_matches = false; + } + + if branch_matches { + matched_children = Some(children); + break; + } + } + + let Some(children) = matched_children else { + self.position = starting_position; + arena.nodes.truncate(starting_node_count); + return Ok(NativeParseMatch::No); + }; + + if children.is_empty() { + Ok(NativeParseMatch::Empty) + } else if rule.is_fragment { + Ok(NativeParseMatch::Fragment(children)) + } else { + Ok(NativeParseMatch::Node(arena.push_node(rule_id, children))) + } + } + + fn create_php_ast(&self, ast: Rc) -> PhpResult { + stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || ast.create_php_ast()) + } +} + +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)?; + 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 parsed_rules = parse_rules( + array + .get("rules") + .and_then(Zval::array) + .ok_or_else(|| php_error("Missing grammar rules"))?, + )?; + let parsed_lookahead = parse_lookahead( + array + .get("lookahead_is_match_possible") + .and_then(Zval::array) + .ok_or_else(|| php_error("Missing grammar lookahead"))?, + )?; + 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 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 = 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, + query_rule_id, + select_statement_rule_id, + }); + + 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(); + 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"))?; + 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((ParserTokenSource::Php(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 { + 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), + } +} + +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::() + .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)) + .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) +} diff --git a/packages/php-ext-wp-mysql-parser/tools/generate-lexer-constants.php b/packages/php-ext-wp-mysql-parser/tools/generate-lexer-constants.php new file mode 100644 index 00000000..10a7c5c2 --- /dev/null +++ b/packages/php-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/php-ext-wp-mysql-parser/wasm-spike/.gitignore b/packages/php-ext-wp-mysql-parser/wasm-spike/.gitignore new file mode 100644 index 00000000..9ad5440f --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/.gitignore @@ -0,0 +1,2 @@ +dist/ +*.log diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/Dockerfile.rust b/packages/php-ext-wp-mysql-parser/wasm-spike/Dockerfile.rust new file mode 100644 index 00000000..188b47dc --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/Dockerfile.rust @@ -0,0 +1,81 @@ +# Layer that adds rustup + the wasm32-unknown-emscripten target on top of +# Playground's compile-extension image. +# +# Base image is the one produced by `@php-wasm/compile-extension`'s +# Dockerfile.ext, tagged as +# `playground-php-wasm:compile-extension-php-`. It +# already contains emsdk and a wasm-targeted PHP install with headers under +# /usr/local/include/php and a matching `php-config` on PATH — exactly what +# ext-php-rs's bindgen step needs to succeed. +# +# Build with: +# docker build \ +# --build-arg BASE_IMAGE=playground-php-wasm:compile-extension-php8-4-jspi \ +# --build-arg HOST_PHP_VERSION=8.4 \ +# --build-arg HOST_PHP_API_VERSION=20240924 \ +# -t playground-php-wasm-ext-rust:8.4-jspi \ +# -f Dockerfile.rust . + +ARG BASE_IMAGE=playground-php-wasm:compile-extension-php8-4-jspi +ARG HOST_PHP_VERSION=8.4 +ARG HOST_PHP_API_VERSION=20240924 + +FROM ${BASE_IMAGE} +ARG HOST_PHP_VERSION=8.4 +ARG HOST_PHP_API_VERSION=20240924 + +ENV RUSTUP_HOME=/root/rustup \ + CARGO_HOME=/root/cargo \ + PATH=/root/cargo/bin:/root/rustup/bin:/usr/local/bin:/usr/bin:/bin \ + HOST_PHP_VERSION=${HOST_PHP_VERSION} \ + HOST_PHP_API_VERSION=${HOST_PHP_API_VERSION} + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl ca-certificates build-essential \ + libclang-dev clang llvm-dev pkg-config libxml2 libonig5 libsqlite3-0 \ + libargon2-1 libssl3 zlib1g libreadline8 \ + && rm -rf /var/lib/apt/lists/* + +# ext-php-rs shells out to `php -i` for version, debug, and ZTS metadata. The +# wasm-targeted PHP install in /usr/local has no CLI binary (--disable-cli), and +# copying a host PHP binary across Debian generations makes this layer depend on +# incompatible shared libraries. Keep the build contract narrow: report only the +# phpinfo keys ext-php-rs reads, while `PHP_CONFIG` continues to point at the +# wasm PHP install for headers and include paths. +RUN set -eux; \ + printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'set -euo pipefail' \ + 'if [ "${1:-}" != "-i" ]; then' \ + ' echo "php-host-info only supports php -i for ext-php-rs build metadata" >&2' \ + ' exit 1' \ + 'fi' \ + 'cat < ${HOST_PHP_VERSION}' \ + 'PHP API => ${HOST_PHP_API_VERSION}' \ + 'Debug Build => no' \ + 'Thread Safety => disabled' \ + 'INFO' \ + > /usr/local/bin/php-host-info; \ + chmod +x /usr/local/bin/php-host-info; \ + /usr/local/bin/php-host-info -i | grep -q "PHP API => ${HOST_PHP_API_VERSION}" + +# Set PHP_CONFIG explicitly so bindgen uses the wasm PHP headers from the +# Playground compile-extension image, not any host PHP package that apt may +# provide as a transitive dependency. +ENV PHP_CONFIG=/usr/local/bin/php-config \ + PHP=/usr/local/bin/php-host-info + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --default-toolchain stable --profile minimal \ + && /root/cargo/bin/rustup target add wasm32-unknown-emscripten \ + && /root/cargo/bin/rustup toolchain install nightly --profile minimal \ + --component rust-src \ + && /root/cargo/bin/rustup target add wasm32-unknown-emscripten --toolchain nightly + +# Make libclang discoverable for bindgen regardless of the exact package path. +RUN set -eux; \ + libclang="$(find /usr/lib -name 'libclang.so*' -print | sort | head -1)"; \ + test -n "$libclang"; \ + ln -sf "$libclang" /usr/lib/libclang.so +ENV LIBCLANG_PATH=/usr/lib diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/RESULT.md b/packages/php-ext-wp-mysql-parser/wasm-spike/RESULT.md new file mode 100644 index 00000000..51360d21 --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/RESULT.md @@ -0,0 +1,138 @@ +# wasm-spike: PHP.wasm side-module build of `wp_mysql_parser` + +## Current status + +The spike now builds and loads `wp_mysql_parser` in Playground across every +PHP version supported by the current `ext-php-rs` binding layer: 8.0 through +8.5, all JSPI. CI verifies each generated side module by loading its manifest +through Playground's compile-extension test harness and running a native lexer +smoke test. PHP 7.4 is outside this Rust path because `ext-php-rs` 0.15 +depends on PHP 8 Zend APIs and does not compile against PHP 7.4 headers. + +## PHP version scope + +This spike intentionally builds only PHP 8.0 through PHP 8.5. Adding PHP 7.4 +would require a different binding layer, or real PHP 7.4 support in +`ext-php-rs`; it is not a workflow-only change. The failures are in the +generated/wrapped Zend API surface itself, including PHP 8-only symbols and +struct fields. + +The build uses the published `@php-wasm/compile-extension` CLI for the phpize +side-module build, static archive force-linking, wasm-opt pass, and manifest +generation. A sparse Playground checkout is still required for the +`packages/php-wasm/compile` Docker assets and for CI's Playground load test. +The CLI is installed into an isolated temporary npm prefix before it is run; +the sparse Playground workspace also contains an unbuilt local +`@php-wasm/compile-extension` package, so relying on workspace `.bin` links +would bypass the published package. +The local glue that remains is Rust-specific: build `libwp_mysql_parser.a` +with the same Emscripten/PHP.wasm ABI and patch the vendored `ext-php-rs` +registry copy so it can run as a PHP.wasm side module. + +## Publishing a Playground extension artifact + +`.github/workflows/publish-wasm-extension-artifact.yml` builds the JSPI side +module for PHP 8.0 through 8.5, collects the side modules into one Actions +artifact, publishes the same bundle to the `gh-pages` branch, and writes a +Playground extension manifest using the sidecar format from +WordPress/wordpress-playground#3580: + +```json +{ + "name": "wp_mysql_parser", + "mode": "php-extension", + "artifacts": [ + { + "phpVersion": "8.4", + "sourcePath": "wp_mysql_parser-php8.4-jspi.so" + } + ] +} +``` + +The per-version build manifest comes from `@php-wasm/compile-extension` and +already uses `sourcePath` while omitting the retired `file` and `sha256` +artifact fields. The publish job writes a combined all-version manifest in the +same shape before uploading the final Actions artifact and publishing the +static bundle to GitHub Pages. The public URLs are: + +- `https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension/latest/manifest.json` +- `https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension//manifest.json` + +The uploaded manifest is intended for Playground builds that include the #3580 +resolver change; older Playground loaders still expect `file`. Checksums are +published separately in `SHA256SUMS`. The repository's GitHub Pages source +must be configured to publish from the `gh-pages` branch root for these URLs to +resolve. + +## What the Playground helper covers + +`@php-wasm/compile-extension` is complete enough to replace the custom +Stage 2 path that previously lived in this spike: + +- Builds the matching `playground-php-wasm:base` and per-version + compile-extension Docker images. +- Runs `phpize`, `emconfigure`, and `emmake` for JSPI side modules. +- Accepts Rust or C static archives via `--extra-ldflags` and force-links + `.a` inputs with `--whole-archive`. +- Runs `wasm-opt` when available. +- Emits a `sourcePath` manifest that the Playground runtime can load at + startup. + +The helper is not a full Rust build system. This crate still needs a +per-PHP-version prebuild step that produces a wasm32-unknown-emscripten +`staticlib` with matching PHP headers and host PHP CLI version, plus a tiny +phpize shim (`config.m4` and `wp_mysql_parser_shim.c`) so the helper has a +normal extension source directory to compile. + +## Remaining local compatibility patches + +The patches are applied only inside the Docker build container: + +- Switch the crate from `cdylib` to `staticlib`. +- Build Rust nightly with `-Zbuild-std=std,panic_abort`, `panic=abort`, and + position-independent code. +- Force bindgen/cc-rs to use the Emscripten sysroot plus + `ZEND_ENABLE_ZVAL_LONG64` and `__x86_64__`, matching PHP.wasm. +- Relax ext-php-rs's `PropertyDescriptor` size assertion for wasm32 + LONG64. +- Avoid ext-php-rs imports of Zend globals/functions that are not part of the + stable PHP.wasm side-module surface used by this extension. +- Use `zend_declare_class_constant_ex`, which PHP.wasm exports, instead of the + legacy class-constant helper. + +These should eventually move upstream into ext-php-rs or into a dedicated +Rust recipe in Playground, but they no longer duplicate the generic PHP.wasm +extension build and manifest machinery. + +## Files + +| Path | What it is | +| --- | --- | +| `Dockerfile.rust` | Rust/nightly/host-PHP layer on top of Playground's compile-extension image. | +| `build-in-docker-rust.sh` | Builds the Rust staticlib, then calls the published `@php-wasm/compile-extension` CLI. | +| `write-extension-manifest.mjs` | Writes the combined all-version artifact manifest for the publish workflow. | +| `shim/config.m4` | Minimal phpize wrapper. | +| `shim/wp_mysql_parser_shim.c` | Pulls the Rust `get_module()` symbol into the side-module link. | +| `run-spike.mjs` | Loads the generated manifest in Playground and verifies the native lexer. | + +## Reproduce + +```bash +cd packages/php-ext-wp-mysql-parser/wasm-spike + +PLAYGROUND_REPO=/abs/path/to/wordpress-playground \ + bash build-in-docker-rust.sh + +PLAYGROUND_REPO=/abs/path/to/wordpress-playground PHP_VERSION=8.4 \ + node run-spike.mjs +``` + +The workflow still uses a sparse checkout of `WordPress/wordpress-playground` +for `packages/php-wasm/compile` Docker assets and the Playground loader smoke +test, while the extension compile itself runs through an isolated install of +the published npm CLI. + +Follow-up for Playground: make `@php-wasm/compile-extension` self-contained so +external extension projects do not need to shallow or sparse checkout +`WordPress/wordpress-playground` just to access Docker assets or test harness +files. diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/build-in-docker-rust.sh b/packages/php-ext-wp-mysql-parser/wasm-spike/build-in-docker-rust.sh new file mode 100755 index 00000000..f18d0e39 --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/build-in-docker-rust.sh @@ -0,0 +1,330 @@ +#!/usr/bin/env bash +# Drives the two-stage build for the Rust ext-php-rs PHP extension targeting +# PHP.wasm side modules: +# +# 1. cargo build --release --target wasm32-unknown-emscripten +# inside playground-php-wasm-ext-rust:-, which +# layers rustup + ext-php-rs build metadata on top of the Playground +# compile-extension image. +# 2. Hand the resulting libwp_mysql_parser.a + C shim + config.m4 to +# `@php-wasm/compile-extension`, which owns phpize, emconfigure, +# emmake, wasm-opt, and manifest generation. +# +# Outputs: +# wasm-spike/dist/libwp_mysql_parser.a (Stage 1) +# wasm-spike/dist/wp_mysql_parser-php-jspi.so (Stage 2, wasm side module) +# wasm-spike/dist/manifest.json (written by @php-wasm/compile-extension) +set -euo pipefail + +PHP_VERSION="${PHP_VERSION:-8.4}" +ASYNC_MODE="${ASYNC_MODE:-jspi}" +SPIKE_DIR="$(cd "$(dirname "$0")" && pwd)" +CRATE_DIR="$(cd "$SPIKE_DIR/.." && pwd)" +OUT_DIR="${OUT_DIR:-$SPIKE_DIR/dist}" +COMPILE_EXTENSION_PACKAGE="${COMPILE_EXTENSION_PACKAGE:-@php-wasm/compile-extension@3.1.27}" +PLAYGROUND_REPO="${PLAYGROUND_REPO:-$(cd "$SPIKE_DIR/../../../../wordpress-playground" 2>/dev/null && pwd || true)}" +mkdir -p "$OUT_DIR" + +if [ "$ASYNC_MODE" != "jspi" ]; then + echo "Unsupported ASYNC_MODE: $ASYNC_MODE. @php-wasm/compile-extension is JSPI-only." >&2 + exit 1 +fi + +case "$PHP_VERSION" in + 8.0) PHP_API_VERSION=20200930; PHP_RELEASE=8.0.30 ;; + 8.1) PHP_API_VERSION=20210902; PHP_RELEASE=8.1.34 ;; + 8.2) PHP_API_VERSION=20220829; PHP_RELEASE=8.2.30 ;; + 8.3) PHP_API_VERSION=20230831; PHP_RELEASE=8.3.30 ;; + 8.4) PHP_API_VERSION=20240924; PHP_RELEASE=8.4.20 ;; + 8.5) PHP_API_VERSION=20250925; PHP_RELEASE=8.5.5 ;; + 7.4) + echo "Unsupported PHP_VERSION: 7.4" >&2 + echo "The WASM Rust build uses ext-php-rs 0.15, which depends on PHP 8 Zend APIs and does not compile against PHP 7.4 headers." >&2 + exit 1 + ;; + *) + echo "Unsupported PHP_VERSION: $PHP_VERSION. Supported values: 8.0, 8.1, 8.2, 8.3, 8.4, 8.5." >&2 + echo "PHP 7.4 is outside this WASM Rust path because ext-php-rs 0.15 requires PHP 8 Zend APIs." >&2 + exit 1 + ;; +esac + +if [ -z "$PLAYGROUND_REPO" ] || [ ! -f "$PLAYGROUND_REPO/packages/php-wasm/compile/Makefile" ] || [ ! -f "$PLAYGROUND_REPO/packages/php-wasm/compile-extension/docker/Dockerfile.ext" ]; then + echo "PLAYGROUND_REPO must point at a wordpress-playground checkout with packages/php-wasm/compile and packages/php-wasm/compile-extension/docker." >&2 + exit 1 +fi + +patch_compile_extension_cli() { + local cli_path="$1" + + # The npm CLI currently rebuilds the Docker images itself. This script has + # already prepared those exact images in Stage 0, so patch the pinned CLI to + # reuse them and fail loudly if the installed package changes shape. + node --input-type=module - "$cli_path" <<'NODE' +import { readFileSync, writeFileSync } from 'node:fs'; + +const cliPath = process.argv[2]; +let source = readFileSync(cliPath, 'utf8'); + +const replacements = [ + [ + 'async function k(e){await p("make",["base-image"],{cwd:e.compileRoot})}', + 'async function k(e){if(process.env.COMPILE_EXTENSION_SKIP_IMAGE_BUILD==="1"){await p("docker",["image","inspect","playground-php-wasm:base"],{stdio:"ignore"});console.log("Skipping compile-extension base image build; using existing playground-php-wasm:base");return}await p("make",["base-image"],{cwd:e.compileRoot})}', + ], + [ + 'async function j(e){const t=$(e);return await p("docker",["build","-f","compile-extension/docker/Dockerfile.ext",".",`--tag=${t}`,"--progress=plain","--build-arg",`PHP_VERSION=${e.phpRelease}`,"--build-arg","JSPI=yes"],{cwd:e.phpWasmRoot}),t}', + 'async function j(e){const t=$(e);if(process.env.COMPILE_EXTENSION_SKIP_IMAGE_BUILD==="1"){await p("docker",["image","inspect",t],{stdio:"ignore"});console.log(`Skipping compile-extension image build; using existing ${t}`);return t}return await p("docker",["build","-f","compile-extension/docker/Dockerfile.ext",".",`--tag=${t}`,"--progress=plain","--build-arg",`PHP_VERSION=${e.phpRelease}`,"--build-arg","JSPI=yes"],{cwd:e.phpWasmRoot}),t}', + ], +]; + +for (const [from, to] of replacements) { + if (!source.includes(from)) { + throw new Error(`Unable to patch @php-wasm/compile-extension CLI. Missing pattern: ${from}`); + } + source = source.replace(from, to); +} + +writeFileSync(cliPath, source); +NODE + + grep -q 'COMPILE_EXTENSION_SKIP_IMAGE_BUILD' "$cli_path" +} + +RUST_IMAGE="playground-php-wasm-ext-rust:${PHP_VERSION}-${ASYNC_MODE}" +BASE_IMAGE="playground-php-wasm:compile-extension-php${PHP_VERSION//./-}-${ASYNC_MODE}" + +echo "==> Stage 0: preparing $BASE_IMAGE via Playground compile-extension tooling" +if [ "${SKIP_BASE_IMAGE_BUILD:-}" = "1" ]; then + if ! docker image inspect playground-php-wasm:base >/dev/null 2>&1; then + echo "SKIP_BASE_IMAGE_BUILD=1 requires a preloaded playground-php-wasm:base image." >&2 + exit 1 + fi + echo "==> Stage 0: using preloaded playground-php-wasm:base" +else + BASE_IMAGE_DIR="$PLAYGROUND_REPO/packages/php-wasm/compile/base-image" + docker build \ + -f "$BASE_IMAGE_DIR/Dockerfile" \ + --tag="playground-php-wasm:base" \ + "$BASE_IMAGE_DIR" +fi +docker build \ + -f "$PLAYGROUND_REPO/packages/php-wasm/compile-extension/docker/Dockerfile.ext" \ + --tag="$BASE_IMAGE" \ + --progress=plain \ + --build-arg "PHP_VERSION=$PHP_RELEASE" \ + --build-arg "JSPI=yes" \ + "$PLAYGROUND_REPO/packages/php-wasm" + +echo "==> Stage 0: building $RUST_IMAGE" +docker build \ + --build-arg "BASE_IMAGE=$BASE_IMAGE" \ + --build-arg "HOST_PHP_VERSION=$PHP_VERSION" \ + --build-arg "HOST_PHP_API_VERSION=$PHP_API_VERSION" \ + -t "$RUST_IMAGE" \ + -f "$SPIKE_DIR/Dockerfile.rust" \ + "$SPIKE_DIR" + +echo "==> Stage 1: cargo build --target wasm32-unknown-emscripten" +# Feed the container script through stdin so Docker-side comments and strings +# cannot break host-shell quoting. +docker run --rm -i \ + -e "PHP_VERSION=$PHP_VERSION" \ + -e "PHP_API_VERSION=$PHP_API_VERSION" \ + -v "$CRATE_DIR":/src:ro \ + -v "$OUT_DIR":/out \ + --entrypoint bash \ + "$RUST_IMAGE" -s <<'EOF' + set -ex + source /root/emsdk/emsdk_env.sh + SYSROOT=/root/emsdk/upstream/emscripten/cache/sysroot + export CC=emcc CXX=em++ AR=emar + # bindgen does not pick up cargo target automatically. Steer its + # libclang invocation at the emscripten sysroot, and force + # ZEND_ENABLE_ZVAL_LONG64 + __x86_64__ so zend_long is i64 (the + # convention compile-extension uses for the C side too). + export BINDGEN_EXTRA_CLANG_ARGS="--target=wasm32-unknown-emscripten --sysroot=$SYSROOT -I$SYSROOT/include -DZEND_ENABLE_ZVAL_LONG64 -D__x86_64__" + + # cc-rs (used by ext-php-rs to compile wrapper.c) does not pick up + # -fPIC by default for wasm32. The side-module link demands PIC, so + # force it via the target-specific CFLAGS env var. + # + # `-sSUPPORT_LONGJMP=wasm` and `-fwasm-exceptions` must match the + # PHP-wasm main module's exception/longjmp ABI. Without them, the + # ext-php-rs `wrapper.c` is compiled with emscripten's legacy JS-based + # SjLj, which imports the `__THREW__` global. The main module exports + # only the wasm-native variants, so dlopen() rejects the side module + # with "bad export type for '__THREW__': undefined". + export CFLAGS_wasm32_unknown_emscripten="-fPIC -DZEND_ENABLE_ZVAL_LONG64 -D__x86_64__ -sSUPPORT_LONGJMP=wasm -fwasm-exceptions" + # Tell cargo/rustc to build with PIE-friendly relocations as well. + # `-C relocation-model=pic` is required for the side-module link. + # `-C panic=abort` keeps the Rust archive from importing the C++ + # exception tag (`__cpp_exception`) that the PHP-wasm main module + # does not export — without this, dlopen() fails with a + # WebAssembly.Instance LinkError on Import "env" "__cpp_exception". + export CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUSTFLAGS="-C relocation-model=pic -C panic=abort" + + # Operate on a copy so the sed Cargo.toml flip never touches /src. + rm -rf /work + mkdir -p /work + cp -a /src/. /work/ + cd /work + sed -i "s/crate-type = \[\"cdylib\"\]/crate-type = [\"staticlib\"]/" Cargo.toml + + # Pre-fetch so the registry sources are extracted, then patch + # ext-php-rs to relax a const assertion that does not hold when + # ZEND_ENABLE_ZVAL_LONG64 is forced on a 32-bit (wasm32) target. + cargo fetch >/dev/null 2>&1 || true + REG=$(find /root/cargo/registry/src -maxdepth 1 -type d -name "index.crates.io-*" | head -1) + chmod -R u+w "$REG" + + sed -i "s/12 \* std::mem::size_of::/24 * std::mem::size_of::/" \ + "$REG/ext-php-rs-0.15.12/src/internal/property.rs" + + # PHP.wasm does not export every Zend global as a wasm global that a side + # module can import. Avoid those imports by resolving class entries from + # the runtime class table when ext-php-rs needs them. + patch_class_entry() { + sed -i "s/unsafe { $1.as_ref() }.unwrap()/ClassEntry::try_find(\"$2\").unwrap()/" \ + "$REG/ext-php-rs-0.15.12/src/zend/ce.rs" + } + patch_class_entry zend_standard_class_def stdClass + patch_class_entry zend_ce_throwable Throwable + patch_class_entry zend_ce_exception Exception + patch_class_entry zend_ce_error_exception ErrorException + patch_class_entry zend_ce_compile_error CompileError + patch_class_entry zend_ce_parse_error ParseError + patch_class_entry zend_ce_type_error TypeError + patch_class_entry zend_ce_argument_count_error ArgumentCountError + patch_class_entry zend_ce_value_error ValueError + patch_class_entry zend_ce_arithmetic_error ArithmeticError + patch_class_entry zend_ce_division_by_zero_error DivisionByZeroError + patch_class_entry zend_ce_unhandled_match_error UnhandledMatchError + patch_class_entry zend_ce_traversable Traversable + patch_class_entry zend_ce_aggregate IteratorAggregate + patch_class_entry zend_ce_iterator Iterator + patch_class_entry zend_ce_arrayaccess ArrayAccess + patch_class_entry zend_ce_serializable Serializable + patch_class_entry zend_ce_countable Countable + patch_class_entry zend_ce_stringable Stringable + + # ClassEntry::try_find can ask Zend directly. The executor global check + # only forces an extra side-module import that PHP.wasm does not provide. + sed -i '/ExecutorGlobals::get().class_table()?;/d' \ + "$REG/ext-php-rs-0.15.12/src/zend/class.rs" + + # Class constant stub strings are only used for generated PHP stubs, but + # ext-php-rs computes them during module startup by walking the actual zval. + # The lexer registers several large array constants, and walking those + # arrays imports helpers such as zend_array_count that PHP.wasm does not + # expose as callable side-module functions. Keep runtime constant + # registration intact, but use a placeholder stub value for this WASM build. + sed -i 's/let stub = crate::convert::zval_to_stub(&zval);/let stub = String::from("null");/' \ + "$REG/ext-php-rs-0.15.12/src/builders/class.rs" + + # PHP.wasm exports zend_declare_class_constant_ex, but not the legacy + # zend_declare_class_constant helper. Use the exported API when ext-php-rs + # registers class constants. + sed -i '/zend_declare_class_constant,/a \ zend_declare_class_constant_ex,' \ + "$REG/ext-php-rs-0.15.12/allowed_bindings.rs" + sed -i \ + 's/zend_declare_class_constant, zend_declare_property,/zend_declare_class_constant_ex, zend_declare_property,/' \ + "$REG/ext-php-rs-0.15.12/src/builders/class.rs" + perl -0pi -e 's/zend_declare_class_constant\(\n\s+class,\n\s+CString::new\(name\.as_str\(\)\)\?\.as_ptr\(\),\n\s+name\.len\(\),\n\s+value,\n\s+\);/let mut name = ZendStr::new_interned(name.as_str(), true);\n zend_declare_class_constant_ex(\n class,\n name.as_mut_ptr(),\n value,\n crate::ffi::ZEND_ACC_PUBLIC as _,\n ptr::null_mut(),\n );/s' \ + "$REG/ext-php-rs-0.15.12/src/builders/class.rs" + + # ext-php-rs keeps helper functions for globals in one C translation unit. + # The Rust staticlib can pull in the whole object even when a helper is not + # called, so mark optional PHP globals weak to keep dlopen from requiring + # PHP.wasm exports for unused helpers. + sed -i \ + -e '/#pragma weak zend_one_char_string/a #pragma weak executor_globals' \ + -e '/#pragma weak zend_one_char_string/a #pragma weak compiler_globals' \ + -e '/#pragma weak zend_one_char_string/a #pragma weak core_globals' \ + -e '/#pragma weak zend_one_char_string/a #pragma weak sapi_globals' \ + -e '/#pragma weak zend_one_char_string/a #pragma weak file_globals' \ + -e '/#pragma weak zend_one_char_string/a #pragma weak sapi_module' \ + "$REG/ext-php-rs-0.15.12/src/wrapper.c" + + # php_eval is not used by this extension, but ext-php-rs exposes wrappers + # for it from wrapper.c. Since the wrapper object is linked into the static + # archive, those wrappers can still pull in PHP compile/execute symbols that + # PHP.wasm does not export. Stub the unused wrappers so module startup does + # not try to resolve them through JS import stubs. + sed -i '/^zend_op_array \*ext_php_rs_zend_compile_string(/,/^}/c\ +zend_op_array *ext_php_rs_zend_compile_string(zend_string *source, const char *filename) {\ + (void) source;\ + (void) filename;\ + return NULL;\ +}' "$REG/ext-php-rs-0.15.12/src/wrapper.c" + sed -i '/^void ext_php_rs_zend_execute(/,/^}/c\ +void ext_php_rs_zend_execute(zend_op_array *op_array) {\ + (void) op_array;\ +}' "$REG/ext-php-rs-0.15.12/src/wrapper.c" + + # Avoid ext-php-rs' direct Rust import of the sapi_module global in output + # helpers. Route it through the weak C wrapper instead. + sed -i 's/ffi::{php_output_write, php_printf, sapi_module}/ffi::{php_output_write, php_printf}/' \ + "$REG/ext-php-rs-0.15.12/src/zend/mod.rs" + sed -i 's/sapi_module\.ub_write/(*crate::ffi::ext_php_rs_sapi_module()).ub_write/' \ + "$REG/ext-php-rs-0.15.12/src/zend/mod.rs" + sed -i 's/sapi_module\.name/(*crate::ffi::ext_php_rs_sapi_module()).name/' \ + "$REG/ext-php-rs-0.15.12/src/zend/mod.rs" + + # Verify the targeted patches matched the registry sources before doing an + # expensive cargo build. + ! grep -Eq 'unsafe \{ (zend_standard_class_def|zend_ce_[a-z_]+)\.as_ref\(\) \}\.unwrap\(\)' \ + "$REG/ext-php-rs-0.15.12/src/zend/ce.rs" + ! grep -q 'ExecutorGlobals::get().class_table()' \ + "$REG/ext-php-rs-0.15.12/src/zend/class.rs" + ! grep -q 'let stub = crate::convert::zval_to_stub(&zval)' \ + "$REG/ext-php-rs-0.15.12/src/builders/class.rs" + grep -q 'zend_declare_class_constant_ex,' \ + "$REG/ext-php-rs-0.15.12/allowed_bindings.rs" + grep -q 'zend_declare_class_constant_ex(' \ + "$REG/ext-php-rs-0.15.12/src/builders/class.rs" + ! grep -q 'zend_declare_class_constant(' \ + "$REG/ext-php-rs-0.15.12/src/builders/class.rs" + ! grep -q 'return zend_compile_string' \ + "$REG/ext-php-rs-0.15.12/src/wrapper.c" + ! grep -q 'zend_execute(op_array' \ + "$REG/ext-php-rs-0.15.12/src/wrapper.c" + grep -q '#pragma weak file_globals' \ + "$REG/ext-php-rs-0.15.12/src/wrapper.c" + + # Use nightly + -Zbuild-std=std,panic_abort so libstd is rebuilt with + # panic=abort. Without rebuilding std, the precompiled libstd still + # contains panic-unwind code that imports __cpp_exception. + cargo +nightly build --release --target wasm32-unknown-emscripten \ + -Zbuild-std=std,panic_abort + cp target/wasm32-unknown-emscripten/release/libwp_mysql_parser.a /out/ +EOF + +echo "==> Stage 2: phpize + emconfigure + emmake (@php-wasm/compile-extension)" +SRC_STAGE="$(mktemp -d)" +CLI_STAGE="$(mktemp -d)" +trap 'rm -rf "$SRC_STAGE" "$CLI_STAGE"' EXIT +cp "$SPIKE_DIR/shim/config.m4" "$SRC_STAGE/" +cp "$SPIKE_DIR/shim/wp_mysql_parser_shim.c" "$SRC_STAGE/" +cp "$OUT_DIR/libwp_mysql_parser.a" "$SRC_STAGE/" + +ARTIFACT="wp_mysql_parser-php${PHP_VERSION}-${ASYNC_MODE}.so" +COMPILE_EXTENSION_CLI="$CLI_STAGE/node_modules/@php-wasm/compile-extension/cli.js" + +npm install --prefix "$CLI_STAGE" --no-audit --no-fund --ignore-scripts "$COMPILE_EXTENSION_PACKAGE" +patch_compile_extension_cli "$COMPILE_EXTENSION_CLI" + +( + cd "$PLAYGROUND_REPO" + COMPILE_EXTENSION_SKIP_IMAGE_BUILD=1 node "$COMPILE_EXTENSION_CLI" \ + --source "$SRC_STAGE" \ + --name wp_mysql_parser \ + --php-versions "$PHP_VERSION" \ + --out "$OUT_DIR" \ + --jobs 1 \ + --extra-ldflags "/build/libwp_mysql_parser.a" +) + +rm -rf "$SRC_STAGE" +trap - EXIT + +echo "==> Built $OUT_DIR/$ARTIFACT" diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/check-playground-web-compat.mjs b/packages/php-ext-wp-mysql-parser/wasm-spike/check-playground-web-compat.mjs new file mode 100755 index 00000000..2d8983af --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/check-playground-web-compat.mjs @@ -0,0 +1,105 @@ +#!/usr/bin/env node +import { existsSync, readdirSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const SPIKE_DIR = resolve(fileURLToPath(new URL('.', import.meta.url))); +const PHP_VERSION = process.env.PHP_VERSION || '8.4'; +const ASYNC_MODE = process.env.ASYNC_MODE || 'jspi'; +const EXTENSION_NAME = process.env.EXTENSION_NAME || 'wp_mysql_parser'; +const SIDE_MODULE = resolve( + process.env.SIDE_MODULE || + `${SPIKE_DIR}/dist/${EXTENSION_NAME}-php${PHP_VERSION}-${ASYNC_MODE}.so` +); +const PLAYGROUND_REPO = resolve( + process.env.PLAYGROUND_REPO || + `${SPIKE_DIR}/../../../../wordpress-playground-spike` +); +const EXPECTED_MISSING = new Set( + (process.env.PLAYGROUND_WEB_EXPECTED_MISSING_SYMBOLS || '') + .split(/[\s,]+/) + .filter(Boolean) +); + +function findWasmFile(dir) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = resolve(dir, entry.name); + if (entry.isDirectory()) { + const nested = findWasmFile(path); + if (nested) { + return nested; + } + continue; + } + if (entry.isFile() && entry.name.endsWith('.wasm')) { + return path; + } + } + return null; +} + +function loadWasmModule(path) { + if (!existsSync(path)) { + console.error(`[web-compat] Missing ${path}`); + process.exit(2); + } + return new WebAssembly.Module(readFileSync(path)); +} + +const phpDir = PHP_VERSION.replace('.', '-'); +const webBuildDir = resolve( + PLAYGROUND_REPO, + `packages/php-wasm/web-builds/${phpDir}/${ASYNC_MODE}` +); +const webRuntime = findWasmFile(webBuildDir); +if (!webRuntime) { + console.error(`[web-compat] Could not find a PHP.wasm file under ${webBuildDir}`); + process.exit(2); +} + +const sideImports = WebAssembly.Module.imports(loadWasmModule(SIDE_MODULE)); +const importedFunctions = sideImports + .filter((entry) => entry.module === 'env' && entry.kind === 'function') + .map((entry) => entry.name) + .sort(); +const runtimeExports = new Map( + WebAssembly.Module.exports(loadWasmModule(webRuntime)).map((entry) => [ + entry.name, + entry.kind, + ]) +); +const missing = importedFunctions.filter( + (name) => runtimeExports.get(name) !== 'function' +); +const unexpectedMissing = missing.filter((name) => !EXPECTED_MISSING.has(name)); +const staleExpectedMissing = [...EXPECTED_MISSING].filter( + (name) => !missing.includes(name) +); + +console.log(`[web-compat] PHP ${PHP_VERSION} side module: ${SIDE_MODULE}`); +console.log(`[web-compat] PHP ${PHP_VERSION} web runtime: ${webRuntime}`); + +if (unexpectedMissing.length > 0 || staleExpectedMissing.length > 0) { + if (unexpectedMissing.length > 0) { + console.error( + `[web-compat] Missing browser runtime exports: ${unexpectedMissing.join(', ')}` + ); + } + if (staleExpectedMissing.length > 0) { + console.error( + `[web-compat] Expected missing exports are now available: ${staleExpectedMissing.join(', ')}` + ); + console.error( + '[web-compat] Update the browser demo PHP pin and remove the stale allowlist.' + ); + } + process.exit(1); +} + +if (missing.length > 0) { + console.log( + `[web-compat] Known browser runtime gap confirmed: ${missing.join(', ')}` + ); +} else { + console.log('[web-compat] Browser runtime exports all imported functions.'); +} diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/probe-host-cargo-wasm.sh b/packages/php-ext-wp-mysql-parser/wasm-spike/probe-host-cargo-wasm.sh new file mode 100755 index 00000000..144243d6 --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/probe-host-cargo-wasm.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Probe 1: try to cross-compile the ext-php-rs crate from the host with +# Emscripten on PATH. Expected to FAIL at the bindgen step inside ext-php-rs's +# build.rs because the host php-config points at native PHP headers and +# emscripten's clang chokes on the host system headers they pull in. +# +# Surfacing that failure is the point: it documents which knobs would have to +# move (PHP headers from a wasm-built PHP, plus an emcc-aware bindgen target) +# before any further work is meaningful. +set -uo pipefail + +cd "$(dirname "$0")/.." + +if ! command -v emcc >/dev/null 2>&1; then + echo "emcc not on PATH. Activate emsdk first." >&2 + exit 2 +fi + +if ! rustup target list --installed 2>/dev/null | grep -q wasm32-unknown-emscripten; then + rustup target add wasm32-unknown-emscripten +fi + +set -x +CC=emcc CXX=em++ AR=emar \ + cargo build --release --target wasm32-unknown-emscripten 2>&1 | tee wasm-spike/probe-host-cargo-wasm.log +exit "${PIPESTATUS[0]}" diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/run-spike.mjs b/packages/php-ext-wp-mysql-parser/wasm-spike/run-spike.mjs new file mode 100644 index 00000000..27c74a0c --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/run-spike.mjs @@ -0,0 +1,103 @@ +// Headless Playground runner for the wasm-spike. Uses the stock +// load-built-extension.mjs harness shipped with @php-wasm/compile-extension. +// We feed it our manifest and a snippet of PHP that pokes the Rust parser, +// then assert the output. +// +// Run with: +// node packages/php-ext-wp-mysql-parser/wasm-spike/run-spike.mjs +// +// Optional env: PLAYGROUND_REPO=/path/to/wordpress-playground checkout. + +import { spawnSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = dirname(fileURLToPath(import.meta.url)); +const SPIKE_DIR = here; +const MANIFEST = resolve(SPIKE_DIR, 'dist/manifest.json'); +const PHP_VERSION = process.env.PHP_VERSION || '8.4'; + +if (!existsSync(MANIFEST)) { + console.error(`[spike] Missing ${MANIFEST}. Run build-in-docker-rust.sh first.`); + process.exit(2); +} + +const PLAYGROUND_REPO = + process.env.PLAYGROUND_REPO || + resolve(here, '../../../../wordpress-playground-spike'); + +if (!existsSync(resolve(PLAYGROUND_REPO, 'package.json'))) { + console.error(`[spike] PLAYGROUND_REPO not found at ${PLAYGROUND_REPO}`); + process.exit(2); +} + +const PHP_CODE = `native_token_stream(); +echo 'COUNT=', $stream->count(); +`; + +const EXPECTED = + 'COUNT=5'; + +// Probe JSPI up front: loadNodeRuntime asks wasm-feature-detect for it, and +// custom extensions only load under JSPI. If the probe fails here we know +// the harness will reject the side module before the runtime ever tries. +const probe = spawnSync( + process.execPath, + [ + '--experimental-wasm-jspi', + '-e', + "import('wasm-feature-detect').then(async ({ jspi }) => { process.exit((await jspi()) ? 0 : 42); })", + ], + { cwd: PLAYGROUND_REPO, stdio: 'inherit' } +); +if (probe.status !== 0) { + console.error( + `[spike] wasm-feature-detect/jspi probe failed (exit ${probe.status}). ` + + `Node ${process.version} cannot enable JSPI; aborting before runtime.` + ); + process.exit(probe.status || 1); +} + +const cmd = [ + '--experimental-wasm-jspi', + '--experimental-strip-types', + '--experimental-transform-types', + '--disable-warning=ExperimentalWarning', + '--import', + resolve( + PLAYGROUND_REPO, + 'packages/meta/src/node-es-module-loader/register.mts' + ), + resolve( + PLAYGROUND_REPO, + 'packages/php-wasm/compile-extension/tests/load-built-extension.mjs' + ), + MANIFEST, + PHP_VERSION, + PHP_CODE, + EXPECTED, +]; + +console.error('[spike] running load-built-extension.mjs against', MANIFEST); +const result = spawnSync(process.execPath, cmd, { + cwd: PLAYGROUND_REPO, + stdio: 'inherit', +}); +process.exit(result.status ?? 1); diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/shim/config.m4 b/packages/php-ext-wp-mysql-parser/wasm-spike/shim/config.m4 new file mode 100644 index 00000000..c0cbb7dd --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/shim/config.m4 @@ -0,0 +1,21 @@ +dnl C shim that lets the Playground compile-extension Docker pipeline (phpize + +dnl emconfigure + emmake) build a side module whose actual code is a +dnl pre-compiled Rust staticlib produced by `cargo build --target +dnl wasm32-unknown-emscripten --release`. +dnl +dnl The staticlib is NOT linked here through PHP_ADD_LIBRARY_WITH_PATH — +dnl libtool refuses to treat a wasm `.a` as a viable input for a `.so` +dnl link and silently degrades the build to a static module. Instead, the +dnl spike's wrapper (`build-in-docker-rust.sh`) passes the archive to +dnl `@php-wasm/compile-extension` via `--extra-ldflags +dnl /build/libwp_mysql_parser.a`, which injects it with `--whole-archive` +dnl into the final libtool link. + +PHP_ARG_ENABLE([wp_mysql_parser], + [whether to enable wp_mysql_parser], + [AS_HELP_STRING([--enable-wp_mysql_parser], [Enable wp_mysql_parser])], + [yes]) + +if test "$PHP_WP_MYSQL_PARSER" != "no"; then + PHP_NEW_EXTENSION(wp_mysql_parser, wp_mysql_parser_shim.c, $ext_shared) +fi diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/shim/wp_mysql_parser_shim.c b/packages/php-ext-wp-mysql-parser/wasm-spike/shim/wp_mysql_parser_shim.c new file mode 100644 index 00000000..3433e3a0 --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/shim/wp_mysql_parser_shim.c @@ -0,0 +1,16 @@ +/* + * Trivial shim. ext-php-rs already exports `get_module()` from the Rust + * crate when built with the `runtime` feature; phpize only needs a C + * translation unit so that Autoconf has something to compile and so the + * libtool archive command gets invoked. The real module entry comes from + * libwp_mysql_parser.a, force-linked through PHP_ADD_LIBRARY_WITH_PATH + + * --whole-archive in build-in-docker.sh. + */ +#include "php.h" + +/* Forward-declare so the linker keeps the symbol from the Rust archive. */ +extern zend_module_entry *get_module(void); + +/* Reference get_module so the static library is not GC'd by the linker + * even if the compile-extension whole-archive path changes. */ +zend_module_entry *(*wp_mysql_parser_keep_alive)(void) = get_module; diff --git a/packages/php-ext-wp-mysql-parser/wasm-spike/write-extension-manifest.mjs b/packages/php-ext-wp-mysql-parser/wasm-spike/write-extension-manifest.mjs new file mode 100644 index 00000000..86b073ae --- /dev/null +++ b/packages/php-ext-wp-mysql-parser/wasm-spike/write-extension-manifest.mjs @@ -0,0 +1,61 @@ +#!/usr/bin/env node +import { existsSync } from 'node:fs'; +import { writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +const DEFAULT_PHP_VERSIONS = ['8.5', '8.4', '8.3', '8.2', '8.1', '8.0']; + +const [, , outDirArg, ...argvPhpVersions] = process.argv; +const outDir = resolve(outDirArg || 'dist'); +const extensionName = process.env.EXTENSION_NAME || 'wp_mysql_parser'; +const asyncMode = process.env.ASYNC_MODE || 'jspi'; +const version = process.env.EXTENSION_VERSION || ''; +const envPhpVersions = (process.env.PHP_VERSIONS || '') + .split(/[,\s]+/) + .filter(Boolean); +const phpVersions = + argvPhpVersions.length > 0 + ? argvPhpVersions + : envPhpVersions.length > 0 + ? envPhpVersions + : DEFAULT_PHP_VERSIONS; + +const missingArtifacts = []; +const artifacts = phpVersions.map((phpVersion) => { + const sourcePath = `${extensionName}-php${phpVersion}-${asyncMode}.so`; + if (!existsSync(resolve(outDir, sourcePath))) { + missingArtifacts.push(sourcePath); + } + return { + phpVersion, + sourcePath, + }; +}); + +if (asyncMode !== 'jspi') { + console.error( + `Unsupported ASYNC_MODE: ${asyncMode}. Playground external extension manifests are JSPI-only.` + ); + process.exit(1); +} + +if (missingArtifacts.length > 0) { + console.error( + `Missing extension artifact(s) in ${outDir}: ${missingArtifacts.join(', ')}` + ); + process.exit(1); +} + +const manifest = { + name: extensionName, + mode: 'php-extension', + artifacts, +}; + +if (version) { + manifest.version = version; +} + +const manifestPath = resolve(outDir, 'manifest.json'); +await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); +console.log(`Wrote ${manifestPath}`); diff --git a/packages/plugin-sqlite-database-integration/LICENSE b/packages/plugin-sqlite-database-integration/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/packages/plugin-sqlite-database-integration/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/activate.php b/packages/plugin-sqlite-database-integration/activate.php similarity index 100% rename from activate.php rename to packages/plugin-sqlite-database-integration/activate.php diff --git a/admin-notices.php b/packages/plugin-sqlite-database-integration/admin-notices.php similarity index 100% rename from admin-notices.php rename to packages/plugin-sqlite-database-integration/admin-notices.php diff --git a/admin-page.php b/packages/plugin-sqlite-database-integration/admin-page.php similarity index 97% rename from admin-page.php rename to packages/plugin-sqlite-database-integration/admin-page.php index cd8613cc..82815626 100644 --- a/admin-page.php +++ b/packages/plugin-sqlite-database-integration/admin-page.php @@ -155,8 +155,7 @@ function sqlite_plugin_adminbar_item( $admin_bar ) { global $wpdb; if ( defined( 'SQLITE_DB_DROPIN_VERSION' ) && defined( 'DB_ENGINE' ) && 'sqlite' === DB_ENGINE ) { - $suffix = defined( 'WP_SQLITE_AST_DRIVER' ) && WP_SQLITE_AST_DRIVER ? ' (AST)' : ''; - $title = '' . __( 'Database: SQLite', 'sqlite-database-integration' ) . $suffix . ''; + $title = '' . __( 'Database: SQLite', 'sqlite-database-integration' ) . ''; } elseif ( stripos( $wpdb->db_server_info(), 'maria' ) !== false ) { $title = '' . __( 'Database: MariaDB', 'sqlite-database-integration' ) . ''; } else { diff --git a/packages/plugin-sqlite-database-integration/composer.json b/packages/plugin-sqlite-database-integration/composer.json new file mode 100644 index 00000000..d77c7f47 --- /dev/null +++ b/packages/plugin-sqlite-database-integration/composer.json @@ -0,0 +1,15 @@ +{ + "name": "wordpress/sqlite-database-integration", + "license": "GPL-2.0-or-later", + "description": "SQLite integration plugin for WordPress.", + "homepage": "https://github.com/wordpress/sqlite-database-integration", + "keywords": [ "wordpress", "plugin", "database", "sqlite" ], + "support": { + "issues": "https://github.com/wordpress/sqlite-database-integration/issues" + }, + "require": { + "php": ">=7.2", + "ext-pdo": "*", + "ext-pdo_sqlite": "*" + } +} diff --git a/constants.php b/packages/plugin-sqlite-database-integration/constants.php similarity index 85% rename from constants.php rename to packages/plugin-sqlite-database-integration/constants.php index 998df4ee..15e6772a 100644 --- a/constants.php +++ b/packages/plugin-sqlite-database-integration/constants.php @@ -51,8 +51,3 @@ define( 'FQDB', FQDBDIR . '.ht.sqlite' ); } } - -// Allow enabling the SQLite AST driver via environment variable. -if ( ! defined( 'WP_SQLITE_AST_DRIVER' ) && isset( $_ENV['WP_SQLITE_AST_DRIVER'] ) && 'true' === $_ENV['WP_SQLITE_AST_DRIVER'] ) { - define( 'WP_SQLITE_AST_DRIVER', true ); -} diff --git a/db.copy b/packages/plugin-sqlite-database-integration/db.copy similarity index 93% rename from db.copy rename to packages/plugin-sqlite-database-integration/db.copy index 0b0797e8..ef829137 100644 --- a/db.copy +++ b/packages/plugin-sqlite-database-integration/db.copy @@ -46,7 +46,10 @@ add_action( if ( ! function_exists( 'activate_plugin' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } - if ( is_plugin_inactive( '{SQLITE_PLUGIN}' ) ) { + + $plugin_path = WP_PLUGIN_DIR . '/' . '{SQLITE_PLUGIN}'; + + if ( file_exists( $plugin_path ) && is_plugin_inactive( '{SQLITE_PLUGIN}' ) ) { // If `activate_plugin()` returns a value other than null (like WP_Error), // the plugin could not be found. Try with a hardcoded string, // because that probably means the file was directly copy-pasted. diff --git a/deactivate.php b/packages/plugin-sqlite-database-integration/deactivate.php similarity index 100% rename from deactivate.php rename to packages/plugin-sqlite-database-integration/deactivate.php diff --git a/health-check.php b/packages/plugin-sqlite-database-integration/health-check.php similarity index 100% rename from health-check.php rename to packages/plugin-sqlite-database-integration/health-check.php diff --git a/integrations/query-monitor/boot.php b/packages/plugin-sqlite-database-integration/integrations/query-monitor/boot.php similarity index 95% rename from integrations/query-monitor/boot.php rename to packages/plugin-sqlite-database-integration/integrations/query-monitor/boot.php index 534d2669..c66e0db1 100644 --- a/integrations/query-monitor/boot.php +++ b/packages/plugin-sqlite-database-integration/integrations/query-monitor/boot.php @@ -67,7 +67,11 @@ function register_sqlite_enhancements_for_query_monitor() { return; } - require_once __DIR__ . '/plugin.php'; + if ( defined( 'QM_VERSION' ) && version_compare( QM_VERSION, '4.0.0', '>=' ) ) { + require_once __DIR__ . '/qm4.php'; + } else { + require_once __DIR__ . '/qm3.php'; + } if ( ! defined( 'SQLITE_QUERY_MONITOR_LOADED' ) ) { define( 'SQLITE_QUERY_MONITOR_LOADED', true ); diff --git a/integrations/query-monitor/plugin.php b/packages/plugin-sqlite-database-integration/integrations/query-monitor/qm3.php similarity index 100% rename from integrations/query-monitor/plugin.php rename to packages/plugin-sqlite-database-integration/integrations/query-monitor/qm3.php diff --git a/packages/plugin-sqlite-database-integration/integrations/query-monitor/qm4.php b/packages/plugin-sqlite-database-integration/integrations/query-monitor/qm4.php new file mode 100644 index 00000000..fe119833 --- /dev/null +++ b/packages/plugin-sqlite-database-integration/integrations/query-monitor/qm4.php @@ -0,0 +1,107 @@ +> + */ + public $queries = array(); +} + +/** + * Collector for SQLite query data. + * + * Extracts SQLite queries from $wpdb->queries and stores them + * indexed by SQL text for the QM 4.0+ JS integration. + */ +class SQLite_QM_Collector extends QM_Collector { + /** @var string */ + public $id = 'sqlite'; + + public function get_storage(): QM_Data { + return new SQLite_QM_Data(); + } + + public function process(): void { + global $wpdb; + + if ( empty( $wpdb->queries ) ) { + return; + } + + // Index by SQL rather than row position — robust to filtering, sorting, etc. + $mapped = array(); + foreach ( $wpdb->queries as $query ) { + // Query Monitor skips queries with 'wp_admin_bar' in the stack. + if ( false !== strpos( $query[2] ?? '', 'wp_admin_bar' ) ) { + continue; + } + if ( ! empty( $query['sqlite_queries'] ) ) { + $sql = trim( preg_replace( '/\s+/', ' ', $query[0] ) ); + $mapped[ $sql ] = array_column( $query['sqlite_queries'], 'sql' ); + } + } + $this->data->queries = $mapped; + } +} + +/** + * HTML outputter for SQLite query data. + * + * With $client_side_rendered = true, QM auto-serializes the collector data + * into "window.QueryMonitorData.data.sqlite". This outputter's only job is to + * emit the inline JS module that reads that data and injects SQLite query + * details into QM 4.0's shadow DOM DB queries panel. + */ +class SQLite_QM_Output_Html extends QM_Output_Html { + /** @var bool */ + public static $client_side_rendered = true; + + public function name(): string { + return 'SQLite'; + } + + public function output(): void { + if ( empty( $this->get_collector()->get_data()->queries ) ) { + return; + } + + $js_path = __DIR__ . '/query-monitor-sqlite.js'; + if ( is_readable( $js_path ) ) { + echo ''; + } + } +} + +/** + * Register the SQLite collector. + */ +function register_sqlite_qm_collector( array $collectors ): array { + $collectors['sqlite'] = new SQLite_QM_Collector(); + return $collectors; +} + +add_filter( 'qm/collectors', 'register_sqlite_qm_collector', 20 ); + +/** + * Register the SQLite HTML outputter. + */ +function register_sqlite_qm_output_html( array $output, QM_Collectors $collectors ): array { + $collector = QM_Collectors::get( 'sqlite' ); + if ( $collector ) { + $output['sqlite'] = new SQLite_QM_Output_Html( $collector ); + } + return $output; +} + +add_filter( 'qm/outputter/html', 'register_sqlite_qm_output_html', 30, 2 ); diff --git a/packages/plugin-sqlite-database-integration/integrations/query-monitor/query-monitor-sqlite.js b/packages/plugin-sqlite-database-integration/integrations/query-monitor/query-monitor-sqlite.js new file mode 100644 index 00000000..d8806974 --- /dev/null +++ b/packages/plugin-sqlite-database-integration/integrations/query-monitor/query-monitor-sqlite.js @@ -0,0 +1,95 @@ +const STYLE = ` + details.qm-sqlite { + margin: 6px 0 0; + } + details.qm-sqlite summary { + cursor: pointer; + } + details.qm-sqlite ol { + margin: 6px 0 0; + padding-left: 24px; + list-style: decimal; + } +`; + +const container = document.getElementById( 'query-monitor-container' ); +const sqliteData = window.QueryMonitorData?.data?.sqlite?.data?.queries; + +if ( container && sqliteData ) { + // QM attaches the shadow root in its own DOMContentLoaded listener. + // Our module is loaded after QM's, so our listener fires after QM's. + document.addEventListener( 'DOMContentLoaded', () => { + const shadowRoot = container.shadowRoot; + if ( ! shadowRoot ) { + return; + } + inject( shadowRoot, sqliteData ); + + // Re-inject after Preact re-renders (panel switches, filters, etc.). + // Debounced to avoid excessive work during rapid DOM updates. + let timer; + new MutationObserver( () => { + clearTimeout( timer ); + timer = setTimeout( () => inject( shadowRoot, sqliteData ), 100 ); + } ).observe( shadowRoot, { childList: true, subtree: true } ); + } ); +} + +function inject( shadowRoot, data ) { + const panel = shadowRoot.getElementById( 'qm-db_queries' ); + if ( ! panel ) { + return; + } + + if ( ! shadowRoot.querySelector( 'style.qm-sqlite-style' ) ) { + const style = document.createElement( 'style' ); + style.className = 'qm-sqlite-style'; + style.textContent = STYLE; + shadowRoot.appendChild( style ); + } + + // Match by SQL rather than row position — robust to filtering, sorting, etc. + for ( const code of panel.querySelectorAll( 'td.qm-cell-sql > code' ) ) { + const cell = code.parentElement; + const key = code.innerText.replace( /\s+/g, ' ' ).trim(); + const queries = data[ key ]; + const existing = cell.querySelector( 'details.qm-sqlite' ); + + // Preact may recycle DOM nodes on filter/sort, leaving stale details + // from a previous query. Remove them when the SQL key no longer matches. + if ( existing ) { + if ( queries?.length && existing.dataset.sqliteKey === key ) { + continue; + } + existing.remove(); + } + + if ( queries?.length ) { + cell.append( buildDetails( key, queries ) ); + } + } +} + +function buildDetails( key, queries ) { + const details = document.createElement( 'details' ); + details.className = 'qm-sqlite'; + details.dataset.sqliteKey = key; + // Prevent QM's row click handlers from firing when toggling. + details.addEventListener( 'click', ( e ) => e.stopPropagation() ); + + const summary = document.createElement( 'summary' ); + summary.textContent = `Executed ${ queries.length } SQLite ${ queries.length === 1 ? 'Query' : 'Queries' }`; + + const ol = document.createElement( 'ol' ); + for ( const sql of queries ) { + const li = document.createElement( 'li' ); + li.className = 'qm-sqlite-query'; + const code = document.createElement( 'code' ); + code.textContent = sql; + li.append( code ); + ol.append( li ); + } + + details.append( summary, ol ); + return details; +} diff --git a/load.php b/packages/plugin-sqlite-database-integration/load.php similarity index 87% rename from load.php rename to packages/plugin-sqlite-database-integration/load.php index ce13e340..83ec9c4c 100644 --- a/load.php +++ b/packages/plugin-sqlite-database-integration/load.php @@ -3,7 +3,7 @@ * Plugin Name: SQLite Database Integration * Description: SQLite database driver drop-in. * Author: The WordPress Team - * Version: 2.2.16 + * Version: 3.0.0-rc.3 * Requires PHP: 7.2 * Textdomain: sqlite-database-integration * @@ -16,11 +16,10 @@ * Load the "SQLITE_DRIVER_VERSION" constant. * This constant needs to be updated on plugin release! */ -require_once __DIR__ . '/version.php'; +require_once __DIR__ . '/wp-includes/database/version.php'; define( 'SQLITE_MAIN_FILE', __FILE__ ); -require_once __DIR__ . '/php-polyfills.php'; require_once __DIR__ . '/admin-page.php'; require_once __DIR__ . '/activate.php'; require_once __DIR__ . '/deactivate.php'; diff --git a/readme.txt b/packages/plugin-sqlite-database-integration/readme.txt similarity index 55% rename from readme.txt rename to packages/plugin-sqlite-database-integration/readme.txt index dc3b008c..8a21c881 100644 --- a/readme.txt +++ b/packages/plugin-sqlite-database-integration/readme.txt @@ -1,47 +1,78 @@ -=== SQLite Database Integration === - -Contributors: wordpressdotorg, aristath, janjakes, zieladam, berislav.grgicak, bpayton, zaerl -Requires at least: 6.4 -Tested up to: 6.9 -Requires PHP: 7.2 -Stable tag: 2.2.16 -License: GPLv2 or later -License URI: https://www.gnu.org/licenses/gpl-2.0.html -Tags: performance, database - -SQLite integration plugin by the WordPress Team. - -== Description == - -The SQLite plugin is a community, feature plugin. The intent is to allow testing an SQLite integration with WordPress and gather feedback, with the goal of eventually landing it in WordPress core. - -This feature plugin includes code from the PHPMyAdmin project (specifically parts of the PHPMyAdmin/sql-parser library), licensed under the GPL v2 or later. More info on the PHPMyAdmin/sql-parser library can be found on [GitHub](https://github.com/phpmyadmin/sql-parser). - -== Frequently Asked Questions == - -= What is the purpose of this plugin? = - -The primary purpose of the SQLite plugin is to allow testing the use of an SQLite database, with the goal to eventually land in WordPress core. - -You can read the original proposal on the [Make blog](https://make.wordpress.org/core/2022/09/12/lets-make-wordpress-officially-support-sqlite/), as well as the [call for testing](https://make.wordpress.org/core/2022/12/20/help-us-test-the-sqlite-implementation/) for more context and useful information. - -= Can I use this plugin on my production site? = - -Per the primary purpose of the plugin (see above), it can mostly be considered a beta testing plugin. To a degree, it should be okay to use it in production. However, as with every plugin, you are doing so at your own risk. - -= Where can I submit my plugin feedback? = - -Feedback is encouraged and much appreciated, especially since this plugin is a future WordPress core feature. If you need help with troubleshooting or have a question, suggestions, or requests, you can [submit them as an issue in the SQLite GitHub repository](https://github.com/wordpress/sqlite-database-integration/issues/new). - -= How can I contribute to the plugin? = - -Contributions are always welcome! Learn more about how to get involved in the [Core Performance Team Handbook](https://make.wordpress.org/performance/handbook/get-involved/). - -= Does this plugin change how WordPress queries are executed? = - -The plugin replaces the default MySQL-based database layer with an -SQLite-backed implementation. Core WordPress code continues to use -the wpdb API, while queries are internally adapted to be compatible -with SQLite syntax and behavior. - - +=== SQLite Database Integration === + +Contributors: wordpressdotorg, aristath, janjakes, zieladam, berislav.grgicak, bpayton, zaerl +Requires at least: 6.4 +Tested up to: 6.9 +Requires PHP: 7.2 +Stable tag: 3.0.0-rc.3 +License: GPLv2 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html +Tags: performance, database + +SQLite integration plugin by the WordPress Team. + +== Description == + +The SQLite plugin is a community, feature plugin. The intent is to allow testing an SQLite integration with WordPress and gather feedback, with the goal of eventually landing it in WordPress core. + +== Frequently Asked Questions == + += What is the purpose of this plugin? = + +The primary purpose of the SQLite plugin is to allow testing the use of an SQLite database, with the goal to eventually land in WordPress core. + +You can read the original proposal on the [Make blog](https://make.wordpress.org/core/2022/09/12/lets-make-wordpress-officially-support-sqlite/), as well as the [call for testing](https://make.wordpress.org/core/2022/12/20/help-us-test-the-sqlite-implementation/) for more context and useful information. + += Can I use this plugin on my production site? = + +Per the primary purpose of the plugin (see above), it can mostly be considered a beta testing plugin. To a degree, it should be okay to use it in production. However, as with every plugin, you are doing so at your own risk. + += Where can I submit my plugin feedback? = + +Feedback is encouraged and much appreciated, especially since this plugin is a future WordPress core feature. If you need help with troubleshooting or have a question, suggestions, or requests, you can [submit them as an issue in the SQLite GitHub repository](https://github.com/wordpress/sqlite-database-integration/issues/new). + += How can I contribute to the plugin? = + +Contributions are always welcome! Learn more about how to get involved in the [Core Performance Team Handbook](https://make.wordpress.org/performance/handbook/get-involved/). + += Does this plugin change how WordPress queries are executed? = + +The plugin replaces the default MySQL-based database layer with an +SQLite-backed implementation. Core WordPress code continues to use +the wpdb API, while queries are internally adapted to be compatible +with SQLite syntax and behavior. + +== Changelog == + += 3.0.0-rc.3 = + +* Lexer: Fix possible OOB read in quoted strings ([#374](https://github.com/WordPress/sqlite-database-integration/pull/374)) +* Add support for `NO_AUTO_VALUE_ON_ZERO` SQL mode ([#366](https://github.com/WordPress/sqlite-database-integration/pull/366)) + += 3.0.0-rc.2 = + +* Support MySQL `BINARY` operator ([#369](https://github.com/WordPress/sqlite-database-integration/pull/369)) +* Add support for `AUTO_INCREMENT` value management ([#367](https://github.com/WordPress/sqlite-database-integration/pull/367)) +* Add support for `DELETE` with `LIMIT` and `ORDER BY` ([#365](https://github.com/WordPress/sqlite-database-integration/pull/365)) + += 3.0.0-rc.1 = + +* Improve concurrent database access ([#361](https://github.com/WordPress/sqlite-database-integration/pull/361)) +* Remove legacy SQLite driver ([#358](https://github.com/WordPress/sqlite-database-integration/pull/358)) + += 2.2.23 = + +* Add Query Monitor 4.0 support ([#357](https://github.com/WordPress/sqlite-database-integration/pull/357)) +* Translate MySQL CONVERT() expressions to SQLite ([#356](https://github.com/WordPress/sqlite-database-integration/pull/356)) + += 2.2.22 = + +* Support INSERT without INTO keyword ([#354](https://github.com/WordPress/sqlite-database-integration/pull/354)) +* Add tests for MySQL row-level locking clauses ([#342](https://github.com/WordPress/sqlite-database-integration/pull/342)) +* Improve automated deploy setup. + += 2.2.21 = + +* Monorepo setup + release automation ([#334](https://github.com/WordPress/sqlite-database-integration/pull/334)) +* Rework release workflow ([#350](https://github.com/WordPress/sqlite-database-integration/pull/350)) +* Fix incorrect PHP polyfill implementations ([#338](https://github.com/WordPress/sqlite-database-integration/pull/338)) diff --git a/packages/plugin-sqlite-database-integration/wp-includes/database b/packages/plugin-sqlite-database-integration/wp-includes/database new file mode 120000 index 00000000..57642c9c --- /dev/null +++ b/packages/plugin-sqlite-database-integration/wp-includes/database @@ -0,0 +1 @@ +../../mysql-on-sqlite/src/ \ No newline at end of file diff --git a/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php similarity index 100% rename from wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php similarity index 83% rename from wp-includes/sqlite/class-wp-sqlite-db.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php index 0af6ac70..072030e7 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php @@ -16,7 +16,7 @@ class WP_SQLite_DB extends wpdb { /** * Database Handle * - * @var WP_SQLite_Translator + * @var WP_SQLite_Driver */ protected $dbh; @@ -94,10 +94,6 @@ public function get_col_charset( $table, $column ) { * @param array $modes Optional. A list of SQL modes to set. Default empty array. */ public function set_sql_mode( $modes = array() ) { - if ( ! $this->dbh instanceof WP_SQLite_Driver ) { - return; - } - if ( empty( $modes ) ) { $result = $this->dbh->query( 'SELECT @@SESSION.sql_mode' ); if ( ! isset( $result[0] ) ) { @@ -175,28 +171,6 @@ public function _real_escape( $data ) { return $this->add_placeholder_escape( $escaped ); } - /** - * Method to dummy out wpdb::esc_like() function. - * - * WordPress 4.0.0 introduced esc_like() function that adds backslashes to %, - * underscore and backslash, which is not interpreted as escape character - * by SQLite. So we override it and dummy out this function. - * - * @param string $text The raw text to be escaped. The input typed by the user should have no - * extra or deleted slashes. - * - * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare() - * or real_escape next. - */ - public function esc_like( $text ) { - // The new driver adds "ESCAPE '\\'" to every LIKE expression by default. - // We only need to overload this function to a no-op for the old driver. - if ( $this->dbh instanceof WP_SQLite_Driver ) { - return parent::esc_like( $text ); - } - return $text; - } - /** * Prints SQL/DB error. * @@ -306,35 +280,48 @@ public function db_connect( $allow_bail = true ) { if ( isset( $GLOBALS['@pdo'] ) ) { $pdo = $GLOBALS['@pdo']; } - if ( defined( 'WP_SQLITE_AST_DRIVER' ) && WP_SQLITE_AST_DRIVER ) { - if ( null === $this->dbname || '' === $this->dbname ) { - $this->bail( - 'The database name was not set. The SQLite driver requires a database name to be set to emulate MySQL information schema tables.', - 'db_connect_fail' - ); - return false; - } - require_once __DIR__ . '/../../wp-pdo-mysql-on-sqlite.php'; - $this->ensure_database_directory( FQDB ); - - try { - $connection = new WP_SQLite_Connection( - array( - 'pdo' => $pdo, - 'path' => FQDB, - 'journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null, - ) - ); - $this->dbh = new WP_SQLite_Driver( $connection, $this->dbname ); - $GLOBALS['@pdo'] = $this->dbh->get_connection()->get_pdo(); - } catch ( Throwable $e ) { - $this->last_error = $this->format_error_message( $e ); + // Migrate the database file from a legacy path, if it exists. + if ( ! defined( 'DB_FILE' ) && ! file_exists( FQDB ) ) { + $old_db_path = FQDBDIR . '.ht.sqlite.php'; + + if ( file_exists( $old_db_path ) ) { + if ( ! rename( $old_db_path, FQDB ) ) { + wp_die( 'Failed to rename database file.', 'Error!' ); + } + + foreach ( array( '-wal', '-shm', '-journal' ) as $suffix ) { + if ( file_exists( $old_db_path . $suffix ) ) { + if ( ! rename( $old_db_path . $suffix, FQDB . $suffix ) ) { + wp_die( 'Failed to rename database file.', 'Error!' ); + } + } + } } - } else { - $this->dbh = new WP_SQLite_Translator( $pdo ); - $this->last_error = $this->dbh->get_error_message(); - $GLOBALS['@pdo'] = $this->dbh->get_pdo(); + } + + if ( null === $this->dbname || '' === $this->dbname ) { + $this->bail( + 'The database name was not set. The SQLite driver requires a database name to be set to emulate MySQL information schema tables.', + 'db_connect_fail' + ); + return false; + } + + $this->ensure_database_directory( FQDB ); + + try { + $connection = new WP_SQLite_Connection( + array( + 'pdo' => $pdo, + 'path' => FQDB, + 'journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null, + ) + ); + $this->dbh = new WP_SQLite_Driver( $connection, $this->dbname ); + $GLOBALS['@pdo'] = $this->dbh->get_connection()->get_pdo(); + } catch ( Throwable $e ) { + $this->last_error = $this->format_error_message( $e ); } if ( $this->last_error ) { return false; @@ -448,11 +435,7 @@ public function query( $query ) { if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) { $return_val = true; } elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) { - if ( $this->dbh instanceof WP_SQLite_Driver ) { - $this->rows_affected = $this->dbh->get_last_return_value(); - } else { - $this->rows_affected = $this->dbh->get_affected_rows(); - } + $this->rows_affected = $this->dbh->get_last_return_value(); // Take note of the insert_id. if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) { @@ -497,11 +480,7 @@ public function query( $query ) { } // Add SQLite query data. - if ( $this->dbh instanceof WP_SQLite_Driver ) { - $this->queries[ $i ]['sqlite_queries'] = $this->dbh->get_last_sqlite_queries(); - } else { - $this->queries[ $i ]['sqlite_queries'] = $this->dbh->executed_sqlite_queries; - } + $this->queries[ $i ]['sqlite_queries'] = $this->dbh->get_last_sqlite_queries(); } return $return_val; } @@ -526,10 +505,6 @@ private function _do_query( $query ) { $this->last_error = $this->format_error_message( $e ); } - if ( $this->dbh instanceof WP_SQLite_Translator ) { - $this->last_error = $this->dbh->get_error_message(); - } - ++$this->num_queries; if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { @@ -554,27 +529,23 @@ protected function load_col_info() { if ( $this->col_info ) { return; } - if ( $this->dbh instanceof WP_SQLite_Driver ) { - $this->col_info = array(); - foreach ( $this->dbh->get_last_column_meta() as $column ) { - $this->col_info[] = (object) array( - 'name' => $column['name'], - 'orgname' => $column['mysqli:orgname'], - 'table' => $column['table'], - 'orgtable' => $column['mysqli:orgtable'], - 'def' => '', // Unused, always ''. - 'db' => $column['mysqli:db'], - 'catalog' => 'def', // Unused, always 'def'. - 'max_length' => 0, // As of PHP 8.1, this is always 0. - 'length' => $column['len'], - 'charsetnr' => $column['mysqli:charsetnr'], - 'flags' => $column['mysqli:flags'], - 'type' => $column['mysqli:type'], - 'decimals' => $column['precision'], - ); - } - } else { - $this->col_info = $this->dbh->get_columns(); + $this->col_info = array(); + foreach ( $this->dbh->get_last_column_meta() as $column ) { + $this->col_info[] = (object) array( + 'name' => $column['name'], + 'orgname' => $column['mysqli:orgname'], + 'table' => $column['table'], + 'orgtable' => $column['mysqli:orgtable'], + 'def' => '', // Unused, always ''. + 'db' => $column['mysqli:db'], + 'catalog' => 'def', // Unused, always 'def'. + 'max_length' => 0, // As of PHP 8.1, this is always 0. + 'length' => $column['len'], + 'charsetnr' => $column['mysqli:charsetnr'], + 'flags' => $column['mysqli:flags'], + 'type' => $column['mysqli:type'], + 'decimals' => $column['precision'], + ); } } diff --git a/wp-includes/sqlite/db.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/db.php similarity index 60% rename from wp-includes/sqlite/db.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/db.php index dc50d22e..03313515 100644 --- a/wp-includes/sqlite/db.php +++ b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/db.php @@ -9,10 +9,10 @@ /** * Load the "SQLITE_DRIVER_VERSION" constant. */ -require_once dirname( __DIR__, 2 ) . '/version.php'; +require_once __DIR__ . '/../database/version.php'; // Require the constants file. -require_once dirname( __DIR__, 2 ) . '/constants.php'; +require_once __DIR__ . '/../../constants.php'; // Bail early if DB_ENGINE is not defined as sqlite. if ( ! defined( 'DB_ENGINE' ) || 'sqlite' !== DB_ENGINE ) { @@ -47,28 +47,11 @@ ); } -require_once __DIR__ . '/class-wp-sqlite-lexer.php'; -require_once __DIR__ . '/class-wp-sqlite-query-rewriter.php'; -require_once __DIR__ . '/class-wp-sqlite-translator.php'; -require_once __DIR__ . '/class-wp-sqlite-token.php'; -require_once __DIR__ . '/class-wp-sqlite-pdo-user-defined-functions.php'; +require_once __DIR__ . '/../database/load.php'; require_once __DIR__ . '/class-wp-sqlite-db.php'; require_once __DIR__ . '/install-functions.php'; -/** - * The DB_NAME constant is required by the new SQLite driver. - * - * There are some existing projects in which the DB_NAME constant is missing in - * wp-config.php. To enable easier early adoption and testing of the new SQLite - * driver, let's allow using a default database name when DB_NAME is not set. - * - * TODO: For version 3.0, enforce the DB_NAME constant and remove the fallback. - */ -if ( defined( 'DB_NAME' ) && '' !== DB_NAME ) { - $db_name = DB_NAME; -} else { - $db_name = apply_filters( 'wp_sqlite_default_db_name', 'database_name_here' ); -} +$db_name = defined( 'DB_NAME' ) ? DB_NAME : ''; /* * Debug: Cross-check with MySQL. @@ -76,7 +59,7 @@ * that are present in the GitHub repository * but not the plugin published on WordPress.org. */ -$crosscheck_tests_file_path = dirname( __DIR__, 2 ) . '/tests/class-wp-sqlite-crosscheck-db.php'; +$crosscheck_tests_file_path = __DIR__ . '/class-wp-sqlite-crosscheck-db.php'; if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK && file_exists( $crosscheck_tests_file_path ) ) { require_once $crosscheck_tests_file_path; $GLOBALS['wpdb'] = new WP_SQLite_Crosscheck_DB( $db_name ); @@ -84,5 +67,5 @@ $GLOBALS['wpdb'] = new WP_SQLite_DB( $db_name ); // Boot the Query Monitor plugin if it is active. - require_once dirname( __DIR__, 2 ) . '/integrations/query-monitor/boot.php'; + require_once __DIR__ . '/../../integrations/query-monitor/boot.php'; } diff --git a/wp-includes/sqlite/install-functions.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/install-functions.php similarity index 92% rename from wp-includes/sqlite/install-functions.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/install-functions.php index bffe1425..5d73e39e 100644 --- a/wp-includes/sqlite/install-functions.php +++ b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/install-functions.php @@ -34,15 +34,11 @@ function sqlite_make_db_sqlite() { wp_die( $message, 'Database Error!' ); } - if ( defined( 'WP_SQLITE_AST_DRIVER' ) && WP_SQLITE_AST_DRIVER ) { - $translator = new WP_SQLite_Driver( - new WP_SQLite_Connection( array( 'pdo' => $pdo ) ), - $wpdb->dbname - ); - } else { - $translator = new WP_SQLite_Translator( $pdo ); - } - $query = null; + $translator = new WP_SQLite_Driver( + new WP_SQLite_Connection( array( 'pdo' => $pdo ) ), + $wpdb->dbname + ); + $query = null; try { $translator->begin_transaction(); @@ -52,21 +48,16 @@ function sqlite_make_db_sqlite() { continue; } - $result = $translator->query( $query ); - if ( false === $result ) { - throw new PDOException( $translator->get_error_message() ); - } + $translator->query( $query ); } $translator->commit(); } catch ( PDOException $err ) { - $err_data = $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $err_code = $err_data[1]; $translator->rollback(); $message = sprintf( 'Error occurred while creating tables or indexes...
Query was: %s
', var_export( $query, true ) ); - $message .= sprintf( 'Error message is: %s', $err_data[2] ); + $message .= sprintf( 'Error message is: %s', $err->getMessage() ); wp_die( $message, 'Database Error!' ); } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index fee618e3..c7dd2133 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -32,8 +32,9 @@ /vendor/* /node_modules/* + /build/* /wordpress/* - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php + /packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - warning - - /tests/phpunit/* - - - warning - - - warning - - - warning - - - warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /wp-includes/sqlite/class-wp-sqlite-translator.php + /packages/mysql-on-sqlite/src/sqlite/*\.php + /tests/* + + + + /tests/* @@ -168,34 +75,11 @@ * - - /src/wp-includes/sqlite/*\.php - /tests/* - - /tests/* - - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - /tests/* - - - /tests/* - /tests/* - - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - diff --git a/tests/WP_SQLite_Metadata_Tests.php b/tests/WP_SQLite_Metadata_Tests.php deleted file mode 100644 index b9c51b69..00000000 --- a/tests/WP_SQLite_Metadata_Tests.php +++ /dev/null @@ -1,368 +0,0 @@ -= 80400 ? PDO\SQLite::class : PDO::class; - $this->sqlite = new $pdo_class( 'sqlite::memory:' ); - $this->engine = new WP_SQLite_Translator( $this->sqlite ); - - $translator = $this->engine; - - try { - $translator->begin_transaction(); - foreach ( $queries as $query ) { - $query = trim( $query ); - if ( empty( $query ) ) { - continue; - } - - $result = $translator->execute_sqlite_query( $query ); - if ( false === $result ) { - throw new PDOException( $translator->get_error_message() ); - } - } - $translator->commit(); - } catch ( PDOException $err ) { - $err_data = - $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $err_code = $err_data[1]; - $translator->rollback(); - $message = sprintf( - 'Error occurred while creating tables or indexes...
Query was: %s
', - var_export( $query, true ) - ); - $message .= sprintf( 'Error message is: %s', $err_data[2] ); - wp_die( $message, 'Database Error!' ); - } - } - - public function testCountTables() { - $this->assertQuery( "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'wpdata'" ); - - $actual = $this->engine->get_query_results(); - $count = array_values( get_object_vars( $actual[0] ) )[0]; - self::assertIsNumeric( $count ); - } - - public function testInformationSchemaTables() { - $result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE TABLE_NAME = 'wp_options'" ); - $this->assertEquals( - array( - 'TABLE_CATALOG' => 'def', - 'TABLE_SCHEMA' => '', - 'TABLE_NAME' => 'wp_options', - 'TABLE_TYPE' => 'BASE TABLE', - 'ENGINE' => 'InnoDB', - 'ROW_FORMAT' => 'Dynamic', - 'TABLE_COLLATION' => 'utf8mb4_general_ci', - 'AUTO_INCREMENT' => null, - 'CREATE_TIME' => null, - 'UPDATE_TIME' => null, - 'CHECK_TIME' => null, - 'TABLE_ROWS' => '0', - 'AVG_ROW_LENGTH' => '0', - 'DATA_LENGTH' => '0', - 'MAX_DATA_LENGTH' => '0', - 'INDEX_LENGTH' => '0', - 'DATA_FREE' => '0', - 'CHECKSUM' => null, - 'CREATE_OPTIONS' => '', - 'VERSION' => '10', - 'TABLE_COMMENT' => '', - ), - (array) $result[0] - ); - - $result = $this->assertQuery( - "SELECT - table_name as 'name', - engine AS 'engine', - CAST( data_length / 1024 / 1024 AS UNSIGNED ) AS 'data' - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_NAME = 'wp_posts' - ORDER BY name ASC;" - ); - - $this->assertEquals( - array( - 'name' => 'wp_posts', - 'engine' => 'InnoDB', - 'data' => '0', - ), - (array) $result[0] - ); - } - - public function testInformationSchemaQueryHidesSqliteSystemTables() { - /** - * By default, system tables are not returned. - */ - $result = $this->assertQuery( "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sqlite_sequence'" ); - $this->assertEquals( 0, count( $result ) ); - - /** - * If we use a custom name for the table_name column, system tables are returned. - */ - $result = $this->assertQuery( "SELECT TABLE_NAME as custom_name FROM INFORMATION_SCHEMA.TABLES WHERE custom_name = 'sqlite_sequence'" ); - $this->assertEquals( 1, count( $result ) ); - } - - private function assertQuery( $sql, $error_substring = null ) { - $retval = $this->engine->query( $sql ); - if ( null === $error_substring ) { - $this->assertEquals( - '', - $this->engine->get_error_message() - ); - $this->assertNotFalse( - $retval - ); - } else { - $this->assertStringContainsStringIgnoringCase( $error_substring, $this->engine->get_error_message() ); - } - - return $retval; - } - - public function testCheckTable() { - - /* a good table */ - $table_name = 'wp_options'; - $expected_result = array( - (object) array( - 'Table' => $table_name, - 'Op' => 'check', - 'Msg_type' => 'status', - 'Msg_text' => 'OK', - ), - ); - - $this->assertQuery( - "CHECK TABLE $table_name;" - ); - - $this->assertEquals( - $expected_result, - $this->engine->get_query_results() - ); - - /* a different good table */ - $table_name = 'wp_postmeta'; - $expected_result = array( - (object) array( - 'Table' => $table_name, - 'Op' => 'check', - 'Msg_type' => 'status', - 'Msg_text' => 'OK', - ), - ); - - $this->assertQuery( - "CHECK TABLE $table_name;" - ); - $this->assertEquals( - $expected_result, - $this->engine->get_query_results() - ); - - /* a bogus, missing, table */ - $table_name = 'wp_sqlite_rocks'; - $expected_result = array( - (object) array( - 'Table' => $table_name, - 'Op' => 'check', - 'Msg_type' => 'Error', - 'Msg_text' => "Table '$table_name' doesn't exist", - ), - (object) array( - 'Table' => $table_name, - 'Op' => 'check', - 'Msg_type' => 'status', - 'Msg_text' => 'Operation failed', - ), - ); - - $this->assertQuery( - "CHECK TABLE $table_name;" - ); - - $this->assertEquals( - $expected_result, - $this->engine->get_query_results() - ); - } - - public function testOptimizeTable() { - - /* a good table */ - $table_name = 'wp_options'; - - $this->assertQuery( - "OPTIMIZE TABLE $table_name;" - ); - - $actual = $this->engine->get_query_results(); - - array_map( - function ( $row ) { - $this->assertIsObject( $row ); - $row = (array) $row; - $this->assertIsString( $row['Table'] ); - $this->assertIsString( $row['Op'] ); - $this->assertIsString( $row['Msg_type'] ); - $this->assertIsString( $row['Msg_text'] ); - }, - $actual - ); - - $ok = array_filter( - $actual, - function ( $row ) { - $row = (array) $row; - - return strtolower( $row['Msg_type'] ) === 'status' && strtolower( $row['Msg_text'] ) === 'ok'; - } - ); - $this->assertIsArray( $ok ); - $this->assertGreaterThan( 0, count( $ok ) ); - } - - public function testRepairTable() { - - /* a good table */ - $table_name = 'wp_options'; - - $this->assertQuery( - "REPAIR TABLE $table_name;" - ); - - $actual = $this->engine->get_query_results(); - - array_map( - function ( $r ) { - $this->assertIsObject( $r ); - $row = $r; - $row = (array) $row; - $this->assertIsString( $row['Table'] ); - $this->assertIsString( $row['Op'] ); - $this->assertIsString( $row['Msg_type'] ); - $this->assertIsString( $row['Msg_text'] ); - }, - $actual - ); - - $ok = array_filter( - $actual, - function ( $row ) { - return strtolower( $row->Msg_type ) === 'status' && strtolower( $row->Msg_text ) === 'ok'; - } - ); - $this->assertIsArray( $ok ); - $this->assertGreaterThan( 0, count( $ok ) ); - } - - // this tests for successful rejection of a bad query - - public function testShowTableStatus() { - - $this->assertQuery( - "INSERT INTO wp_comments ( comment_author, comment_content ) VALUES ( 'PhpUnit', 'Testing' )" - ); - - $this->assertQuery( - "INSERT INTO wp_comments ( comment_author, comment_content ) VALUES ( 'PhpUnit0', 'Testing0' ), ( 'PhpUnit1', 'Testing1' ), ( 'PhpUnit2', 'Testing2' )" - ); - - $this->assertTableEmpty( 'wp_comments', false ); - - $this->assertQuery( - 'SHOW TABLE STATUS FROM wp;' - ); - - $actual = $this->engine->get_query_results(); - - $this->assertIsArray( $actual ); - $this->assertGreaterThanOrEqual( - 1, - count( $actual ) - ); - $this->assertIsObject( $actual[0] ); - - $rows = array_values( - array_filter( - $actual, - function ( $row ) { - $this->assertIsObject( $row ); - $this->assertIsString( $row->Name ); - $this->assertIsNumeric( $row->Rows ); - - return str_ends_with( $row->Name, 'comments' ); - } - ) - ); - $this->assertEquals( 'wp_comments', $rows[0]->Name ); - $this->assertEquals( 4, $rows[0]->Rows ); - } - - private function assertTableEmpty( $table_name, $empty_var ) { - - $this->assertQuery( - "SELECT COUNT(*) num FROM $table_name" - ); - - $actual = $this->engine->get_query_results(); - if ( $empty_var ) { - $this->assertEquals( 0, $actual[0]->num, "$table_name is not empty" ); - } else { - $this->assertGreaterThan( 0, $actual[0]->num, "$table_name is empty" ); - } - } - - public function testTruncateTable() { - - $this->assertQuery( - "INSERT INTO wp_comments ( comment_author, comment_content ) VALUES ( 'PhpUnit', 'Testing' )" - ); - - $this->assertQuery( - "INSERT INTO wp_comments ( comment_author, comment_content ) VALUES ( 'PhpUnit0', 'Testing0' ), ( 'PhpUnit1', 'Testing1' ), ( 'PhpUnit2', 'Testing2' )" - ); - - $this->assertTableEmpty( 'wp_comments', false ); - - $this->assertQuery( - 'TRUNCATE TABLE wp_comments;' - ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( - true, - $actual - ); - $this->assertTableEmpty( 'wp_comments', true ); - } - - public function testBogusQuery() { - - $this->assertQuery( - 'SELECT 1, BOGUS(1) FROM bogus;', - 'no such table: bogus' - ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( - null, - $actual - ); - } -} diff --git a/tests/WP_SQLite_PDO_User_Defined_Functions_Tests.php b/tests/WP_SQLite_PDO_User_Defined_Functions_Tests.php deleted file mode 100644 index 34740194..00000000 --- a/tests/WP_SQLite_PDO_User_Defined_Functions_Tests.php +++ /dev/null @@ -1,28 +0,0 @@ -= 80400 ? PDO\SQLite::class : PDO::class; - $pdo = new $pdo_class( 'sqlite::memory:' ); - $fns = WP_SQLite_PDO_User_Defined_Functions::register_for( $pdo ); - - $this->assertEquals( - $expected, - $fns->field( ...$args ) - ); - } - - public static function dataProviderForTestFieldFunction() { - return array( - array( 1, array( 'a', 'a' ) ), - array( 2, array( 'User 0000019', 'User 0000018', 'User 0000019', 'User 0000020' ) ), - ); - } -} diff --git a/tests/WP_SQLite_Query_RewriterTests.php b/tests/WP_SQLite_Query_RewriterTests.php deleted file mode 100644 index 57531269..00000000 --- a/tests/WP_SQLite_Query_RewriterTests.php +++ /dev/null @@ -1,81 +0,0 @@ -assertEquals( - 'int', - $r->consume( - array( - 'type' => WP_SQLite_Token::TYPE_KEYWORD, - 'flags' => WP_SQLite_Token::FLAG_KEYWORD_DATA_TYPE, - ) - )->value - ); - $this->assertEquals( - 'DATE_ADD', - $r->consume( - array( - 'type' => WP_SQLite_Token::TYPE_KEYWORD, - 'flags' => WP_SQLite_Token::FLAG_KEYWORD_FUNCTION, - ) - )->value - ); - } - public function testSkip() { - $r = new WP_SQLite_Query_Rewriter( - array( - new WP_SQLite_Token( 'DO', WP_SQLite_Token::TYPE_KEYWORD ), - new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), - new WP_SQLite_Token( 'UPDATE', WP_SQLite_Token::TYPE_KEYWORD ), - new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), - new WP_SQLite_Token( 'SET', WP_SQLite_Token::TYPE_KEYWORD ), - new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), - ) - ); - $this->assertEquals( - 'DO', - $r->skip()->value - ); - $this->assertEquals( - 'UPDATE', - $r->skip()->value - ); - } - - public function skip_over() { - $r = new WP_SQLite_Query_Rewriter( - array( - new WP_SQLite_Token( 'DO', WP_SQLite_Token::TYPE_KEYWORD ), - new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), - new WP_SQLite_Token( 'UPDATE', WP_SQLite_Token::TYPE_KEYWORD ), - new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), - new WP_SQLite_Token( 'SET', WP_SQLite_Token::TYPE_KEYWORD ), - new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ), - ) - ); - $buffer = $r->skip_over( - array( - 'values' => array( 'UPDATE' ), - ) - ); - $this->assertCount( 3, $buffer ); - $this->assertEquals( 'DO', $buffer[0]->value ); - $this->assertEquals( ' ', $buffer[1]->value ); - $this->assertEquals( 'UPDATE', $buffer[2]->value ); - } -} diff --git a/tests/WP_SQLite_Query_Tests.php b/tests/WP_SQLite_Query_Tests.php deleted file mode 100644 index 00997631..00000000 --- a/tests/WP_SQLite_Query_Tests.php +++ /dev/null @@ -1,573 +0,0 @@ -= 80400 ? PDO\SQLite::class : PDO::class; - $this->sqlite = new $pdo_class( 'sqlite::memory:' ); - $this->engine = new WP_SQLite_Translator( $this->sqlite ); - - $translator = $this->engine; - - try { - $translator->begin_transaction(); - foreach ( $queries as $query ) { - $query = trim( $query ); - if ( empty( $query ) ) { - continue; - } - - $result = $translator->execute_sqlite_query( $query ); - if ( false === $result ) { - throw new PDOException( $translator->get_error_message() ); - } - } - $translator->commit(); - } catch ( PDOException $err ) { - $err_data = - $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $err_code = $err_data[1]; - $translator->rollback(); - $message = sprintf( - 'Error occurred while creating tables or indexes...
Query was: %s
', - var_export( $query, true ) - ); - $message .= sprintf( 'Error message is: %s', $err_data[2] ); - wp_die( $message, 'Database Error!' ); - } - - /* Mock up some metadata rows. When meta_key starts with _, the custom field isn't visible to the editor. */ - for ( $i = 1; $i <= 40; $i++ ) { - $k1 = 'visible_meta_key_' . str_pad( $i, 2, '0', STR_PAD_LEFT ); - $k2 = '_invisible_meta_key_%_percent' . str_pad( $i, 2, '0', STR_PAD_LEFT ); - $this->assertQuery( - "INSERT INTO wp_postmeta (post_id, meta_key, meta_value) VALUES (1, '$k1', '$k1-value');" - ); - $this->assertQuery( - "INSERT INTO wp_postmeta (post_id, meta_key, meta_value) VALUES (1, '$k2', '$k2-value');" - ); - } - - /* Mock up some transients for testing. Site transients. Two expired, one in the future. */ - $time = - 90; - foreach ( array( 'tag1', 'tag2', 'tag3' ) as $tag ) { - $tv = '_site_transient_' . $tag; - $tt = '_site_transient_timeout_' . $tag; - $this->assertQuery( - "INSERT INTO wp_options (option_name, option_value, autoload) VALUES ('$tv', '$tag', 'no');" - ); - $this->assertQuery( - "INSERT INTO wp_options (option_name, option_value, autoload) VALUES ('$tt', UNIX_TIMESTAMP() + $time, 'no');" - ); - $time += 60; - } - /* Ordinary transients. */ - $time = - 90; - foreach ( array( 'tag4', 'tag5', 'tag6' ) as $tag ) { - $tv = '_transient_' . $tag; - $tt = '_transient_timeout_' . $tag; - $this->assertQuery( - "INSERT INTO wp_options (option_name, option_value, autoload) VALUES ('$tv', '$tag', 'no');" - ); - $this->assertQuery( - "INSERT INTO wp_options (option_name, option_value, autoload) VALUES ('$tt', UNIX_TIMESTAMP() + $time, 'no');" - ); - $time += 60; - } - } - - public function testGreatestLeast() { - $q = <<<'QUERY' -SELECT GREATEST('a', 'b') letter; -QUERY; - - $result = $this->assertQuery( $q ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 1, count( $actual ) ); - $this->assertEquals( 'b', $actual[0]->letter ); - - $q = <<<'QUERY' -SELECT LEAST('a', 'b') letter; -QUERY; - - $result = $this->assertQuery( $q ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 1, count( $actual ) ); - $this->assertEquals( 'a', $actual[0]->letter ); - - $q = <<<'QUERY' -SELECT GREATEST(2, 1.5) num; -QUERY; - - $result = $this->assertQuery( $q ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 1, count( $actual ) ); - $this->assertEquals( 2, $actual[0]->num ); - - $q = <<<'QUERY' -SELECT LEAST(2, 1.5, 1.0) num; -QUERY; - - $result = $this->assertQuery( $q ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 1, count( $actual ) ); - $this->assertEquals( 1, $actual[0]->num ); - } - - public function testLikeEscapingSimpleNoSemicolon() { - $q = <<<'QUERY' -SELECT DISTINCT meta_key FROM wp_postmeta WHERE meta_key LIKE '\_%' -QUERY; - - $result = $this->assertQuery( $q ); - - $actual = $this->engine->get_query_results(); - $this->assertEquals( 40, count( $actual ) ); - } - - public function testLikeEscapingPercent() { - $q = <<<'QUERY' -SELECT DISTINCT meta_key FROM wp_postmeta WHERE meta_key LIKE '%\_\%\_percent%' -QUERY; - - $result = $this->assertQuery( $q ); - - $actual = $this->engine->get_query_results(); - $this->assertEquals( 40, count( $actual ) ); - } - - public function testLikeEscapingSimpleSemicolon() { - $q = <<<'QUERY' -SELECT DISTINCT meta_key FROM wp_postmeta WHERE meta_key LIKE '\_%'; -QUERY; - - $result = $this->assertQuery( $q ); - - $actual = $this->engine->get_query_results(); - $this->assertEquals( 40, count( $actual ) ); - } - - public function testLikeEscapingBasic() { - $q = <<<'QUERY' -SELECT DISTINCT meta_key FROM wp_postmeta WHERE meta_key NOT BETWEEN '_' AND '_z' AND meta_key NOT LIKE '\_%' ORDER BY meta_key LIMIT 30 -QUERY; - - $result = $this->assertQuery( $q ); - - $actual = $this->engine->get_query_results(); - $this->assertEquals( 30, count( $actual ) ); - $last = $actual[ count( $actual ) - 1 ]->meta_key; - $this->assertEquals( 'visible_meta_key_30', $last ); - } - - public function testLikeEscapingParenAfterLike() { - $q = <<<'QUERY' - SELECT DISTINCT meta_key - FROM wp_postmeta - WHERE (meta_key != 'hello' AND meta_key NOT LIKE '\_%') AND meta_id > 0 -QUERY; - - $this->assertQuery( $q ); - - $actual = $this->engine->get_query_results(); - $this->assertEquals( 40, count( $actual ) ); - $last = $actual[ count( $actual ) - 1 ]->meta_key; - $this->assertEquals( 'visible_meta_key_40', $last ); - } - - public function testLikeEscapingWithConcatFunction() { - $q = <<<'QUERY' -SELECT DISTINCT meta_key FROM wp_postmeta WHERE meta_key NOT BETWEEN '_' AND '_z' AND meta_key NOT LIKE CONCAT('\_', '%') ORDER BY meta_key LIMIT 30 -QUERY; - - $result = $this->assertQuery( $q ); - - $actual = $this->engine->get_query_results(); - $this->assertEquals( 30, count( $actual ) ); - $last = $actual[ count( $actual ) - 1 ]->meta_key; - $this->assertEquals( 'visible_meta_key_30', $last ); - } - - // https://github.com/WordPress/sqlite-database-integration/issues/19 - - public function testHavingWithoutGroupBy() { - - $q = <<<'QUERY' -SELECT DISTINCT meta_key FROM wp_postmeta WHERE meta_key NOT BETWEEN '_' AND '_z' HAVING meta_key NOT LIKE '\_%' ORDER BY meta_key LIMIT 30 -QUERY; - - $result = $this->assertQuery( $q ); - - $actual = $this->engine->get_query_results(); - $this->assertEquals( 30, count( $actual ) ); - $last = $actual[ count( $actual ) - 1 ]->meta_key; - $this->assertEquals( 'visible_meta_key_30', $last ); - - $q = <<<'QUERY' -SELECT DISTINCT meta_key FROM wp_postmeta WHERE meta_key NOT BETWEEN '_' AND '_z' HAVING meta_key NOT LIKE CONCAT('\_', '%') ORDER BY meta_key LIMIT 30 -QUERY; - - $result = $this->assertQuery( $q ); - - $actual = $this->engine->get_query_results(); - $this->assertEquals( 30, count( $actual ) ); - $last = $actual[ count( $actual ) - 1 ]->meta_key; - $this->assertEquals( 'visible_meta_key_30', $last ); - } - - public function testCharLengthSimple() { - $query = <<<'QUERY' -SELECT * FROM wp_options WHERE LENGTH(option_name) != CHAR_LENGTH(option_name) -QUERY; - - $this->assertQuery( $query ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 0, count( $actual ) ); - } - - public function testSubstringSimple() { - $query = <<<'QUERY' -SELECT SUBSTR(option_name, 1) ss1, SUBSTRING(option_name, 1) sstr1, - SUBSTR(option_name, -2) es1, SUBSTRING(option_name, -2) estr1 -FROM wp_options -WHERE SUBSTR(option_name, -2) != SUBSTRING(option_name, -2) - OR SUBSTR(option_name, 1) != SUBSTRING(option_name, 1) -QUERY; - - $this->assertQuery( $query ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 0, count( $actual ) ); - } - - public function testCharLengthComplex() { - $query = <<<'QUERY' -SELECT option_name, - CHAR_LENGTH( - CASE WHEN option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_' - WHEN option_name LIKE '\_transient\_%' - THEN '_transient_' - ELSE '' END - ) prefix_length, - - SUBSTR(option_name, CHAR_LENGTH( - CASE WHEN option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_' - WHEN option_name LIKE '\_transient\_%' - THEN '_transient_' - ELSE '' END - ) + 1) suffix -FROM wp_options -WHERE option_name LIKE '\_%transient\_%' -AND option_name NOT LIKE '%\_transient\_timeout\_%' -QUERY; - - $this->assertQuery( $query ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 6, count( $actual ) ); - foreach ( $actual as $row ) { - self::assertTrue( str_ends_with( $row->option_name, '_' . $row->suffix ) ); - } - } - - public function testAllTransients() { - $this->assertQuery( - "SELECT * FROM wp_options WHERE option_name LIKE '\_%transient\_%'" - ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 12, count( $actual ) ); - } - - public function testExpiredTransients() { - $query = <<<'QUERY' -SELECT a.option_id, a.option_name, a.option_value as option_content, a.autoload, b.option_value as option_timeout, - UNIX_TIMESTAMP() - b.option_value as age, - CONCAT ( - CASE WHEN a.option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_timeout_' - ELSE '_transient_timeout_' - END, - SUBSTRING(a.option_name, CHAR_LENGTH( - CASE WHEN a.option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_' - ELSE '_transient_' - END - ) + 1)) AS timeout_name - - - FROM wp_options a LEFT JOIN wp_options b ON b.option_name = - CONCAT( - CASE WHEN a.option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_timeout_' - ELSE '_transient_timeout_' - END - , - SUBSTRING(a.option_name, CHAR_LENGTH( - CASE WHEN a.option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_' - ELSE '_transient_' - END - ) + 1) - ) - WHERE (a.option_name LIKE '\_transient\_%' OR a.option_name LIKE '\_site\_transient\_%') - AND a.option_name NOT LIKE '%\_transient\_timeout\_%' - AND b.option_value < UNIX_TIMESTAMP() -QUERY; - - $this->assertQuery( $query ); - $actual = $this->engine->get_query_results(); - $this->assertEquals( 4, count( $actual ) ); - foreach ( $actual as $row ) { - self::assertLessThan( time(), $row->option_timeout ); - } - } - - public function testDeleteExpiredNonSiteTransients() { - - $now = time(); - - /* option.php: delete_expired_transients is the source of this query. */ - $query = <<<'QUERY' -DELETE a, b FROM wp_options a, wp_options b -WHERE a.option_name LIKE '\_transient\_%' -AND a.option_name NOT LIKE '\_transient\_timeout_%' -AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) ) -AND b.option_value < UNIX_TIMESTAMP() -QUERY; - $this->assertQuery( $query ); - - /* are the expired transients gone? */ - $query = <<<'QUERY' -SELECT a.option_id, a.option_name, a.option_value as option_content, - a.autoload, b.option_value as option_timeout, - UNIX_TIMESTAMP() - b.option_value as age, - CONCAT ( - CASE WHEN a.option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_timeout_' - ELSE '_transient_timeout_' - END, - SUBSTRING(a.option_name, CHAR_LENGTH( - CASE WHEN a.option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_' - ELSE '_transient_' - END - ) + 1)) AS timeout_name - - - FROM wp_options a LEFT JOIN wp_options b ON b.option_name = - CONCAT( - CASE WHEN a.option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_timeout_' - ELSE '_transient_timeout_' - END - , - SUBSTRING(a.option_name, CHAR_LENGTH( - CASE WHEN a.option_name LIKE '\_site\_transient\_%' - THEN '_site_transient_' - ELSE '_transient_' - END - ) + 1) - ) - WHERE (a.option_name LIKE '\_transient\_%' OR a.option_name LIKE '\_site\_transient\_%') - AND a.option_name NOT LIKE '%\_transient\_timeout\_%' -QUERY; - - $this->assertQuery( $query ); - $actual = $this->engine->get_query_results(); - $count_unexpired = 0; - foreach ( $actual as $row ) { - if ( str_starts_with( $row->option_name, '_transient' ) ) { - ++$count_unexpired; - $this->assertGreaterThan( $now, $row->option_timeout ); - } - } - $this->assertEquals( 1, $count_unexpired ); - } - - public function testUserCountsByRole() { - /* commas appear after the LIKE term sometimes, as here. */ - $query = <<<'QUERY' -SELECT COUNT(NULLIF(`meta_value` LIKE '%\"administrator\"%', false)), - COUNT(NULLIF(`meta_value` LIKE '%\"editor\"%', false)), - COUNT(NULLIF(`meta_value` LIKE '%\"author\"%', false)), - COUNT(NULLIF(`meta_value` LIKE '%\"contributor\"%', false)), - COUNT(NULLIF(`meta_value` LIKE '%\"subscriber\"%', false)), - COUNT(NULLIF(`meta_value` = 'a:0:{}', false)), - COUNT(*) -FROM wp_usermeta -INNER JOIN wp_users ON user_id = ID -WHERE meta_key = 'wp_capabilities' - -QUERY; - $this->assertQuery( $query ); - } - - public function testTranscendental() { - $this->markTestIncomplete( 'For some reason sqlite\'s transcendental functions are missing.' ); - $this->assertQuery( 'SELECT 2.0, SQRT(2.0) sqr, SIN(0.5) s;' ); - } - - public function testRecoverSerialized() { - - $obj = array( - 'this' => 'that', - 'that' => array( 'the', 'other', 'thing' ), - 'two' => 2, - 2 => 'two', - 'pi' => pi(), - 'moo' => "Mrs O'Leary's cow!", - ); - $option_name = 'serialized_option'; - $option_value = serialize( $obj ); - $option_value_escaped = $this->engine->get_pdo()->quote( $option_value ); - /* Note well: this is heredoc not nowdoc */ - $insert = <<assertQuery( $insert ); - $get = <<assertQuery( $get ); - - $actual = $this->engine->get_query_results(); - $retrieved_name = $actual[0]->option_name; - $retrieved_string = $actual[0]->option_value; - $this->assertEquals( $option_value, $retrieved_string ); - $unserialized = unserialize( $retrieved_string ); - $this->assertEquals( $obj, $unserialized ); - - ++$obj ['two']; - $obj ['pi'] *= 2; - $option_value = serialize( $obj ); - $option_value_escaped = $this->engine->get_pdo()->quote( $option_value ); - /* Note well: this is heredoc not nowdoc */ - $insert = <<assertQuery( $insert ); - $get = <<assertQuery( $get ); - - $actual = $this->engine->get_query_results(); - $retrieved_string = $actual[0]->option_value; - $this->assertEquals( $option_value, $retrieved_string ); - $unserialized = unserialize( $retrieved_string ); - $this->assertEquals( $obj, $unserialized ); - } - - public function testOnDuplicateKey() { - $this->assertQuery( - 'CREATE TABLE `test` ( - `id` INT PRIMARY KEY, - `text` VARCHAR(255), - );' - ); - // The order is deliberate to test that the query works with the keys in any order. - $this->assertQuery( - 'INSERT INTO test (`text`, `id`) - VALUES ("test", 1) - ON DUPLICATE KEY UPDATE `text` = "test1"' - ); - } - public function testOnDuplicateKeyWithUnnamedKeys() { - $this->assertQuery( - 'CREATE TABLE `test` ( - `id` INT, - `name` VARCHAR(255), - `other` VARCHAR(255), - PRIMARY KEY (id), - UNIQUE KEY (name) - );' - ); - // The order is deliberate to test that the query works with the keys in any order. - $this->assertQuery( - 'INSERT INTO test (`name`, other) - VALUES ("name", "test") - ON DUPLICATE KEY UPDATE `other` = values(other)' - ); - } - - public function testOnCreateTableIfNotExistsWithIndexAdded() { - $this->assertQuery( - 'CREATE TABLE IF not EXISTS `test` ( - `id` INT, - `name` VARCHAR(255), - `other` VARCHAR(255), - PRIMARY KEY (id), - UNIQUE KEY (name) - );' - ); - $this->assertQuery( - 'CREATE TABLE if NOT ExisTS `test` ( - `id` INT, - `name` VARCHAR(255), - `other` VARCHAR(255), - PRIMARY KEY (id), - UNIQUE KEY (name) - );' - ); - } - - public function testShowColumns() { - - $query = 'SHOW COLUMNS FROM wp_posts'; - $this->assertQuery( $query ); - - $actual = $this->engine->get_query_results(); - foreach ( $actual as $row ) { - $this->assertIsObject( $row ); - $this->assertTrue( property_exists( $row, 'Field' ) ); - $this->assertTrue( property_exists( $row, 'Type' ) ); - $this->assertTrue( property_exists( $row, 'Null' ) ); - $this->assertTrue( ( 'NO' === $row->Null ) || ( 'YES' === $row->Null ) ); - $this->assertTrue( property_exists( $row, 'Key' ) ); - $this->assertTrue( property_exists( $row, 'Default' ) ); - } - } - - private function assertQuery( $sql ) { - $retval = $this->engine->query( $sql ); - $this->assertEquals( - '', - $this->engine->get_error_message() - ); - $this->assertNotFalse( - $retval - ); - - return $retval; - } -} diff --git a/tests/WP_SQLite_Translator_Tests.php b/tests/WP_SQLite_Translator_Tests.php deleted file mode 100644 index e53e139e..00000000 --- a/tests/WP_SQLite_Translator_Tests.php +++ /dev/null @@ -1,3560 +0,0 @@ -= 80400 ? PDO\SQLite::class : PDO::class; - $this->sqlite = new $pdo_class( 'sqlite::memory:' ); - $this->engine = new WP_SQLite_Translator( $this->sqlite ); - - // Skip all old driver tests when running on legacy SQLite version. - // The old driver is to be removed in favor of the new AST driver, - // so this is just a temporary measure to pass all CI combinations. - $is_legacy_sqlite = version_compare( $this->engine->get_sqlite_version(), WP_PDO_MySQL_On_SQLite::MINIMUM_SQLITE_VERSION, '<' ); - if ( $is_legacy_sqlite ) { - $this->markTestSkipped( "The old SQLite driver doesn't pass some test on legacy SQLite versions" ); - return; - } - - $this->engine->query( - "CREATE TABLE _options ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - $this->engine->query( - "CREATE TABLE _dates ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value DATE NOT NULL - );" - ); - } - - private function assertQuery( $sql, $error_substring = null ) { - $retval = $this->engine->query( $sql ); - if ( null === $error_substring ) { - $this->assertEquals( - '', - $this->engine->get_error_message() - ); - $this->assertNotFalse( - $retval - ); - } else { - $this->assertStringContainsStringIgnoringCase( $error_substring, $this->engine->get_error_message() ); - } - - return $retval; - } - - public function testRegexp() { - $this->assertQuery( - "INSERT INTO _options (option_name, option_value) VALUES ('rss_0123456789abcdef0123456789abcdef', '1');" - ); - $this->assertQuery( - "INSERT INTO _options (option_name, option_value) VALUES ('transient', '1');" - ); - - $this->assertQuery( "DELETE FROM _options WHERE option_name REGEXP '^rss_.+$'" ); - $this->assertQuery( 'SELECT * FROM _options' ); - $this->assertCount( 1, $this->engine->get_query_results() ); - } - - /** - * @dataProvider regexpOperators - */ - public function testRegexps( $operator, $regexp, $expected_result ) { - $this->assertQuery( - "INSERT INTO _options (option_name) VALUES ('rss_123'), ('RSS_123'), ('transient');" - ); - - $this->assertQuery( "SELECT ID, option_name FROM _options WHERE option_name $operator '$regexp' ORDER BY id LIMIT 1" ); - $this->assertEquals( - array( $expected_result ), - $this->engine->get_query_results() - ); - } - - public static function regexpOperators() { - $lowercase_rss = (object) array( - 'ID' => '1', - 'option_name' => 'rss_123', - ); - $uppercase_rss = (object) array( - 'ID' => '2', - 'option_name' => 'RSS_123', - ); - $lowercase_transient = (object) array( - 'ID' => '3', - 'option_name' => 'transient', - ); - return array( - array( 'REGEXP', '^RSS_.+$', $lowercase_rss ), - array( 'RLIKE', '^RSS_.+$', $lowercase_rss ), - array( 'REGEXP BINARY', '^RSS_.+$', $uppercase_rss ), - array( 'RLIKE BINARY', '^RSS_.+$', $uppercase_rss ), - array( 'NOT REGEXP', '^RSS_.+$', $lowercase_transient ), - array( 'NOT RLIKE', '^RSS_.+$', $lowercase_transient ), - array( 'NOT REGEXP BINARY', '^RSS_.+$', $lowercase_rss ), - array( 'NOT RLIKE BINARY', '^RSS_.+$', $lowercase_rss ), - ); - } - - public function testInsertDateNow() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', now());" - ); - - $this->assertQuery( 'SELECT YEAR(option_value) as y FROM _dates' ); - - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( gmdate( 'Y' ), $results[0]->y ); - } - - public function testUpdateWithLimit() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 00:00:45');" - ); - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2003-05-28 00:00:45');" - ); - - $this->assertQuery( - "UPDATE _dates SET option_value = '2001-05-27 10:08:48' WHERE option_name = 'first' ORDER BY option_name LIMIT 1;" - ); - - $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first';" ); - $result2 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='second';" ); - - $this->assertEquals( '2001-05-27 10:08:48', $result1[0]->option_value ); - $this->assertEquals( '2003-05-28 00:00:45', $result2[0]->option_value ); - - $this->assertQuery( - "UPDATE _dates SET option_value = '2001-05-27 10:08:49' WHERE option_name = 'first';" - ); - $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first';" ); - $this->assertEquals( '2001-05-27 10:08:49', $result1[0]->option_value ); - - $this->assertQuery( - "UPDATE _dates SET option_value = '2001-05-12 10:00:40' WHERE option_name in ( SELECT option_name from _dates );" - ); - $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first';" ); - $result2 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='second';" ); - $this->assertEquals( '2001-05-12 10:00:40', $result1[0]->option_value ); - $this->assertEquals( '2001-05-12 10:00:40', $result2[0]->option_value ); - } - - public function testUpdateWithLimitNoEndToken() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 00:00:45')" - ); - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2003-05-28 00:00:45')" - ); - - $this->assertQuery( - "UPDATE _dates SET option_value = '2001-05-27 10:08:48' WHERE option_name = 'first' ORDER BY option_name LIMIT 1" - ); - $results = $this->engine->get_query_results(); - - $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first'" ); - $result2 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='second'" ); - - $this->assertEquals( '2001-05-27 10:08:48', $result1[0]->option_value ); - $this->assertEquals( '2003-05-28 00:00:45', $result2[0]->option_value ); - - $this->assertQuery( - "UPDATE _dates SET option_value = '2001-05-27 10:08:49' WHERE option_name = 'first'" - ); - $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first'" ); - $this->assertEquals( '2001-05-27 10:08:49', $result1[0]->option_value ); - - $this->assertQuery( - "UPDATE _dates SET option_value = '2001-05-12 10:00:40' WHERE option_name in ( SELECT option_name from _dates )" - ); - $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first'" ); - $result2 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='second'" ); - $this->assertEquals( '2001-05-12 10:00:40', $result1[0]->option_value ); - $this->assertEquals( '2001-05-12 10:00:40', $result2[0]->option_value ); - } - - public function testUpdateWithoutWhereButWithSubSelect() { - $this->assertQuery( - "INSERT INTO _options (option_name, option_value) VALUES ('User 0000019', 'second');" - ); - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48');" - ); - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2003-05-27 10:08:48');" - ); - $return = $this->assertQuery( - "UPDATE _dates SET option_value = (SELECT option_value from _options WHERE option_name = 'User 0000019')" - ); - $this->assertSame( 2, $return, 'UPDATE query did not return 2 when two row were changed' ); - - $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first'" ); - $result2 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='second'" ); - $this->assertEquals( 'second', $result1[0]->option_value ); - $this->assertEquals( 'second', $result2[0]->option_value ); - } - - public function testUpdateWithoutWhereButWithLimit() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48');" - ); - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2003-05-27 10:08:48');" - ); - $return = $this->assertQuery( - "UPDATE _dates SET option_value = 'second' LIMIT 1" - ); - $this->assertSame( 1, $return, 'UPDATE query did not return 2 when two row were changed' ); - - $result1 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='first'" ); - $result2 = $this->engine->query( "SELECT option_value FROM _dates WHERE option_name='second'" ); - $this->assertEquals( 'second', $result1[0]->option_value ); - $this->assertEquals( '2003-05-27 10:08:48', $result2[0]->option_value ); - } - - public function testCastAsBinary() { - $this->assertQuery( - // Use a confusing alias to make sure it replaces only the correct token - "SELECT CAST('ABC' AS BINARY) as binary;" - ); - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( 'ABC', $results[0]->binary ); - } - - public function testSelectFromDual() { - $result = $this->assertQuery( - 'SELECT 1 as output FROM DUAL' - ); - $this->assertEquals( 1, $result[0]->output ); - } - - public function testShowCreateTableNotFound() { - $this->assertQuery( - 'SHOW CREATE TABLE _no_such_table;' - ); - $results = $this->engine->get_query_results(); - $this->assertCount( 0, $results ); - } - - public function testShowCreateTable1() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name VARCHAR(255) default '', - option_value TEXT NOT NULL, - UNIQUE KEY option_name (option_name(100)), - KEY composite (option_name(100), option_value(100)) - );" - ); - - $this->assertQuery( - 'SHOW CREATE TABLE _tmp_table;' - ); - $results = $this->engine->get_query_results(); - # TODO: Should we fix mismatch with original `option_value` text NOT NULL,` without default? - $this->assertEquals( - "CREATE TABLE `_tmp_table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `option_name` varchar(255) DEFAULT '', - `option_value` text NOT NULL DEFAULT '', - PRIMARY KEY (`ID`), - KEY `composite` (`option_name`(100), `option_value`(100)), - UNIQUE KEY `option_name` (`option_name`(100)) -);", - $results[0]->{'Create Table'} - ); - } - - public function testShowCreateTableWithEmptyDatetimeDefault() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID BIGINT PRIMARY KEY AUTO_INCREMENT, - timestamp1 datetime NOT NULL, - timestamp2 date NOT NULL, - timestamp3 time NOT NULL, - timestamp4 timestamp NOT NULL, - timestamp5 year NOT NULL, - notempty1 datetime DEFAULT '1999-12-12 12:12:12', - notempty2 date DEFAULT '1999-12-12', - notempty3 time DEFAULT '12:12:12', - notempty4 year DEFAULT '2024', - notempty5 timestamp DEFAULT '1734539165', - );" - ); - - $this->assertQuery( - 'SHOW CREATE TABLE _tmp_table;' - ); - $results = $this->engine->get_query_results(); - - $this->assertEquals( - "CREATE TABLE `_tmp_table` ( - `ID` bigint AUTO_INCREMENT, - `timestamp1` datetime NOT NULL, - `timestamp2` date NOT NULL, - `timestamp3` time NOT NULL, - `timestamp4` timestamp NOT NULL, - `timestamp5` year NOT NULL, - `notempty1` datetime DEFAULT '1999-12-12 12:12:12', - `notempty2` date DEFAULT '1999-12-12', - `notempty3` time DEFAULT '12:12:12', - `notempty4` year DEFAULT '2024', - `notempty5` timestamp DEFAULT '1734539165', - PRIMARY KEY (`ID`) -);", - $results[0]->{'Create Table'} - ); - } - - public function testShowCreateTableQuoted() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name VARCHAR(255) default '', - option_value TEXT NOT NULL, - UNIQUE KEY option_name (option_name(100)), - KEY composite (option_name, option_value(100)) - );" - ); - - $this->assertQuery( - 'SHOW CREATE TABLE `_tmp_table`;' - ); - $results = $this->engine->get_query_results(); - # TODO: Should we fix mismatch with original `option_value` text NOT NULL,` without default? - $this->assertEquals( - "CREATE TABLE `_tmp_table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `option_name` varchar(255) DEFAULT '', - `option_value` text NOT NULL DEFAULT '', - PRIMARY KEY (`ID`), - KEY `composite` (`option_name`(100), `option_value`(100)), - UNIQUE KEY `option_name` (`option_name`(100)) -);", - $results[0]->{'Create Table'} - ); - } - - public function testShowCreateTableSimpleTable() { - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - ID BIGINT NOT NULL - );' - ); - - $this->assertQuery( - 'SHOW CREATE TABLE _tmp_table;' - ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - 'CREATE TABLE `_tmp_table` ( - `ID` bigint NOT NULL DEFAULT 0 -);', - $results[0]->{'Create Table'} - ); - } - - public function testShowCreateTableWithAlterAndCreateIndex() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name VARCHAR(255) default '', - option_value TEXT NOT NULL - );" - ); - - $this->assertQuery( - 'ALTER TABLE _tmp_table CHANGE COLUMN option_name option_name SMALLINT NOT NULL default 14' - ); - - $this->assertQuery( - 'ALTER TABLE _tmp_table ADD INDEX option_name (option_name);' - ); - - $this->assertQuery( - 'SHOW CREATE TABLE _tmp_table;' - ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - 'CREATE TABLE `_tmp_table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `option_name` smallint NOT NULL DEFAULT 14, - `option_value` text NOT NULL DEFAULT \'\', - PRIMARY KEY (`ID`), - KEY `option_name` (`option_name`) -);', - $results[0]->{'Create Table'} - ); - } - - public function testCreateTablseWithIdenticalIndexNames() { - $this->assertQuery( - "CREATE TABLE _tmp_table_a ( - ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name VARCHAR(255) default '', - option_value TEXT NOT NULL, - KEY `option_name` (`option_name`(100)), - KEY `double__underscores` (`option_name`(100), `ID`) - );" - ); - - $this->assertQuery( - "CREATE TABLE _tmp_table_b ( - ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name VARCHAR(255) default '', - option_value TEXT NOT NULL, - KEY `option_name` (`option_name`(100)), - KEY `double__underscores` (`option_name`(100), `ID`) - );" - ); - } - - public function testShowCreateTablePreservesDoubleUnderscoreKeyNames() { - $this->assertQuery( - "CREATE TABLE _tmp__table ( - ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name VARCHAR(255) default '', - option_value TEXT NOT NULL, - KEY `option_name` (`option_name`(100)), - KEY `double__underscores` (`option_name`(100), `ID`) - );" - ); - - $this->assertQuery( - 'SHOW CREATE TABLE _tmp__table;' - ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - 'CREATE TABLE `_tmp__table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `option_name` varchar(255) DEFAULT \'\', - `option_value` text NOT NULL DEFAULT \'\', - PRIMARY KEY (`ID`), - KEY `double__underscores` (`option_name`(100), `ID`), - KEY `option_name` (`option_name`(100)) -);', - $results[0]->{'Create Table'} - ); - } - - public function testShowCreateTableLimitsKeyLengths() { - $this->assertQuery( - 'CREATE TABLE _tmp__table ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `order_id` bigint(20) unsigned DEFAULT NULL, - `meta_key` varchar(20) DEFAULT NULL, - `meta_value` text DEFAULT NULL, - `meta_data` mediumblob DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `meta_key_value` (`meta_key`(20),`meta_value`(82)), - KEY `order_id_meta_key_meta_value` (`order_id`,`meta_key`(100),`meta_value`(82)), - KEY `order_id_meta_key_meta_data` (`order_id`,`meta_key`(100),`meta_data`(100)) - );' - ); - - $this->assertQuery( - 'SHOW CREATE TABLE _tmp__table;' - ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - 'CREATE TABLE `_tmp__table` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `order_id` bigint(20) unsigned DEFAULT NULL, - `meta_key` varchar(20) DEFAULT NULL, - `meta_value` text DEFAULT NULL, - `meta_data` mediumblob DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `order_id_meta_key_meta_data` (`order_id`, `meta_key`(20), `meta_data`(100)), - KEY `order_id_meta_key_meta_value` (`order_id`, `meta_key`(20), `meta_value`(100)), - KEY `meta_key_value` (`meta_key`(20), `meta_value`(100)) -);', - $results[0]->{'Create Table'} - ); - } - - public function testShowCreateTableWithPrimaryKeyColumnsReverseOrdered() { - $this->assertQuery( - 'CREATE TABLE `_tmp_table` ( - `ID_A` BIGINT NOT NULL, - `ID_B` BIGINT NOT NULL, - `ID_C` BIGINT NOT NULL, - PRIMARY KEY (`ID_B`, `ID_A`, `ID_C`) - );' - ); - - $this->assertQuery( - 'SHOW CREATE TABLE _tmp_table;' - ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - 'CREATE TABLE `_tmp_table` ( - `ID_A` bigint NOT NULL DEFAULT 0, - `ID_B` bigint NOT NULL DEFAULT 0, - `ID_C` bigint NOT NULL DEFAULT 0, - PRIMARY KEY (`ID_B`, `ID_A`, `ID_C`) -);', - $results[0]->{'Create Table'} - ); - } - - public function testShowCreateTableWithColumnKeys() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - `ID` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL, - `option_name` varchar(255) DEFAULT '', - `option_value` text NOT NULL DEFAULT '', - KEY _tmp_table__composite (option_name, option_value), - UNIQUE KEY _tmp_table__option_name (option_name) );" - ); - } - - public function testShowCreateTableWithCorrectDefaultValues() { - $this->assertQuery( - "CREATE TABLE _tmp__table ( - ID BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL, - default_empty_string VARCHAR(255) default '', - null_no_default VARCHAR(255), - );" - ); - - $this->assertQuery( - 'SHOW CREATE TABLE _tmp__table;' - ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - 'CREATE TABLE `_tmp__table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `default_empty_string` varchar(255) DEFAULT \'\', - `null_no_default` varchar(255), - PRIMARY KEY (`ID`) -);', - $results[0]->{'Create Table'} - ); - } - - public function testSelectIndexHintForce() { - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('first');" ); - $result = $this->assertQuery( - 'SELECT 1 as output FROM _options FORCE INDEX (PRIMARY, post_parent) WHERE 1=1' - ); - $this->assertEquals( 1, $result[0]->output ); - } - - public function testSelectIndexHintUseGroup() { - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('first');" ); - $result = $this->assertQuery( - 'SELECT 1 as output FROM _options USE KEY FOR GROUP BY (PRIMARY, post_parent) WHERE 1=1' - ); - $this->assertEquals( 1, $result[0]->output ); - } - - public function testLeftFunction1Char() { - $result = $this->assertQuery( - 'SELECT LEFT("abc", 1) as output' - ); - $this->assertEquals( 'a', $result[0]->output ); - } - - public function testLeftFunction5Chars() { - $result = $this->assertQuery( - 'SELECT LEFT("Lorem ipsum", 5) as output' - ); - $this->assertEquals( 'Lorem', $result[0]->output ); - } - - public function testLeftFunctionNullString() { - $result = $this->assertQuery( - 'SELECT LEFT(NULL, 5) as output' - ); - $this->assertEquals( null, $result[0]->output ); - } - - public function testLeftFunctionNullLength() { - $result = $this->assertQuery( - 'SELECT LEFT("Test", NULL) as output' - ); - $this->assertEquals( null, $result[0]->output ); - } - - public function testInsertSelectFromDual() { - $result = $this->assertQuery( - 'INSERT INTO _options (option_name, option_value) SELECT "A", "b" FROM DUAL WHERE ( SELECT NULL FROM DUAL ) IS NULL' - ); - $this->assertEquals( 1, $result ); - } - - public function testCreateTemporaryTable() { - $this->assertQuery( - "CREATE TEMPORARY TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - $this->assertQuery( - 'DROP TEMPORARY TABLE _tmp_table;' - ); - } - - public function testShowTablesLike() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - $this->assertQuery( - "CREATE TABLE _tmp_table_2 ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - - $this->assertQuery( - "SHOW TABLES LIKE '_tmp_table';" - ); - $this->assertEquals( - array( - (object) array( - 'Tables_in_db' => '_tmp_table', - ), - ), - $this->engine->get_query_results() - ); - } - - public function testShowTableStatusFrom() { - // Created in setUp() function - $this->assertQuery( 'DROP TABLE _options' ); - $this->assertQuery( 'DROP TABLE _dates' ); - - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - - $this->assertQuery( - "SHOW TABLE STATUS FROM 'mydb';" - ); - - $this->assertCount( - 1, - $this->engine->get_query_results() - ); - } - - public function testShowTableStatusIn() { - // Created in setUp() function - $this->assertQuery( 'DROP TABLE _options' ); - $this->assertQuery( 'DROP TABLE _dates' ); - - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - - $this->assertQuery( - "SHOW TABLE STATUS IN 'mydb';" - ); - - $this->assertCount( - 1, - $this->engine->get_query_results() - ); - } - - public function testShowTableStatusInTwoTables() { - // Created in setUp() function - $this->assertQuery( 'DROP TABLE _options' ); - $this->assertQuery( 'DROP TABLE _dates' ); - - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - - $this->assertQuery( - "CREATE TABLE _tmp_table2 ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - $this->assertQuery( - "SHOW TABLE STATUS IN 'mydb';" - ); - - $this->assertCount( - 2, - $this->engine->get_query_results() - ); - } - - public function testShowTableStatusLike() { - // Created in setUp() function - $this->assertQuery( 'DROP TABLE _options' ); - $this->assertQuery( 'DROP TABLE _dates' ); - - $this->assertQuery( - "CREATE TABLE _tmp_table1 ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - - $this->assertQuery( - "CREATE TABLE _tmp_table2 ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - - $this->assertQuery( - "SHOW TABLE STATUS LIKE '_tmp_table%';" - ); - $this->assertCount( - 2, - $this->engine->get_query_results() - ); - $this->assertEquals( - '_tmp_table1', - $this->engine->get_query_results()[0]->Name - ); - } - - public function testCreateTable() { - $result = $this->assertQuery( - "CREATE TABLE wptests_users ( - ID bigint(20) unsigned NOT NULL auto_increment, - user_login varchar(60) NOT NULL default '', - user_pass varchar(255) NOT NULL default '', - user_nicename varchar(50) NOT NULL default '', - user_email varchar(100) NOT NULL default '', - user_url varchar(100) NOT NULL default '', - user_registered datetime NOT NULL default '0000-00-00 00:00:00', - user_activation_key varchar(255) NOT NULL default '', - user_status int(11) NOT NULL default '0', - display_name varchar(250) NOT NULL default '', - PRIMARY KEY (ID), - KEY user_login_key (user_login), - KEY user_nicename (user_nicename), - KEY user_email (user_email) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci" - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'DESCRIBE wptests_users;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Field' => 'ID', - 'Type' => 'bigint(20) unsigned', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'user_login', - 'Type' => 'varchar(60)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'user_pass', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'user_nicename', - 'Type' => 'varchar(50)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'user_email', - 'Type' => 'varchar(100)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'user_url', - 'Type' => 'varchar(100)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'user_registered', - 'Type' => 'datetime', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0000-00-00 00:00:00', - 'Extra' => '', - ), - (object) array( - 'Field' => 'user_activation_key', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'user_status', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'display_name', - 'Type' => 'varchar(250)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ), - $results - ); - } - - public function testCreateTableWithTrailingComma() { - $result = $this->assertQuery( - 'CREATE TABLE wptests_users ( - ID bigint(20) unsigned NOT NULL auto_increment, - PRIMARY KEY (ID), - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - } - - public function testCreateTableSpatialIndex() { - $result = $this->assertQuery( - 'CREATE TABLE wptests_users ( - ID bigint(20) unsigned NOT NULL auto_increment, - UNIQUE KEY (ID), - )' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - } - - public function testCreateTableWithMultiValueColumnTypeModifiers() { - $result = $this->assertQuery( - "CREATE TABLE wptests_users ( - ID bigint(20) unsigned NOT NULL auto_increment, - decimal_column DECIMAL(10,2) NOT NULL DEFAULT 0, - float_column FLOAT(10,2) NOT NULL DEFAULT 0, - enum_column ENUM('a', 'b', 'c') NOT NULL DEFAULT 'a', - PRIMARY KEY (ID), - )" - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'DESCRIBE wptests_users;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Field' => 'ID', - 'Type' => 'bigint(20) unsigned', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'decimal_column', - 'Type' => 'decimal(10,2)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => 0, - 'Extra' => '', - ), - (object) array( - 'Field' => 'float_column', - 'Type' => 'float(10,2)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => 0, - 'Extra' => '', - ), - (object) array( - 'Field' => 'enum_column', - 'Type' => "enum('a','b','c')", - 'Null' => 'NO', - 'Key' => '', - 'Default' => 'a', - 'Extra' => '', - ), - ), - $results - ); - } - - public function testAlterTableAddAndDropColumn() { - $result = $this->assertQuery( - "CREATE TABLE _tmp_table ( - name varchar(20) NOT NULL default '' - );" - ); - - $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD COLUMN `column` int;' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'column', - 'Type' => 'int', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD `column2` int;' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'column', - 'Type' => 'int', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'column2', - 'Type' => 'int', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - $result = $this->assertQuery( 'ALTER TABLE _tmp_table DROP COLUMN `column`;' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'column2', - 'Type' => 'int', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - $result = $this->assertQuery( 'ALTER TABLE _tmp_table DROP `column2`;' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ), - $results - ); - } - - public function testAlterTableAddNotNullVarcharColumn() { - $result = $this->assertQuery( - "CREATE TABLE _tmp_table ( - name varchar(20) NOT NULL default '' - );" - ); - - $result = $this->assertQuery( "ALTER TABLE _tmp_table ADD COLUMN `column` VARCHAR(20) NOT NULL DEFAULT 'foo';" ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'column', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => 'foo', - 'Extra' => '', - ), - ), - $results - ); - } - - public function testColumnWithOnUpdate() { - // CREATE TABLE with ON UPDATE - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - id int(11) NOT NULL, - created_at timestamp NULL ON UPDATE CURRENT_TIMESTAMP - );' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'created_at', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - // ADD COLUMN with ON UPDATE - $this->assertQuery( - 'ALTER TABLE _tmp_table ADD COLUMN updated_at timestamp NULL ON UPDATE CURRENT_TIMESTAMP' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'created_at', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'updated_at', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - // assert ON UPDATE triggers - $results = $this->assertQuery( "SELECT * FROM sqlite_master WHERE type = 'trigger'" ); - $this->assertEquals( - array( - (object) array( - 'type' => 'trigger', - 'name' => '___tmp_table_created_at_on_update__', - 'tbl_name' => '_tmp_table', - 'rootpage' => '0', - 'sql' => "CREATE TRIGGER \"___tmp_table_created_at_on_update__\"\n\t\t\tAFTER UPDATE ON \"_tmp_table\"\n\t\t\tFOR EACH ROW\n\t\t\tBEGIN\n\t\t\t UPDATE \"_tmp_table\" SET \"created_at\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;\n\t\t\tEND", - ), - (object) array( - 'type' => 'trigger', - 'name' => '___tmp_table_updated_at_on_update__', - 'tbl_name' => '_tmp_table', - 'rootpage' => '0', - 'sql' => "CREATE TRIGGER \"___tmp_table_updated_at_on_update__\"\n\t\t\tAFTER UPDATE ON \"_tmp_table\"\n\t\t\tFOR EACH ROW\n\t\t\tBEGIN\n\t\t\t UPDATE \"_tmp_table\" SET \"updated_at\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;\n\t\t\tEND", - ), - ), - $results - ); - - // on INSERT, no timestamps are expected - $this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (1)' ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 1' ); - $this->assertNull( $result[0]->created_at ); - $this->assertNull( $result[0]->updated_at ); - - // on UPDATE, we expect timestamps in form YYYY-MM-DD HH:MM:SS - $this->assertQuery( 'UPDATE _tmp_table SET id = 2 WHERE id = 1' ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 2' ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->created_at ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->updated_at ); - - // drop ON UPDATE - $this->assertQuery( - 'ALTER TABLE _tmp_table - CHANGE created_at created_at timestamp NULL, - CHANGE COLUMN updated_at updated_at timestamp NULL' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'created_at', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'updated_at', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - // assert ON UPDATE triggers are removed - $results = $this->assertQuery( "SELECT * FROM sqlite_master WHERE type = 'trigger'" ); - $this->assertEquals( array(), $results ); - - // now, no timestamps are expected - $this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (10)' ); - $this->assertQuery( 'UPDATE _tmp_table SET id = 11 WHERE id = 10' ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 11' ); - $this->assertNull( $result[0]->created_at ); - $this->assertNull( $result[0]->updated_at ); - } - - public function testDataTypeKeywordsAsKeyNames() { - // CREATE TABLE with a data type as a key name - $this->assertQuery( - 'CREATE TABLE `_tmp_table` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `timestamp` datetime NOT NULL, - PRIMARY KEY (`id`), - KEY `timestamp` (`timestamp`), - );' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'bigint(20) unsigned', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'timestamp', - 'Type' => 'datetime', - 'Null' => 'NO', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - } - - - public function testReservedKeywordsAsFieldNames() { - // CREATE TABLE with a reserved keyword as a field name - $this->assertQuery( - 'CREATE TABLE `_tmp_table` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `INDEX` timestamp, - PRIMARY KEY (`id`) - );' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'bigint(20) unsigned', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'INDEX', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - } - - public function testColumnWithOnUpdateAndNoIdField() { - // CREATE TABLE with ON UPDATE - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - name varchar(20) NOT NULL, - created_at timestamp NULL ON UPDATE CURRENT_TIMESTAMP - );' - ); - - // on INSERT, no timestamps are expected - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('aaa')" ); - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name = 'aaa'" ); - $this->assertNull( $result[0]->created_at ); - - // on UPDATE, we expect timestamps in form YYYY-MM-DD HH:MM:SS - $this->assertQuery( "UPDATE _tmp_table SET name = 'bbb' WHERE name = 'aaa'" ); - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name = 'bbb'" ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->created_at ); - } - - public function testColumnWithOnUpdateAndAutoincrementPrimaryKey() { - // CREATE TABLE with ON UPDATE, AUTO_INCREMENT, and PRIMARY KEY - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - id int(11) NOT NULL AUTO_INCREMENT, - created_at timestamp NULL ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id) - );' - ); - - // on INSERT, no timestamps are expected - $this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (1)' ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 1' ); - $this->assertNull( $result[0]->created_at ); - - // on UPDATE, we expect timestamps in form YYYY-MM-DD HH:MM:SS - $this->assertQuery( 'UPDATE _tmp_table SET id = 2 WHERE id = 1' ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 2' ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->created_at ); - } - - public function testChangeColumnWithOnUpdate() { - // CREATE TABLE with ON UPDATE - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - id int(11) NOT NULL, - created_at timestamp NULL - );' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'created_at', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - // no ON UPDATE is set - $this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (1)' ); - $this->assertQuery( 'UPDATE _tmp_table SET id = 1 WHERE id = 1' ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 1' ); - $this->assertNull( $result[0]->created_at ); - - // CHANGE COLUMN to add ON UPDATE - $this->assertQuery( - 'ALTER TABLE _tmp_table CHANGE COLUMN created_at created_at timestamp NULL ON UPDATE CURRENT_TIMESTAMP' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'created_at', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - // now, ON UPDATE SHOULD BE SET - $this->assertQuery( 'UPDATE _tmp_table SET id = 1 WHERE id = 1' ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 1' ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->created_at ); - - // change column to remove ON UPDATE - $this->assertQuery( - 'ALTER TABLE _tmp_table CHANGE COLUMN created_at created_at timestamp NULL' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'created_at', - 'Type' => 'timestamp', - 'Null' => 'YES', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $results - ); - - // now, no timestamp is expected - $this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (2)' ); - $this->assertQuery( 'UPDATE _tmp_table SET id = 2 WHERE id = 2' ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 2' ); - $this->assertNull( $result[0]->created_at ); - } - - public function testAlterTableWithColumnFirstAndAfter() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - id int(11) NOT NULL, - name varchar(20) NOT NULL default '' - );" - ); - - // ADD COLUMN with FIRST - $this->assertQuery( - "ALTER TABLE _tmp_table ADD COLUMN new_first_column VARCHAR(255) NOT NULL DEFAULT '' FIRST" - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'new_first_column', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ), - $results - ); - - // ADD COLUMN with AFTER - $this->assertQuery( - "ALTER TABLE _tmp_table ADD COLUMN new_column VARCHAR(255) NOT NULL DEFAULT '' AFTER id" - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'new_first_column', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'new_column', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ), - $results - ); - - // CHANGE with FIRST - $this->assertQuery( - "ALTER TABLE _tmp_table CHANGE id id int(11) NOT NULL DEFAULT '0' FIRST" - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'new_first_column', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'new_column', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ), - $results - ); - - // CHANGE with AFTER - $this->assertQuery( - "ALTER TABLE _tmp_table CHANGE id id int(11) NOT NULL DEFAULT '0' AFTER name" - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'new_first_column', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'new_column', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ), - $results - ); - } - - public function testAlterTableWithMultiColumnFirstAndAfter() { - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - id int(11) NOT NULL - );' - ); - - // ADD COLUMN - $this->assertQuery( - 'ALTER TABLE _tmp_table - ADD COLUMN new1 varchar(255) NOT NULL, - ADD COLUMN new2 varchar(255) NOT NULL FIRST, - ADD COLUMN new3 varchar(255) NOT NULL AFTER new1' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'new1', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'new2', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'new3', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ), - $results - ); - - // CHANGE - $this->assertQuery( - 'ALTER TABLE _tmp_table - CHANGE new1 new1 int(11) NOT NULL FIRST, - CHANGE new2 new2 int(11) NOT NULL, - CHANGE new3 new3 int(11) NOT NULL AFTER new2' - ); - $results = $this->assertQuery( 'DESCRIBE _tmp_table;' ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'id', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'new1', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - (object) array( - 'Field' => 'new2', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'new3', - 'Type' => 'int(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ), - $results - ); - } - - public function testAlterTableAddIndex() { - $result = $this->assertQuery( - "CREATE TABLE _tmp_table ( - name varchar(20) NOT NULL default '' - );" - ); - - $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD INDEX name (name);' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'SHOW INDEX FROM _tmp_table;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Table' => '_tmp_table', - 'Non_unique' => '1', - 'Key_name' => 'name', - 'Seq_in_index' => '0', - 'Column_name' => 'name', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - ), - $results - ); - } - - public function testAlterTableAddUniqueIndex() { - $result = $this->assertQuery( - "CREATE TABLE _tmp_table ( - name varchar(20) NOT NULL default '' - );" - ); - - $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD UNIQUE INDEX name (name(20));' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'SHOW INDEX FROM _tmp_table;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Table' => '_tmp_table', - 'Non_unique' => '0', - 'Key_name' => 'name', - 'Seq_in_index' => '0', - 'Column_name' => 'name', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - ), - $results - ); - } - - public function testAlterTableAddFulltextIndex() { - $result = $this->assertQuery( - "CREATE TABLE _tmp_table ( - name varchar(20) NOT NULL default '' - );" - ); - - $result = $this->assertQuery( 'ALTER TABLE _tmp_table ADD FULLTEXT INDEX name (name);' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $this->assertQuery( 'SHOW INDEX FROM _tmp_table;' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( - array( - (object) array( - 'Table' => '_tmp_table', - 'Non_unique' => '1', - 'Key_name' => 'name', - 'Seq_in_index' => '0', - 'Column_name' => 'name', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'FULLTEXT', - 'Comment' => '', - 'Index_comment' => '', - ), - ), - $results - ); - } - - public function testAlterTableModifyColumn() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - name varchar(20) NOT NULL default '', - lastname varchar(20) NOT NULL default '', - KEY composite (name, lastname), - UNIQUE KEY name (name) - );" - ); - // Insert a record - $result = $this->assertQuery( "INSERT INTO _tmp_table (ID, name, lastname) VALUES (1, 'Johnny', 'Appleseed');" ); - $this->assertEquals( 1, $result ); - - // Primary key violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, name, lastname) VALUES (1, 'Mike', 'Pearseed');" ); - $this->assertEquals( false, $result ); - - // Unique constraint violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, name, lastname) VALUES (2, 'Johnny', 'Appleseed');" ); - $this->assertEquals( false, $result ); - - // Rename the "name" field to "firstname": - $result = $this->engine->query( "ALTER TABLE _tmp_table CHANGE column name firstname varchar(50) NOT NULL default 'mark';" ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - // Confirm the original data is still there: - $result = $this->engine->query( 'SELECT * FROM _tmp_table;' ); - $this->assertCount( 1, $result ); - $this->assertEquals( 1, $result[0]->ID ); - $this->assertEquals( 'Johnny', $result[0]->firstname ); - $this->assertEquals( 'Appleseed', $result[0]->lastname ); - - // Confirm the primary key is intact: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, firstname, lastname) VALUES (1, 'Mike', 'Pearseed');" ); - $this->assertEquals( false, $result ); - - // Confirm the unique key is intact: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, firstname, lastname) VALUES (2, 'Johnny', 'Appleseed');" ); - $this->assertEquals( false, $result ); - - // Confirm the autoincrement still works: - $result = $this->engine->query( "INSERT INTO _tmp_table (firstname, lastname) VALUES ('John', 'Doe');" ); - $this->assertEquals( true, $result ); - $result = $this->engine->query( "SELECT * FROM _tmp_table WHERE firstname='John';" ); - $this->assertCount( 1, $result ); - $this->assertEquals( 2, $result[0]->ID ); - } - - - public function testAlterTableModifyColumnWithSkippedColumnKeyword() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - name varchar(20) NOT NULL default '', - lastname varchar(20) NOT NULL default '', - KEY composite (name, lastname), - UNIQUE KEY name (name) - );" - ); - // Insert a record - $result = $this->assertQuery( "INSERT INTO _tmp_table (ID, name, lastname) VALUES (1, 'Johnny', 'Appleseed');" ); - $this->assertEquals( 1, $result ); - - // Primary key violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, name, lastname) VALUES (1, 'Mike', 'Pearseed');" ); - $this->assertEquals( false, $result ); - - // Unique constraint violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, name, lastname) VALUES (2, 'Johnny', 'Appleseed');" ); - $this->assertEquals( false, $result ); - - // Rename the "name" field to "firstname": - $result = $this->engine->query( "ALTER TABLE _tmp_table CHANGE name firstname varchar(50) NOT NULL default 'mark';" ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - // Confirm the original data is still there: - $result = $this->engine->query( 'SELECT * FROM _tmp_table;' ); - $this->assertCount( 1, $result ); - $this->assertEquals( 1, $result[0]->ID ); - $this->assertEquals( 'Johnny', $result[0]->firstname ); - $this->assertEquals( 'Appleseed', $result[0]->lastname ); - - // Confirm the primary key is intact: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, firstname, lastname) VALUES (1, 'Mike', 'Pearseed');" ); - $this->assertEquals( false, $result ); - - // Confirm the unique key is intact: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, firstname, lastname) VALUES (2, 'Johnny', 'Appleseed');" ); - $this->assertEquals( false, $result ); - - // Confirm the autoincrement still works: - $result = $this->engine->query( "INSERT INTO _tmp_table (firstname, lastname) VALUES ('John', 'Doe');" ); - $this->assertEquals( true, $result ); - $result = $this->engine->query( "SELECT * FROM _tmp_table WHERE firstname='John';" ); - $this->assertCount( 1, $result ); - $this->assertEquals( 2, $result[0]->ID ); - } - - public function testAlterTableModifyColumnWithHyphens() { - $result = $this->assertQuery( - 'CREATE TABLE wptests_dbdelta_test2 ( - `foo-bar` varchar(255) DEFAULT NULL - )' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $result = $this->assertQuery( - 'ALTER TABLE wptests_dbdelta_test2 CHANGE COLUMN `foo-bar` `foo-bar` text DEFAULT NULL' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $result = $this->assertQuery( 'DESCRIBE wptests_dbdelta_test2;' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'foo-bar', - 'Type' => 'text', - 'Null' => 'YES', - 'Key' => '', - 'Default' => 'NULL', - 'Extra' => '', - ), - ), - $result - ); - } - - public function testAlterTableModifyColumnComplexChange() { - $result = $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER NOT NULL, - name varchar(20) NOT NULL default '', - lastname varchar(20) default '', - date_as_string varchar(20) default '', - PRIMARY KEY (ID, name) - );" - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - // Add a unique index - $result = $this->assertQuery( - 'ALTER TABLE _tmp_table ADD UNIQUE INDEX "test_unique_composite" (name, lastname);' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - // Add a regular index - $result = $this->assertQuery( - 'ALTER TABLE _tmp_table ADD INDEX "test_regular" (lastname);' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - // Confirm the table is well-behaved so far: - - // Insert a few records - $result = $this->assertQuery( - " - INSERT INTO _tmp_table (ID, name, lastname, date_as_string) - VALUES - (1, 'Johnny', 'Appleseed', '2002-01-01 12:53:13'), - (2, 'Mike', 'Foo', '2003-01-01 12:53:13'), - (3, 'Kate', 'Bar', '2004-01-01 12:53:13'), - (4, 'Anna', 'Pear', '2005-01-01 12:53:13') - ;" - ); - $this->assertEquals( 4, $result ); - - // Primary key violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, name) VALUES (1, 'Johnny');" ); - $this->assertEquals( false, $result ); - - // Unique constraint violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, name, lastname) VALUES (5, 'Kate', 'Bar');" ); - $this->assertEquals( false, $result ); - - // No constraint violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, name, lastname) VALUES (5, 'Joanna', 'Bar');" ); - $this->assertEquals( 1, $result ); - - // Now – let's change a few columns: - $result = $this->engine->query( 'ALTER TABLE _tmp_table CHANGE COLUMN name firstname varchar(20)' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $result = $this->engine->query( 'ALTER TABLE _tmp_table CHANGE COLUMN date_as_string datetime datetime NOT NULL' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - // Finally, let's confirm our data is intact and the table is still well-behaved: - $result = $this->engine->query( 'SELECT * FROM _tmp_table ORDER BY ID;' ); - $this->assertCount( 5, $result ); - $this->assertEquals( 1, $result[0]->ID ); - $this->assertEquals( 'Johnny', $result[0]->firstname ); - $this->assertEquals( 'Appleseed', $result[0]->lastname ); - $this->assertEquals( '2002-01-01 12:53:13', $result[0]->datetime ); - - // Primary key violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, firstname, datetime) VALUES (1, 'Johnny', '2010-01-01 12:53:13');" ); - $this->assertEquals( false, $result ); - - // Unique constraint violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, firstname, lastname, datetime) VALUES (6, 'Kate', 'Bar', '2010-01-01 12:53:13');" ); - $this->assertEquals( false, $result ); - - // No constraint violation: - $result = $this->engine->query( "INSERT INTO _tmp_table (ID, firstname, lastname, datetime) VALUES (6, 'Sophie', 'Bar', '2010-01-01 12:53:13');" ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - } - - public function testCaseInsensitiveUniqueIndex() { - $result = $this->engine->query( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - name varchar(20) NOT NULL default '', - lastname varchar(20) NOT NULL default '', - KEY name (name), - UNIQUE KEY uname (name), - UNIQUE KEY last (lastname) - );" - ); - $this->assertEquals( 1, $result ); - - $result1 = $this->engine->query( "INSERT INTO _tmp_table (name, lastname) VALUES ('first', 'last');" ); - $this->assertEquals( 1, $result1 ); - - $result1 = $this->engine->query( 'SELECT COUNT(*) num FROM _tmp_table;' ); - $this->assertEquals( 1, $result1[0]->num ); - - // Unique keys should be case-insensitive: - $result2 = $this->assertQuery( - "INSERT INTO _tmp_table (name, lastname) VALUES ('FIRST', 'LAST' );", - 'UNIQUE constraint failed' - ); - - $this->assertEquals( false, $result2 ); - - $result1 = $this->engine->query( 'SELECT COUNT(*) num FROM _tmp_table;' ); - $this->assertEquals( 1, $result1[0]->num ); - - // Unique keys should be case-insensitive: - $result1 = $this->assertQuery( - "INSERT IGNORE INTO _tmp_table (name) VALUES ('FIRST');" - ); - - self::assertEquals( 0, $result1 ); - - $result2 = $this->engine->get_query_results(); - $this->assertEquals( 0, $result2 ); - - $result1 = $this->engine->query( 'SELECT COUNT(*)num FROM _tmp_table;' ); - $this->assertEquals( 1, $result1[0]->num ); - - // Unique keys should be case-insensitive: - $result2 = $this->assertQuery( - "INSERT INTO _tmp_table (name, lastname) VALUES ('FIRSTname', 'LASTname' );" - ); - - $this->assertEquals( 1, $result2 ); - - $result1 = $this->engine->query( 'SELECT COUNT(*) num FROM _tmp_table;' ); - $this->assertEquals( 2, $result1[0]->num ); - } - - public function testOnDuplicateUpdate() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - name varchar(20) NOT NULL default '', - UNIQUE KEY myname (name) - );" - ); - - // $result1 = $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('first');" ); - // $this->assertEquals( '', $this->engine->get_error_message() ); - // $this->assertEquals( 1, $result1 ); - - $result2 = $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('FIRST') ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);" ); - $this->assertEquals( 1, $result2 ); - - $this->assertQuery( 'SELECT * FROM _tmp_table;' ); - $this->assertCount( 1, $this->engine->get_query_results() ); - $this->assertEquals( - array( - (object) array( - 'name' => 'FIRST', - 'ID' => 1, - ), - ), - $this->engine->get_query_results() - ); - } - - public function testTruncatesInvalidDates() { - $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2022-01-01 14:24:12');" ); - $this->assertQuery( "INSERT INTO _dates (option_value) VALUES ('2022-31-01 14:24:12');" ); - - $this->assertQuery( 'SELECT * FROM _dates;' ); - $results = $this->engine->get_query_results(); - $this->assertCount( 2, $results ); - $this->assertEquals( '2022-01-01 14:24:12', $results[0]->option_value ); - $this->assertEquals( '0000-00-00 00:00:00', $results[1]->option_value ); - } - - public function testCaseInsensitiveSelect() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - name varchar(20) NOT NULL default '' - );" - ); - $this->assertQuery( - "INSERT INTO _tmp_table (name) VALUES ('first');" - ); - $this->assertQuery( "SELECT name FROM _tmp_table WHERE name = 'FIRST';" ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertCount( 1, $this->engine->get_query_results() ); - $this->assertEquals( - array( - (object) array( - 'name' => 'first', - ), - ), - $this->engine->get_query_results() - ); - } - - public function testSelectBetweenDates() { - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2016-01-15T00:00:00Z');" ); - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2016-01-16T00:00:00Z');" ); - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('third', '2016-01-17T00:00:00Z');" ); - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('fourth', '2016-01-18T00:00:00Z');" ); - - $this->assertQuery( "SELECT * FROM _dates WHERE option_value BETWEEN '2016-01-15T00:00:00Z' AND '2016-01-17T00:00:00Z' ORDER BY ID;" ); - $results = $this->engine->get_query_results(); - $this->assertCount( 3, $results ); - $this->assertEquals( 'first', $results[0]->option_name ); - $this->assertEquals( 'second', $results[1]->option_name ); - $this->assertEquals( 'third', $results[2]->option_name ); - } - - public function testSelectFilterByDatesGtLt() { - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2016-01-15T00:00:00Z');" ); - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2016-01-16T00:00:00Z');" ); - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('third', '2016-01-17T00:00:00Z');" ); - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('fourth', '2016-01-18T00:00:00Z');" ); - - $this->assertQuery( - " - SELECT * FROM _dates - WHERE option_value > '2016-01-15 00:00:00' - AND option_value < '2016-01-17 00:00:00' - ORDER BY ID - " - ); - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( 'second', $results[0]->option_name ); - } - - public function testSelectFilterByDatesZeroHour() { - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2014-10-21 00:42:29');" ); - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('second', '2014-10-21 01:42:29');" ); - - $this->assertQuery( - ' - SELECT * FROM _dates - WHERE YEAR(option_value) = 2014 - AND MONTHNUM(option_value) = 10 - AND DAY(option_value) = 21 - AND HOUR(option_value) = 0 - AND MINUTE(option_value) = 42 - ' - ); - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( 'first', $results[0]->option_name ); - } - - public function testCorrectlyInsertsDatesAndStrings() { - $this->assertQuery( "INSERT INTO _dates (option_name, option_value) VALUES ('2016-01-15T00:00:00Z', '2016-01-15T00:00:00Z');" ); - - $this->assertQuery( 'SELECT * FROM _dates' ); - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( '2016-01-15 00:00:00', $results[0]->option_value ); - if ( '2016-01-15T00:00:00Z' !== $results[0]->option_name ) { - $this->markTestSkipped( 'A datetime-like string was rewritten to an SQLite format even though it was used as a text and not as a datetime.' ); - } - $this->assertEquals( '2016-01-15T00:00:00Z', $results[0]->option_name ); - } - - public function testTransactionRollback() { - $this->assertQuery( 'BEGIN' ); - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('first');" ); - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 1, $this->engine->get_query_results() ); - $this->assertQuery( 'ROLLBACK' ); - - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 0, $this->engine->get_query_results() ); - } - - public function testTransactionCommit() { - $this->assertQuery( 'BEGIN' ); - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('first');" ); - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 1, $this->engine->get_query_results() ); - $this->assertQuery( 'COMMIT' ); - - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 1, $this->engine->get_query_results() ); - } - - public function testStartTransactionCommand() { - $this->assertQuery( 'START TRANSACTION' ); - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('first');" ); - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 1, $this->engine->get_query_results() ); - $this->assertQuery( 'ROLLBACK' ); - - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 0, $this->engine->get_query_results() ); - } - - public function testNestedTransactionWork() { - $this->assertQuery( 'BEGIN' ); - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('first');" ); - $this->assertQuery( 'START TRANSACTION' ); - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('second');" ); - $this->assertQuery( 'START TRANSACTION' ); - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('third');" ); - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 3, $this->engine->get_query_results() ); - - $this->assertQuery( 'ROLLBACK' ); - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 2, $this->engine->get_query_results() ); - - $this->assertQuery( 'ROLLBACK' ); - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 1, $this->engine->get_query_results() ); - - $this->assertQuery( 'COMMIT' ); - $this->assertQuery( 'SELECT * FROM _options;' ); - $this->assertCount( 1, $this->engine->get_query_results() ); - } - - public function testNestedTransactionWorkComplexModify() { - $this->assertQuery( 'BEGIN' ); - // Create a complex ALTER Table query where the first - // column is added successfully, but the second fails. - // Behind the scenes, this single MySQL query is split - // into multiple SQLite queries – some of them will - // succeed, some will fail. - $success = $this->engine->query( - ' - ALTER TABLE _options - ADD COLUMN test varchar(20), - ADD COLUMN test varchar(20) - ' - ); - $this->assertFalse( $success ); - // Commit the transaction. - $this->assertQuery( 'COMMIT' ); - - // Confirm the entire query failed atomically and no column was - // added to the table. - $this->assertQuery( 'DESCRIBE _options;' ); - $fields = $this->engine->get_query_results(); - - $this->assertEquals( - $fields, - array( - (object) array( - 'Field' => 'ID', - 'Type' => 'integer', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'option_name', - 'Type' => 'text', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - (object) array( - 'Field' => 'option_value', - 'Type' => 'text', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '', - 'Extra' => '', - ), - ) - ); - } - - public function testCount() { - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('first');" ); - $this->assertQuery( "INSERT INTO _options (option_name) VALUES ('second');" ); - $this->assertQuery( 'SELECT COUNT(*) as count FROM _options;' ); - - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertSame( '2', $results[0]->count ); - } - - public function testUpdateDate() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48');" - ); - - $this->assertQuery( 'SELECT option_value FROM _dates' ); - - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( '2003-05-27 10:08:48', $results[0]->option_value ); - - $this->assertQuery( - "UPDATE _dates SET option_value = DATE_SUB(option_value, INTERVAL '2' YEAR);" - ); - - $this->assertQuery( 'SELECT option_value FROM _dates' ); - - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( '2001-05-27 10:08:48', $results[0]->option_value ); - } - - public function testInsertDateLiteral() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48');" - ); - - $this->assertQuery( 'SELECT option_value FROM _dates' ); - - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( '2003-05-27 10:08:48', $results[0]->option_value ); - } - - public function testSelectDate1() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2000-05-27 10:08:48');" - ); - - $this->assertQuery( - 'SELECT - YEAR( _dates.option_value ) as year, - MONTH( _dates.option_value ) as month, - DAYOFMONTH( _dates.option_value ) as dayofmonth, - MONTHNUM( _dates.option_value ) as monthnum, - WEEKDAY( _dates.option_value ) as weekday, - WEEK( _dates.option_value, 1 ) as week1, - HOUR( _dates.option_value ) as hour, - MINUTE( _dates.option_value ) as minute, - SECOND( _dates.option_value ) as second - FROM _dates' - ); - - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( '2000', $results[0]->year ); - $this->assertEquals( '5', $results[0]->month ); - $this->assertEquals( '27', $results[0]->dayofmonth ); - $this->assertEquals( '5', $results[0]->weekday ); - $this->assertEquals( '21', $results[0]->week1 ); - $this->assertEquals( '5', $results[0]->monthnum ); - $this->assertEquals( '10', $results[0]->hour ); - $this->assertEquals( '8', $results[0]->minute ); - $this->assertEquals( '48', $results[0]->second ); - } - - public function testSelectDate24HourFormat() { - $this->assertQuery( - " - INSERT INTO _dates (option_name, option_value) - VALUES - ('second', '2003-05-27 14:08:48'), - ('first', '2003-05-27 00:08:48'); - " - ); - - // HOUR(14:08) should yield 14 in the 24 hour format - $this->assertQuery( "SELECT HOUR( _dates.option_value ) as hour FROM _dates WHERE option_name = 'second'" ); - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( '14', $results[0]->hour ); - - // HOUR(00:08) should yield 0 in the 24 hour format - $this->assertQuery( "SELECT HOUR( _dates.option_value ) as hour FROM _dates WHERE option_name = 'first'" ); - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( '0', $results[0]->hour ); - - // Lookup by HOUR(00:08) = 0 should yield the right record - $this->assertQuery( - 'SELECT HOUR( _dates.option_value ) as hour FROM _dates - WHERE HOUR(_dates.option_value) = 0 ' - ); - - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - $this->assertEquals( '0', $results[0]->hour ); - } - - public function testSelectByDateFunctions() { - $this->assertQuery( - " - INSERT INTO _dates (option_name, option_value) - VALUES ('second', '2014-10-21 00:42:29'); - " - ); - - // HOUR(14:08) should yield 14 in the 24 hour format - $this->assertQuery( - ' - SELECT * FROM _dates WHERE - year(option_value) = 2014 - AND monthnum(option_value) = 10 - AND day(option_value) = 21 - AND hour(option_value) = 0 - AND minute(option_value) = 42 - ' - ); - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - } - - public function testSelectByDateFormat() { - $this->assertQuery( - " - INSERT INTO _dates (option_name, option_value) - VALUES ('second', '2014-10-21 00:42:29'); - " - ); - - // HOUR(14:08) should yield 14 in the 24 hour format - $this->assertQuery( - " - SELECT * FROM _dates WHERE DATE_FORMAT(option_value, '%H.%i') = 0.42 - " - ); - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - } - - public function testInsertOnDuplicateKey() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, - name varchar(20) NOT NULL default '', - UNIQUE KEY name (name) - );" - ); - $result1 = $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('first');" ); - $this->assertEquals( 1, $result1 ); - - $result2 = $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('FIRST') ON DUPLICATE KEY SET name=VALUES(`name`);" ); - $this->assertEquals( 1, $result2 ); - - $this->assertQuery( 'SELECT COUNT(*) as cnt FROM _tmp_table' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( 1, $results[0]->cnt ); - } - - public function testCreateTableCompositePk() { - $this->assertQuery( - 'CREATE TABLE wptests_term_relationships ( - object_id bigint(20) unsigned NOT NULL default 0, - term_taxonomy_id bigint(20) unsigned NOT NULL default 0, - term_order int(11) NOT NULL default 0, - PRIMARY KEY (object_id,term_taxonomy_id), - KEY term_taxonomy_id (term_taxonomy_id) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci' - ); - $result1 = $this->engine->query( 'INSERT INTO wptests_term_relationships VALUES (1,2,1),(1,3,2);' ); - $this->assertEquals( 2, $result1 ); - - $result2 = $this->engine->query( 'INSERT INTO wptests_term_relationships VALUES (1,2,2),(1,3,1);' ); - $this->assertEquals( false, $result2 ); - } - - public function testDescribeAccurate() { - $result = $this->assertQuery( - 'CREATE TABLE wptests_term_relationships ( - object_id bigint(20) unsigned NOT NULL default 0, - term_taxonomy_id bigint(20) unsigned NOT NULL default 0, - term_name varchar(11) NOT NULL default 0, - PRIMARY KEY (object_id,term_taxonomy_id), - KEY term_taxonomy_id (term_taxonomy_id), - KEY compound_key (object_id(20),term_taxonomy_id(20)), - FULLTEXT KEY term_name (term_name) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - - $result = $this->assertQuery( 'DESCRIBE wptests_term_relationships;' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - - $fields = $this->engine->get_query_results(); - - $this->assertEquals( - array( - (object) array( - 'Field' => 'object_id', - 'Type' => 'bigint(20) unsigned', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'term_taxonomy_id', - 'Type' => 'bigint(20) unsigned', - 'Null' => 'NO', - 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'term_name', - 'Type' => 'varchar(11)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - ), - $fields - ); - } - - public function testAlterTableAddColumnChangesMySQLDataType() { - $result = $this->assertQuery( - 'CREATE TABLE _test ( - object_id bigint(20) unsigned NOT NULL default 0 - )' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - - $result = $this->assertQuery( "ALTER TABLE `_test` ADD COLUMN object_name varchar(255) NOT NULL DEFAULT 'adb';" ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - - $result = $this->assertQuery( 'DESCRIBE _test;' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - $fields = $this->engine->get_query_results(); - - $this->assertEquals( - array( - (object) array( - 'Field' => 'object_id', - 'Type' => 'bigint(20) unsigned', - 'Null' => 'NO', - 'Key' => '', - 'Default' => '0', - 'Extra' => '', - ), - (object) array( - 'Field' => 'object_name', - 'Type' => 'varchar(255)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => 'adb', - 'Extra' => '', - ), - ), - $fields - ); - } - public function testShowGrantsFor() { - $result = $this->assertQuery( 'SHOW GRANTS FOR current_user();' ); - $this->assertEquals( - $result, - array( - (object) array( - 'Grants for root@localhost' => 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE, CREATE ROLE, DROP ROLE ON *.* TO `root`@`localhost` WITH GRANT OPTION', - ), - ) - ); - } - - public function testShowIndex() { - $result = $this->assertQuery( - 'CREATE TABLE wptests_term_relationships ( - object_id bigint(20) unsigned NOT NULL default 0, - term_taxonomy_id bigint(20) unsigned NOT NULL default 0, - term_name varchar(11) NOT NULL default 0, - FULLTEXT KEY term_name_fulltext (term_name), - FULLTEXT INDEX term_name_fulltext2 (`term_name`), - SPATIAL KEY term_name_spatial (term_name), - PRIMARY KEY (object_id,term_taxonomy_id), - KEY term_taxonomy_id (term_taxonomy_id), - KEY compound_key (object_id(20),term_taxonomy_id(20)) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - - $result = $this->assertQuery( 'SHOW INDEX FROM wptests_term_relationships;' ); - $this->assertNotFalse( $result ); - - $this->assertEquals( - array( - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '0', - 'Key_name' => 'PRIMARY', - 'Seq_in_index' => '0', - 'Column_name' => 'object_id', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '0', - 'Key_name' => 'PRIMARY', - 'Seq_in_index' => '0', - 'Column_name' => 'term_taxonomy_id', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '1', - 'Key_name' => 'compound_key', - 'Seq_in_index' => '0', - 'Column_name' => 'object_id', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '1', - 'Key_name' => 'compound_key', - 'Seq_in_index' => '0', - 'Column_name' => 'term_taxonomy_id', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '1', - 'Key_name' => 'term_taxonomy_id', - 'Seq_in_index' => '0', - 'Column_name' => 'term_taxonomy_id', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '1', - 'Key_name' => 'term_name_spatial', - 'Seq_in_index' => '0', - 'Column_name' => 'term_name', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'SPATIAL', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '1', - 'Key_name' => 'term_name_fulltext2', - 'Seq_in_index' => '0', - 'Column_name' => 'term_name', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'FULLTEXT', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '1', - 'Key_name' => 'term_name_fulltext', - 'Seq_in_index' => '0', - 'Column_name' => 'term_name', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'FULLTEXT', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '0', - 'Key_name' => 'wptests_term_relationships', - 'Seq_in_index' => '0', - 'Column_name' => 'object_id', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - (object) array( - 'Table' => 'wptests_term_relationships', - 'Non_unique' => '0', - 'Key_name' => 'wptests_term_relationships', - 'Seq_in_index' => '0', - 'Column_name' => 'term_taxonomy_id', - 'Collation' => 'A', - 'Cardinality' => '0', - 'Sub_part' => null, - 'Packed' => null, - 'Null' => '', - 'Index_type' => 'BTREE', - 'Comment' => '', - 'Index_comment' => '', - ), - ), - $this->engine->get_query_results() - ); - } - - public function testInsertOnDuplicateKeyCompositePk() { - $result = $this->assertQuery( - 'CREATE TABLE wptests_term_relationships ( - object_id bigint(20) unsigned NOT NULL default 0, - term_taxonomy_id bigint(20) unsigned NOT NULL default 0, - term_order int(11) NOT NULL default 0, - PRIMARY KEY (object_id,term_taxonomy_id), - KEY term_taxonomy_id (term_taxonomy_id) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - - $result1 = $this->assertQuery( 'INSERT INTO wptests_term_relationships VALUES (1,2,1),(1,3,2);' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 2, $result1 ); - - $result2 = $this->assertQuery( 'INSERT INTO wptests_term_relationships VALUES (1,2,2),(1,3,1) ON DUPLICATE KEY SET term_order = VALUES(term_order);' ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 2, $result2 ); - - $this->assertQuery( 'SELECT COUNT(*) as cnt FROM wptests_term_relationships' ); - $results = $this->engine->get_query_results(); - $this->assertEquals( 2, $results[0]->cnt ); - } - - public function testStringToFloatComparison() { - $this->assertQuery( "SELECT ('00.42' = 0.4200) as cmp;" ); - $results = $this->engine->get_query_results(); - if ( 1 !== $results[0]->cmp ) { - $this->markTestSkipped( 'Comparing a string and a float returns true in MySQL. In SQLite, they\'re different. Skipping. ' ); - } - $this->assertEquals( '1', $results[0]->cmp ); - - $this->assertQuery( "SELECT (0+'00.42' = 0.4200) as cmp;" ); - $results = $this->engine->get_query_results(); - $this->assertEquals( '1', $results[0]->cmp ); - } - - public function testZeroPlusStringToFloatComparison() { - - $this->assertQuery( "SELECT (0+'00.42' = 0.4200) as cmp;" ); - $results = $this->engine->get_query_results(); - $this->assertEquals( '1', $results[0]->cmp ); - - $this->assertQuery( "SELECT 0+'1234abcd' = 1234 as cmp;" ); - $results = $this->engine->get_query_results(); - $this->assertEquals( '1', $results[0]->cmp ); - } - - public function testCalcFoundRows() { - $result = $this->assertQuery( - "CREATE TABLE wptests_dummy ( - ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - user_login TEXT NOT NULL default '' - );" - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertNotFalse( $result ); - - $result = $this->assertQuery( - "INSERT INTO wptests_dummy (user_login) VALUES ('test');" - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 1, $result ); - - $result = $this->assertQuery( - 'SELECT SQL_CALC_FOUND_ROWS * FROM wptests_dummy' - ); - $this->assertNotFalse( $result ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( 'test', $result[0]->user_login ); - } - - public function testComplexSelectBasedOnDates() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48');" - ); - - $this->assertQuery( - 'SELECT SQL_CALC_FOUND_ROWS _dates.ID - FROM _dates - WHERE YEAR( _dates.option_value ) = 2003 AND MONTH( _dates.option_value ) = 5 AND DAYOFMONTH( _dates.option_value ) = 27 - ORDER BY _dates.option_value DESC - LIMIT 0, 10' - ); - - $results = $this->engine->get_query_results(); - $this->assertCount( 1, $results ); - } - - public function testUpdateReturnValue() { - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', '2003-05-27 10:08:48');" - ); - - $return = $this->assertQuery( - "UPDATE _dates SET option_value = '2001-05-27 10:08:48'" - ); - $this->assertSame( 1, $return, 'UPDATE query did not return 1 when one row was changed' ); - - $return = $this->assertQuery( - "UPDATE _dates SET option_value = '2001-05-27 10:08:48'" - ); - if ( 1 === $return ) { - $this->markTestIncomplete( - 'SQLite UPDATE query returned 1 when no rows were changed. ' . - 'This is a database compatibility issue – MySQL would return 0 ' . - 'in the same scenario.' - ); - } - $this->assertSame( 0, $return, 'UPDATE query did not return 0 when no rows were changed' ); - } - - public function testOrderByField() { - $this->assertQuery( - "INSERT INTO _options (option_name, option_value) VALUES ('User 0000019', 'second');" - ); - $this->assertQuery( - "INSERT INTO _options (option_name, option_value) VALUES ('User 0000020', 'third');" - ); - $this->assertQuery( - "INSERT INTO _options (option_name, option_value) VALUES ('User 0000018', 'first');" - ); - - $this->assertQuery( 'SELECT FIELD(option_name, "User 0000018", "User 0000019", "User 0000020") as sorting_order FROM _options ORDER BY FIELD(option_name, "User 0000018", "User 0000019", "User 0000020")' ); - - $this->assertEquals( - array( - (object) array( - 'sorting_order' => '1', - ), - (object) array( - 'sorting_order' => '2', - ), - (object) array( - 'sorting_order' => '3', - ), - ), - $this->engine->get_query_results() - ); - - $this->assertQuery( 'SELECT option_value FROM _options ORDER BY FIELD(option_name, "User 0000018", "User 0000019", "User 0000020")' ); - - $this->assertEquals( - array( - (object) array( - 'option_value' => 'first', - ), - (object) array( - 'option_value' => 'second', - ), - (object) array( - 'option_value' => 'third', - ), - ), - $this->engine->get_query_results() - ); - } - - public function testFetchedDataIsStringified() { - $this->assertQuery( - "INSERT INTO _options (option_name, option_value) VALUES ('rss_0123456789abcdef0123456789abcdef', '1');" - ); - - $this->assertQuery( 'SELECT ID FROM _options' ); - - $this->assertEquals( - array( - (object) array( - 'ID' => '1', - ), - ), - $this->engine->get_query_results() - ); - } - - public function testCreateTableQuery() { - $this->assertQuery( - <<<'QUERY' - CREATE TABLE IF NOT EXISTS wptests_users ( - ID bigint(20) unsigned NOT NULL auto_increment, - user_login varchar(60) NOT NULL default '', - user_pass varchar(255) NOT NULL default '', - user_nicename varchar(50) NOT NULL default '', - user_email varchar(100) NOT NULL default '', - user_url varchar(100) NOT NULL default '', - user_registered datetime NOT NULL default '0000-00-00 00:00:00', - user_activation_key varchar(255) NOT NULL default '', - user_status int(11) NOT NULL default '0', - display_name varchar(250) NOT NULL default '', - PRIMARY KEY (ID), - KEY user_login_key (user_login), - KEY user_nicename (user_nicename), - KEY user_email (user_email) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci -QUERY - ); - $this->assertQuery( - <<<'QUERY' - INSERT INTO wptests_users VALUES (1,'admin','$P$B5ZQZ5ZQZ5ZQZ5ZQZ5ZQZ5ZQZ5ZQZ5','admin','admin@localhost', '', '2019-01-01 00:00:00', '', 0, 'admin'); -QUERY - ); - $rows = $this->assertQuery( 'SELECT * FROM wptests_users' ); - $this->assertCount( 1, $rows ); - - $this->assertQuery( 'SELECT SQL_CALC_FOUND_ROWS * FROM wptests_users' ); - $result = $this->assertQuery( 'SELECT FOUND_ROWS()' ); - $this->assertEquals( - array( - (object) array( - 'FOUND_ROWS()' => '1', - ), - ), - $result - ); - } - - public function testTranslatesComplexDelete() { - $this->sqlite->query( - "CREATE TABLE wptests_dummy ( - ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - user_login TEXT NOT NULL default '', - option_name TEXT NOT NULL default '', - option_value TEXT NOT NULL default '' - );" - ); - $this->sqlite->query( - "INSERT INTO wptests_dummy (user_login, option_name, option_value) VALUES ('admin', '_transient_timeout_test', '1675963960');" - ); - $this->sqlite->query( - "INSERT INTO wptests_dummy (user_login, option_name, option_value) VALUES ('admin', '_transient_test', '1675963960');" - ); - - $result = $this->assertQuery( - "DELETE a, b FROM wptests_dummy a, wptests_dummy b - WHERE a.option_name LIKE '\_transient\_%' - AND a.option_name NOT LIKE '\_transient\_timeout_%' - AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) );" - ); - $this->assertEquals( - 2, - $result - ); - } - - public function testTranslatesDoubleAlterTable() { - $result = $this->assertQuery( - 'ALTER TABLE _options - ADD INDEX test_index(option_name(140),option_value(51)), - DROP INDEX test_index, - ADD INDEX test_index2(option_name(140),option_value(51)) - ' - ); - $this->assertEquals( '', $this->engine->get_error_message() ); - $this->assertEquals( - 1, - $result - ); - $result = $this->assertQuery( - 'SHOW INDEX FROM _options' - ); - $this->assertCount( 3, $result ); - $this->assertEquals( 'PRIMARY', $result[0]->Key_name ); - $this->assertEquals( 'test_index2', $result[1]->Key_name ); - $this->assertEquals( 'test_index2', $result[2]->Key_name ); - } - - public function testTranslatesComplexSelect() { - $this->assertQuery( - "CREATE TABLE wptests_postmeta ( - meta_id bigint(20) unsigned NOT NULL auto_increment, - post_id bigint(20) unsigned NOT NULL default '0', - meta_key varchar(255) default NULL, - meta_value longtext, - PRIMARY KEY (meta_id), - KEY post_id (post_id), - KEY meta_key (meta_key(191)) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci" - ); - $this->assertQuery( - "CREATE TABLE wptests_posts ( - ID bigint(20) unsigned NOT NULL auto_increment, - post_status varchar(20) NOT NULL default 'open', - post_type varchar(20) NOT NULL default 'post', - post_date varchar(20) NOT NULL default 'post', - PRIMARY KEY (ID) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci" - ); - $result = $this->assertQuery( - "SELECT SQL_CALC_FOUND_ROWS wptests_posts.ID - FROM wptests_posts INNER JOIN wptests_postmeta ON ( wptests_posts.ID = wptests_postmeta.post_id ) - WHERE 1=1 - AND ( - NOT EXISTS ( - SELECT 1 FROM wptests_postmeta mt1 - WHERE mt1.post_ID = wptests_postmeta.post_ID - LIMIT 1 - ) - ) - AND ( - (wptests_posts.post_type = 'post' AND (wptests_posts.post_status = 'publish')) - ) - GROUP BY wptests_posts.ID - ORDER BY wptests_posts.post_date DESC - LIMIT 0, 10" - ); - - // No exception is good enough of a test for now - $this->assertTrue( true ); - } - - public function testTranslatesUtf8Insert() { - $this->assertQuery( - "INSERT INTO _options VALUES(1,'ąłółźćę†','ąłółźćę†')" - ); - $this->assertCount( - 1, - $this->assertQuery( 'SELECT * FROM _options' ) - ); - $this->assertQuery( 'DELETE FROM _options' ); - } - - public function testTranslatesRandom() { - $this->assertIsNumeric( - $this->sqlite->query( 'SELECT RAND() AS rand' )->fetchColumn() - ); - - $this->assertIsNumeric( - $this->sqlite->query( 'SELECT RAND(5) AS rand' )->fetchColumn() - ); - } - - public function testTranslatesUtf8SELECT() { - $this->assertQuery( - "INSERT INTO _options VALUES(1,'ąłółźćę†','ąłółźćę†')" - ); - $this->assertCount( - 1, - $this->assertQuery( 'SELECT * FROM _options' ) - ); - - $this->assertQuery( - "SELECT option_name as 'ą' FROM _options WHERE option_name='ąłółźćę†' AND option_value='ąłółźćę†'" - ); - - $this->assertEquals( - array( (object) array( 'ą' => 'ąłółźćę†' ) ), - $this->engine->get_query_results() - ); - - $this->assertQuery( - "SELECT option_name as 'ą' FROM _options WHERE option_name LIKE '%ółźć%'" - ); - - $this->assertEquals( - array( (object) array( 'ą' => 'ąłółźćę†' ) ), - $this->engine->get_query_results() - ); - - $this->assertQuery( 'DELETE FROM _options' ); - } - - public function testTranslateLikeBinaryAndGlob() { - // Create a temporary table for testing - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - name varchar(20) NOT NULL default '' - );" - ); - - // Insert data into the table - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('first');" ); - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('FIRST');" ); - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('second');" ); - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('');" ); - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('%special%');" ); - $this->assertQuery( 'INSERT INTO _tmp_table (name) VALUES (NULL);' ); - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special%chars');" ); - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special_chars');" ); - $this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special\\chars');" ); - - // Test case-sensitive LIKE BINARY - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'first'" ); - $this->assertCount( 1, $result ); - $this->assertEquals( 'first', $result[0]->name ); - - // Test case-sensitive LIKE BINARY with wildcard % - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f%'" ); - $this->assertCount( 1, $result ); - $this->assertEquals( 'first', $result[0]->name ); - - // Test case-sensitive LIKE BINARY with wildcard _ - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f_rst'" ); - $this->assertCount( 1, $result ); - $this->assertEquals( 'first', $result[0]->name ); - - // Test case-insensitive LIKE - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE 'FIRST'" ); - $this->assertCount( 2, $result ); // Should match both 'first' and 'FIRST' - - // Test mixed case with LIKE BINARY - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'First'" ); - $this->assertCount( 0, $result ); - - // Test no matches with LIKE BINARY - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'third'" ); - $this->assertCount( 0, $result ); - - // Test GLOB equivalent for case-sensitive matching with wildcard - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'f*'" ); - $this->assertCount( 1, $result ); - $this->assertEquals( 'first', $result[0]->name ); - - // Test GLOB with single character wildcard - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'f?rst'" ); - $this->assertCount( 1, $result ); - $this->assertEquals( 'first', $result[0]->name ); - - // Test GLOB with no matches - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'S*'" ); - $this->assertCount( 0, $result ); - - // Test GLOB case sensitivity with LIKE and GLOB - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'first';" ); - $this->assertCount( 1, $result ); // Should only match 'first' - - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'FIRST';" ); - $this->assertCount( 1, $result ); // Should only match 'FIRST' - - // Test NULL comparison with LIKE BINARY - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'first';" ); - $this->assertCount( 1, $result ); - $this->assertEquals( 'first', $result[0]->name ); - - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE name LIKE BINARY NULL;' ); - $this->assertCount( 0, $result ); // NULL comparison should return no results - - // Test pattern with special characters using LIKE BINARY - $result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY '%special%';" ); - $this->assertCount( 4, $result ); - $this->assertEquals( '%special%', $result[0]->name ); - $this->assertEquals( 'special%chars', $result[1]->name ); - $this->assertEquals( 'special_chars', $result[2]->name ); - $this->assertEquals( 'specialchars', $result[3]->name ); - } - - public function testOnConflictReplace() { - $this->assertQuery( - "CREATE TABLE _tmp_table ( - ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - name varchar(20) NOT NULL default 'default-value', - unique_name varchar(20) NOT NULL default 'unique-default-value', - inline_unique_name varchar(20) NOT NULL default 'inline-unique-default-value', - no_default varchar(20) NOT NULL, - UNIQUE KEY unique_name (unique_name) - );" - ); - - $this->assertQuery( - "INSERT INTO _tmp_table VALUES (1, null, null, null, '');" - ); - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE ID = 1' ); - $this->assertEquals( - array( - (object) array( - 'ID' => '1', - 'name' => 'default-value', - 'unique_name' => 'unique-default-value', - 'inline_unique_name' => 'inline-unique-default-value', - 'no_default' => '', - ), - ), - $result - ); - - $this->assertQuery( - "INSERT INTO _tmp_table VALUES (2, '1', '2', '3', '4');" - ); - $this->assertQuery( - 'UPDATE _tmp_table SET name = null WHERE ID = 2;' - ); - - $result = $this->assertQuery( 'SELECT name FROM _tmp_table WHERE ID = 2' ); - $this->assertEquals( - array( - (object) array( - 'name' => 'default-value', - ), - ), - $result - ); - - // This should fail because of the UNIQUE constraint - $this->assertQuery( - 'UPDATE _tmp_table SET unique_name = NULL WHERE ID = 2;', - 'UNIQUE constraint failed: _tmp_table.unique_name' - ); - - // Inline unique constraint aren't supported currently, so this should pass - $this->assertQuery( - 'UPDATE _tmp_table SET inline_unique_name = NULL WHERE ID = 2;', - '' - ); - - // WPDB allows for NULL values in columns that don't have a default value and a NOT NULL constraint - $this->assertQuery( - 'UPDATE _tmp_table SET no_default = NULL WHERE ID = 2;', - '' - ); - - $result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE ID = 2' ); - $this->assertEquals( - array( - (object) array( - 'ID' => '2', - 'name' => 'default-value', - 'unique_name' => '2', - 'inline_unique_name' => 'inline-unique-default-value', - 'no_default' => '', - ), - ), - $result - ); - } - - public function testDefaultNullValue() { - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - name varchar(20) NOT NULL default NULL, - no_default varchar(20) NOT NULL - );' - ); - - $result = $this->assertQuery( - 'DESCRIBE _tmp_table;' - ); - $this->assertEquals( - array( - (object) array( - 'Field' => 'name', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => 'NULL', - 'Extra' => '', - ), - (object) array( - 'Field' => 'no_default', - 'Type' => 'varchar(20)', - 'Null' => 'NO', - 'Key' => '', - 'Default' => null, - 'Extra' => '', - ), - ), - $result - ); - } - - public function testCurrentTimestamp() { - // SELECT - $results = $this->assertQuery( - 'SELECT - current_timestamp AS t1, - CURRENT_TIMESTAMP AS t2, - current_timestamp() AS t3, - CURRENT_TIMESTAMP() AS t4' - ); - $this->assertIsArray( $results ); - $this->assertCount( 1, $results ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $results[0]->t1 ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $results[0]->t2 ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $results[0]->t3 ); - - // INSERT - $this->assertQuery( - "INSERT INTO _dates (option_name, option_value) VALUES ('first', CURRENT_TIMESTAMP())" - ); - $results = $this->assertQuery( 'SELECT option_value AS t FROM _dates' ); - $this->assertCount( 1, $results ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $results[0]->t ); - - // UPDATE - $this->assertQuery( 'UPDATE _dates SET option_value = NULL' ); - $results = $this->assertQuery( 'SELECT option_value AS t FROM _dates' ); - $this->assertCount( 1, $results ); - $this->assertEmpty( $results[0]->t ); - - $this->assertQuery( 'UPDATE _dates SET option_value = CURRENT_TIMESTAMP()' ); - $results = $this->assertQuery( 'SELECT option_value AS t FROM _dates' ); - $this->assertCount( 1, $results ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $results[0]->t ); - - // DELETE - // We can only assert that the query passes. It is not guaranteed that we'll actually - // delete the existing record, as the delete query could fall into a different second. - $this->assertQuery( 'DELETE FROM _dates WHERE option_value = CURRENT_TIMESTAMP()' ); - } - - public function testGroupByHaving() { - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - name varchar(20) - );' - ); - - $this->assertQuery( - "INSERT INTO _tmp_table VALUES ('a'), ('b'), ('b'), ('c'), ('c'), ('c')" - ); - - $result = $this->assertQuery( - 'SELECT name, COUNT(*) as count FROM _tmp_table GROUP BY name HAVING COUNT(*) > 1' - ); - $this->assertEquals( - array( - (object) array( - 'name' => 'b', - 'count' => '2', - ), - (object) array( - 'name' => 'c', - 'count' => '3', - ), - ), - $result - ); - } - - public function testHavingWithoutGroupBy() { - $this->assertQuery( - 'CREATE TABLE _tmp_table ( - name varchar(20) - );' - ); - - $this->assertQuery( - "INSERT INTO _tmp_table VALUES ('a'), ('b'), ('b'), ('c'), ('c'), ('c')" - ); - - // HAVING condition satisfied - $result = $this->assertQuery( - "SELECT 'T' FROM _tmp_table HAVING COUNT(*) > 1" - ); - $this->assertEquals( - array( - (object) array( - ':param0' => 'T', - ), - ), - $result - ); - - // HAVING condition not satisfied - $result = $this->assertQuery( - "SELECT 'T' FROM _tmp_table HAVING COUNT(*) > 100" - ); - $this->assertEquals( - array(), - $result - ); - - // DISTINCT ... HAVING, where only some results meet the HAVING condition - $result = $this->assertQuery( - 'SELECT DISTINCT name FROM _tmp_table HAVING COUNT(*) > 1' - ); - $this->assertEquals( - array( - (object) array( - 'name' => 'b', - ), - (object) array( - 'name' => 'c', - ), - ), - $result - ); - } - - /** - * @dataProvider mysqlVariablesToTest - */ - public function testSelectVariable( $variable_name ) { - // Make sure the query does not error - $this->assertQuery( "SELECT $variable_name;" ); - } - - public static function mysqlVariablesToTest() { - return array( - // NOTE: This list was derived from the variables used by the UpdraftPlus plugin. - // We will start here and plan to expand supported variables over time. - array( '@@character_set_client' ), - array( '@@character_set_results' ), - array( '@@collation_connection' ), - array( '@@GLOBAL.gtid_purged' ), - array( '@@GLOBAL.log_bin' ), - array( '@@GLOBAL.log_bin_trust_function_creators' ), - array( '@@GLOBAL.sql_mode' ), - array( '@@SESSION.max_allowed_packet' ), - array( '@@SESSION.sql_mode' ), - - // Intentionally mix letter casing to help demonstrate case-insensitivity - array( '@@cHarActer_Set_cLient' ), - array( '@@gLoBAL.gTiD_purGed' ), - array( '@@sEssIOn.sqL_moDe' ), - ); - } - - /** - * Test CREATE TABLE with DEFAULT (now()) - GitHub issue #300 - * Tests that DEFAULT with function calls in parentheses works correctly. - */ - public function testCreateTableWithDefaultNowFunction() { - // Test the exact SQL from the issue - $this->assertQuery( - 'CREATE TABLE `test_now_default` ( - `id` int NOT NULL, - `updated` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;' - ); - - // Verify the table was created successfully - $results = $this->assertQuery( 'DESCRIBE test_now_default;' ); - $this->assertCount( 2, $results ); - - // Verify the updated column has the correct properties - $updated_field = $results[1]; - $this->assertEquals( 'updated', $updated_field->Field ); - $this->assertEquals( 'timestamp', $updated_field->Type ); - $this->assertEquals( 'NO', $updated_field->Null ); - - // Insert a row to verify the default value works - $this->assertQuery( 'INSERT INTO test_now_default (id) VALUES (1)' ); - $result = $this->assertQuery( 'SELECT * FROM test_now_default WHERE id = 1' ); - $this->assertCount( 1, $result ); - - // Verify the updated timestamp was set (should match YYYY-MM-DD HH:MM:SS format) - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->updated ); - - // Test ON UPDATE trigger works - $this->assertQuery( 'UPDATE test_now_default SET id = 2 WHERE id = 1' ); - $result = $this->assertQuery( 'SELECT * FROM test_now_default WHERE id = 2' ); - $this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->updated ); - } -} diff --git a/tests/e2e/specs/query-monitor-plugin.test.js b/tests/e2e/specs/query-monitor-plugin.test.js index 64b13213..3a848753 100644 --- a/tests/e2e/specs/query-monitor-plugin.test.js +++ b/tests/e2e/specs/query-monitor-plugin.test.js @@ -12,22 +12,169 @@ test.describe( 'Query Monitor plugin', () => { expect( plugin.status ).toBe( 'inactive' ); } - test.beforeEach( async ( { requestUtils }) => { + test.beforeEach( async ( { requestUtils } ) => { await deactivateQueryMonitor( requestUtils ); } ); - test.afterEach( async ( { requestUtils }) => { + test.afterEach( async ( { requestUtils } ) => { await deactivateQueryMonitor( requestUtils ); } ); - test( 'should activate', async ( { admin, page } ) => { + test( 'should activate and show SQLite queries (QM 4.0+)', async ( { admin, page } ) => { // Activate the Query Monitor plugin on the plugins page. await admin.visitAdminPage( '/plugins.php' ); await page.getByLabel( 'Activate Query Monitor', { exact: true } ).click(); await page.getByText( 'Plugin activated.', { exact: true } ).waitFor(); + // Skip this test if QM 4.0+ is not active (no shadow DOM container). + const hasContainer = await page.locator( '#query-monitor-container' ).count(); + test.skip( hasContainer === 0, 'QM 4.0+ not detected' ); + + // Click on the Query Monitor admin bar item. + // QM 4.0 re-renders the admin bar with Preact — use the ab-item class. + await page.locator( '#wp-admin-bar-query-monitor > a.ab-item' ).click(); + + // Wait for the QM panel to render inside the shadow DOM. + const container = page.locator( '#query-monitor-container' ); + await expect( async () => { + const hasShadow = await container.evaluate( + ( el ) => el.shadowRoot !== null + ); + expect( hasShadow ).toBe( true ); + } ).toPass(); + + // Click on the Database Queries tab inside the shadow DOM. + // QM 4.0 renders nav items as