[2d] Add bulk_upsert() and make bulk_create insert-only#86
Draft
davegaeddert wants to merge 5 commits into
Draft
[2d] Add bulk_upsert() and make bulk_create insert-only#86davegaeddert wants to merge 5 commits into
davegaeddert wants to merge 5 commits into
Conversation
- Fold _batched_upsert into _batched_insert via optional conflict kwargs - Compute each object's unique key once, reused for null-check, sort, and RETURNING row matching - Remove unreachable OnConflict.IGNORE / ON CONFLICT DO NOTHING branch - Drop redundant ManyToManyField check (non-concrete already covers it) - Use enumerate() for db_returning row indices - Move the unique-fields-match-constraint predicate onto Options
update_fields and unique_fields now accept Field references (Model.field) instead of column-name strings. A shared _validate_field_refs() helper enforces that each ref is a Field belonging to the queryset's model (also now backing returning()'s validation), so a string or a wrong-model ref raises a clear error pointing at Model.field. unique_fields_match_constraint still works on the derived name set. Migrates the plain-cache set_many() caller to CachedItem field references.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
QuerySet.bulk_upsert()— a clearly-named, RETURNING-backed replacement for the oldbulk_create(update_conflicts=True, ...)surface — and narrowsbulk_createto insert-only.Stacked on the
returningbranch.API
update_fieldsandunique_fieldstake typed field references (e.g.update_fields=[CachedItem.value],unique_fields=[CachedItem.key]), validated by a shared_validate_field_refs()— a string or a field from the wrong model raisesTypeError/FieldErrorat the call, before any batch is issued.Issues one
INSERT ... ON CONFLICT (unique_fields) DO UPDATE SET col = EXCLUDED.col ... RETURNING ...per batch, wrapped in a single atomic transaction. Every object comes back — both freshly inserted and conflict-updated rows — with its DB-returned fields (primary key, DB defaults) populated.DO UPDATEreturns all targeted rows, so updates are hydrated too, not just inserts.Row matching by unique key (not zip order)
Under
ON CONFLICT, Postgres does not guarantee RETURNING order matches VALUES order, so results can't be zipped back to the input objects positionally. The RETURNING clause carries the DB-returned fields plus the unique fields; after execution we build a lookup keyed by the tuple of unique-field values from each returned row, then assign every object the fields from its matching row by that key. The unique fields are excluded fromupdate_fields(validated), so a conflicting row's key column keeps its value and always matches.Deadlock-avoidance ordering
Each call sorts the objects by the conflict key before issuing the batches. Concurrent
bulk_upserts touching overlapping keys in different input orders then lock rows in the same order and can't deadlock. Input order is preserved in the returned list (only the batch issuance is sorted).Validation (at call time)
unique_fieldsmust name the primary key, or a condition-free / expression-freeUniqueConstraintdeclared on the model (checked viaunique_fields_match_constraint, which matches againsttotal_unique_constraints). Clear error naming the model and fields otherwise.update_fieldsmust be concrete, non-PK, and must not overlapunique_fields.NULLnever conflicts in Postgres, so upsert semantics are meaningless and row-matching impossible.bulk_create narrowing
bulk_createloses itsupdate_conflicts/update_fields/unique_fieldsparameters and the top-levelOnConflictplumbing behind them; it is now insert-only. The shared_batched_inserthelper still threadson_conflict/update_fields/unique_fieldsthrough to_insert()per batch —bulk_upsert()is the only caller that now passes them. No deprecation shim — the upgrade agent fixes user code.Internal caller migrated
plain.cache'sset_many()switched frombulk_create(update_conflicts=True, ...)tobulk_upsert(update_fields=[model.value, model.expires_at, model.updated_at], unique_fields=[model.key]). No otherupdate_conflictsusages exist in the repo.Tests
New public tests in
plain-postgres/tests/public/test_bulk_upsert.py: insert-only, mixed insert/update batch (PKs + updated values),update_fieldsscoping, by-key matching when every row conflicts, batching, empty input, and each validation error (unmatched constraint, null unique value, overlapping fields, string field, wrong-model field). Abulk_createtest asserts the conflict kwargs now raiseTypeError. plain-cache's suite still passes after the migration. Full./scripts/test,./scripts/type-check plain-postgres, and./scripts/fixare green.