Skip to content
Draft
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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ 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
composer run prepare-release <version> # Prepare a new release

# SQLite driver tests (under packages/mysql-on-sqlite)
cd packages/mysql-on-sqlite
Expand Down Expand Up @@ -70,7 +70,7 @@ Release is streamlined with a local preparation script and GitHub Actions:
```
The script will:
- Bump version numbers and generate a changelog from merged PRs.
- Create a `release/<version>` branch with a preparation commit.
- Create a `release/v<version>` branch with a preparation commit.
- Push the branch and create a PR.

2. **Review the PR.**
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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.
- [**SQLite driver**](packages/mysql-on-sqlite/) — 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.
Expand All @@ -35,7 +35,7 @@ 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
composer run prepare-release <version> # Prepare a new release

# SQLite driver tests (under packages/mysql-on-sqlite)
cd packages/mysql-on-sqlite
Expand Down Expand Up @@ -77,7 +77,7 @@ Release is streamlined with a local preparation script and GitHub Actions:
```
The script will:
- Bump version numbers and generate a changelog from merged PRs.
- Create a `release/<version>` branch with a preparation commit.
- Create a `release/v<version>` branch with a preparation commit.
- Push the branch and create a PR.

2. **Review the PR.**
Expand Down
4 changes: 2 additions & 2 deletions grammar-tools/MySQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -1902,7 +1902,7 @@ filterStringList:
;

filterWildDbTableString:
textStringNoLinebreak // sql_yacc.yy checks for the existance of at least one dot char in the string.
textStringNoLinebreak // sql_yacc.yy checks for the existence of at least one dot char in the string.
;

filterDbPairList:
Expand Down Expand Up @@ -3844,7 +3844,7 @@ dataType: // type in sql_yacc.yy
| type = NATIONAL_SYMBOL CHAR_SYMBOL VARYING_SYMBOL
| type = NCHAR_SYMBOL VARYING_SYMBOL
) fieldLength BINARY_SYMBOL?
/* @CHANGED: Moved "nchar fieldLength? BINARY_SYMBOL?" after othe nchar definitions to solve conflicts. */
/* @CHANGED: Moved "nchar fieldLength? BINARY_SYMBOL?" after the other nchar definitions to solve conflicts. */
| nchar fieldLength? BINARY_SYMBOL?
| type = VARBINARY_SYMBOL fieldLength
| type = YEAR_SYMBOL fieldLength? fieldOptions?
Expand Down
141 changes: 141 additions & 0 deletions packages/mysql-on-sqlite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# MySQL on SQLite

A **`PDO\Mysql` drop-in** for running MySQL-based PHP applications on SQLite.

## Overview

**MySQL on SQLite** is a pure-PHP database driver that exposes SQLite through
a `PDO\Mysql`-compatible API.

At a glance:

- **MySQL compatibility:** Broad coverage of MySQL syntax, semantics, types, and metadata.
- **`PDO\Mysql` drop-in:** Extensive PDO API coverage with MySQL behavior emulation.
- **Pure PHP:** No third-party runtime dependencies.
- **Lean runtime:** Small footprint, low overhead, and efficient query processing.
- **Extensive validation:** Comprehensive test suites covering real-world patterns.

## Usage

Load the package and create a connection using a `mysql-on-sqlite` DSN:

```php
// Use a PDO-like constructor.
$pdo = new WP_MySQL_On_SQLite(
'mysql-on-sqlite:path=/path/to/database.sqlite;dbname=app'
);

// Use PDO API to talk to the database as with PDO\Mysql.
$statement = $pdo->query( 'SELECT * FROM users' );
$users = $statement->fetchAll( PDO::FETCH_ASSOC );
```

Switching an existing `PDO\Mysql` application to MySQL on SQLite can be as
simple as:

```diff
-$pdo = new PDO\Mysql( 'mysql:host=localhost;dbname=app', $username, $password );
+$pdo = new WP_MySQL_On_SQLite( 'mysql-on-sqlite:path=database.sqlite;dbname=app' );
```

## Configuration

The driver is configured through the standard PDO API, closely mirroring
`PDO\Mysql` while providing additional SQLite-specific options.

### DSN

The DSN has the following format:

```text
mysql-on-sqlite:path=<sqlite-path>;dbname=<mysql-database-name>
```

| Field | Description | Default |
| --- | --- | --- |
| `path` | SQLite database path or `:memory:` | `:memory:` |
| `dbname` | Logical MySQL database name | `sqlite_database` |

Use `;;` to include a literal semicolon in either value.

### PDO options

The constructor follows the PDO signature and accepts most common attributes
supported by `PDO\Mysql` in its fourth argument. Its `username` and `password`
arguments are accepted for compatibility and ignored. Driver-specific
`PDO\Mysql` attributes are currently not supported.

#### Driver options

