Skip to content

feat(exporter): GORM 백엔드 - #197

Open
yyuneu wants to merge 6 commits into
dev-five-git:mainfrom
yyuneu:gorm-exporter
Open

yyuneu wants to merge 6 commits into
dev-five-git:mainfrom
yyuneu:gorm-exporter

Conversation

@yyuneu

@yyuneu yyuneu commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

#169 에서 GORM 백엔드 관련 변경 사항만 분리한 PR입니다. Django는 #198 로, 랜딩 페이지는 #199 로 각각 분리하여 올립니다. 기존 #169 는 이 세 PR로 대체하고 닫을 예정입니다.

작업은 @2heunxun 님이 시작하셨으며, 중간부터 제가 이어받았습니다. 첫 커밋은 해당 작성자의 GORM 구현을 upstream과 병합한 상태 그대로입니다.

#169의 리뷰 코멘트와 답변은 기존 PR에 남아 있으며, 이 PR의 범위에 해당하는 내용은 아래에 다시 정리했습니다.

#169 리뷰 반영

  • SimpleColumnTypeReferenceAction#[non_exhaustive]를 제거하고, 미러 enum인 SimpleColumnKindReferenceActionKind를 삭제했습니다. 이에 따라 불필요해진 와일드카드 arm 12곳(query 3, exporter 8, planner 1)도 제거했습니다. schemas/의 JSON Schema는 description만 재생성되었습니다. 0.x 기준 breaking change이므로 changepack에 기재했습니다. ComplexColumnType은 변경 범위를 넓히지 않기 위해 그대로 두었습니다. 같은 방향으로 정리하기를 원하시면 함께 처리하겠습니다.
  • gorm 설정 섹션을 제거했습니다. 패키지명은 export 디렉터리의 마지막 경로 요소에서만 결정되며, 해당 추론 로직은 config가 아닌 exporter 내부(GormExporterWithConfig::for_export_dir)에 배치했습니다. Go 식별자로 사용할 수 없는 이름은 유효한 식별자로 변환하고, 변환할 수 없는 경우에만 models를 사용합니다.
  • 테스트 전용 re-export를 제거하고, 테스트를 #[cfg(test)] mod tests 내부로 옮겼습니다.
  • CLAUDE.md, CHANGELOG.md, .github, .idea, JPA 스냅샷, erd/mod.rs는 upstream과 동일하므로 diff에 포함되지 않습니다.

CLI 출력

vespertide export --orm gorm은 전체 스키마를 단일 models.go 파일로 출력합니다.

Go에서는 디렉터리가 곧 패키지이며, 관계가 양쪽에서 렌더링됩니다. 따라서 모델 디렉터리를 나누면 두 패키지가 서로를 import하는 순환 참조가 발생해 컴파일되지 않습니다.

출력 파일이 하나로 고정되어 있으므로 확장자 sweep은 수행하지 않습니다(Drizzle과 동일).

파일 첫 줄에는 Code generated by vespertide. DO NOT EDIT.를 기록하며, 이 생성 표식으로 시작하지 않는 기존 models.go는 덮어쓰지 않고 에러로 알립니다.

GORM 출력에서 수정한 사항

작업을 이어받은 후 생성물을 gofmt -lgo vet(실제 gorm.io/gorm 의존)에 넣어 검증하는 과정에서 컴파일 오류를 일으키는 출력들을 확인했습니다.

수정한 내용은 다음과 같습니다.

대상 수정 사항
컴파일 Go로 export할 수 없는 식별자(1usersX1users), 구조체의 TableName() 메서드와 이름이 겹치는 필드, 값 타입 belongs-to로 인한 재귀 타입(항상 포인터로 처리), enum 타입·상수의 중복 선언
관계 PK가 아닌 컬럼을 참조하는 FK의 references: 누락, 복합 FK의 역방향 관계 누락, one-to-one이 has-many로 출력되던 문제(unique FK 및 FK 자체가 해당 테이블의 PK인 경우)
태그 네이밍 빌더와 일치하지 않던 index·unique 이름, char(N)과 네트워크 타입의 type:, 기본값 태그(GORM은 문자열 필드의 기본값을 값 그대로 읽고 양끝 따옴표를 모두 제거합니다)
이름 하나의 패키지에 전체 스키마가 들어가므로 구조체·enum 타입·enum 상수 이름을 패키지 단위에서 한 번만 점유하도록 변경했습니다(scope_names.rs). 테이블 role과 enum role, 상수 Status + code와 테이블 status_code가 중복 선언되지 않습니다.
리터럴 설명·컬럼명·enum 값·기본값에 포함된 "·\·개행을 string_literal로 이스케이프합니다. 기존 Drizzle의 헬퍼를 공용으로 옮긴 것으로, Drizzle 출력은 바이트 단위로 동일합니다.
레이아웃 기존 출력 전체가 gofmt 레이아웃과 일치하지 않던 문제를 수정했습니다. 현재는 gofmt -l을 그대로 통과합니다.

다른 백엔드에 영향을 주는 변경

기존 스냅샷은 변경되지 않았습니다. 새로 추가된 스냅샷 125개는 GORM 스냅샷 75개와 신규 fixture에 대한 다른 ORM 출력 50개로 구성됩니다.

  • Prisma·Drizzle: FK 자체가 해당 테이블의 PK인 one-to-one 관계의 역방향이 has-many가 아닌 has-one으로 출력됩니다(Profile[]Profile?). 기존 공용 역방향 스캔이 unique 여부만 확인하고 있던 문제를 수정했습니다. SeaORM은 기존에도 has-one으로 출력하고 있었습니다. 신규 fixture에서만 드러나는 변경이며, 기존 스냅샷은 그대로입니다.
  • 출력 변경이 없는 정리: Drizzle의 ts_string을 공용 string_literal로 이동하고, 정수 enum 기본값을 값으로 변환하는 integer_enum_variant_value를 Drizzle에서 공용으로 옮겼습니다. single_column_fk_targetssingle_column_fk_details로 통합하고(SQLAlchemy·SQLModel 호출부), BackRelation에 FK·참조 컬럼 및 액션 필드를 추가했습니다(Prisma·Drizzle이 사용하는 필드는 그대로 유지). SeaORM의 JSONB 판정도 공용 is_jsonb_custom_type으로 옮겼습니다.
  • codegen 벤치마크는 7개 ORM을 대상으로 실행됩니다.

테스트

렌더링 결과를 contains()로 확인하던 모듈별 테스트와 모듈별 스냅샷을 공용 7-ORM 스냅샷으로 통합했습니다. 각 모듈에는 타입·기본값·이름 규칙에 대한 함수 단위 매핑 테스트만 남겼습니다.

CLI에는 GORM 출력 스냅샷 2개를 추가했습니다. orm_cases!로 검증되지 않는 CLI 배선(단일 파일 출력, 생성 표식, 패키지명)을 고정하기 위한 테스트입니다.

검증

검증 항목 결과
fmt, clippy(-D warnings), 전체 테스트, line-budget, schema-gen 무변경, cargo-deny 모두 통과
동일한 커밋을 제 포크의 PR에서 실행 CI 전체 잡(coverage 100%, changepacks, semver-checks 포함), mutation 16개 샤드, benchmarks 통과
go vet(실제 gorm.io/gorm 의존), gofmt -l examples/app(모델 11개)과 오류가 발생하기 쉬운 입력만 모은 스키마 모두 통과. GORM 스냅샷 75개도 모두 gofmt 레이아웃과 일치
런타임 vespertide가 생성한 SQLite DDL로 DB를 구성하고 GORM으로 삽입·조회했습니다. 자연키 FK 조인, one-to-one 양방향 관계, self-reference, 복합 PK 정션, CHECK, ON DELETE, 기본값을 확인했습니다.

현재 이 PR의 워크플로는 승인 대기(action_required) 상태입니다. 실행을 승인해 주시면 감사하겠습니다.

미리 말씀드릴 사항

  • scope_names.rs는 Drizzle의 drizzle::bindings와 역할이 일부 겹칩니다. 다만 Drizzle은 import 심볼과 콜백 파라미터를 먼저 점유하고, customType·relations const까지 처리하며, enum 이름을 항상 테이블로 한정하는 등 이름 규칙이 다릅니다. 따라서 이 PR에서는 통합하지 않았습니다. 통합하는 편이 낫다고 보시면 후속 PR로 진행하겠습니다.
  • GORM은 문자열 기본값의 양끝 따옴표를 모두 제거하므로, 따옴표로 시작하거나 끝나는 값은 기본값 태그를 생략합니다. DB 측 기본값은 그대로 유지됩니다.

이 PR에서 다루지 않은 사항

