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

@spec clear(t()) :: t()

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.

@spec delete(t(), CubDB.key()) :: t()

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.

Link to this function

get(tx, key, default \\ nil)

View Source
@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.

@spec has_key?(t(), CubDB.key()) :: boolean()

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.

Link to this function

refetch(tx, key, snapshot)

View Source
@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
Link to this function

select(tx, options \\ [])

View Source
@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.