The fourth argument accepts additional options for selecting the emulated MySQL
version and configuring the SQLite connection:

| Option | Description | Default |
| --- | --- | --- |
| `mysql_version` | MySQL version to emulate, represented as an integer | `80038` |
| `pdo` | Existing PDO SQLite connection | A new connection for `path` |
| `journal_mode` | SQLite journal mode | `WAL` |
| `synchronous` | SQLite synchronous setting | `NORMAL` in WAL mode; otherwise the SQLite default |

## Compatibility

The driver covers extensive MySQL functionality behind a `PDO\Mysql`-compatible
API.

### MySQL

Supported areas include:

- **Queries:** Joins, subqueries, common table expressions, unions, grouping,
`HAVING`, ordering, limits, and index hints.
- **Data manipulation:** `INSERT`, `UPDATE`, `DELETE`, and `REPLACE`, including
MySQL-specific forms such as `INSERT IGNORE`, `ON DUPLICATE KEY UPDATE`, and
joined updates.
- **Schema definition:** Creating, altering, dropping, and truncating tables,
including temporary tables and complex column definitions.
- **Indexes and constraints:** Index definitions and primary, unique,
foreign-key, and check constraints.
- **Data types:** Numeric, character, binary, temporal, `ENUM`, `SET`, `JSON`,
and spatial type declarations, plus character sets and collations.
- **Value semantics:** MySQL-style casting, coercion, defaults, auto-increment
values, and date and time behavior.
- **Expressions and functions:** MySQL operators and string, numeric, date/time,
aggregate, regular-expression, conversion, and utility functions.
- **Metadata:** `INFORMATION_SCHEMA`, `SHOW`, `DESCRIBE`, and database selection
with `USE`.
- **Session state:** SQL modes and system and user variables.
- **Transactions and locking:** Transactions, savepoints, table locks, and
row-locking clauses.

### PDO

The driver broadly supports the PDO API and aims for full `PDO\Mysql`
compatibility. Some APIs, such as prepared statements, parameter binding, and
multi-statement queries, are not yet supported.

## Development

Install the development dependencies and run the tests from this directory:

```bash
composer install
composer run test
```

Run an individual test file or test method with:

```bash
composer run test tests/SomeTest.php
composer run test -- --filter testName
```

## Requirements

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

## License

MySQL on SQLite is licensed under the
[GNU General Public License v2 or later](../../LICENSE).
2 changes: 1 addition & 1 deletion packages/mysql-on-sqlite/src/parser/class-wp-parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private function parse_recursive( $rule_id ) {
return false;
}

