You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Keep the user transaction active after a failed statement - #536
Part of #473 (the "Explicit transactions" section).
When a statement fails inside a transaction, the driver rolls back the whole transaction and ends it. MySQL undoes only the failed statement and keeps the transaction active:
CREATETABLEt (id INTPRIMARY KEY);
START TRANSACTION;
INSERT INTO t VALUES (1);
INSERT INTO t VALUES (1); -- fails with a duplicate key errorINSERT INTO t VALUES (2);
COMMIT;
SELECT id FROM t;
-- Driver: 2 MySQL: 1, 2
The work before the error is lost, and the work after it runs in autocommit mode. A savepoint created before the error is gone too, so ROLLBACK TO SAVEPOINT fails with error 1305.
Fix
Each statement already runs in a wrapper: a transaction when none is active, or a savepoint inside a user transaction. On failure, only that wrapper is now rolled back. This:
keeps the user transaction and its savepoints active, and still undoes all changes of the failed statement, including the earlier rows of a multi-row INSERT;
keeps the transaction opened by LOCK TABLES, so UNLOCK TABLES commits the rows written before the error, as in MySQL;
replaces the special case for error 1305 (missing savepoint), since savepoint statements are not wrapped and have nothing to roll back.
Without a user transaction, the behavior is unchanged: the wrapper transaction is rolled back.
Some SQLite errors roll back the whole transaction automatically (SQLite docs). Then rolling back to the savepoint fails, and the driver ends the transaction as before. This path has no test, because those errors (such as SQLITE_FULL) are hard to trigger in a unit test.
The rest of #473 (error modes and diagnostics) is not part of this PR.
Tests
testFailedStatementKeepsTransactionActive checks that a failed write and a failed read keep the transaction active, and that COMMIT keeps the work before and after them.
testFailedStatementIsRolledBackInTransaction checks that a failed multi-row INSERT leaves none of its rows.
testRollbackToSavepointAfterFailedStatement checks that a savepoint survives a failed statement.
testFailedStatementKeepsTableLock checks that UNLOCK TABLES keeps the rows written before a failed statement.
test_failed_query_keeps_transaction_active checks the same through PDO::beginTransaction() and PDO::commit().
All five fail on trunk. The existing testMissingSavepointDoesNotRollbackTransaction still passes.
This change was prepared with Claude Code (Claude Opus 5.5). I reviewed the code, the tests, and this description, and I take responsibility for the change.
Note: this PR and #535 both add tests at the same spot of WP_MySQL_On_SQLite_PDO_API_Tests.php, so whichever merges second needs a trivial rebase.
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.
Walkthrough
The SQLite wrapper now handles failed queries, commits, and rollbacks with updated transaction and savepoint state. Regression tests cover statement failures, failed commits, savepoints, and table locks.
Failed queries roll back the wrapper transaction. If the user transaction remains active, its state and table lock are preserved. Tests cover statement failures, partial changes, savepoints, and table locks.
PDO commit executes SQLite COMMIT directly and clears state after success. MySQL commit attempts rollback after a SQLite commit error and rethrows the original error. Tests cover failed commits through explicit and implicit commit paths.
Rollback state cleanup packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php, packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php
User rollback clears transaction and savepoint state in a finally block. Tests check the state after SQLite rolls back the full transaction and releases a table lock.
WordPress/sqlite-database-integration#567: Adds savepoint rollback behavior for failed table rebuilds; this PR changes rollback behavior for failed queries inside transactions.
Suggested reviewers:janjakes, chubes4
Merge Risk: Minimal · up to 771c5
No actionable merge-blocking issue is identified; the change appears mergeable after normal checks.
Pre-merge checks: 4 · 1
Failed checks: 1 warning
Check name
Status
Explanation
Resolution
Docstring Coverage
Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surv…
Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed checks: 4 passed
Check name
Status
Explanation
Description Check
Check skipped - CodeRabbit’s high-level summary is enabled.
Title check
The title "Keep the user transaction active after a failed statement" matches the main change in the changeset. The raw summary and PR objectives both describe rolling back only the statement's wrappe…
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Full details: Title check
Explanation
The title "Keep the user transaction active after a failed statement" matches the main change in the changeset. The raw summary and PR objectives both describe rolling back only the statement's wrapper savepoint so the user transaction and its savepoints stay active after a failed query. The title is short, specific, and free of file lists or vague wording. The title does not mention the failed-commit handling or the table-lock preservation, but it covers the primary change.
Full details: Docstring Coverage
Explanation
Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 15 functions across 3 files.
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
Finishing Touches
🧪 Generate unit tests (beta)
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Warning
Some tools did not complete. Review the errors below.
PHPStan (2.3.0)
PHPStan reported 1 non-file analysis error(s); analysis is incomplete
PHPMD could not process this file (exit code 255): 0/1 [░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20971520 bytes) in phar:///usr/bin/phpmd/vendor/pdepend/pdepend/src/Util/Cache/Driver/FileCacheDriver.php on line 183
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
When a statement failed, the driver rolled back the whole user
transaction, including the work done before the failed statement, and
ended it. MySQL undoes only the failed statement and keeps the
transaction active, so the caller can decide whether to commit or roll
back the rest. A later COMMIT kept only the work done after the error,
and a savepoint created before the error could no longer be used.
Roll back only the wrapper transaction of the failed statement. Inside a
user transaction, the wrapper is a savepoint, so the transaction stays
active. This also keeps an active LOCK TABLES transaction, and replaces
the special case for a missing savepoint, since savepoint statements are
not wrapped.
Part of WordPress#473.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thanks, @Nandan108! I pushed a few commits on top: a failed COMMIT now rolls back instead of leaving the transaction open, the rollback test uses ALTER TABLE since SQLite already undoes a failed INSERT by itself, and the rollback code is simpler, with tests for SQLite rolling back the whole transaction.
I also extracted one more issue to address as a follow-up: #576
SQLite undoes a failed INSERT by itself, so the multi-row INSERT test
passed even without the savepoint rollback.
A multi-table DELETE runs a DELETE query for each table. When deleting
from the second table fails, only the savepoint rollback restores the
rows deleted from the first one.
ALTER TABLE also runs multiple queries, but MySQL commits the active
transaction before DDL statements, so the transaction would not stay
active after a failed ALTER TABLE.
Some SQLite errors, such as SQLITE_FULL, roll back the whole transaction,
including the wrapper savepoint. A RAISE(ROLLBACK) trigger makes this
path testable without such errors.
Check that the transaction, its savepoints, and its table lock end, and
that the connection stays usable.
Reuse rollback_user_transaction() to end the transaction, both for a
transaction wrapper and when SQLite has already rolled back the whole
transaction. It now always resets the transaction state, as ROLLBACK
always ends the transaction, even when it fails.
End the user transaction only when ROLLBACK TO fails. When only RELEASE
fails, the statement is already undone, and the leftover savepoint is
harmless.
Release the table lock outside of the try block, as the rollback can now
throw on PHP < 8.4. Don't reset the wrapper transaction type, as flush()
already does that before every query.
Failed statements no longer roll back the user transaction, and COMMIT
runs without a wrapper transaction. So when COMMIT failed, nothing was
rolled back and the transaction stayed open. It kept the write lock, and
later statements silently joined the uncommitted transaction.
Like MySQL, roll back a transaction that fails to commit. This covers
COMMIT, UNLOCK TABLES, and the implicit commits of START TRANSACTION,
BEGIN, and LOCK TABLES.
PDO::commit() still keeps the transaction active on failure. PDO MySQL
still reports an active transaction after a failed commit, so callers
can call PDO::rollBack().
The tests make COMMIT fail with a deferred foreign key check.
## Release `3.1.2`
Version bump and changelog update for release `3.1.2`.
**Changelog draft:**
* Allow configuring the SQLite busy timeout with `SQLITE_BUSY_TIMEOUT`
([#558](#558))
* Keep the user transaction active after a failed statement
([#536](#536))
* Support odd-length `0x` hexadecimal literals
([#573](#573))
* Fix rebuilds of legacy text and binary columns
([#565](#565))
* Use `'0'` column defaults in non-strict `INSERT` statements
([#569](#569))
* Roll back failed `OPTIMIZE TABLE` and `REPAIR TABLE` rebuilds
([#567](#567))
* Remove the Rust parser extension
([#566](#566))
* Preserve `AUTO_INCREMENT` counters when rebuilding tables
([#561](#561))
* Fix `ALTER TABLE ... ADD (...)` for columns, indexes, and constraints
([#557](#557))
* Fix `ON UPDATE CURRENT_TIMESTAMP` for explicit values and unchanged
rows
([#560](#560))
* Normalize zero defaults in date and time columns
([#542](#542))
* Add support for `ALTER COLUMN` with `SET DEFAULT` and `DROP DEFAULT`
([#555](#555))
**Full changelog:**
v3.1.1...release/v3.1.2
## Next steps
1. **Review** the changes in this pull request.
2. **Push** any additional edits to this branch (`release/v3.1.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
-->
<!-- coderabbit-ai-generated:v1 -->
## Summary by CodeRabbit
* **Release**
* Updated the SQLite database integration to version 3.1.2.
* **New Features**
* Added support for more `ALTER TABLE` operations and configurable
SQLite busy timeouts.
* **Bug Fixes**
* Improved transaction handling after failed statements and addressed
SQL compatibility, table rebuild, and default-value handling issues.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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
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.
Summary
Part of #473 (the "Explicit transactions" section).
When a statement fails inside a transaction, the driver rolls back the whole transaction and ends it. MySQL undoes only the failed statement and keeps the transaction active:
The work before the error is lost, and the work after it runs in autocommit mode. A savepoint created before the error is gone too, so
ROLLBACK TO SAVEPOINTfails with error 1305.Fix
Each statement already runs in a wrapper: a transaction when none is active, or a savepoint inside a user transaction. On failure, only that wrapper is now rolled back. This:
INSERT;LOCK TABLES, soUNLOCK TABLEScommits the rows written before the error, as in MySQL;Without a user transaction, the behavior is unchanged: the wrapper transaction is rolled back.
Some SQLite errors roll back the whole transaction automatically (SQLite docs). Then rolling back to the savepoint fails, and the driver ends the transaction as before. This path has no test, because those errors (such as
SQLITE_FULL) are hard to trigger in a unit test.The rest of #473 (error modes and diagnostics) is not part of this PR.
Tests
testFailedStatementKeepsTransactionActivechecks that a failed write and a failed read keep the transaction active, and thatCOMMITkeeps the work before and after them.testFailedStatementIsRolledBackInTransactionchecks that a failed multi-rowINSERTleaves none of its rows.testRollbackToSavepointAfterFailedStatementchecks that a savepoint survives a failed statement.testFailedStatementKeepsTableLockchecks thatUNLOCK TABLESkeeps the rows written before a failed statement.test_failed_query_keeps_transaction_activechecks the same throughPDO::beginTransaction()andPDO::commit().All five fail on
trunk. The existingtestMissingSavepointDoesNotRollbackTransactionstill passes.packages/mysql-on-sqlite: 1048 tests pass (PHP 8.3, SQLite 3.45.1); PHPCS is clean.Use of AI
This change was prepared with Claude Code (Claude Opus 5.5). I reviewed the code, the tests, and this description, and I take responsibility for the change.
Note: this PR and #535 both add tests at the same spot of
WP_MySQL_On_SQLite_PDO_API_Tests.php, so whichever merges second needs a trivial rebase.🤖 Generated with Claude Code
Summary by CodeRabbit