# `MimirAnalytics.Store`
[🔗](https://github.com/cash-mckeeman/mimir/blob/mimir_analytics-v0.8.1/mimir_analytics/lib/mimir_analytics/store.ex#L1)

DuckDB run-record store for local files or MotherDuck databases. Inserts
accept structs and derive SQL columns from their fields.

`transaction/2` commits successful results and rolls back errors or
exceptions, keeping source rows and their ingest-ledger entry atomic.

# `t`

```elixir
@type t() :: %MimirAnalytics.Store{conn: reference(), db: reference()}
```

# `target`

```elixir
@type target() :: :memory | Path.t() | {:motherduck, String.t()}
```

# `close`

```elixir
@spec close(t()) :: :ok
```

Close the connection and the database now, rather than whenever the
garbage collector reaches them. DuckDB writes its log back into the
database file as it closes, so a second instance opened on the same file
in the same OS process must not overlap the first one's close. The store
cannot be used afterwards.

# `digest`

```elixir
@spec digest(binary()) :: String.t()
```

The ledger key for a source file's contents: the hex sha256 of its bytes.

The ledger is keyed by content, not by name, because a producer can reuse
a file name for different contents, and a name match would skip the new
contents silently.

# `ingested?`

```elixir
@spec ingested?(t(), String.t()) :: boolean()
```

Whether contents with `digest` were already ingested, under any file name.

# `ingested_name?`

```elixir
@spec ingested_name?(t(), String.t(), String.t()) :: boolean()
```

Whether a file named `source_file` was ingested from `source`, with any contents.

# `insert`

```elixir
@spec insert(t(), String.t(), [struct()]) ::
  {:ok, non_neg_integer()} | {:error, term()}
```

# `open`

```elixir
@spec open(target()) :: {:ok, t()} | {:error, term()}
```

# `query`

```elixir
@spec query(t(), String.t(), list()) :: {:ok, [[term()]]} | {:error, term()}
```

# `record_ingest`

```elixir
@spec record_ingest(t(), String.t(), String.t(), String.t(), non_neg_integer()) ::
  :ok | {:error, term()}
```

# `record_rejection`

```elixir
@spec record_rejection(t(), String.t(), String.t(), String.t(), String.t()) ::
  :ok | {:error, term()}
```

Record that contents with `digest` were rejected for `reason`, so a file
that is left in place is reported once per distinct contents, not on every
run.

# `rejected?`

```elixir
@spec rejected?(t(), String.t()) :: boolean()
```

Whether contents with `digest` were already rejected, under any file name.

# `transaction`

```elixir
@spec transaction(t(), (t() -&gt; {:ok, term()} | {:error, term()})) ::
  {:ok, term()} | {:error, term()}
```

---

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