MimirAnalytics.Store (mimir_analytics v0.8.1)

Copy Markdown View Source

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.

Summary

Functions

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.

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

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

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

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.

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

Types

t()

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

target()

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

Functions

close(store)

@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(bytes)

@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?(store, digest)

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

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

ingested_name?(store, source, source_file)

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

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

insert(store, table, rows)

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

open(path)

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

query(store, sql, params \\ [])

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

record_ingest(store, digest, source_file, source, rows)

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

record_rejection(store, digest, source_file, source, reason)

@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?(store, digest)

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

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

transaction(store, fun)

@spec transaction(t(), (t() -> {:ok, term()} | {:error, term()})) ::
  {:ok, term()} | {:error, term()}