Track transactions opened by SQL savepoints - #496
Open
chubes4 wants to merge 4 commits into
Open
Conversation
JanJakes
reviewed
Aug 24, 2026
| * @var bool | ||
| */ | ||
| private $transaction_started_by_savepoint = false; | ||
|
|
Member
There was a problem hiding this comment.
Could we use a single property by tracking only savepoints in a transaction opened by SAVEPOINT? Savepoints inside a transaction opened by BEGIN do not need tracking because releasing them cannot end the outer transaction. This would remove the need for $transaction_started_by_savepoint.
Let’s also document why this state is needed. Something like:
/**
* User savepoints in a transaction opened by a SAVEPOINT statement.
*
* On PHP < 8.4, PDO SQLite cannot detect transactions opened with raw SQL.
* Tracking the savepoint stack keeps the inTransaction() polyfill accurate
* when the outermost savepoint is released.
*
* Savepoints inside a transaction opened by BEGIN are not tracked because
* releasing them cannot end the outer transaction.
*
* @var string[]
*/
private $savepoint_transaction_stack = array();
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Track transactions opened by translated SQL
SAVEPOINTstatements so write wrappers use an internal savepoint instead of attempting a nested top-levelBEGIN IMMEDIATE.Add adapter and WordPress integration regressions for standalone write wrapping, release/commit, rollback, nested and duplicate savepoints, quoted/case-varied identifiers, affected-row behavior, and failure cleanup.
Why?
On PHP versions before 8.4, PDO SQLite does not reliably expose transactions opened through SQL via
PDO::inTransaction(). The adapter already maintains an internal transaction-state polyfill, but translatedSAVEPOINT,ROLLBACK TO SAVEPOINT, andRELEASE SAVEPOINTstatements did not update it. ASAVEPOINTissued without an existing transaction therefore opened an SQLite transaction that remained invisible to the write wrapper, which then failed onBEGIN IMMEDIATEbefore executing the translated write.The transaction-state invariant after this change is: the adapter's polyfilled state remains true from any successful top-level
BEGINor outermost SQLSAVEPOINTuntil the corresponding top-level commit, rollback, or outermost release. A normalized savepoint stack preserves nested, duplicate-name, and case-insensitive rollback/release semantics while standalone writes retain their atomicBEGIN IMMEDIATEwrapper.Testing Instructions
composer run test tests/WP_MySQL_On_SQLite_PDO_API_Tests.phpfrompackages/mysql-on-sqlite.composer run testfrompackages/mysql-on-sqlite.composer run check-csfrom the repository root.composer run wp-test-sqlite-plugin-phpandcomposer run wp-test-sqlite-plugin-php-multisitein a Docker-enabled environment.Local results:
$wpdb->update()integration regression usesPHPUnit_Adapter_TestCase, avoiding the transaction automatically opened byWP_UnitTestCase; it asserts no transaction is active before the standalone SQLSAVEPOINTand after outermost release.Extra Chill Reproduction Evidence
The original failure was retained from the deterministic Extra Chill/Data Machine reproduction:
runtime-mt6bqbcc-dfymmzartifact-bundle-sha256-38b3a05a217740e7b6801de40989b68ad518c5fa789c97c5a683ea9ae0df5161/mnt/extrachill-workspace/tmp/opencode/verify-2578-after-3346-filesystem-trace-wp692/runtime-mt6bqbcc-dfymmzwpdb->update()returnedfalse,rows_affected=0, withcannot start a transaction within a transactioninlast_error.Executing: BEGIN IMMEDIATE; the translatedUPDATEnever executed.Fixes #495