Skip to content

Reject models declaring multiple primary_key=True columns #257

Description

@0x054

Context

Follow-up from ADR-0002 (docs/adr/0002-unified-column-fact-derivation.md) and PR #256 (ColumnSpec refactor, #255).

Ferro does not validate that a model declares at most one primary key. A model can legally declare primary_key=True on more than one column — including one via FerroField/Field() and another via raw json_schema_extra={"primary_key": True} — with no error at class-definition time.

Before #256, the runtime PK-resolution helpers (save()'s id assignment, _primary_key_field_name, relationship descriptors, shadow-FK typing) scanned ferro_fields first and therefore always preferred the ferro-declared PK for such a malformed model. After #256 (which unified column-fact derivation to be declaration-path-agnostic — the whole point of the refactor), PK resolution is by column declaration order instead. Neither behaviour is defensibly correct, because the model is already malformed: it has two primary keys, which no SQL backend supports (the Rust layer resolves by first-flagged-in-IR-order regardless).

Ask

Add validation at class-definition time (the metaclass, where the other column-shape validations live) that raises a clear TypeError when more than one column is flagged primary_key=True, naming the offending columns and both declaration paths. This turns a silent declaration-order ambiguity into a loud, actionable error.

Notes

  • Composite primary keys are not currently a Ferro feature, so "at most one PK column" is the correct invariant today. If composite PKs are ever added, this validation would need to move to "a coherent PK definition" rather than "exactly one column".
  • Low-risk: no valid existing model declares two PKs (it would already produce broken DDL / a Postgres CREATE panic, per ADR-0002).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions