Skip to content

Sync with upstream trunk (2 commits) - #10

Open
batonac wants to merge 8 commits into
d1-supportfrom
upstream-sync
Open

Sync with upstream trunk (2 commits)#10
batonac wants to merge 8 commits into
d1-supportfrom
upstream-sync

Conversation

@batonac

@batonac batonac commented Sep 2, 2026

Copy link
Copy Markdown
Member

Automated sync of WordPress/sqlite-database-integration@trunk into d1-support.

This pull request tracks every upstream commit that has not landed on d1-support yet.
It is updated in place as upstream moves, and merges itself once the merge is clean and
every check is green. While it is open, it needs a person.

Upstream commits (2)


Branch shape: merged. Maintained by upstream-sync.yml. Closing this pull request is not permanent — the next scheduled run reopens it. Disable the workflow to stop the sync.

## What?

Align SQL savepoint handling with MySQL semantics.

A standalone `SAVEPOINT` in the default autocommit mode no longer opens
an SQLite transaction. It is discarded immediately, so following writes
use the normal `BEGIN IMMEDIATE` wrapper and commit independently.

Inside explicit transactions, track active savepoint names to emulate
MySQL behavior for nested savepoints, reused and case-insensitive names,
`ROLLBACK TO`, `RELEASE`, and missing-savepoint error 1305.
Missing-savepoint errors leave the surrounding transaction active.

Add regression coverage across the MySQL-on-SQLite, concurrency, PDO
API, and WordPress integration test suites.

## Why?

SQLite treats a standalone `SAVEPOINT` as opening a transaction, but
MySQL does not. Passing it through to SQLite created a hidden
transaction on PHP versions before 8.4. Subsequent writes then either
attempted a nested `BEGIN IMMEDIATE` or bypassed the normal
write-locking path, depending on how the transaction state was tracked.

The original Data Machine reproduction relied on SQLite-only behavior:
issuing a bare `SAVEPOINT`, performing a write, and committing with
`RELEASE SAVEPOINT`. Real MySQL and MariaDB discard the standalone
savepoint, so the later release reports error 1305. That downstream
issue is tracked in Extra-Chill/data-machine#3436.

> [!NOTE]
> Consumers that relied on a bare `SAVEPOINT` opening a transaction on
SQLite must open an explicit transaction instead, matching MySQL
behavior.

Fixes WordPress#495

---------

