Skip to content

feat!: composite primary keys (relation keys, phase 2) - #1926

Merged
collerek merged 22 commits into
masterfrom
feat/relation-keys-phase-2
Nov 4, 2026
Merged

collerek merged 22 commits into
masterfrom
feat/relation-keys-phase-2

Conversation

@collerek

@collerek collerek commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Part of #137. Models can mark several fields with primary_key=True; the key value is a tuple (order.pk == (1, 7)) and works with get(pk=...), filter(pk__in=...), exclude, order_by("pk"), bulk operations and update_or_create. Python-style filters work on composite keys too (Order.pk == (1, 7), Order.pk.in_([...]), Order.pk.desc()), and .pk works after relations for every key (Book.author.pk == 1). ormar now remembers the key a model was loaded with, so update()/delete()/load() after a key change target the original row, and upsert() inserts never-saved models whose key is already set instead of silently updating nothing. Relations to composite-key models follow in the next PR. New docs page: docs/models/primary-keys.md.

  • Breaking: upsert() / key-change semantics above; save() raises ModelPersistenceError for a missing non-generated key value.
  • Fixes ordering by pk, which produced invalid SQL everywhere: order_by("pk"), through relations (order_by("author__pk")), ormar_config order_by=["-pk"] and relation orders_by / related_orders_by, single keys included. Also adds pk__<op> lookups.
  • Edited existing tests (behaviour the change intentionally fixes): test_model_definition.py::test_two_pks_in_model_definition, test_upsert.py::test_updating_selected_columns.

…se-2

# Conflicts:
#	ormar/models/descriptors/descriptors.py
#	tests/test_model_definition/test_primary_key.py
…phase-2

# Conflicts:
#	docs/releases.md
#	ormar/fields/foreign_key.py
#	ormar/fields/many_to_many.py
#	ormar/models/descriptors/descriptors.py
#	ormar/models/helpers/sqlalchemy.py
#	ormar/models/mixins/save_mixin.py
#	ormar/models/model.py
#	ormar/models/newbasemodel.py
#	ormar/models/primary_key.py
#	ormar/queryset/clause.py
#	ormar/queryset/queryset.py
#	ormar/queryset/templates/writes.py
#	tests/test_model_definition/test_primary_key.py
Model.pk on a composite key is now a key accessor: == and in_() / <<
compile to the same row-value comparison as pk= / pk__in=, and asc() /
desc() order by every key field. .pk also works after relations
(Book.author.pk == 1) for single and composite keys.
Ordering by pk only worked in order_by() on the queried model. pk after
relations (order_by("stock__pk"), Shop.stock.pk.desc()), ormar_config
order_by=["-pk"] and relation orders_by / related_orders_by produced
invalid SQL, for single keys too. A shared order_actions_for() helper now
expands pk into the key fields of the model the chain points at, and is
used by QuerySet.order_by, model default ordering and join orderings.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 25.9%

2 warnings, 1 note

⚠️ 112 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ 5 benchmarks with a significant change ran on different hardware

Their base and head results were measured on machines with a different CPU, core count or memory, so the changes might not come from the code. Run base and head on the same runner type.

Learn more about Macro Runners

See affected benchmarks

ℹ️ 126 benchmarks have no flamegraph

The flamegraph wasn't generated: the symbols of the profiling trace could not be resolved, because no information about the language frames was collected.

See affected benchmarks

⚡ 3 improved benchmarks
❌ 2 (👁 2) regressed benchmarks
✅ 121 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_get_documents_page_with_json[250] 35.2 ms 15.8 ms ×2.2
⚡ test_translate_columns_to_aliases[10000] 99.6 ms 71.8 ms +38.71%
⚡ test_translate_columns_to_aliases[1000] 9.9 ms 7.2 ms +37.8%
👁 test_exists[500] 4.7 ms 5.3 ms -11.5%
👁 test_first[1000] 5.8 ms 6.9 ms -15.97%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/relation-keys-phase-2 (119260d) with master (095c52d)

Open in CodSpeed

@collerek
collerek merged commit 123bc9a into master Nov 4, 2026
12 checks passed
@collerek
collerek deleted the feat/relation-keys-phase-2 branch November 4, 2026 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants