Skip to main content

README.md

# Bulkinup

> #### Note
>
> This library is pre-1.0: the API may change between minor versions (see the changelog).
> The test suite exercises it against a real Postgres database, but review the documented
> limitations before using it in production.

Bulk inserts and upserts for nested Ecto schemas.

Unlike a plain `insert_all/3`, this package passes each attrs map through Ecto changesets. This lets it validate your data and write a parent **and its children across multiple tables** in one call — as a pure insert (`Bulkinup.insert/4`) or an upsert (`Bulkinup.upsert/4`).

Supported features:
  - Nested associations: write a parent and its `has_many`, `has_one`, and `many_to_many` associations across multiple tables from a single list of attrs, recursively at any depth (embedded schemas are stored inline on the parent)
  - Validation and data processing (via Ecto changesets)
  - Repo-scoped `bulk_insert/3` and `bulk_upsert/3` with app-wide defaults, via `use Bulkinup`
  - Custom values for autogenerated fields (e.g. insert/update timestamps)
  - Recovery of invalid field values via per-schema fallbacks
  - A single transaction wraps the entire call (by default), so any failure rolls back all changes
  - Streaming input: pass any `Enumerable` (including a lazy `Stream`) to write arbitrarily large inputs with bounded memory
  - Optional concurrent writes via `:max_concurrency`, trading the single transaction for throughput

## Installation

Add this package to your list of dependencies in `mix.exs`, then run `mix deps.get`:

```elixir
def deps do
  [
    {:bulkinup, "~> 0.6.0"}
  ]
end
```

## Basic example

Given a `Person` schema with a 1-arity-callable changeset function:

```elixir
iex> Bulkinup.insert(
...>   YourProject.Repo,
...>   YourProject.Persons.Person,
...>   [%{id: 1, name: "Alice"}, %{id: 2, name: "Bob"}]
...> )
{:ok, %{inserted: 2, skipped: 0}}

iex> Bulkinup.upsert(
...>   YourProject.Repo,
...>   YourProject.Persons.Person,
...>   [%{id: 1, name: "Alicia"}, %{id: 2, name: "Bobby"}]
...> )
{:ok, %{upserted: 2, skipped: 0}}
```

Rows whose changesets are invalid are skipped rather than written, visibly: the counts in the return value show it, and each call that skips rows emits one `:warning` log summarizing them.

## Learn more

- [Getting Started](https://hexdocs.pm/bulkinup/getting_started.html) — installation, first calls, `use Bulkinup`
- [Nested Associations](https://hexdocs.pm/bulkinup/nested_associations.html) — writing parents and children across tables
- [Recipes](https://hexdocs.pm/bulkinup/recipes.html) — timestamps, streaming, conflict handling, dirty data
- [Migrating from bulk_upsert](https://hexdocs.pm/bulkinup/migrating_from_bulk_upsert.html) — this package's former name (versions <= 0.5.x)
- [`Bulkinup` module documentation](https://hexdocs.pm/bulkinup/Bulkinup.html) — the full options reference

---

This project made possible by Interline Travel and Tour Inc.

https://www.perx.com/

https://www.touchdown.co.uk/

https://www.touchdownfrance.com/