fix: emit composite foreign keys as one constraint, not one per column - #23
Merged
TheCrab13 merged 2 commits intoAug 20, 2026
Conversation
BREAKING CHANGE: foreign keys moved from Column to Table Column.foreign_key is removed; a table now exposes Table.foreign_keys, a list of ForeignKey carrying paired columns/ref_columns. The per-column shape could not represent a composite key: it was emitted one column at a time, and each half referenced a key that is not unique on its own. Iterate table.foreign_keys and read fk.columns / fk.ref_columns instead of column.foreign_key and fk.column.
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #23 +/- ##
==========================================
+ Coverage 92.05% 92.09% +0.03%
==========================================
Files 78 79 +1
Lines 3159 3161 +2
==========================================
+ Hits 2908 2911 +3
+ Misses 251 250 -1
🚀 New features to boost your workflow:
|
TheCrab13
deleted the
fix/foreign-keys-definition-issue-for-composite-primary-key/no-ref
branch
August 20, 2026 09:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Foreign keys were carried on
Column, so a composite key had nowhere to live: theemitters wrote one
ALTER TABLE … ADD FOREIGN KEYper column, and each half pointedat a key that isn't unique on its own. Postgres rejects the result with "there is no
unique constraint matching given keys for referenced table". The split also invented
constraints the source never had — a two-column FK came out as two unrelated ones.
This moves foreign keys to the table level.
ForeignKeynow holdscolumnsandref_columns(paired positionally), a shared helper groups the per-column rows everycatalog returns into one constraint, and the emitters write one statement per
constraint. All five readers select the constraint name and order by column position.
Type of change
fix:) — non-breaking change that fixes an issuefeat:) — non-breaking change that adds capabilityperf:)refactor:) — no behaviour changedocs:)ci:/chore:)Column.foreign_keyis gone; useTable.foreign_keys, a list ofForeignKey. The oldshape could not represent a composite key, so keeping it as an alias would have meant
lying about half the constraints.
Linked issues
How was this tested?
Simple and composite keys are now covered at each layer: the five readers (SQLite gets
its
PRAGMArows out of order on purpose, to pin theseqsort), both emitters, and aCLI end-to-end run over a real SQLite database with a composite PK and FK. Reader tests
keep both kinds of constraint on the same table, so a regression that merged two
distinct FKs into one would fail too.
The four Docker fixtures gained an
assembly/assembly_votepair with a compositekey, with an assertion per engine.
I also checked the reported dump (510 FK statements): 481 were already valid, the other
29 span the 9 composite-PK parents this PR fixes. No FK there references a non-PK
column, so regenerating should apply cleanly.
pytest tests/unit tests/clipasses locally — 598 passedlint-importspasses (no driver leaked into domain/application)tox -e syntaxpasses (black, isort, flake8, mypy, pylint) — ran black, isort,flake8 and mypy directly on
db2sql/andinstaller/; pylint and tox not runtests/functionalran against thedocker stack — not run, no Docker available where I worked. The new fixture
SQL was only parsed per dialect, never executed. Needs a
run-functionalrun, andrun-oraclefor the Oracle half.Checklist
docs/andCHANGELOG.md— the autodocpages pick up the new model, but there is no migration note yet;
CHANGELOG.mdisgenerated by semantic-release
Notes for the reviewer
The
fix:commit carries aBREAKING CHANGE:footer, so semantic-release will cut amajor release.
Two things left out on purpose:
UNIQUEconstraints. An FK referencing a uniquenon-primary key would fail the same way — not the case in the dump that triggered this,
but it is the same class of bug and deserves its own PR.
PRAGMA foreign_key_listreturns a NULL target column when the child clauseomits the parent columns. That was already mishandled before this change and still is;
resolving it needs a second pass over the parent's primary key.