# `QuackDB.DuckLake`
[🔗](https://github.com/elixir-vibe/quackdb/blob/main/lib/quack_db/duck_lake.ex#L1)

SQL builders for DuckLake catalogs: listing snapshots, reading the rows that
changed between them, tagging commits, and expiring old snapshots.

Every commit to a DuckLake catalog creates a snapshot, one numbering for all
of its tables. Read tables at a snapshot with `QuackDB.Ecto.Snapshot`, or
`AT (VERSION => ...)` in SQL. Attach a catalog with `QuackDB.DDL.attach/3`.

The table function helpers return SQL fragments that work as Ecto sources
and in raw SQL, like `QuackDB.Source`:

    alias QuackDB.DuckLake

    from snapshot in DuckLake.snapshots(:lake),
      where: snapshot.snapshot_time <= ^event_at,
      order_by: [desc: snapshot.snapshot_id],
      limit: 1,
      select: snapshot.snapshot_id

    QuackDB.query!(conn, ["SELECT * FROM ", DuckLake.changes(:lake, :orders, from: 2, to: 5)])

`set_commit_message/3` and `expire_snapshots/2` build `CALL` statements.

# `catalog`

```elixir
@type catalog() :: atom() | String.t()
```

# `snapshot`

```elixir
@type snapshot() :: non_neg_integer() | DateTime.t()
```

# `table`

```elixir
@type table() ::
  atom() | String.t() | {atom() | String.t(), atom() | String.t()} | module()
```

# `changes`

```elixir
@spec changes(catalog(), table(), keyword(snapshot())) :: String.t()
```

Builds a `ducklake_table_changes(...)` fragment with the rows of `table`
that changed in snapshots `:from` through `:to`, both included. Both are
snapshot ids or `DateTime`s; `DateTime`s take the snapshots committed
between them. Each row has the table's columns and `snapshot_id`, `rowid`,
and `change_type`: `insert`, `delete`, `update_preimage`, or
`update_postimage`.

`table` is a table name in the `main` schema, a `{schema, table}` pair, or
an Ecto schema. A schema's `@schema_prefix` may name the catalog, as
`"lake"` or `"lake.sales"`, or the schema within it.

# `current_snapshot`

```elixir
@spec current_snapshot(catalog()) :: String.t()
```

Builds a `ducklake_current_snapshot(...)` fragment with the catalog's latest snapshot `id`.

# `deletions`

```elixir
@spec deletions(catalog(), table(), keyword(snapshot())) :: String.t()
```

Like `changes/3`, but only the deleted rows, without change columns. An
update deletes the row's old version, so it appears here too.

# `expire_snapshots`

```elixir
@spec expire_snapshots(
  catalog(),
  keyword()
) :: iodata()
```

Builds a `CALL ducklake_expire_snapshots(...)` statement. Expired snapshots
can no longer be read; DuckLake keeps every snapshot until they expire.

    DuckLake.expire_snapshots(:lake, older_than: DateTime.add(DateTime.utc_now(), -30, :day))
    DuckLake.expire_snapshots(:lake, versions: [1, 2], dry_run: true)

Requires `:older_than` or `:versions`.

# `insertions`

```elixir
@spec insertions(catalog(), table(), keyword(snapshot())) :: String.t()
```

Like `changes/3`, but only the inserted rows, without change columns.

# `set_commit_message`

```elixir
@spec set_commit_message(catalog(), String.t(), keyword()) :: iodata()
```

Builds a `CALL ducklake_set_commit_message(...)` statement that tags the
snapshot the current transaction commits. Run it inside the transaction,
after its writes:

    Repo.transaction(fn ->
      Repo.insert!(order)
      Repo.query!(DuckLake.set_commit_message(:lake, "checkout", extra_info: %{visit: 42}))
    end)

Supports `:author` and `:extra_info`, a string or a map stored as JSON.

# `snapshots`

```elixir
@spec snapshots(catalog()) :: String.t()
```

Builds a `ducklake_snapshots(...)` fragment listing the catalog's snapshots,
with `snapshot_id`, `snapshot_time`, `schema_version`, `changes`, `author`,
`commit_message`, and `commit_extra_info` columns.

---

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