Thin wrapper around :ets that provides the read/write/delete primitives
used throughout SuperCache.
All functions accept either an atom (named ETS table) or an :ets.tid()
(anonymous table reference) as the partition argument.
This module is intentionally low-level. Application code should go
through the higher-level SuperCache, SuperCache.KeyValue,
SuperCache.Queue, etc., not through this module directly.
Key position
The ETS keypos is set from :key_pos config during table creation
(EtsHolder.create_table/1). The keypos is 1-based in ETS, so a
:key_pos of 0 corresponds to keypos: 1.
Concurrency
All tables are created with {:write_concurrency, true} and
{:read_concurrency, true}. Multiple processes can read and write
the same partition concurrently without external locking, with the
exception of structural mutations in Queue and Stack which use a
soft application-level lock (see those modules).
Example
alias SuperCache.Storage
# Assuming a table named :my_table already exists:
Storage.put({:user, 1, "Alice"}, :my_table)
Storage.get(:user, :my_table)
# => [{:user, 1, "Alice"}]
Storage.delete(:user, :my_table)
Storage.get(:user, :my_table)
# => []
Summary
Functions
Delete the record at key.
Delete all records in partition.
Delete all records matching pattern using compiled match spec.
Look up all records with key in partition.
Pattern match using compiled match spec via :ets.select/2.
Pattern match using compiled match spec via :ets.select/2.
Typed version of get_by_match_object/2 that returns typed records.
Typed version of get/2 that returns records of a specific type.
Insert term only if no record with the same key exists.
Insert one or more tuples into partition.
Fold over all records in partition using :ets.foldl/3.
Create num ETS partitions, named <prefix>_0 through <prefix>_{num-1}.
Return {partition, record_count} for partition.
Delete all num ETS partitions.
Atomically remove and return the record at key.
Atomically increment or decrement a counter field.
Update one or more fields in an existing record at key.
Functions
Delete the record at key.
Always succeeds (no-op when the key does not exist).
Examples
Storage.delete(:session_tok, :my_table)
# => true
Delete all records in partition.
Examples
Storage.delete_all(:my_table)
# => true
@spec delete_match(atom() | tuple(), atom() | :ets.tid()) :: non_neg_integer()
Delete all records matching pattern using compiled match spec.
Pattern semantics are the same as get_by_match/2.
Uses MatchSpec.delete_match/1 to create and compile the match spec.
Examples
# Remove all expired sessions
Storage.delete_match({:session, :_, :expired}, :my_table)
# => non_neg_integer (number of deleted records)
Look up all records with key in partition.
Returns a list of tuples (empty when the key does not exist).
Examples
Storage.get(:user, :my_table)
# => [{:user, 1, "Alice"}]
Storage.get(:missing, :my_table)
# => []
Pattern match using compiled match spec via :ets.select/2.
Returns a list of binding lists. Wildcards are :_; captures are
:"$1", :"$2", etc.
Uses MatchSpec.match/2 to create and compile the match spec,
which is more efficient than :ets.match/2 for repeated queries.
Examples
Storage.get_by_match({:user, :"$1", :admin}, :my_table)
# => [[1], [42]] (ids of admin users)
Storage.get_by_match({:session, :_, :expired}, :my_table)
# => [] (no expired sessions)
Pattern match using compiled match spec via :ets.select/2.
Returns full matching tuples rather than capture binding lists.
Uses MatchSpec.match_object/1 to create and compile the match spec.
Examples
Storage.get_by_match_object({:user, :_, :admin}, :my_table)
# => [{:user, 1, :admin}, {:user, 42, :admin}]
Storage.get_by_match_object({:user, :_, :banned}, :my_table)
# => []
Typed version of get_by_match_object/2 that returns typed records.
Examples
Storage.get_by_match_object_typed(MyRecord, {:user, :_, :admin}, :my_table)
Typed version of get/2 that returns records of a specific type.
Uses a callback module to define the record type and pattern matching.
Examples
defmodule MyRecord do
@type t :: {atom, integer, String.t()}
def pattern, do: {:"$1", :"$2", :"$3"}
def from_tuple(tuple), do: tuple
end
Storage.get_typed(MyRecord, :my_key, :my_table)
Insert term only if no record with the same key exists.
Returns true on success, false if the key is already present.
Only meaningful for :set / :ordered_set tables.
Used by Queue and Stack as a compare-and-swap for initialisation.
Insert one or more tuples into partition.
For :set / :ordered_set tables, inserting a record whose key already
exists overwrites the previous record.
Examples
Storage.put({:session, "tok-1", :active}, :my_table)
Storage.put([{:a, 1}, {:b, 2}], :my_table) # batch insert
Fold over all records in partition using :ets.foldl/3.
fun/2 receives (record, accumulator) and must return the new
accumulator.
Examples
Storage.scan(fn {_, score}, acc -> acc + score end, 0, :my_table)
# => 1024 (sum of all scores)
Storage.scan(fn _rec, acc -> acc + 1 end, 0, :my_table)
# => 50 (count of records)
@spec start(pos_integer()) :: :ok
Create num ETS partitions, named <prefix>_0 through <prefix>_{num-1}.
Called by Bootstrap.start!/1 during system startup.
Examples
SuperCache.Storage.start(4)
# => :ok
@spec stats(atom() | :ets.tid()) :: {atom() | :ets.tid(), non_neg_integer() | :undefined}
Return {partition, record_count} for partition.
Uses :ets.info(partition, :size) which is an O(1) operation thanks
to the :decentralized_counters option set during table creation.
Examples
Storage.stats(:my_table)
# => {:my_table, 1024}
Storage.stats(:nonexistent)
# => {:nonexistent, :undefined}
@spec stop(pos_integer()) :: :ok
Delete all num ETS partitions.
Called by Bootstrap.stop/0 during system shutdown.
Examples
SuperCache.Storage.stop(4)
# => :ok
Atomically remove and return the record at key.
Returns [tuple] (empty when the key does not exist). The removal and
the return are a single atomic ETS operation — no other process can
observe the record between the read and the delete.
Used by Queue and Stack for lock-free counter management.
Examples
Storage.take(:session_tok, :my_table)
# => [{:session_tok, "active"}]
Storage.take(:missing, :my_table)
# => []
Atomically increment or decrement a counter field.
counter_spec follows the :ets.update_counter/3 convention.
default is inserted when the key does not yet exist (four-argument form).
Update one or more fields in an existing record at key.
element_spec follows the :ets.update_element/3 convention:
{position, new_value} or [{position, new_value}, …].
default is inserted as a new record when the key does not yet exist
(requires the four-argument form).