View Source ActiveMemory.Operations (ActiveMemory v0.8.1)

The shared implementation of the table operations and store setup used by ActiveMemory.Store and ActiveMemory.ActiveRepo.

Every function takes the table module explicitly and dispatches to that table's configured adapter, applying the common validation, uuid handling, seeding and before_init logic. This keeps the single-table Store and the multi-table Repo sharing one implementation rather than duplicating it.

Link to this section Summary

Functions

Get every record in table, optionally ordered and paged.

Run the before_init methods for a store.

Count the records in table without reading them.

Create table in its configured backend.

Delete the record provided.

Delete every record in table, leaving the table itself in place.

Delete every record in table whose expires_at is at or before now (milliseconds). Used by the Store/ActiveRepo sweep to reclaim memory; reads already hide expired records, so this is only about freeing them.

Whether any record in table matches the query.

Like get/2 but raises ActiveMemory.NotFoundError when there is no such record.

Get the record whose primary key is key.

Like get_by/2 but raises ActiveMemory.NotFoundError when nothing matches.

Get the single record matching an attributes map.

Like one/2 but raises ActiveMemory.NotFoundError when nothing matches.

Get one record matching an attributes map or a match query. An expired record is treated as {:error, :not_found}.

Sort and page a list of records.

Like reload/2 but raises ActiveMemory.NotFoundError when the record is gone.

Re-read struct from table by its primary key.

Schedule the calling process's next expiry sweep when any of tables uses a ttl, and do nothing when none of them do.

Evaluate seed_file and write its records to table.

Get all records matching an attributes map or a match query, optionally ordered and paged. See order/2 for the options.

Delete every expired record from each of tables that uses a ttl.

Get one record matching the query, delete it, and return it. An expired record is treated as {:error, :not_found}.

Write a record to table.

Link to this section Functions

@spec all(
  atom(),
  keyword()
) :: [map()]

Get every record in table, optionally ordered and paged.

See order/2 for the :order_by, :limit and :offset options.

Link to this function

before_init(methods, module)

View Source
@spec before_init(:default | tuple() | list(), module()) :: {:ok, atom()}

Run the before_init methods for a store.

spec is :default, a single {method, args} tuple, or a list of such tuples. module is the module the methods are defined on (the Store or Repo).

Link to this function

count(table, opts \\ [])

View Source
@spec count(
  atom(),
  keyword()
) :: non_neg_integer()

Count the records in table without reading them.

The count comes from the table itself (:ets.info/2, :mnesia.table_info/2), so it is O(1) and does not copy records out of the table. It is the size of the backend table, not a count of records matching any application level rule:

  • On a table with a ttl it includes records that have expired but have not been swept yet, so it can exceed what the reads return. Pass sweep: true to delete those first and get a count that agrees with the reads, at the cost of a full pass over the table.
  • On a replicated Mnesia table it is the size of the replica this node reads from. Nodes whose replicas have diverged — see the majority option in ActiveMemory.Table — will report different counts.
@spec create_table(atom()) :: {:ok, :created | :recovered} | {:error, any()}

Create table in its configured backend.

Called by a Store or ActiveRepo as it starts. Returns {:ok, :created} for a new table, or {:ok, :recovered} when an existing one was reclaimed — an ETS table held by ActiveMemory.TableHeir across a crash, or a Mnesia table that was already loaded.

The table's configuration is validated first, so a schema that cannot work raises here rather than misbehaving later: a ttl with no expires_at field, a primary key ActiveMemory cannot generate or that is not the table key, or a composite primary key.

@spec delete(any(), atom()) :: :ok | {:error, any()}

Delete the record provided.

The record is matched in full by the adapter (:ets.delete_object/2, :mnesia.delete_object/3), so a struct that has diverged from the stored copy removes nothing and still returns :ok — deleting is idempotent and never reports whether a record was present. withdraw/2 is the query based, atomic alternative that returns {:error, :not_found} when nothing matched.

Returns :ok for a struct matching table or for nil, and {:error, :bad_schema} when the struct does not match table.

@spec delete_all(atom()) :: :ok | {:error, any()}

Delete every record in table, leaving the table itself in place.

Returns :ok. This is not transactional with respect to concurrent writes: a record written while the clear is in flight may survive it.

Link to this function

delete_expired(table, now)

View Source
@spec delete_expired(atom(), integer()) :: :ok

Delete every record in table whose expires_at is at or before now (milliseconds). Used by the Store/ActiveRepo sweep to reclaim memory; reads already hide expired records, so this is only about freeing them.

Link to this function

exists?(query, table, opts \\ [])

View Source
@spec exists?(map() | tuple(), atom(), keyword()) :: boolean()

Whether any record in table matches the query.

