# `ExDataSketch.Storage.Ecto`
[🔗](https://github.com/thanos/ex_data_sketch/blob/main/lib/ex_data_sketch/storage/ecto.ex#L2)

Ecto-backed persistence for sketches.

Stores serialized EXSK v2 frames in a SQL database via Ecto. This backend
is useful for applications already using Ecto for persistence, or when
sketches need to survive process and node restarts without managing ETS/DETS.

## Dependency

This module requires `:ecto_sql`. If Ecto is not available, calls will
raise a clear error directing you to add the dependency.

## Setup

1. Add the migration to your application:

     mix ex_data_sketch.gen.migration --repo MyApp.Repo

2. Run the migration:

     mix ecto.migrate

3. Use the storage API:

     ExDataSketch.Storage.Ecto.save(sketch, MyApp.Repo, "cardinality:2024-01")
     {:ok, sketch} = ExDataSketch.Storage.Ecto.load(ExDataSketch.HLL, MyApp.Repo, "cardinality:2024-01")

> #### Non-executable examples {: .info}
>
> The examples below use `MyApp.Repo` which must be configured in your
> application. They are included for documentation purposes and will not
> run in the standard test suite.

## Examples

    # Save a sketch
    :ok = ExDataSketch.Storage.Ecto.save(sketch, MyApp.Repo, "cardinality:2024-01")

    # Load a sketch
    {:ok, sketch} = ExDataSketch.Storage.Ecto.load(ExDataSketch.HLL, MyApp.Repo, "cardinality:2024-01")

    # Atomic merge (uses Ecto transaction)
    :ok = ExDataSketch.Storage.Ecto.merge(partial, MyApp.Repo, "cardinality:2024-01")

    # Delete
    :ok = ExDataSketch.Storage.Ecto.delete(MyApp.Repo, "cardinality:2024-01")

# `delete`

```elixir
@spec delete(module(), ExDataSketch.Storage.key()) :: :ok
```

Deletes a sketch from the database by key.

## Arguments

- `repo` -- the Ecto repo module.
- `key` -- the key to delete.

## Returns

`:ok` always (even if the key did not exist).

## Examples

    iex> ExDataSketch.Storage.Ecto.delete(MyApp.Repo, "hll:test")
    :ok

# `ecto_available?`

```elixir
@spec ecto_available?() :: boolean()
```

Returns whether Ecto is available at runtime.

## Examples

    iex> is_boolean(ExDataSketch.Storage.Ecto.ecto_available?())
    true

# `load`

```elixir
@spec load(module(), module(), ExDataSketch.Storage.key()) ::
  {:ok, struct()} | {:error, :not_found | term()}
```

Loads a sketch from the database by key.

Finds the most recent entry for the given key and deserializes it using
the given sketch module's `deserialize/1` function.

## Arguments

- `sketch_module` -- the sketch module atom (e.g., `ExDataSketch.HLL`).
- `repo` -- the Ecto repo module.
- `key` -- the key to look up.

## Returns

- `{:ok, sketch}` on success.
- `{:error, :not_found}` if no entry exists for the key.
- `{:error, %DeserializationError{}}` if the stored binary is corrupted.
- `{:error, reason}` on database failure.

## Raises

- `RuntimeError` if Ecto is not available.

## Examples

    iex> {:ok, loaded} = ExDataSketch.Storage.Ecto.load(ExDataSketch.HLL, MyApp.Repo, "hll:test")
    iex> ExDataSketch.HLL.estimate(loaded) > 0.0
    true

# `merge`

```elixir
@spec merge(struct(), module(), ExDataSketch.Storage.key()) :: :ok | {:error, term()}
```

Atomically merges a sketch into the persisted value at the given key.

Uses an Ecto transaction for atomicity. If no sketch exists at the key,
this is equivalent to `save/3`.

## Arguments

- `sketch` -- the sketch to merge.
- `repo` -- the Ecto repo module.
- `key` -- the key whose persisted sketch to merge into.

## Returns

`:ok` on success, `{:error, reason}` on failure.

## Raises

- `RuntimeError` if Ecto is not available.

## Examples

    iex> sketch_a = ExDataSketch.HLL.new(p: 10) |> ExDataSketch.HLL.update("a")
    iex> ExDataSketch.Storage.Ecto.save(sketch_a, MyApp.Repo, "hll:test")
    :ok
    iex> sketch_b = ExDataSketch.HLL.new(p: 10) |> ExDataSketch.HLL.update("b")
    iex> ExDataSketch.Storage.Ecto.merge(sketch_b, MyApp.Repo, "hll:test")
    :ok

# `save`

```elixir
@spec save(struct(), module(), ExDataSketch.Storage.key()) :: :ok | {:error, term()}
```

Persists a sketch under the given key via Ecto.

If a sketch with the same key and type already exists, it is replaced
(upsert). The sketch is serialized to an EXSK v2 binary frame before storage.

## Arguments

- `sketch` -- a sketch struct.
- `repo` -- the Ecto repo module (e.g., `MyApp.Repo`).
- `key` -- the key under which to store the sketch.

## Returns

`:ok` on success, `{:error, changeset}` on validation failure.

## Raises

- `RuntimeError` if Ecto is not available.

## Examples

    iex> sketch = ExDataSketch.HLL.new(p: 10) |> ExDataSketch.HLL.update("a")
    iex> ExDataSketch.Storage.Ecto.save(sketch, MyApp.Repo, "hll:test")
    :ok

---

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