TransactionPlan
A sequence of operations to run as one transaction, assembled before any of it runs.
A plan is for the case a transaction { } block does not cover: the sequence itself is data. Where one layer decides what has to happen - a screen with a variable number of rows on it, a service turning a request into operations - and another runs it, the block form would mean passing a lambda that closes over everything it touched. A plan is a value; it can be built up, counted, inspected and handed on.
Steps run in the order they were added, and a step may use what an earlier one produced through the StepHandle that add returned:
val plan = TransactionPlan()
val edictId = plan.add(
db.insertInto("edicts").values(edict).returning("id")
.asStep().fetchFieldStrict<Int>(edict)
)
for (item in levy) {
plan.add(
db.insertInto("edict_items").values(listOf("edict_id", "province_id", "amount"))
.asStep().update(
"edict_id" to edictId.value(),
"province_id" to item.provinceId,
"amount" to item.amount
)
)
}
val results = db.executeTransactionPlan(plan)Where the sequence is fixed and written out in one place, a transaction { } block says the same thing in fewer moving parts and with the values in plain Kotlin locals. Reach for a plan when the sequence is not known where it is executed.
Executing a plan does not consume it: the steps are copied out and the results kept in a map of the run's own, with nothing written back. The same plan runs again unchanged, which is what makes retrying a serialization failure or a deadlock a loop around executeTransactionPlan rather than a rebuild - and each run resolves its handles against its own results, so the second run reads what the second run produced.
Types
A step and the handle it was filed under, which is all the executor needs of either.
Functions
Appends a step, and returns the handle later steps use to refer to what it will produce.
Appends every step of other, in its order, after the steps already here.