# kura_ets

ETS backend for [kura](https://github.com/Taure/kura) — an in-memory,
zero-dependency backend in the spirit of [etso](https://hex.pm/packages/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

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

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

Everything else is standard kura:

```erlang
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

| Module | Role |
|---|---|
| `kura_backend_ets` | Backend aggregator (`backend => kura_backend_ets`) |
| `kura_ets_pool` | `kura_pool` impl; gen_server-owned table registry |
| `kura_ets_driver` | `kura_driver` impl; executes plans |
| `kura_ets_dialect` | `kura_dialect` impl; emits `{kura_ets, Plan}` terms |
| `kura_ets_query` | Plan 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

```sh
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](https://github.com/longlene/kura_cache) (keyed memoization
with TTL, schema-keyed row cache, cluster invalidation hook).
