Skip to content

fix: Transaction::run() never commits — closure signature change#103

Merged
RAprogramm merged 1 commit into
mainfrom
102-fix-transaction-run-commit
May 11, 2026
Merged

fix: Transaction::run() never commits — closure signature change#103
RAprogramm merged 1 commit into
mainfrom
102-fix-transaction-run-commit

Conversation

@RAprogramm
Copy link
Copy Markdown
Owner

Closes #102

Summary

  • entity_core::transaction::Transaction::run() previously took the closure as FnOnce(TransactionContext), consumed ctx, and dropped it before commit() could be called — every successful run() silently rolled back via sqlx::Transaction::Drop.
  • The fix changes the signature to AsyncFnOnce(&mut TransactionContext) -> Result<T, E> so run keeps ownership and calls ctx.commit().await explicitly on Ok. On Err, dropping ctx performs the rollback as before (now intentional rather than accidental).
  • run_with_commit is unchanged — it deliberately moves ctx into the closure so callers can decide when/whether to commit.

Breaking change

This changes a public function's closure signature. Per STABILITY.md, pre-1.0 minor bumps may include breaking changes. Versions bumped:

Crate Old New
entity-core 0.4.0 0.5.0
entity-derive-impl 0.4.0 0.5.0
entity-derive 0.6.0 0.7.0

Old broken versions (0.4.0 / 0.4.0 / 0.6.0) should be yanked from crates.io after this PR is released.

Migration

// Before (silently rolled back on success)
.run(|mut ctx| async move { ctx.users().create(dto).await })

// After (commits on Ok)
.run(async |ctx| ctx.users().create(dto).await)

Why this slipped through 0.6.0

Codecov flagged crates/entity-core/src/transaction.rs at 25% on PR #101. Neither run nor run_with_commit had any test exercising actual commit/rollback semantics; only TransactionError variants were covered. The bug was visible in the diff but the PR was merged without addressing the Codecov warning.

Test plan

  • cargo check --all-features — entity-core, entity-derive, entity-derive-impl, examples/transactions, examples/full-app
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo +nightly fmt --all -- --check
  • cargo test --all-features (existing unit tests still pass)
  • Compile-time regression guard added in transaction.rs test module — ensures the signature requires &mut TransactionContext and rejects by-value TransactionContext
  • Real Postgres integration test for commit/rollback semantics — requires adding a postgres service to CI, tracked as follow-up

The previous implementation took the closure as `FnOnce(TransactionContext)`,
which consumed `ctx` and dropped it before `commit()` was ever called. The
underlying `sqlx::Transaction::Drop` then rolled back, so every successful
`run()` silently lost the writes.

Change the closure signature to `AsyncFnOnce(&mut TransactionContext) ->
Result<T, E>` so `run` retains ownership of `ctx`. On Ok, call
`ctx.commit().await` and propagate any commit error. On Err, drop `ctx` so
sqlx's Drop performs the rollback (existing behavior, now intentional).

`run_with_commit` keeps its by-value closure signature — it intentionally
hands ownership of `ctx` to the closure for explicit commit/rollback flow.

Bump versions to reflect the breaking signature change on a pre-1.0 crate:
- entity-core 0.4.0 -> 0.5.0
- entity-derive-impl 0.4.0 -> 0.5.0
- entity-derive 0.6.0 -> 0.7.0

Update internal docs and both transaction examples to the new
`async |ctx| { ... }` form. Add a compile-time regression guard in the
transaction.rs test module so the signature can never silently regress to
consuming `ctx` by value again. A real commit/rollback integration test
requires Postgres in CI and is tracked separately.

Closes #102
@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

❌ Patch coverage is 0% with 11 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
crates/entity-core/src/transaction.rs 0.00% 11 Missing ⚠️

📢 Thoughts on this report? Let us know!

@RAprogramm RAprogramm merged commit 21ede5e into main May 11, 2026
16 of 17 checks passed
@RAprogramm RAprogramm deleted the 102-fix-transaction-run-commit branch May 11, 2026 04:43
RAprogramm added a commit that referenced this pull request May 11, 2026
…ctions example (#121)

README had drifted from the current crates:

- Installation block still pinned `entity-derive = "0.4"`. Updated to `0.8`
  (latest published).
- Added a "Feature flags" section enumerating every Cargo feature:
  `postgres`, `clickhouse`, `mongodb`, `streams`, `api`, `validate`,
  `tracing`. Each gets a one-line description so users can pick what they
  need without reading the source.
- Added a "Transactions" subsection under Quick Reference with a current,
  copy-pasteable `.run(async |ctx| { ... })` example. The previous text
  mentioned transactions only as a feature-table bullet; the API changed
  in 0.7.0 (#103) and the README never demonstrated the fixed signature.
  Cross-links to `run_with_commit` for conditional-commit use cases.
- Added a "Tracing" subsection showing the feature flag, expected
  dependencies, sample log output, and the zero-cost guarantee when the
  flag is off — the feature was added in 0.8.0 (#118 / #119) and was
  previously undocumented at the README level.
- Added "Structured Logging" to the Features table.

Closes #120
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.

fix: Transaction::run() never commits — context dropped on Ok causes silent rollback

1 participant