kura_ets

View Source

ETS backend for kura — an in-memory, zero-dependency backend in the spirit of etso (Ecto's ETS adapter), for tests, caches, and small in-memory datasets.

Unlike the SQL backends (kura_postgres, kura_sqlite), this backend does not compile queries to SQL. The dialect wraps the portable #kura_query{} AST into an opaque {kura_ets, Plan} term, and the driver interprets that plan directly against per-table ETS tables.

Usage

%% rebar.config
{deps, [kura, kura_ets]}.

%% sys.config
[{kura, [
    {repos, #{
        my_repo => #{
            backend => kura_backend_ets
        }
    }}
]}].

Everything else is standard kura:

CS = kura_changeset:cast(my_user, #{}, #{id => 1, name => ~"alice"}, [id, name]),
{ok, Row} = kura_repo_worker:insert(my_repo, CS),

Q = kura_query:order_by(
    kura_query:where(kura_query:from(my_user), {active, true}),
    [{name, asc}]
),
{ok, Rows} = kura_repo_worker:all(my_repo, Q).

No migrations are needed (or possible): tables are created on first use and data lives only as long as the pool. kura_ets_pool:reset(PoolName) clears every table, which is handy between tests.

Modules

ModuleRole
kura_backend_etsBackend aggregator (backend => kura_backend_ets)
kura_ets_poolkura_pool impl; gen_server-owned table registry
kura_ets_driverkura_driver impl; executes plans
kura_ets_dialectkura_dialect impl; emits {kura_ets, Plan} terms
kura_ets_queryPlan interpreter (filter / sort / project / aggregate)

Supported

  • insert / insert_all, including on_conflict (nothing, replace_all, {replace, Fields}) — conflict detection is on the primary key only
  • get / get_by / one / all / exists / reload
  • Where conditions: {F, V}, =, !=, <, >, <=, >=, in, not_in, composite-column in, between, like, ilike, is_nil, is_not_nil, nested and / or / not
  • order_by, limit, offset, distinct, field selection
  • Primary-key point lookups short-circuit to ets:lookup/2 (O(1); the storage key is the primary key), including composite keys
  • Aggregates: count, sum, avg, min, max
  • update / delete by primary key, update_all / delete_all
  • Soft delete (deleted_at), schema defaults, composite primary keys
  • Attribute-based multitenancy
  • Sandbox checkout (kura_sandbox)

Not supported

Queries using these return {error, {kura_ets_unsupported, _}}; raw SQL returns {error, {kura_ets, raw_sql_unsupported}}:

  • Joins, group_by / having, CTEs, union / intersect / except, subqueries, fragments, window functions, full-text matches, row locks, prefix-based multitenancy
  • Raw SQL — including migrations, many_to_many association persistence, and optimistic locking (kura builds SQL strings directly for those)
  • Transactions: transaction/2 runs the fun without atomicity — there is no rollback. The backend does not declare the transactions capability.

SQL semantics notes

  • Rows store kura-dumped values; comparison values in queries are dumped through the schema's types before comparing, matching how a SQL client encodes parameters.
  • NULL (undefined / missing) never satisfies a comparison; only is_nil / is_not_nil match it.
  • Duplicate primary keys produce a PG-shaped 23505 unique-violation error, so kura_changeset constraint mapping behaves as with SQL backends.

Development

rebar3 compile
rebar3 eunit
rebar3 fmt

kura is pulled from hex.pm. To develop against an unreleased local kura, a _checkouts/kura symlink works, but note rebar3 then treats kura as a project app and rebar3 eunit will also run kura's own test suite (which needs a Postgres container) — scope with rebar3 eunit --app=kura_ets in that case.

Guides in docs/:

Looking for caching? That is application policy, not backend capability — see the sibling library kura_cache (keyed memoization with TTL, schema-keyed row cache, cluster invalidation hook).