Unlike count/2 this is a scan, not an indexed lookup: a query has to be matched against every record's fields. It is a convenience over select/3, not a cheap existence check. Accepts sweep: true to reclaim expired records first; the answer itself is unaffected, since reads already ignore an expired record.

@spec get!(any(), atom()) :: map()

Like get/2 but raises ActiveMemory.NotFoundError when there is no such record.

@spec get(any(), atom()) :: {:ok, map()} | {:error, any()}

Get the record whose primary key is key.

The primary key is the table's first field — :uuid on a table using auto_generate_uuid: true, an Ecto schema's declared key, or the first field declared. Returns {:error, :not_found} when there is no such record.

@spec get_by!(map(), atom()) :: map()

Like get_by/2 but raises ActiveMemory.NotFoundError when nothing matches.

@spec get_by(map(), atom()) :: {:ok, map()} | {:error, any()}

Get the single record matching an attributes map.

Raises ActiveMemory.MultipleResultsError when more than one record matches, as Ecto.Repo.get_by/3 does.

@spec one!(map() | tuple(), atom()) :: map()

Like one/2 but raises ActiveMemory.NotFoundError when nothing matches.

@spec one(map() | tuple(), atom()) :: {:ok, map()} | {:error, any()}

Get one record matching an attributes map or a match query. An expired record is treated as {:error, :not_found}.

Raises ActiveMemory.MultipleResultsError when the query matches more than one record, mirroring Ecto.Repo.one/2. Use select/3 when many records are expected.

@spec order(
  [map()],
  keyword()
) :: [map()]

Sort and page a list of records.

Neither ETS nor Mnesia can order a result for us, so this sorts in the caller after reading — O(n log n) over the matched records, not an index backed sort. :limit and :offset are convenience pagination, not indexed pagination: every matched record is read and sorted first, and the offset records are then thrown away, so offset: 10_000, limit: 10 pays for all 10,010.

Options:

  • :order_by a field, {:asc | :desc, field}, or a list of either to break ties

  • :offset records to drop after ordering
  • :limit records to keep after the offset

Values are compared with the struct's own compare/2 when it has one, so Decimal, DateTime, NaiveDateTime, Date and Time fields order correctly rather than by Erlang term order.

@spec reload!(map(), atom()) :: map()

Like reload/2 but raises ActiveMemory.NotFoundError when the record is gone.

@spec reload(map(), atom()) :: {:ok, map()} | {:error, any()}

Re-read struct from table by its primary key.

Reads and writes match a record in full, so a struct held across a change can go stale. reload/2 gets the current copy. Returns {:error, :not_found} when the record is gone.

Link to this function

schedule_sweep(table, interval)

View Source
@spec schedule_sweep(atom() | [atom()], integer()) :: :ok

Schedule the calling process's next expiry sweep when any of tables uses a ttl, and do nothing when none of them do.

The ttl lookup happens here, at runtime, rather than while a Store or ActiveRepo compiles. Reading it at compile time would make every table a compile time dependency of its store, which breaks tooling that compiles the store's file without the table module loaded.

@spec seed(binary() | nil, atom()) :: {:ok, :seed_success} | {:error, any()}

Evaluate seed_file and write its records to table.

A nil seed_file is a no-op. Returns {:ok, :seed_success} or {:error, reason}.

Link to this function

select(query, table, opts \\ [])

View Source
@spec select(map() | tuple(), atom(), keyword()) :: {:ok, [map()]} | {:error, any()}

Get all records matching an attributes map or a match query, optionally ordered and paged. See order/2 for the options.

Returns {:error, :bad_select_query} for any other query shape.

Link to this function

sweep_expired(tables, now)

View Source
@spec sweep_expired([atom()], integer()) :: :ok

Delete every expired record from each of tables that uses a ttl.

Tables without a ttl are skipped, so a repo holding a mix of both only pays for the ones that expire.

@spec withdraw(map() | tuple(), atom()) :: {:ok, map()} | {:error, any()}

Get one record matching the query, delete it, and return it. An expired record is treated as {:error, :not_found}.

@spec write(map() | Ecto.Changeset.t(), atom()) ::
  {:ok, map()} | {:error, Ecto.Changeset.t() | any()}

Write a record to table.

Takes a struct or an Ecto.Changeset. A valid changeset is applied and its struct written; an invalid one returns {:error, changeset} with the changeset's action set to :insert, mirroring Ecto.Repo.insert/2 so a Phoenix form renders the errors.

Any field the table declares as autogenerated and which is still nil is populated: a uuid attribute, or an Ecto schema's autogenerated primary key and timestamps(). When the table has a ttl the record's expires_at is stamped from the current time. Returns {:error, :bad_schema} when the struct does not match table.