-
Notifications
You must be signed in to change notification settings - Fork 60
Add native Rust-based MySQL parser extension #381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
adamziel
wants to merge
40
commits into
trunk
Choose a base branch
from
codex/native-lazy-ast-facade
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
83001bc
Add lazy native parser node facade
adamziel c9e2eb6
Add lazy native parser node facade
adamziel dacfd78
Add Rust MySQL parser extension
adamziel d34c6ca
Speed up native MySQL parser materialization
adamziel b84cbac
Speed up native parser token streaming
adamziel e8aace5
Add lazy native AST facade
adamziel 85d71b2
Add native SQLite facade smoke test
adamziel 7416c2f
Run SQLite tests with native parser extension in CI
adamziel f7c4c40
Run WordPress PHPUnit with native parser extension
adamziel b570487
Fix native WordPress CI service mounts
adamziel 98cebfd
Fix native WordPress CI verifier command
adamziel 8aa3cc4
Fix native parser lifetime caches
adamziel ee0b788
Limit native parser PHP changes
adamziel a64a7ef
Cache native AST child materialization
adamziel d85da3e
Use native parser smoke checks before full materialization
adamziel 41b0fac
Instantiate native parser node subclass
adamziel 7b2099a
Rename native node helper to was_mutated()
adamziel 91d821e
Use instanceof check for native lexer fast path
adamziel 33b1fdc
Remove tmp-test-native dev scratch directory
adamziel d969d24
Update CI to match instanceof and removed tmp dir
adamziel 80d8a64
Move Rust extension to packages/php-ext-wp-mysql-parser
adamziel 09b9c1a
Cache native grammar on parser grammar object
adamziel 5275791
Cache native grammar on parser grammar object (#387)
adamziel 076b7e5
Speed up native AST materialization (#388)
adamziel 61a00fa
Preserve child wrapper identity in native AST facade
adamziel a8a4283
Fix code style and PHP 8.2 dynamic-property deprecation in identity test
adamziel 68524ef
Benchmark identity-cache cost against PR base in CI
adamziel 6bf1153
Fix baseline extension path in perf workflow
adamziel 6c94572
Speed up identity-cache hot path
adamziel 2c07dac
Add hit-heavy perf scenarios to compare cache vs baseline
adamziel 2faa18b
Preserve child wrapper identity in the native AST facade (#391)
adamziel b47b748
Move native AST identity cache into the Rust extension (#392)
adamziel 4f5c4a2
Merge native AST identity work into Rust parser branch
adamziel 6978176
Require native parser during WordPress PHPUnit job
adamziel 71a250a
Require native parser in extension PHPUnit suites
adamziel 2b595f3
Restore `instanceof WP_Parser` for the native parser (#393)
adamziel bb77258
Assert native parser delegate in extension test guards
adamziel b186e84
Fix native parser guard coding standards
adamziel e6d5b2c
Run native parser extension suite across PHP versions
adamziel 13f131b
Run SQLite integration matrix in parser extension CI
adamziel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| 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 | ||
|
|
||
| jobs: | ||
| extension-tests: | ||
| name: PHP ${{ matrix.php }} / ${{ matrix.coverage }} / ubuntu-latest | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| 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: Run native parser smoke tests | ||
| if: matrix.native | ||
| run: | | ||
| php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' | ||
| require "src/load.php"; | ||
| $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 ) ) { | ||
| fwrite( STDERR, "Native lexer is not available.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $tokens = $lexer->native_token_stream(); | ||
| $rules = include "src/mysql/mysql-grammar.php"; | ||
| $grammar = new WP_Parser_Grammar( $rules ); | ||
| $parser = new WP_MySQL_Parser( $grammar, $tokens ); | ||
| $parser_reflection = new ReflectionObject( $parser ); | ||
| if ( ! $parser_reflection->hasProperty( "native" ) ) { | ||
| fwrite( STDERR, "WP_MySQL_Parser did not select the native parser delegate.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $native_property = $parser_reflection->getProperty( "native" ); | ||
| $native_property->setAccessible( true ); | ||
| if ( ! ( $native_property->getValue( $parser ) instanceof WP_MySQL_Native_Parser ) ) { | ||
| fwrite( STDERR, "WP_MySQL_Parser did not select the native parser delegate.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $ast = $parser->parse(); | ||
| if ( ! $ast instanceof WP_MySQL_Native_Parser_Node || "query" !== $ast->rule_name ) { | ||
| fwrite( STDERR, "Native parser did not produce the expected query AST.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| ' | ||
| working-directory: packages/mysql-on-sqlite | ||
|
|
||
| - name: Verify SQLite driver selects the native parser path | ||
|
adamziel marked this conversation as resolved.
|
||
| if: matrix.native | ||
| run: | | ||
| php -d extension="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/debug/libwp_mysql_parser.so" -r ' | ||
| require "packages/mysql-on-sqlite/src/load.php"; | ||
| $lexer = new WP_MySQL_Lexer( "SELECT 1" ); | ||
| if ( ! ( $lexer instanceof WP_MySQL_Native_Lexer ) ) { | ||
| fwrite( STDERR, "Native lexer is not available.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $driver = new WP_PDO_MySQL_On_SQLite( "mysql-on-sqlite:path=:memory:;dbname=wp;" ); | ||
| $parser = $driver->create_parser( "SELECT 1" ); | ||
| $parser_reflection = new ReflectionObject( $parser ); | ||
| if ( ! $parser_reflection->hasProperty( "native" ) ) { | ||
| fwrite( STDERR, "SQLite driver did not create a native parser delegate.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $native_property = $parser_reflection->getProperty( "native" ); | ||
| $native_property->setAccessible( true ); | ||
| if ( ! ( $native_property->getValue( $parser ) instanceof WP_MySQL_Native_Parser ) ) { | ||
| fwrite( STDERR, "SQLite driver did not create a native parser delegate.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $parser->next_query(); | ||
| $ast = $parser->get_query_ast(); | ||
| if ( ! ( $ast instanceof WP_MySQL_Native_Parser_Node ) ) { | ||
| fwrite( STDERR, "SQLite driver did not return a native-backed AST.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $reflection = new ReflectionObject( $ast ); | ||
| if ( $reflection->hasProperty( "native_ast" ) || $reflection->hasProperty( "native_node_index" ) ) { | ||
| fwrite( STDERR, "Native wrapper still stores Rust AST handle properties.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $first = $ast->get_first_child_node(); | ||
| if ( ! ( $first instanceof WP_MySQL_Native_Parser_Node ) ) { | ||
| fwrite( STDERR, "Native wrapper did not return a native-backed child node.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| if ( $first !== $ast->get_first_child_node() ) { | ||
| fwrite( STDERR, "Native wrapper identity is not stable across reads.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| $synthetic = new WP_Parser_Node( 0, "synthetic" ); | ||
| $first->append_child( $synthetic ); | ||
| $same_first = $ast->get_first_child_node(); | ||
| if ( $same_first !== $first || ! in_array( $synthetic, $same_first->get_children(), true ) ) { | ||
| fwrite( STDERR, "Materialized native wrapper was lost from the parent cache.\n" ); | ||
| exit( 1 ); | ||
| } | ||
| ' | ||
|
|
||
| - name: Run 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 | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With these path filters, a future change that touches only
packages/php-ext-wp-mysql-parser/**will not run the parser extension workflow on push or pull request. Since the Rust crate lives there, extension build/test regressions can be merged without this CI running unless another watched path also changes.