Skip to content
Open
Show file tree
Hide file tree
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 Apr 30, 2026
c9e2eb6
Add lazy native parser node facade
adamziel Apr 30, 2026
dacfd78
Add Rust MySQL parser extension
adamziel Apr 27, 2026
d34c6ca
Speed up native MySQL parser materialization
adamziel Apr 28, 2026
b84cbac
Speed up native parser token streaming
adamziel Apr 28, 2026
e8aace5
Add lazy native AST facade
adamziel Apr 28, 2026
85d71b2
Add native SQLite facade smoke test
adamziel Apr 28, 2026
7416c2f
Run SQLite tests with native parser extension in CI
adamziel Apr 28, 2026
f7c4c40
Run WordPress PHPUnit with native parser extension
adamziel Apr 28, 2026
b570487
Fix native WordPress CI service mounts
adamziel Apr 28, 2026
98cebfd
Fix native WordPress CI verifier command
adamziel Apr 28, 2026
8aa3cc4
Fix native parser lifetime caches
adamziel Apr 28, 2026
ee0b788
Limit native parser PHP changes
adamziel Apr 28, 2026
a64a7ef
Cache native AST child materialization
adamziel Apr 29, 2026
d85da3e
Use native parser smoke checks before full materialization
adamziel Apr 29, 2026
41b0fac
Instantiate native parser node subclass
adamziel Apr 29, 2026
7b2099a
Rename native node helper to was_mutated()
adamziel Apr 30, 2026
91d821e
Use instanceof check for native lexer fast path
adamziel Apr 30, 2026
33b1fdc
Remove tmp-test-native dev scratch directory
adamziel Apr 30, 2026
d969d24
Update CI to match instanceof and removed tmp dir
adamziel Apr 30, 2026
80d8a64
Move Rust extension to packages/php-ext-wp-mysql-parser
adamziel Apr 30, 2026
09b9c1a
Cache native grammar on parser grammar object
adamziel Apr 28, 2026
5275791
Cache native grammar on parser grammar object (#387)
adamziel Apr 30, 2026
076b7e5
Speed up native AST materialization (#388)
adamziel Apr 30, 2026
61a00fa
Preserve child wrapper identity in native AST facade
adamziel Apr 30, 2026
a8a4283
Fix code style and PHP 8.2 dynamic-property deprecation in identity test
adamziel Apr 30, 2026
68524ef
Benchmark identity-cache cost against PR base in CI
adamziel Apr 30, 2026
6bf1153
Fix baseline extension path in perf workflow
adamziel Apr 30, 2026
6c94572
Speed up identity-cache hot path
adamziel Apr 30, 2026
2c07dac
Add hit-heavy perf scenarios to compare cache vs baseline
adamziel Apr 30, 2026
2faa18b
Preserve child wrapper identity in the native AST facade (#391)
adamziel May 1, 2026
b47b748
Move native AST identity cache into the Rust extension (#392)
adamziel May 1, 2026
4f5c4a2
Merge native AST identity work into Rust parser branch
adamziel May 1, 2026
6978176
Require native parser during WordPress PHPUnit job
adamziel May 1, 2026
71a250a
Require native parser in extension PHPUnit suites
adamziel May 1, 2026
2b595f3
Restore `instanceof WP_Parser` for the native parser (#393)
adamziel May 1, 2026
bb77258
Assert native parser delegate in extension test guards
adamziel May 1, 2026
b186e84
Fix native parser guard coding standards
adamziel May 1, 2026
e6d5b2c
Run native parser extension suite across PHP versions
adamziel May 1, 2026
13f131b
Run SQLite integration matrix in parser extension CI
adamziel May 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions .github/workflows/mysql-parser-extension-tests.yml
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/**'
Copy link
Copy Markdown
Member

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.

- '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
Comment thread
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
Loading
Loading