Migrate an application's data access layer from one database schema to another. Use when tables are renamed, consolidated, split, or columns change — and the app's queries, mappings, and abstraction layer must be updated without data loss. Read-only against the database.
Install
npx skillscat add saajunaid/caddis-plugin/schema-migration Install via the SkillsCat registry.
Schema Migration Skill
Systematically migrate an application from a legacy database schema to a new schema with zero data loss and full functional parity.
When to Use
- Tables are being renamed, consolidated, or split
- Columns are renamed, retyped, or moved between tables
- The DB team has restructured tables and the app must follow
- Data previously in file storage has been migrated into database tables
- Query files, column mappings, or config need updating after a schema change
- User says "migrate to new tables", "schema changed", or "tables restructured"
Constraints (Read Before Starting)
- Read-Only — Never execute DDL or DML (
CREATE,ALTER,INSERT,UPDATE,DELETE) against the database. All analysis is SELECT-only. - No SQL Aliases — Proposed queries must use direct
Table.Columnnotation. Aliases obscure the mapping and make future changes harder to trace. - No Assumptions — Every column mapping must be verified against the actual schema DDL. If a mapping is ambiguous or a source column has no clear target, flag it as a GAP rather than guessing.
- Scratchpad Protocol — If context becomes large, save intermediate findings to
.caddis/plans/temp_migration_scratchpad.mdto preserve state across reasoning steps.
Phase 1: Schema Forensics
Objective
Build a complete picture of old and new schemas. Produce the Schema Manifest — the single source of truth for the migration.
Actions
Collect Legacy Schema DDL
- Extract
CREATE TABLEstatements for all legacy tables (from schema files, INFORMATION_SCHEMA, or DDL scripts) - Note all columns, data types, nullability, defaults, and foreign keys
- Document the table-to-variable mapping (e.g.,
.enventries, config files)
- Extract
Collect Target Schema DDL
- Extract
CREATE TABLEstatements for all new/consolidated tables - Note structural differences: merged tables, renamed columns, new columns, removed columns, type changes
- Extract
Audit the Abstraction Layer
- Read the app's current mapping files (e.g.,
column_mappings.yaml,.env, config modules) - Read the current query files (e.g.,
queries.yaml,.sqlfiles) - Identify every table and column the app references
- Read the app's current mapping files (e.g.,
Produce the Mapping Manifest
## Schema Mapping Manifest
### Table Mapping
| Legacy Table | Target Table | Notes |
|-------------|-------------|-------|
| OldTableA | NewTable1 | Merged with OldTableB |
### Column Mapping
| Legacy Table | Legacy Column | Type | Target Table | Target Column | Type | Transform |
|-------------|--------------|------|-------------|--------------|------|-----------|
| OldTableA | user_name | NVARCHAR(100) | NewTable1 | UserFullName | NVARCHAR(200) | Direct |
| OldTableA | status_code | INT | NewTable1 | Status | VARCHAR(20) | Map: 1='Active', 2='Inactive' |
### Gaps (Unmapped)
| Source | Column | Issue |
|--------|--------|-------|
| OldTableC | legacy_flag | No equivalent in target schema |
| File: results/*.json | summary_json | Verify if present in CallTranscript table |Output: Schema Mapping Manifest (table in the migration report)
Phase 2: Query Translation
Objective
Rewrite every application query against the new schema. No aliases — direct Table.Column references only.
Actions
Inventory Current Queries
- List every query from query files, repository classes, and inline SQL
- Tag each query with its page/feature (e.g., homepage, search, analytics)
Translate Each Query
- Apply the column mapping from Phase 1
- Rewrite JOINs to reflect the new table structure
- Handle type conversions inline (e.g.,
CAST,CASE WHEN) where the new schema uses a different data type
Document Translation
### Query: [query_name]
**Old Query:**
```sql
SELECT col_a, col_b FROM OldTable WHERE col_c = ?New Query:
SELECT NewTable.ColA, NewTable.ColB FROM NewTable WHERE NewTable.ColC = ?Changes:
OldTable→NewTablecol_a→ColA(renamed)- No type changes
4. **File Architecture Decision** (if applicable)
- If the app uses a monolithic query file, evaluate splitting into per-page/per-feature files
- Compare `.yaml` vs `.sql` for query storage:
| Format | Pros | Cons |
|--------|------|------|
| `.yaml` | Structured metadata (tags, descriptions), easy Python parsing | No SQL syntax highlighting, IDE support limited |
| `.sql` | Full IDE support, syntax checking, execution in DB tools | Needs a loader; metadata requires naming conventions or comments |
- Recommend based on the project's existing patterns and tooling
**Output**: Translated Query Library + File Architecture Recommendation
---
## Phase 3: Parity Testing
### Objective
Prove that the new queries return the same data as the old queries. Evidence-based — not theoretical.
### Actions
1. **Run Old Queries** — Execute each legacy query and capture:
- Row count
- Column names and types
- Sample rows (5–10 representative rows)
- Aggregates where applicable (SUM, COUNT, DISTINCT)
2. **Run New Queries** — Execute each translated query against the new schema and capture the same metrics
3. **Compare Results**
```markdown
### Parity Report: [query_name]
| Metric | Old Query | New Query | Match |
|--------|-----------|-----------|-------|
| Row count | 1,247 | 1,247 | ✅ |
| Columns | 8 | 8 | ✅ |
| Sample hash | abc123 | abc123 | ✅ |
**Data Type Check:**
| Column | Old Type | New Type | Compatible |
|--------|---------|---------|------------|
| created_at | DATETIME | DATETIMEOFFSET | ⚠️ App must handle offset |- Flag Discrepancies
- Row count mismatch → possible fan-out from JOINs or missing WHERE filters
- Type mismatch → document required CAST/conversion in the app layer
- NULL differences → document new nullable columns or default changes
- Missing data → flag as GAP with severity
Output: Parity Report per query (pass/fail with evidence)
Phase 4: Abstraction Layer Update
Objective
Update the application's configuration and mapping layer to reflect the new schema.
Actions
Update Column Mappings — Produce the new version of
column_mappings.yaml(or equivalent) reflecting target table/column namesUpdate Environment Config — Produce the new
.envvariable values (table names, schema names)Update Query Files — Produce the new query file(s) using the translated queries from Phase 2
Trace Application Code Impact
- Identify every file that reads from the mapping/config layer
- List files that will need code changes (e.g., new column names in data models, display logic, filters)
- Categorize impact:
no change needed|config-only change|code change required
### Impact Analysis
| File | Current Reference | Change Type | Details |
|------|------------------|-------------|---------|
| services/data_loader.py | OldTable via .env | Config-only | Update .env mapping |
| pages/home.py | col_a in display | Code change | Rename to ColA |
| models/complaint.py | OldTable.status_code (INT) | Code change | Now VARCHAR, update parsing |Output: Updated config files + application impact matrix
Phase 5: Migration Report
Objective
Produce a single, comprehensive document suitable for use as an Implementation Plan. This document must be complete enough that a developer can execute the migration with zero ambiguity.
Report Structure
# Schema Migration Report
## Executive Summary
- Source: [N] legacy tables → Target: [M] consolidated tables
- Total queries migrated: [count]
- Parity status: [all pass / N failures]
- Gaps identified: [count]
## 1. Schema Mapping Manifest
[From Phase 1]
## 2. Translated Query Library
[From Phase 2, grouped by page/feature]
## 3. Parity Test Results
[From Phase 3, pass/fail per query with evidence]
## 4. Abstraction Layer Changes
[From Phase 4 — updated YAML, .env, config diffs]
## 5. Application Impact Matrix
[From Phase 4 — file-by-file change list]
## 6. File Architecture Recommendation
[From Phase 2 — split strategy and format decision]
## 7. Edge-Case Registry
[Every risk identified during analysis]
| # | Risk | Severity | Mitigation |
|---|------|----------|------------|
| 1 | DATETIME → DATETIMEOFFSET | Medium | Add .replace(tzinfo=None) in Python |
| 2 | Nullable column X now NOT NULL | High | Add default value in query |
## 8. Migration Execution Checklist
- [ ] Column mappings updated
- [ ] Environment config updated
- [ ] Query files updated and tested
- [ ] Application code changes identified and assigned
- [ ] All parity tests passing
- [ ] Edge cases mitigated
- [ ] Rollback plan documentedOutput Location
Place the final report at the path specified by the user (default: .caddis/agent-docs/migration_report.md — it is an agent-produced report, not a backlog item).
Checklist
- Legacy schema DDL collected (all tables)
- Target schema DDL collected (all tables)
- Every legacy column mapped to target (or flagged as GAP)
- Every app query translated to new schema
- Parity tests executed (old vs new query results compared)
- No row count mismatches unexplained
- Data type changes documented with app-level mitigation
- Abstraction layer (mappings, config) updated
- Application code impact traced (file-by-file)
- Edge-case registry complete (no known risks undocumented)
- File architecture decision documented (split strategy, format)
- Report placed at specified output path