# `Blink.Adapter.Postgres`
[🔗](https://github.com/nerds-and-company/blink/blob/v0.10.0/lib/blink/adapter/postgres.ex#L1)

PostgreSQL adapter for Blink bulk copy operations.

This adapter uses PostgreSQL's `COPY FROM STDIN` command for efficient bulk
insertion of data. It is the default adapter used by Blink.

## Usage

This adapter is used automatically by default:

    Blink.copy_to_table(rows, "users", MyApp.Repo)

Or explicitly:

    Blink.copy_to_table(rows, "users", MyApp.Repo, adapter: Blink.Adapter.Postgres)

# `call`

```elixir
@spec call(
  rows :: Enumerable.t(),
  table_name :: String.t(),
  repo :: Ecto.Repo.t(),
  opts :: Keyword.t()
) :: :ok
```

Copies rows into a database table using PostgreSQL's COPY command.

## Parameters

  * `rows` - An enumerable (list or stream) of maps where each map represents
    a row to insert. All maps must have the same keys, which correspond to the
    table columns; a row whose keys differ from the first row's raises
    `Blink.RowError`. Using a stream allows for memory-efficient seeding of
    large datasets. The input is consumed exactly once, so single-use streams
    are safe.
  * `table_name` - The name of the table to insert into (string).
  * `repo` - An Ecto repository module configured with a Postgres adapter.
  * `opts` - Keyword list of options. Unknown keys and invalid values raise
    `ArgumentError`:
    * `:atomic` - Whether the copy is all-or-nothing (default: `true`).
      * `false` - Workers copy batches over up to `:concurrency` database
        connections in parallel and each batch commits independently.
        Fastest, but a failure can leave earlier batches committed. The
        worker connections do not enroll in a transaction of the caller's
        own: they cannot see its uncommitted data, and their commits
        survive its rollback.
      * `true` - All batches are copied over one connection inside a single
        transaction while `:concurrency` workers encode rows in parallel.
        Any failure rolls back the whole COPY, and the copy enrolls in a
        surrounding transaction such as the one `Blink.Seeder.run/3` opens
        for atomic seeds. Rows are copied in input order.
    * `:concurrency` - Number of parallel workers (default: 6 when
      `atomic: false`, `System.schedulers_online/0` when `atomic: true`).
      With `atomic: false` each worker encodes and copies batches over its
      own database connection, so configure the repo's `pool_size` to at
      least `:concurrency`. With `atomic: true` workers only encode; a
      single connection performs the COPY.
    * `:batch_size` - Number of rows per batch (default: 8,000). Items are
      chunked into batches, each written via a separate COPY operation (or a
      separate write to the single COPY when `atomic: true`). To disable
      batching, set this to a value equal to or greater than the total
      number of rows.
    * `:timeout` - Time in milliseconds allowed for each database operation
      (default: 15,000). With `atomic: false` this bounds each batch's COPY
      transaction. With `atomic: true` it is enforced server-side as a
      `statement_timeout` on each COPY statement, because a connection
      checkout deadline cannot bound individual operations inside one
      transaction. Set to `:infinity` to disable Blink's timeout (a
      server-configured `statement_timeout` still applies).
    * `:reset_sequences` - After the copy, advance the sequence behind each
      of the table's `serial` or identity primary key columns past the
      highest copied value (default: `false`). Explicit IDs do not advance
      a sequence, so without this the application's next ordinary insert
      collides with a seeded row. Primary keys without a sequence (`uuid`,
      self-managed integers) are unaffected. No rows, no reset: the option
      does nothing when the input is empty. Intended for seed-time use —
      the reset derives its target from the `MAX()` of *visible* rows, so
      on a table receiving concurrent inserts it can move the sequence
      backwards past values already handed out to in-flight transactions,
      causing unique violations later.
    * `:truncate` - Truncate the table (with `RESTART IDENTITY`) before
      copying, so the copy replaces the table's contents instead of adding
      to them (default: `false`). With `atomic: true` the truncate joins
      the copy's transaction — a failed copy rolls it back and the previous
      contents survive; with `atomic: false` it commits on its own before
      the first batch. The table is truncated even when the input is empty:
      either way the copy leaves the table equal to its input. Postgres
      refuses to truncate a table referenced by a foreign key from another
      table; truncate such a table through `Blink.Seeder.run/3`, whose
      run-level `truncate: true` covers every declared table in one
      statement. See `truncate/3` for the statement's semantics.

## Returns

  * `:ok` - When the copy operation succeeds

Raises an exception when the copy operation fails.

## Examples

    iex> rows = [%{id: 1, name: "Alice"}, %{id: 2, name: "Bob"}]
    iex> Blink.Adapter.Postgres.call(rows, "users", MyApp.Repo)
    :ok

    # Atomic, all-or-nothing copy
    iex> Blink.Adapter.Postgres.call(rows, "users", MyApp.Repo, atomic: true)
    :ok

    # Using a stream for memory-efficient seeding
    iex> stream = Stream.map(1..1_000_000, fn i -> %{id: i, name: "User #{i}"} end)
    iex> Blink.Adapter.Postgres.call(stream, "users", MyApp.Repo)
    :ok

## Notes

The column list is read from the keys of the first row, and every subsequent
row must have exactly the same keys — a mismatch raises `Blink.RowError` when
the offending row is reached. With `atomic: true` (the default) a failed
validation therefore leaves nothing behind; with `atomic: false` it surfaces
like any other mid-copy failure, with earlier batches possibly committed.
NULL values are represented as `\N` in the CSV format. Nested maps are automatically JSON-encoded for
JSONB columns; values that are already JSON strings are inserted as-is, so
passing pre-encoded JSON avoids a redundant `Jason.encode!/1` call. Elixir
lists are encoded as PostgreSQL array literals for array columns (`int[]`,
`text[]`, `jsonb[]`, nested arrays, ...). A JSONB column holding a top-level
JSON array should be passed as a pre-encoded JSON string.

Structs are maps, so a struct value (a `DateTime`, `Date`, `Decimal`, ...) is
also JSON-encoded. PostgreSQL's date/time parsers accept the quoted result,
so calendar structs work in `timestamp`, `date`, and `time` columns; in a
`text` column the stored value keeps the JSON quotes — pass
`to_string(value)` instead.

# `truncate`

```elixir
@spec truncate([String.t(), ...], Ecto.Repo.t(), Keyword.t()) :: :ok
```

Truncates `table_names` with one `TRUNCATE ... RESTART IDENTITY` statement.

Called by `Blink.Seeder.run/3` (with every declared table, before the first
copy) and by `call/4` (with the copied table) when run with
`truncate: true`. One statement means foreign keys between the listed
tables need no ordering. A foreign key from an unlisted table makes
Postgres refuse the truncate — `CASCADE` is deliberately not used, because
it would empty tables the caller never named.

`RESTART IDENTITY` restarts the truncated tables' sequences, so
database-assigned ids come out identical on every run. Unusually for
sequence operations, the restart is transactional: it rolls back with a
failed atomic seed or copy, as does the truncation itself.

## Options

  * `:timeout` - Time in milliseconds allowed for the statement (default:
    15,000), which also caps how long the truncate waits for its
    `ACCESS EXCLUSIVE` locks on the tables.

---

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