작업 중 확인한 upstream의 기존 동작 관련 결함입니다. 다른 백엔드나 크레이트까지 수정해야 하는 내용이므로 이번 PR에서는 다루지 않았습니다.

아래 표의 첫 세 건은 실제 사용에 미치는 영향이 큰 문제입니다.

결함 증상
export의 확장자 sweep이 사용자 파일을 삭제 파일을 쓰기 전에 export 디렉터리에서 해당 확장자의 파일을 재귀적으로 모두 삭제합니다. 생성된 파일인지 구분하지 않으므로 SeaORM·SQLAlchemy·SQLModel·JPA는 함께 둔 사용자 소스가, Prisma는 schema.prisma가 삭제됩니다. Drizzle과 이 PR의 GORM은 sweep을 수행하지 않습니다.
SQLAlchemy 출력의 import 실패 모델이 DeclarativeBase를 직접 상속하여 SQLAlchemy 2.0에서 InvalidRequestError가 발생합니다. 모든 모델에 해당하며, 파일 간 relationship 참조를 위한 공용 Base__init__.py도 없습니다.
JSON 기본값의 DDL 실패 json 컬럼의 default{"a": 1}로 지정하면 SQL 계층에서 DEFAULT {"a": 1}을 그대로 출력하여 DDL이 실패합니다(SQLite에서 확인). '{"a": 1}'처럼 SQL 리터럴로 출력해야 합니다. exporter fixture의 json_default도 전자의 형태입니다.
출력 경로 충돌 검사 없음 서로 다른 모델 파일이 동일한 출력 경로로 매핑되면 경고 없이 한쪽이 덮어써집니다. 쓰기가 동시에 진행되므로 어느 쪽이 남을지도 정해져 있지 않습니다.
JPA package 선언 누락 모델 디렉터리가 중첩되면 하위 디렉터리에 파일이 생성되지만 package 선언이 없어 컴파일되지 않습니다.
JPA enum 상수 값에 하이픈이나 선두 숫자가 포함되어 있으면 Java 식별자로 사용할 수 없는 상수가 그대로 출력됩니다.
case 변환 후 중복되는 컬럼명 user_iduserId가 한 테이블에 있으면 SeaORM·JPA·Drizzle에서 동일한 필드가 두 번 선언됩니다. GORM은 공용 claim_binding으로 접미사를 붙여 처리합니다.
SQLAlchemy의 custom 타입 컬럼 mapped_column("JSONB", …)로 렌더링되어 타입이 아닌, 이름이 "JSONB"인 컬럼이 선언됩니다.
SQLAlchemy·SQLModel enum enum 값을 이스케이프하지 않고 클래스 이름도 유효한 식별자로 변환하지 않습니다. 값에 "·\가 포함되거나 이름이 1st·info-level이면 문법 오류가 발생합니다.
SQLAlchemy·SQLModel 파일명 모델 파일 이름이 Python 식별자가 아니면(1st-users.json) import할 수 없는 모듈 파일이 생성됩니다. #169에서는 수정했으나 이 PR의 주제와 무관하여 제외했습니다.
cmd_exportvalidate_schema를 거치지 않음 FK 대상 테이블이 없거나 PK가 없는 스키마가 그대로 입력되어, 단일 파일 백엔드에서 정의되지 않은 이름이 출력됩니다. Prisma는 같은 이름으로 변환되는 enum과 테이블을 하나로 합칩니다.
모델 로더의 미정렬 collect_model_pathsread_dir 순서를 그대로 사용하므로, 동일한 식별자로 변환되는 테이블의 번호가 환경에 따라 달라질 수 있습니다.
동일한 컬럼 목록을 가진 이름 없는 인덱스 동일한 컬럼 목록을 가진 이름 없는 인덱스가 두 개 있으면 SQL 계층과 모든 exporter에서 이름이 중복됩니다.
SeaORM의 공용 헬퍼 사본 pluralize 사본, 별도의 역방향 관계 스캔, 동작이 다른 primary_key_columns가 남아 있습니다.
--export-dir 도움말 기본값을 modelsDir로 안내하지만 실제 값은 modelExportDir이며, 파일이 삭제될 수 있다는 안내도 없습니다.
CLI export의 죽은 경로 build_output_path의 마지막 arm은 도달할 수 없지만 테스트 2개가 이를 고정하고 있으며, tests/prisma.rs는 생성 파일을 contains()로 확인합니다.

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.

2 participants