Co-authored-by: Jan Jakeš <jan@jakes.pro>
@batonac batonac added upstream-sync Automated sync with the WordPress upstream upstream-conflict Upstream sync needs a manual merge labels Sep 2, 2026
…keys (WordPress#500)

## Summary

`SHOW CREATE TABLE` reconstructs the `PRIMARY KEY` clause from the
information schema, but unlike the branch that handles all other
indexes, it emitted only the quoted column names. Index prefix lengths
(`SUB_PART`) and descending order (`COLLATION = 'D'`) were dropped, so a
table created with `PRIMARY KEY (session_id(100))` on a `MEDIUMTEXT`
column came back as `PRIMARY KEY (session_id)`.

The practical impact: exports built on top of `SHOW CREATE TABLE` (for
example `wp sqlite export`, which WordPress Studio uses for its push
feature) produce a dump that MySQL rejects with `ERROR 1170 (42000):
BLOB/TEXT column used in key specification without a key length`. The
prefix is recorded correctly in
`_wp_sqlite_mysql_information_schema_statistics` (`SUB_PART = 100`); the
bug was emission only. Discovered through Automattic/studio#4739.

Fixes WordPress#501

## Fix

The column formatting closure that already handled `SUB_PART` and `DESC`
for regular keys is hoisted and shared by the `PRIMARY KEY` branch, so
both paths emit identical column definitions.

## Testing

- New regression test: a `PRIMARY KEY (session_id(100))` on `MEDIUMTEXT`
round-trips through `SHOW CREATE TABLE` with its prefix, a composite key
`PRIMARY KEY (a, b(50))` keeps the prefix only where defined, and
`PRIMARY KEY (a DESC, b)` keeps its descending key part. The test fails
on the previous code.
- Verified against a real MySQL 8 server: its `SHOW CREATE TABLE` emits
`PRIMARY KEY (`session_id`(100))` and `PRIMARY KEY (`a` DESC,`b`)` for
the same tables, matching the fixed output; the previous output fails to
import there with `ERROR 1170`, the fixed output imports cleanly.
- Full `mysql-on-sqlite` unit suite: 875 tests, 1428750 assertions, no
failures (the same 17 skipped and 2 incomplete as on trunk, PHP 8.5).
- `composer run check-cs` is clean on both changed files.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Corrected `SHOW CREATE TABLE` output to preserve primary-key index
prefix lengths for single-column and composite keys.
- Ensured descending key parts and column definitions are represented
accurately in generated table-creation statements.
- Improved consistency between primary-key and secondary-index
formatting.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@batonac batonac changed the title Sync with upstream trunk (1 commit) Sync with upstream trunk (2 commits) Sep 7, 2026
## Summary

This PR fixes lexer edge cases and related string handling:

- **String values:** Preserve binary and single-byte charset payloads
and correctly decode escaped newlines.
- **Function names:** Exclude trailing whitespace from function-name
token ranges under `IGNORE_SPACE`.
- **Version comments:** Support six-digit versions from MySQL 8.1,
preserve five-digit fallback parsing and unversioned numeric content,
and recognize vertical tabs as whitespace.
- **WordPress validation:** Mirror the parent `wpdb::query()` charset
checks, retaining its binary-data and prevalidated-query exemptions.
- **`LIKE BINARY` patterns:** Preserve raw bytes instead of producing
`NULL` during GLOB conversion, and handle escaped newlines.

The lexer fixes cover both PHP packages and the native extension.

## Why

The token decoder's UTF-8 regex modifier caused an accidental
`TypeError` for legitimate binary data as well as malformed text.
Decoding must preserve bytes; charset enforcement needs column types and
SQL mode. Mirroring WordPress's checks restores its expected validation
behavior.

Regression tests cover decoding, token ranges, version comments, and
pattern conversion. CI expectations remove the `test_strip_invalid_text`
cases fixed by decoding and the two invalid-query tests fixed by
WordPress validation.

## Remaining limitations

- Direct MySQL-on-SQLite writes can still accept invalid text under
strict mode; full charset enforcement remains driver work. WordPress's
checks retain their exemptions, including tables containing binary
columns.
- SQLite GLOB can still treat distinct invalid UTF-8 sequences as equal.
The `LIKE BINARY` fix preserves pattern bytes but does not provide fully
byte-correct matching.
- Legacy multibyte charsets such as Big5 and GBK remain unsupported by
the decoder.
@batonac batonac changed the title Sync with upstream trunk (2 commits) Sync with upstream trunk (3 commits) Sep 9, 2026
batonac and others added 4 commits September 9, 2026 14:38
Brings in WordPress#496 (MySQL savepoint semantics), WordPress#500 (index prefix lengths in
SHOW CREATE TABLE) and WordPress#505 (lexer edge cases and string handling).

The one conflict is in the engine's transaction and savepoint handling: WordPress#496
reintroduced direct connection->query('BEGIN IMMEDIATE'/'COMMIT'/'ROLLBACK')
plus its own $in_transaction and $savepoint_names tracking, while this branch
routes transaction control through WP_SQLite_Connection_Interface so that remote
backends can implement it themselves.

Resolved by keeping upstream's semantics and this branch's dispatch: the new
savepoint-name bookkeeping (MySQL replaces a savepoint on name reuse where
SQLite shadows it, RELEASE and ROLLBACK TO drop the savepoints created after the
named one, and an unknown name raises the new exception) is preserved, but the
statements go through begin_transaction()/commit()/rollback()/savepoint()/
release_savepoint()/rollback_to_savepoint(). Upstream's engine-level
$in_transaction polyfill is dropped as dead state, because inTransaction() on
this branch already delegates to the connection.

