lode/multi

Ecto.Multi — compose several named operations into one transaction.

Each step runs in order inside the repo’s transaction, with access to the results of earlier steps (keyed by name). If any step fails, the transaction rolls back and transaction returns the failing step’s name and error.

Results are heterogeneous, so they’re stored as Dynamic and recovered with the typed get helper (the value round-trips unchanged, so the coerce is safe).

Beyond the changeset steps (insert/update/delete) and run, there are bulk steps (insert_all/update_all/delete_all — the stored result is the affected count), query steps (one/all/exists), and put for injecting a precomputed value. Multis compose with merge (build steps from earlier results), append, and prepend. Step names must be unique across the whole composed multi: transaction rejects a duplicate with Error(MultiFailure(..)) before running any step (a duplicate introduced by merge fails at the merge point and rolls back). Anything not covered by a named step — returning-variants, expression update_all_sets, changes-dependent queries — goes through a run step, which receives the transaction-scoped repo.

multi.new() |> multi.insert(“user”, user_schema, user_changeset) |> multi.run(“welcome”, fn(repo, changes) { let assert Ok(user) = multi.get(changes, “user”) send_welcome(repo, user) }) |> multi.transaction(repo)

Types

Accumulated results of completed steps, keyed by step name.

pub type Changes =
  dict.Dict(String, dynamic.Dynamic)
pub opaque type Multi

A failed multi: the step that failed and why.

pub type MultiFailure {
  MultiFailure(name: String, reason: error.LodeError)
}

Constructors

Values

pub fn all(
  multi m: Multi,
  name name: String,
  schema s: schema.Schema(row),
  query q: query.Query,
) -> Multi

Load every row of a query under name (Ecto’s Multi.all, via repo.all). The stored result is a List(row).

pub fn append(multi m: Multi, other other: Multi) -> Multi

Run other’s steps after multi’s (Ecto’s Multi.append). Duplicate step names across the two are rejected by transaction.

pub fn delete(
  multi m: Multi,
  name name: String,
  schema s: schema.Schema(row),
  row row: row,
) -> Multi

Delete a struct under name.

pub fn delete_all(
  multi m: Multi,
  name name: String,
  query q: query.Query,
) -> Multi

Bulk-delete rows matching query under name (Ecto’s Multi.delete_all, via repo.delete_all). The stored result is the affected count (Int).

pub fn exists(
  multi m: Multi,
  name name: String,
  query q: query.Query,
) -> Multi

Store whether any row matches query under name (Ecto’s Multi.exists?, via repo.exists). The stored result is a Bool.

pub fn get(
  changes changes: dict.Dict(String, dynamic.Dynamic),
  name name: String,
) -> Result(a, Nil)

Recover a typed result for a named step.

pub fn insert(
  multi m: Multi,
  name name: String,
  schema s: schema.Schema(row),
  changeset cs: changeset.Changeset(row),
) -> Multi

Insert a changeset under name.

pub fn insert_all(
  multi m: Multi,
  name name: String,
  schema s: schema.Schema(row),
  rows rows: List(row),
) -> Multi

Bulk-insert typed rows under name (Ecto’s Multi.insert_all, via repo.insert_all). The stored result is the inserted count (Int).

pub fn merge(
  multi m: Multi,
  with build: fn(dict.Dict(String, dynamic.Dynamic)) -> Multi,
) -> Multi

Merge a dynamically-built multi (Ecto’s Multi.merge): at execution time build receives the results so far and its multi’s steps run at this position, before any remaining steps. A merged step name that collides with any other step in the composed multi fails the transaction at the merge point (and rolls back).

pub fn new() -> Multi

An empty multi.

pub fn one(
  multi m: Multi,
  name name: String,
  schema s: schema.Schema(row),
  query q: query.Query,
) -> Multi

Fetch at most one row under name (Ecto’s Multi.one, via repo.one). The stored result is an Option(row).

pub fn prepend(multi m: Multi, other other: Multi) -> Multi

Run other’s steps before multi’s (Ecto’s Multi.prepend). Duplicate step names across the two are rejected by transaction.

pub fn put(
  multi m: Multi,
  name name: String,
  value value: a,
) -> Multi

Store a precomputed value under name (Ecto’s Multi.put) — no repo access, cannot fail. Useful for injecting parameters later steps read via get.

pub fn run(
  multi m: Multi,
  name name: String,
  with f: fn(repo.Repo, dict.Dict(String, dynamic.Dynamic)) -> Result(
    a,
    error.LodeError,
  ),
) -> Multi

Run an arbitrary function with access to the repo and prior results.

pub fn transaction(
  multi m: Multi,
  repo r: repo.Repo,
) -> Result(dict.Dict(String, dynamic.Dynamic), MultiFailure)

Run all steps in one transaction. On success, returns the accumulated results; on failure, the failing step’s name and error (and rolls back).

Step names must be unique across the whole composed multi (including append/prepended and merged steps): a duplicate returns Error(MultiFailure(name: <duplicate>, reason: QueryError(..))) — before any step runs for statically-known names, at the merge point (rolling back) for names a merge introduces. DIVERGENCE: Ecto raises at add/merge time; lode raises nothing, so the check runs here. See the “Divergences from Ecto” guide (multi-breadth).

pub fn update(
  multi m: Multi,
  name name: String,
  schema s: schema.Schema(row),
  changeset cs: changeset.Changeset(row),
) -> Multi

Update a changeset under name.

pub fn update_all(
  multi m: Multi,
  name name: String,
  query q: query.Query,
  set sets: List(#(String, value.Value)),
) -> Multi

Bulk-update rows matching query with literal column values under name (Ecto’s Multi.update_all, via repo.update_all). The stored result is the affected count (Int). For expression-valued sets, use a run step with repo.update_all_set.

Search Document