Skip to content

feat: foreign keys in primary keys, to_field and db_constraint (relation keys, phase 3a) - #1927

Merged
collerek merged 32 commits into
masterfrom
feat/relation-keys-phase-3a
Nov 5, 2026
Merged

collerek merged 32 commits into
masterfrom
feat/relation-keys-phase-3a

Conversation

@collerek

@collerek collerek commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #137. ForeignKey(..., primary_key=True) lets a foreign key be (part of) the primary key — link tables keyed by their foreign keys and shared primary keys — with key values holding the referenced values (enrollment.pk == (student.id, course.id)). ForeignKey(..., to_field="iso_code") references a unique non-pk column, and db_constraint=False skips the database FOREIGN KEY. Relations to models keyed by a foreign key, bulk writes, QuerySet.update, flattened pydantic models and key-only models all use the referenced value; pk ordering and Model.pk filters from phase 2 work on these keys too. Multi-column foreign keys follow in phase 3b.

  • Changing a key that contains a relation (enrollment.pk = (...)) updates the relation itself.
  • Model.load() of a model without a key raises NoMatch without querying.
  • The tests/cross_check.py safety net now keeps forward relations on its detached copies, so reverse select_related into models keyed by a foreign key is cross-checked too instead of being switched off.

…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.
…phase-3a

# Conflicts:
#	docs/models/primary-keys.md
#	docs/releases.md
#	ormar/fields/foreign_key.py
#	ormar/models/model.py
#	ormar/models/newbasemodel.py
#	ormar/models/primary_key.py
#	ormar/queryset/clause.py
#	tests/test_model_definition/test_composite_pk_definition.py
@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 degrade performance by 6.48%

3 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

⚠️ 3 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

⚠️ 3 benchmarks with a significant change ran in a different software environment

Their base and head results were measured with a different OS or toolchain, such as compiler, runtime or library versions, so the changes might come from this difference rather than from the code. Open an affected benchmark to see what changed.

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

⚡ 1 improved benchmark
❌ 2 (👁 2) regressed benchmarks
✅ 123 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_get_one_wide_model[250] 11 ms 10 ms +10.45%
👁 test_get_one[500] 4.4 ms 5 ms -12.07%
👁 test_first[1000] 5.8 ms 6.9 ms -15.78%

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-3a (1493cfd) with master (123bc9a)

Open in CodSpeed

Detached copies dropped their relations, so a required foreign key (one in
the primary key) read None and the validated rebuild failed. Register bare
copies of the forward relations instead and run the reverse select_related
test with the cross-check on.
@collerek
collerek merged commit 5ff3dff into master Nov 5, 2026
12 checks passed
@collerek
collerek deleted the feat/relation-keys-phase-3a branch November 5, 2026 08:25
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