Verified: packages/mysql-on-sqlite 1080 tests, 1429272 assertions, no failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sync_column_key_info() assigned COLUMN_KEY and IS_NULLABLE together as a row
value fed by a correlated aggregate subquery. On SQLite that is safe: an
aggregate with no GROUP BY always yields exactly one row, so the CASE ELSE
branches always supply a value.

Turso does not always yield that row. When the row being updated sorts before a
row that does have a matching statistics entry, the subquery produces no row,
both columns are set to NULL, and the NOT NULL constraint on IS_NULLABLE fails:

    NOT NULL constraint failed: _wp_sqlite_mysql_information_schema_columns.IS_NULLABLE

Reproduced against Turso 0.8.0-pre.7 with no PHP involved: two rows in a STRICT
table, only the later one indexed, and the same UPDATE. Whether it fails depends
purely on the sort order of the stored COLUMN_NAME values. WordPress hits it on
9 of the 12 core tables, because wp_users.comment_id-style columns sort before
the indexed primary key column -- so CREATE TABLE fails and wp_install() cannot
complete.

Assign the two columns separately and wrap each subquery in COALESCE(), falling
back to the values the ELSE branches would have produced for a column with no
index entries. Verified to produce identical results to the row-value form on
real SQLite, and to fix the failure on Turso.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary

Make **schema reconstruction independent of
`PDO::ATTR_STRINGIFY_FETCHES`**. Interpret SQLite's primary-key,
nullability, and uniqueness flags as integers so reconstruction
preserves auto-increment columns, `NOT NULL`, defaults, and unique
indexes in either fetch mode.

Add a regression test that creates an existing SQLite table before
driver initialization and verifies its reconstructed column and index
metadata with string fetching enabled and disabled.

## Why

Reconstruction compared SQLite's numeric flags strictly against strings.
Since connection initialization stopped forcing string results in WordPress#291,
reconstruction can receive integers before WordPress enables string
fetching. This could mark every column in an auto-increment table as
auto-incrementing, causing a later `ALTER TABLE` to fail with "has more
than one primary key."

Related to WordPress#422.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved compatibility when SQLite returns column and index metadata
as either strings or integers.
* Information schema reconstruction now consistently handles numeric
metadata values regardless of fetch settings or PHP version.
* Schema and index details are reconstructed more reliably across
different SQLite metadata formats.

* **Tests**
* Added coverage for schema reconstruction with stringified and native
SQLite fetch values.
* Verified reconstructed column metadata and index statistics across
supported configurations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Release `3.0.2`

Version bump and changelog update for release `3.0.2`.

**Changelog draft:**
* Fix schema reconstruction with native numeric results
([WordPress#506](WordPress#506))
* Fix lexer edge cases and string handling
([WordPress#505](WordPress#505))
* Preserve index prefix lengths and order in SHOW CREATE TABLE primary
keys
([WordPress#500](WordPress#500))
* Align savepoint handling with MySQL semantics
([WordPress#496](WordPress#496))

**Full changelog:**
WordPress/sqlite-database-integration@v3.0.1...release/v3.0.2

## Next steps

1. **Review** the changes in this pull request.
2. **Push** any additional edits to this branch (`release/v3.0.2`).
3. **Merge** this pull request to complete the release.

Merging will automatically build the plugin ZIP, create a [GitHub
release](https://github.com/WordPress/sqlite-database-integration/releases),
and deploy to
[WordPress.org](https://wordpress.org/plugins/sqlite-database-integration/).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
  - Improved schema reconstruction when handling native numeric results.
  - Corrected lexer edge cases and string processing.
- Fixed index prefix lengths and ordering in `SHOW CREATE TABLE` output
for primary keys.
  - Improved savepoint handling to better match MySQL behavior.

- **Release**
  - Updated the SQLite integration to version 3.0.2.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@batonac batonac changed the title Sync with upstream trunk (3 commits) Sync with upstream trunk (5 commits) Sep 10, 2026
@batonac batonac changed the title Sync with upstream trunk (5 commits) Sync with upstream trunk (2 commits) Sep 11, 2026
@batonac batonac removed the upstream-conflict Upstream sync needs a manual merge label Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

upstream-sync Automated sync with the WordPress upstream

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants