Skip to content

Add scoped read and write transaction APIs - #22

Open
daniel-vacic wants to merge 1 commit into
powersync-ja:mainfrom
daniel-vacic:codex/scoped-transactions
Open

Add scoped read and write transaction APIs#22
daniel-vacic wants to merge 1 commit into
powersync-ja:mainfrom
daniel-vacic:codex/scoped-transactions

Conversation

@daniel-vacic

Copy link
Copy Markdown
Contributor

What changed

  • add PowerSyncDatabase::read_transaction and write_transaction
  • keep the transaction callback synchronous after asynchronous lease acquisition,
    so it cannot suspend while SQLite owns a transaction or WAL snapshot
  • add PowerSyncTransaction::with_statement, whose higher-ranked callback
    prevents a prepared statement or row borrow from escaping the scope
  • use BEGIN IMMEDIATE for public write transactions
  • keep compatibility for current internal transaction call sites

Lifecycle guarantees

  • normal return commits
  • operation errors roll back and preserve the original error
  • panics roll back during unwind
  • failed deferred-constraint commits leave the guard active so Drop rolls back
  • manual BEGIN/COMMIT/ROLLBACK/savepoint control is rejected inside the
    scoped callback

This is the scoped API discussed in #19. It complements pool-boundary cleanup in
#21: callers get a structurally safe API, while the pool boundary remains the
last line of defense for raw leases.

Validation

  • rustup run 1.96.0 cargo test -p powersync
  • rustup run 1.96.0 cargo clippy -p powersync --all-targets -- -D warnings
  • rustup run 1.96.0 cargo fmt --all -- --check

The suite includes real file-backed WAL checks for success, error, panic, failed
commit, rejected transaction control and read-snapshot release. A compile-fail
doctest proves that a statement cannot escape with_statement.

@daniel-vacic
daniel-vacic marked this pull request as ready for review July 29, 2026 18:24

@simolus3 simolus3 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the contribution! However, I don't think we should have these helpers in the Rust SDK (even though they make sense for our other SDKs).

This SDK is intentionally more low-level, and encourages the use of rusqlite for higher-level connection management. rusqlite happens to have excellent APIs for this:

  1. The Transaction struct is a RAII guard that calls BEGIN when constructed and ROLLBACK when dropped in Rust (unless explicitly committed). The only reason we have an internal TransactionGuard is that we want rusqlite to be optional in our implementation, but it's not a public API.
  2. For statements, I also think something like Statement which finalizes on drop is a more appropriate API than a callback. It has a lifetime bound to the connection so it can't out-live a leased connection.

So rusqlite already provides the tools for statements to work here, and I don't think the powersync crate should expose high-level SQLite connection APIs. With the rusqlite feature enabled (it is by default), ConnectionLease dereferences to rusqlite::Connection and you can use these transaction / statement helpers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants