# `QuackDB.Ecto.Snapshot`
[🔗](https://github.com/elixir-vibe/quackdb/blob/main/lib/quack_db/ecto/snapshot.ex#L2)

Reads Delta, DuckLake, and Iceberg tables as they were at an earlier
snapshot, through DuckDB's `AT (VERSION => ...)` and `AT (TIMESTAMP => ...)`
clauses.

A snapshot is a `DateTime`, which reads the latest snapshot committed at or
before it, or `version: n`, a Delta table version or a DuckLake snapshot id:

    import QuackDB.Ecto.Snapshot

    Order
    |> join(:inner, [o], c in assoc(o, :customer))
    |> put_snapshot(~U[2026-10-20 14:03:11Z])
    |> Repo.all()

`put_snapshot/2` applies to every table the query reads: its joins,
including assoc joins, subqueries, and CTE bodies. CTE names, `values/2`,
fragments, and `QuackDB.Source` helpers are left as they are. Plain DuckDB
tables do not keep snapshots, so a query that also reads one fails with
DuckDB's `Catalog type does not support time travel`; give the snapshot to
the time-travelling binding alone with `put_snapshot/3`.

## Repo option

To give a whole repo call a snapshot, preloads included, apply the
`:snapshot` option in your repo's `c:Ecto.Repo.prepare_query/3`:

    defmodule MyApp.Repo do
      use Ecto.Repo, otp_app: :my_app, adapter: Ecto.Adapters.QuackDB

      @impl true
      defdelegate prepare_query(operation, query, opts), to: QuackDB.Ecto.Snapshot
    end

    order = Repo.get!(Order, id, snapshot: event.at)
    Repo.preload(order, :items, snapshot: event.at)

Ecto passes a call's options on to its preload queries, so one snapshot
covers the records a call loads. Snapshots are read-only: the option
raises for `update_all` and `delete_all`.

# `t`

```elixir
@type t() ::
  DateTime.t() | [{:version, non_neg_integer()}] | [{:timestamp, DateTime.t()}]
```

A snapshot to read: a commit time or a version.

# `prepare_query`

```elixir
@spec prepare_query(atom(), Ecto.Query.t(), keyword()) :: {Ecto.Query.t(), keyword()}
```

Applies a repo call's `:snapshot` option to its query. Delegate
`c:Ecto.Repo.prepare_query/3` to it, or call it from your own.

# `put_snapshot`

```elixir
@spec put_snapshot(Ecto.Queryable.t(), t() | nil) :: Ecto.Query.t()
```

Reads every table in the query at `snapshot`. A `nil` snapshot leaves the
query unchanged.

# `put_snapshot`

```elixir
@spec put_snapshot(Ecto.Queryable.t(), atom(), t() | nil) :: Ecto.Query.t()
```

Reads only the source bound to the named `binding` at `snapshot`, so the
query can join tables that do not keep snapshots, or read two snapshots of
one table. The snapshot overrides one given to the whole query.

    from(order in Order, as: :order, join: region in "regions", on: ...)
    |> put_snapshot(:order, version: 42)

Assoc joins lose their hints in Ecto's planner, so the binding must come
from `from` or a join on an explicit source.

---

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