Skip to content

fix: type relation columns by their target, not by the relation - #60

Merged
imakecodes merged 1 commit into
mainfrom
fix/relation-column-types
Sep 11, 2026
Merged

imakecodes merged 1 commit into
mainfrom
fix/relation-column-types

Conversation

@imakecodes

@imakecodes imakecodes commented Sep 11, 2026 •

Copy link
Copy Markdown
Member

Closes #38.

Top of stack #61, based on #59. This one changes output for every user, which is why it is separated from the test work below it.

What was still broken

The symptom the reporter hit, a ref naming a column its table never declared, was fixed in #41. I reproduced their exact models and confirmed it. The rest of their report was not fixed:

prod_id foreign_key [not null]

foreign_key and one_to_one name the relation, which DBML already carries in the ref. As a column type they say nothing about what the column holds, and they never match the column they point at, so a reader cannot tell that Book.author_id and Author.id are the same thing.

The README has documented the correct behavior all along, with author_id bigint in its example. That is a type this package has never emitted.

The change

SchemaBuilder.get_field_type resolves a ForeignKey or OneToOneField to field.target_field. The synthesized many-to-many join table resolves both of its columns and its own primary key the same way, instead of hardcoding auto.

Reporter's models from #38, before and after:

Column Before After Target
prod.TestA.prod_id foreign_key auto prod.Prod.id is auto

Across the fixture app:

Column Before After
testapp.Book.author_id foreign_key big_auto
testapp.AuthorProfile.author_id one_to_one big_auto
testapp.Shipment.warehouse_id foreign_key auto
testapp.book_tags.book_id auto big_auto

Because the type now follows the target, a project that mixes DEFAULT_AUTO_FIELD values reports each relation correctly instead of uniformly. Nothing else about the schema moves: names, refs, indexes, notes and enums are untouched.

What holds it

Two invariants, both running over the whole fixture app rather than named columns:

  • both ends of every relation must declare the same type
  • no column may be typed by a relation kind

Plus unit tests on get_field_type and explicit assertions that the type tracks the target primary key.

Verification

53 tests, 99% coverage, make ci clean, green on Python 3.11 with Django 4.2 and Python 3.14 with Django 6.1. The reporter's original models now render prod_id auto against Prod.id auto.

README example corrected, and the rule documented in CLAUDE.md.

🤖 Generated with Claude Code

@imakecodes
imakecodes added this pull request to stack #61 September 11, 2026 15:08
Base automatically changed from test/cover-library-behavior to main September 11, 2026 15:09
Closes #38.

The reported symptom, a ref naming a column its table never declared, was
fixed in #41. The rest of the report was not: a foreign key column was
still emitted as `foreign_key` and a one-to-one as `one_to_one`.

Those name the relation, which DBML already carries in the ref. As a
column type they said nothing about what the column holds, and they never
matched the column they point at, so a reader could not tell that
Book.author_id and Author.id are the same thing.

SchemaBuilder.get_field_type now resolves a ForeignKey or OneToOneField
to field.target_field. The synthesized many-to-many join table resolves
both of its columns and its own primary key the same way, instead of
hardcoding `auto`.

Output changes for every user. A foreign key to a BigAutoField primary
key renders as `big_auto` rather than `foreign_key`; one to an explicit
AutoField renders as `auto`. Nothing else about the schema moves, and the
type now follows the target, so a project mixing DEFAULT_AUTO_FIELD
values reports each relation correctly instead of uniformly.

Two invariants hold it: both ends of every relation must declare the same
type, and no column may be typed by a relation kind. Both run over the
whole fixture app rather than named columns.

The README example claimed `author_id bigint`, a type this package has
never emitted. Corrected, and the rule is now documented.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@imakecodes
imakecodes force-pushed the fix/relation-column-types branch from 00b7f63 to cc993e4 Compare September 11, 2026 15:09
@imakecodes
imakecodes merged commit 2179303 into main Sep 11, 2026
21 checks passed
@imakecodes imakecodes mentioned this pull request Sep 11, 2026
imakecodes added a commit that referenced this pull request Sep 11, 2026
Minor rather than patch: #60 changes the generated output for every user.
A relation column is now typed by the column it points at, so a foreign
key to a BigAutoField primary key renders as `big_auto` where it used to
render as `foreign_key`. No API or CLI break, but anyone versioning a
.dbml file will see a diff.

Shipping since 1.1.2:

- #57 CLAUDE.md and shared .claude configuration
- #58 CI covers the full declared support matrix, Django 4.2 to 6.1 on
      Python 3.11 to 3.14, plus Framework :: Django classifiers so the
      tested range is visible on PyPI
- #59 command surface and relation invariants under test, 89% to 99%
- #60 relation columns typed by their target (closes #38)

Regenerate uv.lock alongside the version. The package is a member of its
own workspace, so uv.lock records project.version and `uv sync --locked`
fails without it. That would have broken the release workflow at the
sync step, before it built anything.

Drop the hardcoded version from the release example in CONTRIBUTING.md;
it went stale on every release.

Co-authored-by: Michel Wilhelm <michel@revealhealth.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

The ForeignKey field was not generated correctly

1 participant