- docs/design/226-catalog-gallery/README.md — 12 changed files, DisplayCategory enum, sealed CatalogItem, 5 scenarios - docs/design/226-catalog-gallery/fn-catalog_repository.md — 3-source unification algorithm + helpers - docs/design/226-catalog-gallery/fn-migration_v1_to_v2.md — first schema migration (DROP + reseed pattern) - docs/adr/0004-catalog-recategorization-and-first-migration.md — Catalog 재분류 + 첫 마이그레이션 정책 Refs #226
6.0 KiB
6.0 KiB
함수 설계서: migrateV1ToV2 (#226)
부모 설계서: ./README.md · 상태: Draft 작성: [AI] Architect (2026-06-12) · 구현:
app/lib/data/db/app_database.dart의migration.onUpgrade인라인 또는 file-private top-level · 테스트:app/test/data/db/migration_v1_to_v2_test.dart
1. 시그니처
Future<void> _migrateV1ToV2(Migrator m, AppDatabase db) async { ... }
MigrationStrategy.onUpgrade 에서 dispatch:
onUpgrade: (m, from, to) async {
if (from == 1 && to >= 2) {
await _migrateV1ToV2(m, this); // this = AppDatabase
}
// future:
// if (from <= 2 && to >= 3) await _migrateV2ToV3(m, this);
if (from > to || to > schemaVersion) {
assert(false, 'Unknown upgrade path: from=$from to=$to (schemaVersion=$schemaVersion)');
}
},
2. 책임 (단일 책임, 1줄)
v1 DB 의 protocols 테이블을 v2 CHECK 제약으로 교체하고 시드 flag 클리어 — read-only catalog 의 첫 마이그레이션 패턴.
3. 입력
| 파라미터 | 타입 | 제약 | 설명 |
|---|---|---|---|
m |
Migrator |
drift 의 schema migrator | DDL API |
db |
AppDatabase |
non-null | metaKv 클리어용 |
4. 출력
- 반환:
Future<void>. - 부수효과:
protocols테이블 DROP + CREATE (CHECK 제약 7 카테고리로) + 인덱스IDX_protocols_category재생성.meta_kv에서kSeededV1Flagrow DELETE.- 다음 부팅 시
SeedImporter.importIfNeeded()가 재시드 트리거.
- user 테이블 (Habits, Phases, TrackerEntries, ...) 무변화.
5. 동작 / 알고리즘
1. await m.deleteTable(db.protocols);
# SQLite: DROP TABLE protocols
# 인덱스도 자동 cascade drop.
2. await m.createTable(db.protocols);
# v2 schema 로 CREATE TABLE protocols (
# id TEXT PRIMARY KEY,
# category TEXT CHECK (category IN (
# 'light_circadian','sleep','movement','nutrition',
# 'focus_cognition','recovery_stress','emotion_relationship'
# )) NOT NULL,
# title TEXT NOT NULL,
# ...
# );
3. await m.createIndex(Index(
'IDX_protocols_category',
'CREATE INDEX IDX_protocols_category ON protocols(category)',
));
# drop 시 자동 cascade 됐어도 명시적 재생성.
4. await (db.delete(db.metaKv)
..where((t) => t.key.equals(kSeededV1Flag)))
.go();
# 시드 flag 1 row 삭제. 다음 부팅이 importIfNeeded() 호출 → 새 JSON 으로 reseed.
6. 에러 & 실패 모드
| 조건 | 처리 | 반환/예외 |
|---|---|---|
| DROP 실패 (이론상 없음, 사용자 락) | drift 가 transaction 롤백 → 부팅 실패. 사용자에겐 명시적 에러. | SqliteException 전파 |
| CREATE 실패 (이론상 없음) | 동상 | SqliteException 전파 |
| metaKv 삭제 실패 (이론상 없음) | 동상 — but 여기까지 도달 시 protocols 테이블은 v2 형태. 다음 부팅 시 flag 가 'true' 인 채라 reseed 안 함 → protocols 빈 상태. 위험. 그래서 metaKv 삭제는 트랜잭션 내 마지막 단계가 아니라 순서가 중요. | drift onUpgrade 전체가 트랜잭션 — drop/create 와 metaKv 삭제 같이 묶임. |
| 다음 부팅 시 seed JSON 손상 | SeedImporter 가 CHECK 위배 throw → 부팅 실패. dev 단말 1대 영향이라 수용. | FormatException / SqliteException |
7. 엣지케이스
- 신규 설치 (
onCreate) — 본 함수 호출 0. createAll 이 v2 schema 그대로 적용 후 seed 가 v2 JSON 로드. 정상. - v1 → v3+ 점프 (이론상 없음, 현재 schemaVersion=2) —
from=1, to=3이면 v1→v2 → v2→v3 순차 실행 가정._migrateV2ToV3가 아직 없어 dispatch 가 발견 못 함 → assert false. v3 도입 시점에 명시. - 트랜잭션 중단 — drift 의 onUpgrade 는 db.transaction 안에서 실행. 부분 실패 시 자동 롤백 → 사용자 DB 는 v1 그대로. 다음 시도에서 재실행.
- 사용자 데이터 보호 — 본 함수는 Protocols 만 건드림. Habits/TrackerEntries 등 user 테이블 0 영향.
migration_v1_to_v2_test.dart가 명시적 검증. - 인덱스 재생성 누락 시 — query latency ↓ 만 영향 (정상 동작). 본 함수가 명시적으로 createIndex 호출하므로 보호.
8. 복잡도 / 성능
- 시간: O(1) — DDL 4건.
- 실측: < 50ms (dev 단말, drift 의 transaction overhead 포함).
- 호출 빈도: dev 단말 평생 1회 (v1 → v2 한 번). 사용자 신규 설치는 호출 0.
9. 의존성
- drift
MigratorAPI (deleteTable, createTable, createIndex). kSeededV1Flag상수 (core/constants.dart).- AppDatabase 의
protocolsgetter (스키마 가져오기).
10. 테스트 케이스
- smoke: in-memory DB 를 v1 schema 로 raw SQL 로 생성 →
_migrateV1ToV2(m, db)호출 → protocols 테이블의 CHECK 제약이 v2 7 카테고리인지 검증 (PRAGMA / sqlite_master 조회) - flag 클리어: 사전에 metaKv 에
seeded_v1='true'insert → migrate → metaKv 조회 시 row 없음 - user 데이터 보호: 사전에 Habits / Phases / TrackerEntries 에 row insert → migrate → 모두 그대로
- v2 CHECK 위배 negative: migrate 후
INSERT INTO protocols (..., category='health', ...)시도 → SqliteException - v2 CHECK 통과 positive:
category='light_circadian'insert → 성공 - 인덱스 존재: migrate 후
sqlite_master에서IDX_protocols_category발견 - 이중 호출 안전성: 동일 DB 에 migrate 2회 호출 → 두 번째도 성공 (idempotent 가정. drift
deleteTable이 미존재 테이블에 graceful 인지 확인 필요 — OQ) - integration with onUpgrade: schemaVersion=2 인 AppDatabase 로 v1 DB 열기 → onUpgrade 자동 호출 → 정상 동작
11. 추적성
- 인수조건: #226 AC-4 (8 카테고리 재분류 — DB CHECK 갱신 부분), AC-8 (user 테이블 무변화), AC-9 (마이그레이션 unit test).
- 관련 ADR: ADR-0004 (본 이슈에서 발행 — Catalog re-categorization + first schema migration policy).