# `ExDataSketch.Storage.CubDB`
[🔗](https://github.com/thanos/ex_data_sketch/blob/main/lib/ex_data_sketch/storage/cubdb.ex#L1)

CubDB-backed persistence for sketches.

Stores serialized EXSK v2 frames in a CubDB key-value store. CubDB provides
disk-backed, MVCC-based storage with atomic transactions.

## Dependency

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

## Concurrency

- CubDB supports concurrent reads via MVCC.
- `merge/3` uses a CubDB transaction for atomicity.

> #### Non-executable examples {: .info}
>
> The examples below use `CubDB.start_link/1` which requires the `:cubdb`
> dependency. They are included for documentation purposes and will not
> run in the standard test suite.

## Examples

    # Start a CubDB database (application concern)
    {:ok, db} = CubDB.start_link(data_dir: "/tmp/sketches")

    # Save a sketch
    :ok = ExDataSketch.Storage.CubDB.save(sketch, db, "cardinality:2024-01")

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

    # Atomic merge
    :ok = ExDataSketch.Storage.CubDB.merge(partial, db, "cardinality:2024-01")

# `cubdb_available?`

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

Returns whether CubDB is available at runtime.

## Examples

    iex> is_boolean(ExDataSketch.Storage.CubDB.cubdb_available?())
    true

# `delete`

```elixir
@spec delete(pid() | atom(), ExDataSketch.Storage.key()) :: :ok
```

Deletes a sketch from the CubDB database by key.

## Arguments

- `db` -- the CubDB process.
- `key` -- the key to delete.

## Returns

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

## Examples

    iex> {:ok, db} = CubDB.start_link(data_dir: System.tmp_dir!())
    iex> sketch = ExDataSketch.HLL.new(p: 10)
    iex> ExDataSketch.Storage.CubDB.save(sketch, db, "hll:test")
    :ok
    iex> ExDataSketch.Storage.CubDB.delete(db, "hll:test")
    :ok
    iex> ExDataSketch.Storage.CubDB.load(ExDataSketch.HLL, db, "hll:test")
    {:error, :not_found}
    iex> GenServer.stop(db, :normal)
    :ok

# `load`

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

Loads a sketch from the CubDB database by key.

The binary value is deserialized using the given sketch module's
`deserialize/1` function.

## Arguments

- `sketch_module` -- the sketch module atom (e.g., `ExDataSketch.HLL`).
- `db` -- the CubDB process.
- `key` -- the key to look up.

## Returns

- `{:ok, sketch}` on success.
- `{:error, :not_found}` if the key does not exist.
- `{:error, %DeserializationError{}}` if the stored binary is corrupted.
- `{:error, reason}` on other deserialization failures.

## Raises

- `RuntimeError` if CubDB is not available.

## Examples

    iex> {:ok, db} = CubDB.start_link(data_dir: System.tmp_dir!())
    iex> sketch = ExDataSketch.HLL.new(p: 10) |> ExDataSketch.HLL.update("a")
    iex> ExDataSketch.Storage.CubDB.save(sketch, db, "hll:test")
    :ok
    iex> {:ok, loaded} = ExDataSketch.Storage.CubDB.load(ExDataSketch.HLL, db, "hll:test")
    iex> ExDataSketch.HLL.estimate(loaded) > 0.0
    true
    iex> ExDataSketch.Storage.CubDB.load(ExDataSketch.HLL, db, "nonexistent")
    {:error, :not_found}
    iex> GenServer.stop(db, :normal)
    :ok

# `merge`

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

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

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

## Arguments

- `sketch` -- the sketch to merge.
- `db` -- the CubDB process.
- `key` -- the key whose persisted sketch to merge into.

## Returns

`:ok` on success.

## Raises

- `RuntimeError` if CubDB is not available.

## Examples

    iex> {:ok, db} = CubDB.start_link(data_dir: System.tmp_dir!())
    iex> sketch_a = ExDataSketch.HLL.new(p: 10) |> ExDataSketch.HLL.update("a")
    iex> ExDataSketch.Storage.CubDB.save(sketch_a, db, "hll:test")
    :ok
    iex> sketch_b = ExDataSketch.HLL.new(p: 10) |> ExDataSketch.HLL.update("b")
    iex> ExDataSketch.Storage.CubDB.merge(sketch_b, db, "hll:test")
    :ok
    iex> {:ok, merged} = ExDataSketch.Storage.CubDB.load(ExDataSketch.HLL, db, "hll:test")
    iex> ExDataSketch.HLL.estimate(merged) >= 1.9
    true
    iex> GenServer.stop(db, :normal)
    :ok

# `save`

```elixir
@spec save(struct(), pid() | atom(), ExDataSketch.Storage.key()) :: :ok
```

Persists a sketch under the given key in the CubDB database.

The sketch is serialized to an EXSK v2 binary frame before storage.

## Arguments

- `sketch` -- a sketch struct.
- `db` -- the CubDB process (pid or registered name).
- `key` -- the key under which to store the sketch.

## Returns

`:ok` on success.

## Raises

- `RuntimeError` if CubDB is not available.

## Examples

    iex> {:ok, db} = CubDB.start_link(data_dir: System.tmp_dir!())
    iex> sketch = ExDataSketch.HLL.new(p: 10) |> ExDataSketch.HLL.update("a")
    iex> ExDataSketch.Storage.CubDB.save(sketch, db, "hll:test")
    :ok
    iex> GenServer.stop(db, :normal)
    :ok

---

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