Packages
ferricstore
0.11.5
0.11.14
0.11.12
0.11.11
0.11.10
0.11.9
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.0
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.2.0
0.1.0
FerricFlow durable workflows and queues with native-protocol storage, Raft durability, and Bitcask persistence.
Current section
Files
Jump to
Current section
Files
lib/ferricstore/tx.ex
defmodule FerricStore.Tx do
@moduledoc """
Transaction accumulator for executing multiple FerricStore commands atomically.
Used with `FerricStore.multi/1` to batch multiple operations. Commands are
accumulated in reverse order and executed sequentially when the transaction
completes.
## Usage
FerricStore.multi(fn tx ->
tx
|> FerricStore.Tx.set("key1", "val1")
|> FerricStore.Tx.get("key1")
end)
"""
@type command ::
{:set, binary(), binary(), keyword()}
| {:get, binary()}
| {:del, binary()}
| {:incr, binary()}
| {:incr_by, binary(), integer()}
| {:hset, binary(), map()}
| {:hget, binary(), binary()}
| {:lpush, binary(), [binary()]}
| {:rpush, binary(), [binary()]}
| {:sadd, binary(), [binary()]}
| {:zadd, binary(), [{number(), binary()}]}
| {:expire, binary(), non_neg_integer()}
@type t :: %__MODULE__{commands: [command()]}
defstruct commands: []
alias Ferricstore.Commands.PreparedAccumulatorCommand
@doc "Creates a new empty transaction."
@spec new() :: t()
def new, do: %__MODULE__{}
@doc "Adds a SET command to the transaction."
@spec set(t(), binary(), binary(), keyword()) :: t()
def set(%__MODULE__{} = tx, key, value, opts \\ []) do
%{tx | commands: [{:set, key, value, opts} | tx.commands]}
end
@doc "Adds a GET command to the transaction."
@spec get(t(), binary()) :: t()
def get(%__MODULE__{} = tx, key) do
%{tx | commands: [{:get, key} | tx.commands]}
end
@doc "Adds a DEL command to the transaction."
@spec del(t(), binary()) :: t()
def del(%__MODULE__{} = tx, key) do
%{tx | commands: [{:del, key} | tx.commands]}
end
@doc "Adds an INCR command to the transaction."
@spec incr(t(), binary()) :: t()
def incr(%__MODULE__{} = tx, key) do
%{tx | commands: [{:incr, key} | tx.commands]}
end
@doc "Adds an INCRBY command to the transaction."
@spec incr_by(t(), binary(), integer()) :: t()
def incr_by(%__MODULE__{} = tx, key, amount) do
%{tx | commands: [{:incr_by, key, amount} | tx.commands]}
end
@doc "Adds an HSET command to the transaction."
@spec hset(t(), binary(), map()) :: t()
def hset(%__MODULE__{} = tx, key, fields) do
%{tx | commands: [{:hset, key, fields} | tx.commands]}
end
@doc "Adds an HGET command to the transaction."
@spec hget(t(), binary(), binary()) :: t()
def hget(%__MODULE__{} = tx, key, field) do
%{tx | commands: [{:hget, key, field} | tx.commands]}
end
@doc "Adds an LPUSH command to the transaction."
@spec lpush(t(), binary(), [binary()]) :: t()
def lpush(%__MODULE__{} = tx, key, elements) do
%{tx | commands: [{:lpush, key, elements} | tx.commands]}
end
@doc "Adds an RPUSH command to the transaction."
@spec rpush(t(), binary(), [binary()]) :: t()
def rpush(%__MODULE__{} = tx, key, elements) do
%{tx | commands: [{:rpush, key, elements} | tx.commands]}
end
@doc "Adds a SADD command to the transaction."
@spec sadd(t(), binary(), [binary()]) :: t()
def sadd(%__MODULE__{} = tx, key, members) do
%{tx | commands: [{:sadd, key, members} | tx.commands]}
end
@doc "Adds a ZADD command to the transaction."
@spec zadd(t(), binary(), [{number(), binary()}]) :: t()
def zadd(%__MODULE__{} = tx, key, score_member_pairs) do
%{tx | commands: [{:zadd, key, score_member_pairs} | tx.commands]}
end
@doc "Adds an EXPIRE command to the transaction."
@spec expire(t(), binary(), non_neg_integer()) :: t()
def expire(%__MODULE__{} = tx, key, ttl_ms) do
%{tx | commands: [{:expire, key, ttl_ms} | tx.commands]}
end
@doc """
Executes all accumulated transaction commands atomically.
Groups commands by shard. If all target a single shard, dispatches them
as a batch to the shard GenServer (atomic, no interleaving). If commands
span multiple shards, returns a CROSSSLOT error.
"""
@spec execute(t()) :: [term()] | FerricStore.write_error()
def execute(%__MODULE__{commands: []}), do: []
def execute(%__MODULE__{commands: commands}) do
with :ok <- authorize_commands(commands),
{:ok, queue} <- commands |> Enum.reverse() |> PreparedAccumulatorCommand.prepare_all() do
Ferricstore.Transaction.Coordinator.execute(queue, %{}, nil)
end
end
defp authorize_commands(commands) do
commands
|> Enum.map(&elem(&1, 1))
|> Ferricstore.Flow.InternalKey.authorize_public()
end
end