View Source CubDB.Tx (cubdb v2.0.2)
The CubDB.Tx
module contains functions to read or write within atomic
transactions created with CubDB.transaction/2
.
The functions in this module mirror the ones with the same name in the CubDB
module, but work on transactions instead of on the live database.
Link to this section Summary
Functions
Deletes all entries, resulting in an empty database.
Deletes the entry associated to key
from the transaction.
Fetches the value for the given key
from the transaction, or returns
:error
if key
is not present.
Gets the value associated to key
from the transaction.
Returns whether an entry with the given key
exists in the transaction.
Writes an entry in the transaction, associating key
to value
.
Writes an entry in the transaction, associating key
to value
, only if
key
is not yet present.
If key
was not written since the point in time represented by snapshot
,
returns :unchanged
. Otherwise, behaves like fetch/2
.
Selects a range of entries from the transaction, returning a lazy stream.
Returns the number of entries present in the transaction.
Link to this section Types
@type t() :: transaction()
Link to this section Functions
Deletes all entries, resulting in an empty database.
The deletion is much more performant than deleating each entry manually.
It works like CubDB.clear/1
, but on a transaction.
If a compaction is in progress when clear/1
is called, and the transaction
is committed, the compaction is halted, and a new one started immediately
after. The new compaction should be very fast, as the database is empty as a
result of the clear/1
call.
Like other functions in this module, it does not mutate the transaction in place, and instead returns a modified transaction.
Deletes the entry associated to key
from the transaction.
If key
was not present, nothing is done.
Like other functions in this module, it does not mutate the transaction in place, and instead returns a modified transaction.
@spec fetch(t(), CubDB.key()) :: {:ok, CubDB.value()} | :error
Fetches the value for the given key
from the transaction, or returns
:error
if key
is not present.
If the snapshot contains an entry with the given key
and value value
, it
returns {:ok, value}
. If key
is not found, it returns :error
.
It works the same as CubDB.fetch/2
, but reads from a transaction instead of
the live database.
@spec get(t(), CubDB.key(), any()) :: CubDB.value()
Gets the value associated to key
from the transaction.
If no value is associated with key
, default
is returned (which is nil
,
unless specified otherwise).
It works the same as CubDB.get/3
, but reads from a transaction instead of
the live database.
Returns whether an entry with the given key
exists in the transaction.
It works the same as CubDB.has_key?/2
, but reads from a transaction instead
of the live database.
@spec put(t(), CubDB.key(), CubDB.value()) :: t()
Writes an entry in the transaction, associating key
to value
.
If key
was already present, it is overwritten.
Like other functions in this module, it does not mutate the transaction in place, and instead returns a modified transaction.
@spec put_new(t(), CubDB.key(), CubDB.value()) :: t() | {:error, :exists}
Writes an entry in the transaction, associating key
to value
, only if
key
is not yet present.
If key
is already present, it does not change it, and returns {:error, :exists}
.
Like other functions in this module, it does not mutate the transaction in place, and instead returns a modified transaction.
@spec refetch(t(), CubDB.key(), CubDB.Snapshot.t()) :: :unchanged | {:ok, CubDB.value()} | :error
If key
was not written since the point in time represented by snapshot
,
returns :unchanged
. Otherwise, behaves like fetch/2
.
Checking if the key was written is done without fetching the whole entry, but
instead only checking index pages. This makes refetch/3
useful as a
performance optimization in cases when one needs to verify if an entry changed
with respect to a snapshot: using refetch/3
can save some disk access if
CubDB
is able to determine that the key was not written since the snapshot.
In some situations, such as when the entry is not present in the database, or
when a compaction completed after snapshot
, the function cannot determine if
the entry was written or not since snapshot
, and therefore fetches it. In
other words, refetch/3
is a performance optimization to save some disk reads
when possible, but it might fetch an entry even if it technically did not
change.
example
Example
The function refetch/3
can be useful when implementing optimistic
concurrency control. Suppose, for example, that computing the updated value of
some entry is a slow operation. In order to avoid holding a transaction open
for too long, one could compute the update outside of the transaction, and
then check if the value on which the update was computed is still the same: if
so, commit the update, otherwise perform the computation again. This kind of
optimistic concurrency control can use refetch/3
to avoid reading the value
from disk when nothing has changed:
def update_optimistically(db, key) do
outcome = CubDB.with_snapshot(db, fn snap ->
{:ok, value} = CubDB.Snapshot.fetch(snap, key)
# Perform the slow calculation outside of the transaction
new_value = some_slow_calculation(value)
# In a transaction, check if the value changed, and update it if not
write_if_unchanged(db, key, value, snap)
end)
# Depending on the outcome, return or recompute
case outcome do
:recompute ->
update_optimistically(db, key)
:ok ->
:ok
end
end
defp write_if_unchanged(db, key, value, snap) do
CubDB.transaction(db, fn tx ->
# Check if the value changed in the meanwhile
case CubDB.Tx.refetch(tx, key, snap) do
:unchanged ->
# The entry was not written since we last read it. Commit the
# new value and return :ok
{:commit, CubDB.Tx.put(tx, key, new_value), :ok}
{:ok, ^value} ->
# The entry was written, but its value did not change. Commit the
# new value and return :ok
{:commit, CubDB.Tx.put(tx, key, new_value), :ok}
_ ->
# The entry changed since we last read it, cancel the
# transaction and return :recompute
{:cancel, :recompute}
end
end)
end
@spec select(t(), [CubDB.select_option()]) :: Enumerable.t()
Selects a range of entries from the transaction, returning a lazy stream.
The lazy stream should be evaluated within the transaction scope, or a
RuntimeError
will be raised.
It works the same and accepts the same options as CubDB.select/2
, but reads
from a transaction instead of the live database.
@spec size(t()) :: non_neg_integer()
Returns the number of entries present in the transaction.
It works the same as CubDB.size/1
, but works on a transaction instead of the
live database.