// Bale out from processing the current branch if none of its rules can
// Bail out from processing the current branch if none of its rules can
// possibly match the current token.
if ( isset( $this->grammar->lookahead_is_match_possible[ $rule_id ] ) ) {
$token_id = $this->tokens[ $this->position ]->id;
Expand Down
2 changes: 0 additions & 2 deletions packages/mysql-on-sqlite/src/php-polyfills.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
* Implementation follows the Symfony polyfill-php80 package.
*
* @see https://github.com/symfony/polyfill-php80
*
* @package wp-sqlite-integration
*/

if ( ! function_exists( 'str_starts_with' ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
*/

/**
* Exception raised by the MySQL-on-SQLite driver.
*
* Provides PDO-style error information and access to the driver that originated
* the exception.
*/
class WP_MySQL_On_SQLite_Exception extends PDOException {
/**
* The MySQL-on-SQLite driver that originated the exception.
Expand Down Expand Up @@ -35,6 +41,11 @@ public function __construct(
$this->errorInfo = $error_info ?? $this->create_error_info( $message, $code, $previous );
}

/**
* Get the MySQL-on-SQLite driver that originated the exception.
*
* @return WP_MySQL_On_SQLite The originating driver.
*/
public function get_driver(): WP_MySQL_On_SQLite {
return $this->driver;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public function getColumnMeta( int $column ) {
* - PDO::FETCH_KEY_PAIR: key-value pair
* - PDO::FETCH_OBJ: object (stdClass)
* - PDO::FETCH_CLASS: object (custom class) [1-2 extra args]
* - PDO::FETCH_INTO: update an exisisting object, can't be used with fetchAll() [1 extra arg]
* - PDO::FETCH_INTO: update an existing object, can't be used with fetchAll() [1 extra arg]
* - PDO::FETCH_LAZY: lazy fetch via PDORow, can't be used with fetchAll()
* - PDO::FETCH_BOUND: bind values to PHP variables, can't be used with fetchAll()
* - PDO::FETCH_FUNC: custom function, only works with fetchAll(), can't be default [1 extra arg]
Expand Down Expand Up @@ -391,7 +391,7 @@ public function bindValue( $param, $value, $type = PDO::PARAM_STR ): bool {
/**
* Dump information about the statement.
*
* Dupms the SQL query and parameters information.
* Dumps the SQL query and parameter information.
*
* @return bool|null Returns null, or false on failure.
*/
Expand Down
10 changes: 4 additions & 6 deletions packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,7 @@ function ( $key ) {
* with SQLite versions < 3.37.0 when "PRAGMA writable_schema" is
* set to "ON", which also enables error-tolerant schema parsing.
*
* This is an unsafe opt-in feature for special back compatibility
* This is an unsafe opt-in feature for special backward compatibility
* use cases, as it can corrupt the database by allowing incorrect
* types into STRICT tables. Additionally, depending on the legacy
* SQLite version used, there is no guarantee that all features of
Expand Down Expand Up @@ -1440,8 +1440,6 @@ public function getAttribute( $attribute ) {
* Exposes the SQLite PDO for advanced use, bypassing MySQL emulation.
* Do not retain it across reconnections or modify driver-owned state.
*
* @since 3.0.0
*
* @return PDO The underlying SQLite PDO instance.
*/
public function get_sqlite_pdo(): PDO {
Expand Down Expand Up @@ -2340,7 +2338,7 @@ private function execute_insert_or_replace_statement( WP_Parser_Node $node ): vo
* ON CONFLICT clause differently, and at this stage, we only
* save the translated update list to a variable.
*
* See bellow at "Handle ON CONFLICT clause for SQLite < 3.35.0".
* See below at "Handle ON CONFLICT clause for SQLite < 3.35.0".
*/
$sqlite_version = $this->get_sqlite_version();
if ( version_compare( $sqlite_version, '3.35.0', '<' ) ) {
Expand Down Expand Up @@ -3914,7 +3912,7 @@ private function execute_set_system_variable_statement(
/*
* Handle ON/OFF values. They are accepted as both strings and keywords.
*
* @TODO: This is actually variable-specific and depends on the its type.
* @TODO: This is actually variable-specific and depends on its type.
* For example:
* SET autocommit = OFF; SELECT @@autocommit; -> 0
* SET autocommit = false; SELECT @@autocommit; -> 0
Expand Down Expand Up @@ -4821,7 +4819,7 @@ private function translate_simple_expr_body( WP_Parser_Node $node ): string {
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.
// TODO: Consider rejecting UTF-8-incompatible charsets.
return $this->translate( $expr );
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function __construct( array $options ) {
$this->pdo = $options['pdo'];
} else {
if ( ! isset( $options['path'] ) || ! is_string( $options['path'] ) ) {
throw new InvalidArgumentException( 'Option "path" is required when "connection" is not provided.' );
throw new InvalidArgumentException( 'Option "path" is required when "pdo" is not provided.' );
}
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class;
$pdo_options = $options['pdo_options'] ?? array();
Expand Down Expand Up @@ -148,7 +148,7 @@ public function __construct( array $options ) {
*
* The synchronous=NORMAL setting provides the best balance between
* performance and safety for most applications running in WAL mode.
* You lose durability across power lose with synchronous NORMAL in WAL
* You lose durability across power loss with synchronous NORMAL in WAL
* mode, but that is not important for most applications. Transactions
* are still atomic, consistent, and isolated, which are the most
* important characteristics in most use cases.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/

/**
* For back compatibility with dependencies that use their own loader scripts
* For backward compatibility with dependencies that use their own loader scripts
* (e.g., WP CLI SQLite Command), ensure the PDO-based classes are loaded.
*/
require_once __DIR__ . '/class-wp-mysql-on-sqlite.php';
Expand Down Expand Up @@ -261,7 +261,7 @@ public function beginTransaction(): void {
}

/**
* A temporary alias for back compatibility.
* A temporary alias for backward compatibility.
*
* @see self::beginTransaction()
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
*/

/**
* SQLite information schema recconstructor for MySQL.
* SQLite information schema reconstructor for MySQL.
*
* This class checks and reconstructs the MySQL INFORMATION_SCHEMA data in SQLite
* when it becomes out of sync with the actual SQLite database schema.
*
* Currently, it reconstructs schema infromation for missing tables, and removes
* Currently, it reconstructs schema information for missing tables, and removes
* stale data for tables that no longer exist. When used with WordPress, it uses
* the "wp_get_db_schema()" function to reconstruct WordPress table information.
*
Expand Down Expand Up @@ -768,7 +768,7 @@ private function get_mysql_column_type( string $column_type ): string {
* See WP_MySQL_On_SQLite::quote_mysql_utf8_string_literal().
*
* TODO: This is a copy of WP_MySQL_On_SQLite::quote_mysql_utf8_string_literal().
* We may consider extracing it to reusable MySQL helpers.
* We may consider extracting it to reusable MySQL helpers.
*
* @param string $utf8_literal The UTF-8 string literal to escape.
* @return string The escaped string literal.
Expand Down
Loading
Loading