Skip to content
This repository was archived by the owner on Sep 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/cs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
php-version: '8.1'
coverage: none
tools: cs2pr

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/d1-backend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '7.2', '8.1', '8.4' ]
php: [ '8.0', '8.1', '8.4' ]
steps:
- uses: actions/checkout@v4

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mysql-parser-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '7.2', '8.5' ]
php: [ '8.0', '8.5' ]

steps:
- name: Checkout repository
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mysql-proxy-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.2'
php-version: '8.0'
coverage: none

- name: Configure monorepo package repository
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/phpunit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,8 @@ jobs:
matrix:
include:
# Pure-PHP parser, across the supported PHP versions, each pinned to a
# representative SQLite version spanning the supported range.
- { php: '7.2', sqlite: '3.27.0', extension: false } # minimum with WP_SQLITE_UNSAFE_ENABLE_UNSUPPORTED_VERSIONS
- { php: '7.3', sqlite: '3.31.1', extension: false } # Ubuntu 20.04 LTS
- { php: '7.4', sqlite: '3.34.1', extension: false } # Debian 11 (Bullseye)
# representative SQLite version spanning the supported range. PHP 8.0
# is the floor: this fork does not carry the 7.x series.
- { php: '8.0', sqlite: '3.37.0', extension: false } # minimum supported version (STRICT tables)
- { php: '8.1', sqlite: '3.40.1', extension: false } # Debian 12 (Bookworm)
- { php: '8.2', sqlite: '3.45.1', extension: false } # Ubuntu 24.04 LTS
Expand Down
89 changes: 89 additions & 0 deletions .github/workflows/turso-backend-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Turso Backend Tests

on:
push:
branches:
- trunk
- d1-support
- turso-support
paths:
- '.github/workflows/turso-backend-tests.yml'
- 'packages/mysql-on-sqlite/**'
- 'packages/turso-snapshot-publisher/**'
pull_request:
paths:
- '.github/workflows/turso-backend-tests.yml'
- 'packages/mysql-on-sqlite/**'
- 'packages/turso-snapshot-publisher/**'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions: {}

jobs:
# The driver test suites running against the Turso connection backend
# (over the fake Turso transport; no server needed).
driver-suites:
name: Driver suites on Turso backend / PHP ${{ matrix.php }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
strategy:
fail-fast: false
matrix:
php: [ '8.0', '8.1', '8.4' ]
steps:
- uses: actions/checkout@v4

- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: pdo, pdo_sqlite, curl
coverage: none

- name: Install dependencies
working-directory: packages/mysql-on-sqlite
run: composer install --prefer-dist --no-progress

- name: Run the driver suites against the Turso backend
working-directory: packages/mysql-on-sqlite
env:
WP_SQLITE_TEST_BACKEND: turso
run: ./vendor/bin/phpunit tests/WP_MySQL_On_SQLite_Tests.php tests/WP_MySQL_On_SQLite_Query_Tests.php tests/WP_MySQL_On_SQLite_Metadata_Tests.php tests/WP_MySQL_On_SQLite_PDO_API_Tests.php

# The snapshot publisher: lint and build. It is exercised against a real
# server by src/turso/verify-against-server.php, which needs a tursodb; that
# stays a local check until one is packaged for CI.
publisher:
name: Snapshot publisher
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- uses: Swatinem/rust-cache@v2
with:
workspaces: packages/turso-snapshot-publisher

- name: Lint
working-directory: packages/turso-snapshot-publisher
run: cargo fmt --check && cargo clippy --no-deps -- -D warnings

- name: Build
working-directory: packages/turso-snapshot-publisher
run: cargo build --release

- name: Smoke test
working-directory: packages/turso-snapshot-publisher
run: ./target/release/turso-snapshot-publisher --help
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ composer.lock
/packages/d1-proxy-worker/node_modules/
/packages/d1-proxy-worker/.wrangler/
/packages/php-ext-wp-d1-client/target/
/packages/turso-snapshot-publisher/target/
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The monorepo packages are placed under the `packages` directory.
The WordPress plugin links the MySQL on SQLite package using a symlink. The build
script replaces the symlink with a copy of the package for release.

The codebase is pure PHP with zero dependencies. It supports PHP 7.2 through 8.5,
The codebase is pure PHP with zero dependencies. It supports PHP 8.0 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).

Expand Down Expand Up @@ -177,7 +177,7 @@ In particular:
- **PDO API:** MySQL on SQLite must match the PDO MySQL driver 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.
- **PHP version support:** All PHP versions starting from **PHP 8.0** must be supported. This fork does not carry the 7.x series; the `PHP_VERSION_ID < 80000` shims that remain are inherited and may be removed.
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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The monorepo packages are placed under the `packages` directory.
The WordPress plugin links the MySQL on SQLite package using a symlink. The build
script replaces the symlink with a copy of the package for release.

