# `SuperCache.Storage`
[🔗](https://github.com/ohhi-vn/super_cache/blob/main/lib/storage/storage_api.ex#L3)

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)
    # => []

# `delete`

```elixir
@spec delete(any(), atom() | :ets.tid()) :: true
```

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`

```elixir
@spec delete_all(atom() | :ets.tid()) :: true
```

Delete all records in `partition`.

## Examples

    Storage.delete_all(:my_table)
    # => true

# `delete_match`

```elixir
@spec delete_match(atom() | tuple(), atom() | :ets.tid()) :: true
```

Delete all records matching `pattern` using `:ets.match_delete/2`.

Pattern semantics are the same as `get_by_match/2`.

## Examples

    # Remove all expired sessions
    Storage.delete_match({:session, :_, :expired}, :my_table)
    # => true

# `get`

```elixir
@spec get(any(), atom() | :ets.tid()) :: [tuple()]
```

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)
    # => []

# `get_by_match`

```elixir
@spec get_by_match(atom() | tuple(), atom() | :ets.tid()) :: [[any()]]
```

Pattern match using `:ets.match/2`.

Returns a list of binding lists. Wildcards are `:_`; captures are
`:"$1"`, `:"$2"`, etc.

## 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)

# `get_by_match_object`

```elixir
@spec get_by_match_object(atom() | tuple(), atom() | :ets.tid()) :: [tuple()]
```

Pattern match using `:ets.match_object/2`.

Returns full matching tuples rather than capture binding lists.

## 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)
    # => []

# `insert_new`

```elixir
@spec insert_new(tuple(), atom() | :ets.tid()) :: boolean()
```

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.

# `put`

```elixir
@spec put([tuple()] | tuple(), atom() | :ets.tid()) :: true
```

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

# `scan`

```elixir
@spec scan((any(), any() -&gt; any()), any(), atom() | :ets.tid()) :: any()
```

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)

# `start`

```elixir
@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

# `stats`

```elixir
@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}

# `stop`

```elixir
@spec stop(pos_integer()) :: :ok
```

Delete all `num` ETS partitions.

Called by `Bootstrap.stop/0` during system shutdown.

## Examples

    SuperCache.Storage.stop(4)
    # => :ok

# `take`

```elixir
@spec take(any(), atom() | :ets.tid()) :: [tuple()]
```

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)
    # => []

# `update_counter`

# `update_counter`

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

# `update_element`

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).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
