Skip to content

refactor(domain): merge the desired and observed constraint classes - #418

Merged
Tomoscorbin merged 2 commits into
mainfrom
refactor/merge-constraint-pairs
Sep 2, 2026
Merged

Tomoscorbin merged 2 commits into
mainfrom
refactor/merge-constraint-pairs

Conversation

@Tomoscorbin

Copy link
Copy Markdown
Owner

What

Merges each desired/observed constraint class pair into one class:
PrimaryKeyConstraint and ForeignKeyConstraint now serve both sides, with
name: str | None = field(default=None, compare=False). Deleted:
ObservedPrimaryKeyConstraint, ObservedForeignKeyConstraint, and the
_PrimaryKey / _ForeignKey equality mixins.

Why

constraints.py carried seven entities for two concepts. Four of them existed
to buy exactly one thing — a typed non-optional name at the differ's two drop
sites — and the price was hand-rolled __eq__/__hash__ (generated dataclass
equality is class-strict, and the differ compares desired against observed),
typing-fiction annotations on the mixins, eq=False on all four dataclasses,
and isinstance re-checks in ObservedTable.__post_init__ anyway.

With one class per kind, compare=False gives the documented semantics —
names are creation preferences, not structural identity — straight from
generated equality and hashing.

How the old guarantees are kept

  • "An observed constraint carries its catalog name" moves from the type
    system to ObservedTable.__post_init__: a nameless constraint is rejected
    with ValueError, same boundary and same count of runtime checks as the old
    isinstance gate.
  • The differ's drop sites (DropPrimaryKey / DropForeignKey take
    name: str) discharge the now-optional name with an explicit
    _require_catalog_name narrow, backed by the Identifier check both drop
    actions already perform at construction.

One deliberate behaviour widening: ObservedTable now accepts any constraint
that carries a name, rather than demanding a distinct class. That is the point
of the merge — the class split never encoded anything beyond name presence.

Nothing changes on the public delta_engine.schema surface; the Observed*
names were exported from domain.model only.

Tests

  • test_observed_table_requires_observed_constraint_types reworked into
    test_observed_table_requires_named_constraints (unnamed constraint →
    ValueError), pinning the relocated boundary.
  • The class-level "observed constraint requires a name" test is deleted — the
    classes no longer enforce it; the table-level test above owns it now.
  • The test builders' as_observed_primary_key / as_observed_foreign_keys
    projections became identity functions, so they are deleted and fixtures pass
    constraints straight through.
  • Everything else is a mechanical rename.

Gate: 1400 passed (baseline 1402 — the deleted class-level test was 2
parametrized cases, now covered at the table level), ruff check/format clean,
mypy clean (180 files), all 7 import contracts kept. Net −146 lines.

🤖 Generated with Claude Code

A key constraint is the same concept whether it comes from a declaration
or a catalog read, but constraints.py modelled each kind as a class pair
kept comparable by hand-rolled equality mixins. All the split bought was
a typed non-optional name on the observed side.

Each pair is now one frozen dataclass whose name is a compare=False
field, so generated equality gives the documented semantics — names are
creation preferences, not structural identity — for free. The rule the
Observed* types encoded moves to ObservedTable: an observed constraint
must carry its catalog name. The differ's drop sites narrow the now
optional name explicitly, backed by the Identifier check the drop
actions already perform.

The Observed* names were internal (exported from domain.model, which no
user-facing module re-exports), so nothing public changes.
* refactor(domain): drop the primary key positionally, not by name

The compiler already emits ALTER TABLE ... DROP PRIMARY KEY IF EXISTS —
the platform addresses a primary key positionally, a table having at
most one — so the name on DropPrimaryKey was never compiled. It existed
only to fill the action's subject, and it forced the differ to narrow
the observed key's optional name through _require_catalog_name.

DropPrimaryKey now carries the observed key's columns instead,
mirroring SetPrimaryKey: subject and diff entries report what is being
dropped rather than a catalog-owned name. Both primary-key narrows
disappear with the field.

The foreign-key drop genuinely needs the catalog name (DROP CONSTRAINT
is the only unambiguous form: two FKs may share local columns), so its
check stays — inlined at its single site now the helper has no other
callers.

* refactor(domain): state the foreign-key name narrow as an assert

ObservedTable already raises when an observed foreign key is nameless,
so the differ's ValueError duplicated an invariant enforced upstream and
could never fire. An assert says what is true here: this branch is
unreachable, and the check exists only to narrow str | None for mypy.
@Tomoscorbin
Tomoscorbin merged commit f55835f into main Sep 2, 2026
8 checks passed
@Tomoscorbin
Tomoscorbin deleted the refactor/merge-constraint-pairs branch September 2, 2026 15:01
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.

1 participant