The codebase is pure PHP with zero dependencies. It supports PHP 7.2 through 8.5,
The codebase is pure PHP with zero dependencies. It supports PHP 8.0 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).

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"issues": "https://github.com/wordpress/sqlite-database-integration/issues"
},
"require": {
"php": ">=7.2",
"php": ">=8.0",
"ext-pdo": "*",
"ext-pdo_sqlite": "*"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/mysql-on-sqlite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ composer run test -- --filter testName

## Requirements

- **PHP:** 7.2+
- **PHP:** 8.0+
- **PHP extensions:** `pdo`, `pdo_sqlite`
- **SQLite:** 3.37.0+

Expand Down
2 changes: 1 addition & 1 deletion packages/mysql-on-sqlite/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"source": "https://github.com/wordpress/sqlite-database-integration"
},
"require": {
"php": ">=7.2",
"php": ">=8.0",
"ext-pdo": "*",
"ext-pdo_sqlite": "*"
},
Expand Down
26 changes: 24 additions & 2 deletions packages/mysql-on-sqlite/src/d1/class-wp-sqlite-d1-connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -512,11 +512,23 @@ private function maybe_intercept_query( string $sql ): ?PDOStatement {
return $this->create_empty_statement();
}

// Attempt other PRAGMA statements, degrading to an empty result.
/*
* Attempt other PRAGMA statements, degrading to an empty result when
* D1 will not run them -- but not when the statement names something
* that does not exist. That is a real error the caller has to see:
* "CHECK TABLE missing" has to report a missing table rather than a
* clean bill of health.
*/
try {
$result = $this->transport->query( $sql );
return $this->create_statement( $result );
} catch ( WP_SQLite_D1_Exception $e ) {
// The "errorInfo" property name is defined by PDOException.
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$message = $e->errorInfo[2] ?? $e->getMessage();
if ( 1 === preg_match( '/\bno such (?:table|column|index)\b/i', (string) $message ) ) {
throw $e;
}
return $this->create_empty_statement();
}
}
Expand Down Expand Up @@ -547,7 +559,17 @@ private function is_schema_information_read( string $sql ): bool {
if ( 1 !== preg_match( '/^\s*SELECT\b/i', $sql ) ) {
return false;
}
return false !== stripos( $sql, '_wp_sqlite_' ) || false !== stripos( $sql, 'sqlite_master' );
if ( false === stripos( $sql, '_wp_sqlite_' ) && false === stripos( $sql, 'sqlite_master' ) ) {
return false;
}

/*
* Some information schema reads join "sqlite_sequence" to report the next
* AUTO_INCREMENT value. That depends on row data, not on the schema: a
* plain INSERT moves it without touching anything this cache invalidates
* on. Such a read has to go to the database every time.
*/
return false === stripos( $sql, 'sqlite_sequence' );
}

/**
Expand Down
43 changes: 30 additions & 13 deletions packages/mysql-on-sqlite/src/d1/class-wp-sqlite-d1-exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,22 @@ public static function from_proxy_error( ?string $error_code, string $message, ?

if ( null !== $error_code && 0 === strpos( $error_code, 'SQLITE_CONSTRAINT' ) ) {
// SQLite error code 19: SQLITE_CONSTRAINT.
$sqlstate = '23000';
$message = 'Integrity constraint violation: 19 ' . $message;
$sqlstate = '23000';
$driver_code = 19;
$prefix = 'Integrity constraint violation: 19 ';
} elseif ( 'SQLITE_BUSY' === $error_code || 'SQLITE_LOCKED' === $error_code ) {
// SQLite error codes 5 and 6: SQLITE_BUSY and SQLITE_LOCKED.
$sqlstate = 'HY000';
$message = 'General error: 5 ' . $message;
$sqlstate = 'HY000';
$driver_code = 5;
$prefix = 'General error: 5 ';
} else {
// SQLite error code 1: SQLITE_ERROR (also used as a fallback).
$sqlstate = 'HY000';
$message = 'General error: 1 ' . $message;
$sqlstate = 'HY000';
$driver_code = 1;
$prefix = 'General error: 1 ';
}

return self::create( $sqlstate, $message );
return self::create( $sqlstate, $prefix . $message, $driver_code, $message );
}

/**
Expand All @@ -62,18 +65,32 @@ public static function from_proxy_error( ?string $error_code, string $message, ?
* @return self
*/
public static function from_transport_failure( string $message, ?Throwable $previous = null ): self {
return self::create( '08006', $message, $previous );
return self::create( '08006', $message, null, $message, $previous );
}

/**
* Create an exception with the PDO error shape.
*
* @param string $sqlstate The SQLSTATE error code.
* @param string $message The error message.
* @param Throwable|null $previous The previous exception, if any.
* PDO splits the two: getMessage() carries the SQLSTATE and the driver's
* decoration, while errorInfo carries the driver's own code and its
* *undecorated* message. The driver matches on errorInfo to recognise
* SQLite errors and give them their MySQL identity, so the raw message has
* to survive there.
*
* @param string $sqlstate The SQLSTATE error code.
* @param string $message The decorated message, as PDO renders it.
* @param int|null $driver_code The SQLite error code, if known.
* @param string|null $driver_message The undecorated driver message.
* @param Throwable|null $previous The previous exception, if any.
* @return self
*/
private static function create( string $sqlstate, string $message, ?Throwable $previous = null ): self {
private static function create(
string $sqlstate,
string $message,
?int $driver_code = null,
?string $driver_message = null,
?Throwable $previous = null
): self {
$exception = new self(
sprintf( 'SQLSTATE[%s]: %s', $sqlstate, $message ),
0,
Expand All @@ -86,7 +103,7 @@ private static function create( string $sqlstate, string $message, ?Throwable $p

// The "errorInfo" property name is defined by PDOException.
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$exception->errorInfo = array( $sqlstate, null, $message );
$exception->errorInfo = array( $sqlstate, $driver_code, $driver_message ?? $message );
return $exception;
}
}
Loading
Loading