@@ -398,6 +398,77 @@ try {
398398}
399399```
400400
401+ ### How transactions work under the hood (coroutines-aware)
402+
403+ Terpal controllers are implemented to be coroutine-first. Instead of relying on thread-local storage or
404+ manually passing a JDBC Connection (or driver session) around, Terpal attaches the current session and
405+ transaction state to the CoroutineContext. This makes the correct connection automatically available to
406+ all suspend functions that run within the same coroutine scope (and its children), even if those
407+ functions hop threads on Dispatchers.IO.
408+
409+ Key pieces involved (from the controller core):
410+ - CoroutineSession(session, sessionKey): A CoroutineContext element that stores the current driver session
411+ (e.g. a JDBC Connection). When you call ctx.withConnection { ... } or enter a ctx.transaction { ... },
412+ Terpal installs a CoroutineSession into the context using withContext(... + Dispatchers.IO).
413+ - CoroutineTransaction: A CoroutineContext element that marks that a transaction is in progress. The
414+ outer transaction installs this marker and is responsible for commit/rollback. Nested transactions
415+ detect the marker and reuse the same connection and enclosing transaction, avoiding starting a second
416+ physical transaction.
417+
418+ Because these are regular CoroutineContext elements, the session/transaction information is propagated to
419+ child coroutines created with coroutineScope/withContext/launch within the same parent scope. No thread-local
420+ is required, and you never need to pass a Connection by hand.
421+
422+ Practical implications:
423+ - Inside ctx.transaction { ... } you can call .run() directly on queries/actions without passing ctx; the
424+ correct connection is discovered from the coroutine context.
425+ - Suspending and switching threads (e.g. to Dispatchers.IO) does not lose the connection—the context element
426+ comes along for the ride.
427+ - Flows: When you stream results, Terpal uses flowOn with the same CoroutineSession so the same connection is
428+ used consistently for the pipeline, and it is properly closed at completion.
429+
430+ ### Nested transactions
431+
432+ Terpal supports nesting transaction blocks. The semantics are:
433+ - The first (outermost) transaction actually begins the database transaction and is responsible for
434+ committing or rolling back.
435+ - Inner transaction blocks detect that a transaction is already in progress via CoroutineTransaction and
436+ simply execute within the same connection/transaction scope. They do not commit independently; any failure
437+ that escapes to the outer block will cause the outer transaction to roll back.
438+
439+ Example:
440+ ``` kotlin
441+ ctx.transaction {
442+ // Outer transaction started
443+ insertPerson(1 , " Joe" )
444+
445+ // Inner block sees the active transaction and reuses it
446+ ctx.transaction {
447+ insertPerson(2 , " Jim" )
448+ }
449+
450+ // If an exception is thrown here, both inserts are rolled back together
451+ }
452+ ```
453+
454+ ### Launching child coroutines inside a transaction
455+
456+ Since Terpal stores the connection/transaction in the CoroutineContext, launching child coroutines inside a
457+ transaction is safe as long as they remain children of the transaction scope. All of them will transparently
458+ see and use the same connection.
459+
460+ ``` kotlin
461+ ctx.transaction {
462+ coroutineScope {
463+ launch { Sql (" INSERT INTO Person (id, firstName, lastName) VALUES (3, 'Ann', 'Smith')" ).action().run () }
464+ launch { Sql (" INSERT INTO Person (id, firstName, lastName) VALUES (4, 'Bob', 'Jones')" ).action().run () }
465+ }
466+ // Both launches used the same connection/transaction
467+ }
468+ ```
469+
470+ If any child fails and the exception escapes the transaction block, the whole transaction is rolled back.
471+
401472
402473## Custom Parameters
403474When a variable used in a Sql clause e.g. ` Sql("... $dollar_sign_variable ...") ` it needs
0 commit comments