feat(exporter): Drizzle ORM exporter 추가 - #186
Open
yyuneu wants to merge 6 commits into
Open
Conversation
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.
개요
6번째 ORM 백엔드로 Drizzle(TypeScript)을 추가합니다.
Drizzle은 백엔드 중립 표현이 없어서, 한 번의 export가 방언별 파일 3개를 씁니다.
완성 기준은 vespertide로 만든 DB에
drizzle-kit push를 돌려도 변경이 없어야 한다로 잡았습니다.도커(postgres:17 / mysql:8)에 vespertide가 생성한 DDL을 적용하고 push를 반복해 확인했습니다.
설계상 특이사항
1. 출력이 방언별 3파일인 이유
Drizzle에는 백엔드 중립 표현이 없습니다.
pgTable/mysqlTable/sqliteTable이 서로 다른 패키지(drizzle-orm/pg-core등)에 있어 import 라인에서 갈라지고, 타입 생성자와 기본값 표기도 방언마다 다릅니다. Prisma처럼 중립 단일 파일을 만들 수 없어 한 번의 export가 방언당 한 파일을 씁니다.OrmExporter트레이트 경로(크로스-ORM 비교용 단일String)는 PostgreSQL을 정본으로 렌더합니다.2. 이름·타입·기본값 철자를 전부 SQL 계층에 맞춘 이유
drizzle-kit push는 모델과 DB를 사실상 문자열 수준으로 비교하므로, 모델은 vespertide가 실제로 만든 것을 그대로 말해야 합니다. 제약 이름은 SQL 계층과 같은vespertide-naming빌더(build_unique_constraint_name/build_index_name/build_foreign_key_name/build_enum_type_name)를 사용하고, 나머지 규칙은 왕복에서 드리프트 문장이 나온 항목을 하나씩 제거하며 도출했습니다:uniqueIndex(): 컬럼 체인.unique()금지CREATE UNIQUE INDEX.unique()는 제약으로 introspect되어 kit가 드랍 후 재생성.generatedByDefaultAsIdentity()DROP IDENTITY를 시도{table}_pkey/ mysql{table}_{cols}{table}_{enum}, 테이블당pgEnum1개CREATE TYPE이 항상 테이블-프리픽스. Prisma처럼 전역 dedup하면 타입명 자체가 드리프트customType헬퍼Uint8Array라@types/node없이 컴파일sql`CURRENT_TIMESTAMP`/ mysql.defaultNow()/ sqlitesql`(CURRENT_TIMESTAMP)`sql`(uuid())`소문자3. FK가
.references()체인이 아니라foreignKey()연산자인 이유체인이 못 하는 것 세 가지 때문입니다: 제약 이름 운반(위 표), 복합키, 자기참조(foreignColumns를 콜백의
t에서 가져와 테이블 const가 자기 초기화식 타입 추론에 들어가는 것을 회피).4. 파일-스코프 바인딩 충돌 처리 (
drizzle/bindings.rs)한 파일에 top-level
const가 4종(customType / pgEnum / 테이블 /{table}Relations) 공존하고, import 심볼·콜백 파라미터와도 네임스페이스를 공유합니다.to_camel_case가_와-를 접기 때문에 서로 다른 DB 이름이 한 바인딩으로 겹칠 수 있고(user_relations테이블 vsuser의 relations 블록), 테이블명이 import(sql,integer)나 콜백 파라미터(t,one,many)와 같을 수도 있습니다. 특히t가 겹치면 FK의 foreignColumns가 콜백 파라미터로 해석되어 컴파일은 되는데도 틀린 출력이 됩니다. 그래서 선언 순서대로 바인딩을 claim하고 충돌 시 숫자 접미사를 붙이며, 모든 참조가 확정된 이름을 따라갑니다. 충돌이 없는 스키마의 출력은 바이트 단위로 동일합니다.5. drizzle export가 확장자 글롭 클린을 쓰지 않는 이유
기존 export 경로는 재생성 전에 export 디렉터리에서 해당 확장자 파일을 재귀 삭제합니다. drizzle의 확장자는
.ts인데, export 루트(기본src/models)가 사용자 소스 디렉터리와 겹치는 프로젝트에서는 사용자가 직접 쓴 파일까지 지워집니다. 출력 파일명이 3개로 고정이라 덮어쓰기로 충분하므로 drizzle 경로는 클린을 생략하게 했습니다.테스트 설계
orm_cases!픽스처 1개 + 매크로 1줄로 6-ORM 스냅샷을 만드는 기존 규칙을 따랐습니다. Drizzle 추가 자체는 각 매크로에#[case::drizzle]1줄render_schema(tables, dialect))이라 Prisma-exception 패턴대로 모듈 인라인 스냅샷(스냅샷 파일은 공유src/tests/snapshots/)types.rs는 전 타입 × 3방언 rstest 매트릭스(기대 문자열 직접 명시),render.rs는 default_chain·FK entry 등 분기 단위 테스트렌더링 밖 검증:
drizzle-kit push반복 → 수렴 확인 (개요의 결과)tsc --strict무오류함께 들어간 변경
enum_scan(테이블 enum 수집),constraint_scan(FK 관계 네이밍·역관계 수집),utils/common(필드명 claim). Prisma는 위임으로 전환했고 기존 5개 백엔드의 스냅샷은 전부 무변경입니다vespertide-naming에to_camel_case/infer_relation_field_name을 추가했습니다 (JPA의_id스트립도 후자로 위임)prepare_export_dir로 공용화했습니다vespertide-exporter0.3.0 → 0.4.0:Orm이 exhaustive pub enum이라 변형 추가가 breaking입니다(cargo-semver-checks 기준).Orm에#[non_exhaustive]를 붙여 이후 백엔드 추가를 minor로 만들지는 API 계약 판단이라 이번에는 손대지 않았습니다한계
timestamp_text,enum_text등)은 sqlite-core 생성자로 표현할 수 없습니다. 런타임 동작(타입 affinity)은 문제가 없어 문서화로 종결했습니다