Conversation
Conflict resolution: kept GORM exporter support added in fork while integrating upstream's 0.2.0 API changes, LSP features, newtype identifiers, and refactored test structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds tests for all branches identified as uncovered (59 lines): - Django: SmallAutoField, BigAutoField, Macaddr, Numeric, Custom type, UUID functional default, export() multi-table, nullable FK with db_column - GORM: conflicting enum qualified names, Char type tag, FK relation field name collision, reverse relation disambiguation (two FKs same target) - CLI: OrmArg::Django mapping, build_output_path Gorm .go extension, clean_export_dir Gorm .go cleanup Also removes the erroneous targets line from rust-toolchain.toml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds `cache-key: no-musl` to every `setup-rust-toolchain@v1` step that does not already specify an explicit cross-compilation target. This changes the Rust toolchain cache key so that the previous cache (written when rust-toolchain.toml briefly had `targets = ["x86_64-unknown-linux-musl"]`) is not restored, eliminating the recurring "override toolchain 'stable-x86_64-unknown-linux-musl' is not installed" error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sea-orm v2.0.0-rc.42 drops its dependency on ouroboros v0.18.5 (and aliasable, id-arena). ouroboros has a RUSTSEC advisory for unsound self-referential structs; without an explicit ignore entry in deny.toml the cargo-deny CI gate was flagging it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…h 100%
Django: build_default Bool(false) and functional-default-on-non-special-type
branches; reference_action_str Restrict/SetDefault/NoAction arms; column
comment rendering; unnamed Index and unnamed composite UniqueConstraint in
Meta; to_pascal_case None arm via double-underscore input.
GORM: Numeric column (add_column_type, build_gorm_tag, decimal import);
unnamed and named Index (collect_index_info body + build_gorm_tag loop);
auto-named composite unique (collect_composite_unique_info None closure);
singular source-table plural (find_reverse_relations format!("{pascal}s"));
FK on_update body + nullable FK pointer type (render_fk_relation_field);
reference_action_str SetNull/SetDefault/NoAction; to_pascal_case None arm
via double-underscore table name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…enerator arb_default_string() could produce reserved words like "in" as bare SQL DEFAULT expressions, causing pg_query to reject the emitted CREATE TABLE with "syntax error at or near 'in'". Added is_pg_reserved_keyword() (full PG 17 §C.1 Type-A list) and a prop_filter on the bare-ident branch so the strategy only generates non-reserved identifiers as unquoted defaults. Also fixes fmt issues in the coverage tests added in the previous commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- erd: add test for inline FK referencing absent parent table, covering the None early-return branch of inline_foreign_key_relation - gorm/django: convert single-expression #[cfg(not(tarpaulin_include))] arms to block form so tarpaulin's source-level exclusion correctly identifies and skips the non_exhaustive future-variant guards Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…bution erd: normalize_tables' map(closure).collect() becomes an explicit for loop (the map closure was the same LLVM source-coverage attribution blind spot documented in this crate's AGENTS.md; the prior fix only made the inner with_context closure eager but left the outer map closure in place). django: replace the enum max_length iterator chain (map(String::len).max()) with an explicit loop, and fold the single-statement primary_key/unique kwargs into a for-loop over conditions instead of standalone trivial ifs. gorm: render_enum's match was the last statement of a unit-returning function; convert to sequential if let with an early return so the function body ends on a plain statement instead of a match tail-expression. All three spots are proven to execute today (existing snapshots already show unique=True, max_length=9, and rendered enum consts), so this is a pure coverage-attribution fix with no behavior change — snapshots are byte-identical and all local build/test/clippy/fmt checks pass. Local tarpaulin isn't runnable on Windows, so the 100% gate result is confirmed by CI on push. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diagnosed via a local tarpaulin run (Docker, same xd009642/tarpaulin
container CI uses) inspecting raw LLVM coverage regions instead of
guessing from reformatted line numbers. Each of the 5 previously-uncovered
lines had a distinct, verified cause:
- erd: normalize_tables' `?` error-propagation branch was never exercised
by any test (all existing tests only feed valid tables). Added a test
with a malformed inline FK reference to trigger normalize()'s Err path.
- erd: collect_foreign_key_relations' table-level FK `let-else { continue }`
branch (absent referenced table) was untested — only the *inline* FK
equivalent had a test (from 584b04b). Added the table-level counterpart.
- django: build_default's Bool(true) path was never tested (only
Bool(false) was). Added test_bool_true_default.
- django: build_default's `return match { guarded-arm => {...} }` construct
had a proven LLVM gap-region artifact on the match/guard header lines
(arm bodies demonstrably execute via existing tests, e.g.
test_server_default_timezone). Restructured into plain if-chains.
- gorm: render_enum's trailing if-let block's closing brace showed 0 hits
despite its body executing (same gap-region artifact); restructured to
collect into a Vec and lines.extend() it as a genuine trailing statement.
- gorm: go_base_type's ComplexColumnType::Enum arm was genuinely dead code
(its only caller, go_type_for_column_mapped, already intercepts Enum
before ever calling go_base_type) — removed.
Verified locally end-to-end: cargo tarpaulin --engine llvm against the
exact CI container reports erd/mod.rs 212/212, django/types.rs 94/94,
gorm/mod.rs 289/289 (100% each), with no other regressions across either
crate. cargo build/test/clippy/fmt and the line-budget check all pass on
the real (normally-formatted) source.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI fmt job flagged these two spots as unformatted (from 510e868); rustfmt --check now passes locally with no other diffs.
…hema DjangoExporter previously fell back to the OrmExporter trait's default render_entity_with_schema (schema-context ignored), so composite-PK junction tables never produced a ManyToManyField on either side. Detect 2-FK junction tables (mirroring the SeaORM junction-detection pattern) and emit ManyToManyField(..., through=..., related_name="+") for both sides, with _via_<junction> disambiguation when multiple junctions link the same pair of tables. Purely self-referential junctions are skipped rather than guessed at.
Both exporters only recognized single-column FKs (columns.len() == 1), so composite FKs silently dropped to plain scalar columns with no relation info at all. - GORM: composite FKs are a genuine native feature (comma-separated foreignKey/references tags), so emit a real belongs-to relation field, with numeric-suffix disambiguation on field-name collisions. - Django: there is no native multi-column FK field, so emit a comment documenting the relationship instead of silently dropping it; the underlying columns still render normally and referential integrity is enforced by the generated database schema. Reuses the existing crate::utils::python::collect_composite_fks helper already shared with SQLAlchemy.
find_reverse_relations() skipped any "other" table equal to the current
table name, which meant a self-referencing FK (e.g. categories.parent_id
-> categories.id) only ever produced the forward belongs-to relation
("Parent"), never the reverse has-many ("Children"). The forward FK and
reverse-scan loop are independent, so the skip was unconditionally
dropping half of every self-referential relationship.
Removed the skip and special-cased self-ref naming to "Children" instead
of a pluralized table name (which would otherwise collide with the
struct's own name). Added regression tests for both directions, split
into gorm/tests/relations.rs (composite-FK + self-ref tests) to keep
gorm/tests/mod.rs under the 1200-line test-file budget.
… suite Extends orm_cases! (60 fixtures) and render_entity_with_schema_snapshots (14 relation-heavy scenarios) to render through Gorm/Django alongside the existing 4 ORMs, matching the project's "every scenario cross-compared across all ORMs" convention. Adds 120 new baseline snapshots; all render successfully with no panics. Reviewing the new baselines surfaced two real, pre-existing Django correctness bugs (never caught because Django had zero cross-ORM fixture coverage before this): 1. build_default()'s final fallback emitted unrecognized SQL constants verbatim as a bare Python identifier (e.g. `default=SOME_CONSTANT`), which is an undefined name and would crash at Django import time. Now omits the default unless it parses as a numeric literal. 2. Any auto-increment primary key (AutoField/SmallAutoField/BigAutoField) was rendered WITHOUT `primary_key=True` on the assumption that the Auto*Field type alone implies it — it does not. Django's own system checks (fields.E100) reject an explicit AutoField without primary_key=True, so every auto-PK schema this exporter has ever produced was invalid at `manage.py check`. Fixed by always emitting primary_key=True when the column is the (non-composite) PK; removed the now-dead is_auto_field() helper and the auto_increment parameter it existed solely to feed.
Django and GORM had no vespertide.json config surface at all, unlike SeaOrmConfig. Adds two minimal, well-scoped knobs mirroring SeaOrmExporterWithConfig's existing pattern: - DjangoConfig.app_label: optional explicit `app_label` written into every generated model's Meta class, for projects where models don't live inside a standard Django app package (Django can't infer the label there). Omitted from Meta and from JSON when None. - GormConfig.package_name: Go package name emitted at the top of every file (`package <name>`), default "models". Both structs are #[non_exhaustive] and threaded through new DjangoExporterWithConfig / GormExporterWithConfig wrappers, wired into the CLI's cmd_export alongside the existing SeaOrmExporterWithConfig special-case. Regenerated schemas/config.schema.json (schema-drift CI gate) to include the two new sections.
SeaOrm/SqlAlchemy/SqlModel/Jpa/Gorm each had a dedicated clean_export_dir_removes_*_for_* test; Django was the only export target without one, even though it shares the .py cleanup path with SqlAlchemy/SqlModel.
|
@2heunxun 님의 작업을 중간부터 제가 이어받아 마무리했습니다. 앞으로의 리뷰 대응도 제가 하겠습니다. 브랜치는 그대로 두고 커밋만 추가했습니다. PR 본문의 GormConfig 설명 등 일부 내용은 현재 코드와 맞지 않습니다. 이어받은 뒤 생성물을 실제 Django와 Go 툴체인에 넣어 확인해 보니, 체크나 컴파일 단계에서 걸리는 출력이 적지 않았습니다. 그 때문에 리뷰에서 지적해 주신 부분 외에도 수정 범위가 많이 늘어났습니다. 아래에는 리뷰 지적 반영과 그 밖의 변경을 나누어 적었습니다. non_exhaustive 제거말씀하신 대로 이 변경으로 필요 없어진 와일드카드 arm 12곳(query 3, exporter 8, planner 1)도 함께 제거했고,
그 밖의 리뷰 지적 반영
CLI 출력 방식을 바꿨습니다GORM과 Django는 Prisma·Drizzle처럼 스키마 전체를 한 파일(
생성 코드에서 고친 것
다른 백엔드에 닿는 변경기존 스냅샷은 하나도 바뀌지 않았습니다. 출력이 달라지는 것은 지금까지 fixture가 없던 입력입니다.
출력이 그대로인 정리는 다음과 같습니다.
테스트렌더 결과를 CLI에는 GORM·Django 출력 스냅샷 4개가 있습니다. 검증
미리 말씀드릴 것
이 PR에서 다루지 않은 것작업 중 확인한 upstream 기존 동작의 결함입니다. 다른 백엔드나 다른 크레이트의 동작을 바꿔야 하는 내용이라 이 PR에서는 손대지 않았습니다.
|
요약
Django, GORM exporter를 검토해서 발견한 부족한 점 7가지를 전부 구현했습니다.
관계(FK) 코드생성이 두 백엔드에서 부분적으로만 지원되고 있었고, 공유 테스트
스위트에도 편입돼 있지 않았습니다. 이번 작업으로 두 백엔드를 나머지 4개
ORM(SeaORM/SQLAlchemy/SQLModel/JPA)과 동등한 수준으로 끌어올렸습니다.
변경 사항
감지해서
ManyToManyField(..., through=...)를 양쪽에 생성. 기존에는스키마 컨텍스트를 아예 무시하고 있었음.
(
foreignKey:...;references:...), Django는 네이티브 지원이 없어서 주석으로관계 정보를 남기도록 처리 (기존엔 컬럼만 남고 관계 정보가 조용히 사라짐).
역방향(has-many) 관계가 아예 생성되지 않던 버그 발견 및 수정.
공유 테스트로 교차검증되고 있었음. 편입 과정에서 Django의 실제 버그 2개를
추가로 발견:
출력되던 문제 (import 시점에 크래시)
primary_key=True가 누락되던 문제 (Django자체 시스템 체크(
fields.E100)에 걸림 — auto PK를 쓰는 거의 모든스키마에 영향)
vespertide.json에서Django
app_label, GORMpackage_name을 커스터마이징할 수 있도록 지원(기존엔 SeaORM만 이런 설정 진입점이 있었음).
clean_export_dir회귀 테스트 추가 — 다른 ORM들은 다 있었는데Django만 빠져 있었음.
머지된 지 오래됐는데도 문서에 전혀 언급이 안 되고 있었음).
다른 5개 백엔드와 다시 비교 검토하다 발견. 복합 PK 테이블에서 어떤
필드에도
primary_key=True가 안 붙고Meta에도 아무 표시가 없어서,Django가 자체적으로 엉뚱한 auto
idPK를 암묵적으로 추가해버리는문제였음 (실제 DB의 PK와 전혀 안 맞음). GORM/SeaORM은 둘 다 이미 제대로
처리하고 있어서 비교하다 바로 드러남. Django 5.2+ 의 네이티브
pk = models.CompositePrimaryKey(...)로 수정.커버리지
위 작업 도중 실제 coverage 회귀(99.92%)가 발생한 걸 CI 실패로 확인하고,
Docker로 CI와 동일한 환경(특수 rustfmt 설정 +
RUST_TEST_THREADS=1+PROPTEST_CASES=1024)을 재현해서 정밀 진단 후 전부 수정했습니다.최종적으로 로컬 재현 환경에서 100.00% (12234/12234 lines) 확인.
검증
cargo test --workspace전체 통과cargo clippy --workspace -- -D warnings클린cargo fmt --all --check클린scripts/check-line-budget.sh통과cargo tarpaulin --engine llvm --fail-under 100— 100% (Docker로 CI 환경동일 재현하여 확인)
CODECOV_TOKEN미설정 이슈로 실패 — 코드와 무관)