Skip to content

multiple function calls within single transaction#160

Open
salamonpavel wants to merge 1 commit into
masterfrom
feature/transactions
Open

multiple function calls within single transaction#160
salamonpavel wants to merge 1 commit into
masterfrom
feature/transactions

Conversation

@salamonpavel
Copy link
Copy Markdown
Contributor

@salamonpavel salamonpavel commented May 28, 2026

Closes #159
Adds support for multiple function calls within single transaction in Doobie module.

Summary by CodeRabbit

  • New Features

    • Added support for composing multiple database operations into a single atomic transaction with automatic rollback on failure.
  • Tests

    • Added integration tests for transaction composition scenarios, including multi-operation transactions and rollback behavior verification.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Walkthrough

This PR adds support for composing multiple Doobie database function calls within a single transaction. Three new Scala source files introduce ConnectionIO-based methods to function wrappers, a transaction executor in the engine, and comprehensive integration tests validating the transactional composition feature.

Changes

Transactional composition of database function calls

Layer / File(s) Summary
ConnectionIO-based APIs in DoobieFunction
doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieFunction.scala
Imports updated to include ConnectionIO and status types. Base traits DoobieFunction and DoobieFunctionWithStatus expose toConnectionIO methods returning ConnectionIO[Seq[R]] and ConnectionIO[Seq[FailedOrRow[R]]]. Concrete subclasses add shape-specific methods: DoobieSingleResultFunction.toConnectionIOSingle, DoobieOptionalResultFunction.toConnectionIOOptional, and their status-aware variants, all returning unevaluated ConnectionIO programs.
Transaction executor in DoobieEngine
doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieEngine.scala
Adds public runConnectionIO[R](cio: ConnectionIO[R]): F[R] method that executes a composed ConnectionIO program as a single transaction via cio.transact(transactor), returning the result lifted into the engine's effect type F.
Integration tests for transactional composition
doobie/src/test/scala/za/co/absa/db/fadb/doobie/DoobieTransactionCompositionIntegrationTests.scala
New test suite with three helper function wrappers (CreateActor, GetActorById, GetActors) and five test cases: single function composition, mixed toConnectionIOSingle/toConnectionIO, batch creation with verification, failure-induced rollback assertion, and backward-compatibility confirmation that direct apply() execution still works.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant DoobieFunction
  participant ConnectionIO
  participant DoobieEngine
  participant Database
  Caller->>DoobieFunction: toConnectionIO(values)
  DoobieFunction->>ConnectionIO: return unevaluated program
  Caller->>DoobieFunction: toConnectionIO(values2)
  DoobieFunction->>ConnectionIO: return unevaluated program
  Caller->>ConnectionIO: compose multiple programs
  Caller->>DoobieEngine: runConnectionIO(composed)
  DoobieEngine->>Database: transact(composed)
  Database-->>DoobieEngine: result or rollback
  DoobieEngine-->>Caller: F[Result]
Loading

🎯 2 (Simple) | ⏱️ ~12 minutes

🐰 A rabbit hops through transactions deep,
Composing calls within a dance so neat,
No premature runs, just programs that wait,
Until DoobieEngine unlocks the gate,
Multiple queries now share a single fate!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: exposing ConnectionIO-based methods to enable multiple database function calls within a single transaction.
Linked Issues check ✅ Passed The PR implements support for multiple function calls within a single transaction [#159] by adding ConnectionIO methods to DoobieEngine and DoobieFunction classes.
Out of Scope Changes check ✅ Passed All changes are directly related to the objective: DoobieEngine.runConnectionIO executes transactions, DoobieFunction classes expose ConnectionIO methods for composition, and integration tests validate the functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/transactions

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

JaCoCo core module code coverage report - scala 2.13.11

Overall Project 59.84% 🍏

There is no coverage information present for the Files changed

@github-actions
Copy link
Copy Markdown

JaCoCo doobie module code coverage report - scala 2.13.11

Overall Project 71.07% -2.72% 🍏
Files changed 77.6%

File Coverage
DoobieEngine.scala 100% 🍏
DoobieFunction.scala 74.13% -4.64%

@github-actions
Copy link
Copy Markdown

JaCoCo slick module code coverage report - scala 2.13.11

Overall Project 91.42% 🍏

There is no coverage information present for the Files changed

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieFunction.scala`:
- Around line 131-134: The eager evaluation of toFragmentsSeq(values) in
toConnectionIO (and the similar status/single/optional variants) allows
exceptions to escape before entering the ConnectionIO context; change these to
defer evaluation by wrapping the call in the library's effectful delay (e.g.,
ConnectionIO/FC delay) and then flatMap into composeFragments(...).query...
(i.e., replace the val fragments = toFragmentsSeq(values) with a delayed effect
that computes fragments inside ConnectionIO and then .flatMap(fragments =>
composeFragments(fragments).query[R].to[Seq]/.unique/.option/.status) so any
thrown exceptions are captured by the ConnectionIO/MonadError machinery.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c36cd8b5-9b2d-4f4d-af81-41290e8dfd3b

📥 Commits

Reviewing files that changed from the base of the PR and between 3b3efd6 and 0011c6d.

📒 Files selected for processing (3)
  • doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieEngine.scala
  • doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieFunction.scala
  • doobie/src/test/scala/za/co/absa/db/fadb/doobie/DoobieTransactionCompositionIntegrationTests.scala

Comment on lines +131 to +134
def toConnectionIO(values: I): ConnectionIO[Seq[R]] = {
val fragments = toFragmentsSeq(values)
composeFragments(fragments).query[R].to[Seq]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Defer toFragmentsSeq(values) inside ConnectionIO to preserve error semantics.

DoobieFunction.scala eagerly evaluates val fragments = toFragmentsSeq(values) in toConnectionIO (131-134) and similarly in the status, single, and optional variants (also applies to 216-221, 255-258, 356-359). If toFragmentsSeq throws, the exception occurs before the ConnectionIO logic (and any catchNonFatal/MonadError handling in the query path) can capture it, leading to inconsistent behavior.

Replace the eager val fragments = ... with an effectful deferral using the appropriate ConnectionIO/FC “delay” API from this codebase, then flatMap into composeFragments(fragments).query... in all affected methods.

🤖 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 `@doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieFunction.scala` around
lines 131 - 134, The eager evaluation of toFragmentsSeq(values) in
toConnectionIO (and the similar status/single/optional variants) allows
exceptions to escape before entering the ConnectionIO context; change these to
defer evaluation by wrapping the call in the library's effectful delay (e.g.,
ConnectionIO/FC delay) and then flatMap into composeFragments(...).query...
(i.e., replace the val fragments = toFragmentsSeq(values) with a delayed effect
that computes fragments inside ConnectionIO and then .flatMap(fragments =>
composeFragments(fragments).query[R].to[Seq]/.unique/.option/.status) so any
thrown exceptions are captured by the ConnectionIO/MonadError machinery.

@ABLL526
Copy link
Copy Markdown
Contributor

ABLL526 commented May 29, 2026

Read the code, easy to understand. It is really good and will be useful. Just some random thoughts:

  • Will it transact the successful operations in the event of a single (or multiple) operation(s) failure within one composed transaction?
  • Is there a limit to the number of composed operations? Should there be?
  • Can it be mistakenly nested?

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.

Add support for multiple function calls within single transaction

2 participants