Bloccs.Effects.DB behaviour (bloccs v0.9.0)

Copy Markdown View Source

DB effect facade + backend behaviour.

Nodes call Bloccs.Effects.DB.insert/3 / get/3 / all/3 / one/3 against ctx.effects.db; the facade dispatches to the bound backend (DB.Mock in tests, DB.Ecto against a real repo in production). Scope enforcement ("table:action", where action is insert or read) is the backend's job. Backends @behaviour Bloccs.Effects.DB.

Reads

get/3 looks a row up by primary key (id); all/3 and one/3 filter by a restricted equality spec — a map of column => value, ANDed (deliberately not a query language). All three require a "table:read" scope and return rows as string-keyed maps, regardless of backend. Reads belong in effect_shell (a read touches the world); feed the result into pure logic by emitting the enriched payload to a downstream node, or read-and-reply in one shell for request/response.

Summary

Types

A column assignment for update/4: a literal value sets col = value; the tuple {:inc, n} sets col = col + n (a negative n decrements), which is an atomic read-free increment — the right primitive for counters.

An ANDed equality filter: %{column => value}. Empty matches all.

A step for the declarative transaction/2 form.

Functions

Fetch every row in table matching filter (empty filter = all rows).

Delete the row in table with primary key id. Returns {:ok, rows_deleted}.

Fetch the row in table whose primary key (id) is id, or nil.

Insert attrs into table through the bound DB backend.

Fetch the single row in table matching filter. Returns {:ok, nil} for no match and {:error, :multiple_results} if more than one matches.

Run a unit of work atomically. The second argument is either

Update the row in table with primary key id, applying changes (%{column => value | {:inc, n}}). Returns {:ok, rows_updated} (0 or 1).

Types

attrs()

@type attrs() :: keyword() | map()

cap()

@type cap() :: struct()

change()

@type change() :: term() | {:inc, number()}

A column assignment for update/4: a literal value sets col = value; the tuple {:inc, n} sets col = col + n (a negative n decrements), which is an atomic read-free increment — the right primitive for counters.

declaration()

@type declaration() :: %{allow: [String.t()]}

filter()

@type filter() :: map()

An ANDed equality filter: %{column => value}. Empty matches all.

op()

@type op() ::
  {:insert, table(), attrs()}
  | {:update, table(), id :: term(), %{optional(term()) => change()}}
  | {:delete, table(), id :: term()}

A step for the declarative transaction/2 form.

row()

@type row() :: %{optional(String.t()) => term()}

table()

@type table() :: atom() | String.t()

Callbacks

all(cap, table, filter)

@callback all(cap(), table(), filter()) :: {:ok, [row()]} | {:error, term()}

delete(cap, table, id)

@callback delete(cap(), table(), id :: term()) ::
  {:ok, non_neg_integer()} | {:error, term()}

get(cap, table, id)

@callback get(cap(), table(), id :: term()) :: {:ok, row() | nil} | {:error, term()}

insert(cap, table, attrs)

@callback insert(cap(), table(), attrs()) :: {:ok, map()} | {:error, term()}

new(declaration)

@callback new(declaration()) :: cap()

one(cap, table, filter)

@callback one(cap(), table(), filter()) ::
  {:ok, row() | nil} | {:error, :multiple_results | term()}

transaction(cap, arg2)

@callback transaction(cap(), (cap() -> {:ok, term()} | {:error, term()}) | [op()]) ::
  {:ok, term()} | {:error, term()}

update(cap, table, id, map)

@callback update(cap(), table(), id :: term(), %{optional(term()) => change()}) ::
  {:ok, non_neg_integer()} | {:error, term()}

Functions

all(cap, table, filter \\ %{})

@spec all(cap(), table(), filter()) :: {:ok, [row()]} | {:error, term()}

Fetch every row in table matching filter (empty filter = all rows).

delete(cap, table, id)

@spec delete(cap(), table(), term()) :: {:ok, non_neg_integer()} | {:error, term()}

Delete the row in table with primary key id. Returns {:ok, rows_deleted}.

get(cap, table, id)

@spec get(cap(), table(), term()) :: {:ok, row() | nil} | {:error, term()}

Fetch the row in table whose primary key (id) is id, or nil.

insert(cap, table, attrs)

@spec insert(cap(), table(), attrs()) :: {:ok, map()} | {:error, term()}

Insert attrs into table through the bound DB backend.

one(cap, table, filter)

@spec one(cap(), table(), filter()) :: {:ok, row() | nil} | {:error, term()}

Fetch the single row in table matching filter. Returns {:ok, nil} for no match and {:error, :multiple_results} if more than one matches.

transaction(cap, fun_or_ops)

@spec transaction(cap(), (cap() -> {:ok, term()} | {:error, term()}) | [op()]) ::
  {:ok, term()} | {:error, term()}

Run a unit of work atomically. The second argument is either:

  • a function fn db -> {:ok, result} | {:error, reason} enddb is the same capability, so DB calls inside run in the transaction; returning {:error, _} (or raising) rolls everything back; or

  • a list of ops (op/0) run in order, short-circuiting on the first {:error, _} (returned as {:error, {index, reason}}).

Returns {:ok, result} (the function's result, or the list of per-op results) or {:error, reason}. Each inner op is scope-checked like a standalone call.

update(cap, table, id, changes)

@spec update(cap(), table(), term(), %{optional(term()) => change()}) ::
  {:ok, non_neg_integer()} | {:error, term()}

Update the row in table with primary key id, applying changes (%{column => value | {:inc, n}}). Returns {:ok, rows_updated} (0 or 1).