Add support for primary key and foreign key attributes #6
Copy link
Copy link
Closed
Labels
enhancementNew feature or requestNew feature or request
Description
The problem
Summary
Logical entities can currently only mark an attribute with
role: business_key. There is no way to declare:
- which attribute(s) form the primary key of an entity, and
- which attribute(s) are foreign keys, i.e. which entity/attribute they
reference.
What you have in mind
Current state
logical/entities/customer.yaml:
id: 01M084QDTE7ZVZ2E8JDYM3FEY9
kind: logical_entity
name: customer
realises: 01M084QDTE7D6ER1SCBHTZEM33
attributes:
- id: 01M085A3ENVRY7C8JV8FRCP9XZ
name: customer_id
domain: integer
role: business_key
nullable: truecustomer_id is clearly the identifier of the entity, but there is no way to
say so, and nothing stops it from being nullable: true.
Proposal
-
Extend the attribute
rolewithprimary_keyandforeign_key
(keepingbusiness_keyas is — a business/natural key is not the same
thing as a surrogate primary key). -
For foreign keys, allow the target to be declared, e.g.:
attributes: - id: ... name: customer_id domain: integer role: primary_key nullable: false - id: ... name: address_id domain: integer role: foreign_key references: entity: address # or the entity id attribute: address_id # optional, defaults to the target PK nullable: true
Composite keys should be supported as well (multiple attributes marked
primary_key, or an explicitprimary_key: [col_a, col_b]block on the
entity, plus an FK that maps several columns at once).
Expected effects
- Validation: an attribute with
role: primary_keyshould implicitly be
nullable: false(or at least warn when it is not); an FK should fail
validation when the referenced entity/attribute does not exist, and when the
domains/types on both sides do not match. - Documentation: generated docs show PK/FK markers per attribute and list
the relationships of each entity. - Diagram / visual canvas: relationships are drawn as lines between
entities, with cardinality derived from FK nullability and uniqueness. - Physical/dbt output: PK and FK information is available for generating
constraints and tests (unique,not_null,relationships).
Context
No response
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity
Merge pull request #10 from dbose/6-on-keys
dbose commentedon Sep 10, 2026
Thanks for the detailed writeup - this is a real gap, and you're right that
role: business_keyalone can't express a primary key or a foreign key. But Modelith already models both, just not on the attribute'srole. Keys are first-class objects, which is what lets them be composite and multi-column in a way a singlerole:field can't.Primary keys - a
pkKeyGroupA primary key is a KeyGroup of
type: pkwhose orderedmembersare the PK attributes. Composite keys are just multiple members:typecan also bealternate,unique, orindexfor candidate/secondary keys. ApkKeyGroup is the authoritative primary key; the legacy per-attributerole: business_keystill works but apkKeyGroup wins.Foreign keys — a Relationship
A foreign key is a Relationship whose
from/toends carry the attributes that map across. Multi-column FKs are just longerattributeslists (paired positionally):Your "expected effects" - where they already land
MDL-E103), at most one PK per entity (MDL-E108), and PK members belong to their entity (MDL-E106). I've just added the two you specifically called out:MDL-W115- warns when a PK member isnullable: true.MDL-E114- errors when an FK's paired attributes have mismatched domains.schema.yml:primary_key,unique, andnot_nullconstraints (where the adapter supports them), and dbtrelationshipstests for FKs. So the PK/FK information is already available for constraints and tests.identifyingdraws solid vs. dashed (IDEF1X).On the proposed
role: primary_key/references:syntaxI deliberately did not add this. It would be a second, weaker way to state a fact the KeyGroup/Relationship model already states - and it can't express composite keys or multi-column FKs without spilling into an entity-level block anyway, which is exactly the object model we already have. Two sources of truth for "what's the PK?" is how models drift, so I'd rather keep the one that's already richer.
The two new validators (
MDL-W115,MDL-E114) ship on branch6-on-keys. If the docs on modelling keys weren't discoverable, that's fair feedback - point me at where you looked and I'll make sure the KeyGroup/Relationship path is easy to find.