Fix sqlite autocommit lifecycle - #8387
Conversation
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: Codex:gpt-5.6-sol
📝 WalkthroughWalkthroughChangesSQLite autocommit lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant VirtualMachine
participant Connection
participant SQLiteDatabase
Connection->>SQLiteDatabase: BEGIN for disabled autocommit
VirtualMachine->>Connection: rollback()
Connection->>SQLiteDatabase: ROLLBACK
Connection->>SQLiteDatabase: BEGIN
VirtualMachine->>Connection: close()
Connection->>SQLiteDatabase: ROLLBACK when required
Connection->>SQLiteDatabase: drop database
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
📦 Library DependenciesThe following Lib/ modules were modified. Here are their dependencies: [x] test: cpython/Lib/test/test_float.py (TODO: 3) dependencies: dependent tests: (no tests depend on float) [x] test: cpython/Lib/test/test_format.py (TODO: 6) dependencies: dependent tests: (no tests depend on format) [x] lib: cpython/Lib/sqlite3 dependencies:
dependent tests: (2 tests)
Legend:
|
There was a problem hiding this comment.
Pull request overview
This PR aligns RustPython’s _sqlite3 transaction lifecycle with CPython when sqlite3.Connection(autocommit=False), ensuring a transaction is opened immediately, commit()/rollback() restart a new transaction, and close/context-manager exit explicitly roll back pending transactions.
Changes:
- Start a transaction immediately on connect when
autocommit=False, and restart it aftercommit()/rollback(). - Roll back any pending transaction on connection close and on connection reinitialization.
- Unblock several CPython
test_sqlite3transaction tests by removingexpectedFailuredecorators.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
crates/stdlib/src/_sqlite3.rs |
Implements CPython-aligned autocommit-disabled transaction lifecycle and close/reinit rollback behavior. |
Lib/test/test_sqlite3/test_transactions.py |
Removes expectedFailure markers for tests now passing with the updated lifecycle. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn set_autocommit(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { | ||
| let mode = AutocommitMode::try_from_borrowed_object(vm, &val)?; | ||
| let db = self.db_lock(vm)?; | ||
| *self.autocommit.lock() = mode; | ||
|
|
There was a problem hiding this comment.
The literal NUL bytes were already present, but I think using the escaped \0 form is clearer and more consistent with the rest of the file. The second point is intentional. This implementation follows CPython's set_autocommit(), which stores the requested autocommit mode before executing COMMIT or BEGIN. If the SQL statement fails, CPython also retains the requested mode. Since this PR aims to match CPython's behavior, I will keep the current ordering.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/stdlib/src/_sqlite3.rs (1)
1133-1165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider extracting the shared "exec + reopen BEGIN" logic in
commit/rollback.The
Disabledarms ofcommit()(1140-1143) androllback()(1160-1163) differ only in the first statement (COMMITvsROLLBACK) but share an identical trailingdb._exec(b"BEGIN\0", vm). Per repo guidelines, extract the differing value and call the shared logic once.♻️ Suggested refactor
+ fn exec_and_reopen(db: &Sqlite, stmt: &[u8], vm: &VirtualMachine) -> PyResult<()> { + db._exec(stmt, vm)?; + db._exec(b"BEGIN\0", vm) + } + fn commit(&self, vm: &VirtualMachine) -> PyResult<()> { let db = self.db_lock(vm)?; let mode = *self.autocommit.lock(); match mode { AutocommitMode::Legacy => db.implicit_commit(vm), AutocommitMode::Enabled => Ok(()), - AutocommitMode::Disabled => { - db._exec(b"COMMIT\0", vm)?; - db._exec(b"BEGIN\0", vm) - } + AutocommitMode::Disabled => Self::exec_and_reopen(&db, b"COMMIT\0", vm), } } fn rollback(&self, vm: &VirtualMachine) -> PyResult<()> { let db = self.db_lock(vm)?; let mode = *self.autocommit.lock(); match mode { AutocommitMode::Legacy => { if db.is_autocommit() { Ok(()) } else { db._exec(b"ROLLBACK\0", vm) } } AutocommitMode::Enabled => Ok(()), - AutocommitMode::Disabled => { - db._exec(b"ROLLBACK\0", vm)?; - db._exec(b"BEGIN\0", vm) - } + AutocommitMode::Disabled => Self::exec_and_reopen(&db, b"ROLLBACK\0", vm), } }As per coding guidelines, "When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/stdlib/src/_sqlite3.rs` around lines 1133 - 1165, Extract the shared Disabled-mode transaction flow from commit and rollback into a helper or common local path that accepts the differing SQL command, COMMIT or ROLLBACK, executes it through db._exec, then executes BEGIN once. Update both commit and rollback to supply their respective command while preserving the existing Legacy and Enabled behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/stdlib/src/_sqlite3.rs`:
- Around line 1133-1165: Extract the shared Disabled-mode transaction flow from
commit and rollback into a helper or common local path that accepts the
differing SQL command, COMMIT or ROLLBACK, executes it through db._exec, then
executes BEGIN once. Update both commit and rollback to supply their respective
command while preserving the existing Legacy and Enabled behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro Plus
Run ID: 26f213f0-894c-4893-9b6f-73408826abcd
⛔ Files ignored due to path filters (1)
Lib/test/test_sqlite3/test_transactions.pyis excluded by!Lib/**
📒 Files selected for processing (1)
crates/stdlib/src/_sqlite3.rs
* fix: open disabled sqlite autocommit transactions Assisted-by: Codex:gpt-5.6-sol * fix: reopen disabled sqlite transactions after commit Assisted-by: Codex:gpt-5.6-sol * fix: reopen disabled sqlite transactions after rollback Assisted-by: Codex:gpt-5.6-sol * fix: roll back disabled sqlite connections on close Assisted-by: Codex:gpt-5.6-sol
Summary
Align the
sqlite3.Connectionlifecycle forautocommit=Falsewith CPython.Previously, creating a connection with
autocommit=Falsedid not immediately open a transaction. Callingcommit()orrollback()also left the connection outside a transaction, and closing the connection did not explicitly roll back the pending transaction.When a connection is created with
autocommit=False, it now begins a transaction immediately. In this mode,commit()androllback()complete the current transaction and then open a new one. Both methods remain no-ops withautocommit=True, whileautocommit=sqlite3.LEGACY_TRANSACTION_CONTROLpreserves the existing transaction behavior. Closing the connection explicitly rolls back any pending transaction.The same behavior is applied when leaving a connection context manager and when an existing
Connectionobject is reinitialized.Four CPython tests now pass without
expectedFailure:test_autocommit_disabledtest_autocommit_disabled_implicit_rollbacktest_autocommit_disabled_then_enabledtest_autocommit_disabled_ctx_mgrCursor transaction handling in
execute(),executemany(), andexecutescript()is not included in this PR and will be validated separately.Summary by CodeRabbit