Changelog
View SourceAll notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Versions <= 0.5.x were released as
bulk_upsert.
v0.6.0 - 2026-07-05
Changed
- Breaking: The package is renamed from
bulk_upserttobulkinup, andBulkUpsert.bulk_upsert/4is nowBulkinup.upsert/4— same arguments, same options, same return shape. See the migration guide - Breaking: The log metadata
:reasonatoms follow the rename::bulk_upsert_changeset_erroris now:upsert_changeset_error, and:bulk_upsert_items_skippedis now:upsert_items_skipped(with:insert_changeset_errorand:insert_items_skippedas theinsert/4counterparts)
Added
Bulkinup.insert/4: a pure bulk insert of nested schemas. Noon_conflictorconflict_targetdefaults are applied at any level (includingmany_to_manyjoin tables), so a duplicate raises. Returns{:ok, %{inserted: n, skipped: n}}. The upsert-only option:replace_all_exceptraises anArgumentErroruse Bulkinup: injects repo-scopedbulk_insert/3andbulk_upsert/3into the calling repo module, with shared defaults declared once atusetime. Flat keys apply to each verb they are valid for;insert:/upsert:namespaces hold per-verb defaults; per-call opts win. Option names are validated at compile time, and values are evaluated per call at runtime, so dynamic defaults work
v0.5.0 - 2026-07-04
Added
attrs_listnow accepts anyEnumerable, including a lazyStream: items are validated and upserted chunk-by-chunk, so memory stays bounded for arbitrarily large inputs. Plain lists behave as before, and skipped rows are still summarized in a single:warninglog per call- Option
:max_concurrencyto upsert chunks concurrently, each in its own transaction. Setting it trades the single-transaction guarantee for insert throughput — see the option's documentation for the consequences before using it
Changed
bulk_upsert/4now raises anArgumentErrorwhen:chunk_sizeis not a positive integer (previously an invalid value failed deeper in the pipeline with an unclear error)
v0.4.0 - 2026-07-04
Changed
- Breaking:
bulk_upsert/4now raises anArgumentErroron unknown option names, and on BulkUpsert options nested inside:insert_all_optsvalues (previously both were silently ignored).:timeoutand:placeholdersremain valid inside:insert_all_opts, sinceinsert_all/3also accepts them - Skipped rows are now summarized in a single
:warninglog per call (schema, counts, and up to 50 skipped primary keys in the metadata), with per-row detail moved to the:debuglevel. Previously each skipped row logged its own:warning, which could flood the log during large imports of dirty data
v0.3.0 - 2026-07-04
Added
- Nested associations are now upserted recursively at any depth (previously only the parent's direct associations were handled, and a child carrying its own nested associations crashed)
:recover_changeset_errorsnow applies recursively to nested association and embedded changesets, with fallbacks looked up by each changeset's schema (previously only the parent schema's changesets were recovered, and any nested error caused the row to be skipped)
Changed
- Breaking:
bulk_upsert/4now returns{:ok, %{upserted: count, skipped: count}}(counts of top-level attrs) instead of:ok, making silently skipped invalid rows visible to callers - Require
ecto ~> 3.6(the:placeholdersoption has required Ecto v3.6.0 all along; the previous~> 3.0requirement overstated compatibility) - Skipped rows are now logged at the
:warninglevel instead of:debug, since silently dropping invalid rows is data loss
Fixed
- Wrap the entire bulk upsert in a single transaction. Previously each chunk of
:chunk_sizeparents ran in its own transaction, so a failure in a later chunk left earlier chunks committed - Placeholder fields can now be included in the changeset's
validate_required/2. Each placeholder value is injected into the attrs (at every nesting level) before the changeset is built, so the field is cast and validated like any other field. Previously the value was absent during validation, so requiring a placeholder field silently skipped every row
v0.2.0 - 2026-05-31
Added
- Support for
has_oneassociations - Support for
many_to_manyassociations (related records and join table rows) - Option
:placeholdersto set fields from shared values via Ecto'sinsert_all/3placeholders
Fixed
- Default
:insert_all_optsto a map so string-keyed sources (e.g. join tables) work
v0.1.5 - 2026-03-23
Fixed
- Apply
chunk_sizeoption tohas_manyassociation upserts
v0.1.4 - 2026-02-23
Fixed
- Use separate variable for parent
insert_all_opts
v0.1.3 - 2026-02-20
Fixed
- "Has many" assoc upserts now working
Added
- Option
:chunk_size(used to specify the maximum number of items to upsert in a single query)
v0.1.2 - 2025-05-03
Added
- Add changelog to HexDocs
v0.1.1 - 2025-04-29
Added
- Fix issues with docs
v0.1.0 - 2025-04-28
Added
- Initial release