Skip to content

Clean up native parser follow-ups #196

Clean up native parser follow-ups

Clean up native parser follow-ups #196

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
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