11. Review and generate¶
Declarations describe intent; a migration is what actually runs. This page turns the declarations you wrote in earlier pages into reviewable, frozen artifacts.
What you'll learn¶
- What the three paired migration artifacts are and how their manifest secures them
- How to inspect compiled semantics with
data render,data describe, anddata docs - How to validate, plan, and probe a transition before generating anything
- When to use
make-data-migrationinstead ofmake-migrations - How to freeze parameters and generate offline for CI
Prerequisites¶
- A working dbwarden project with a model
Data(DataMeta)declaration (see the basics example). dbwardenon yourPATH, and the project already initialized withdbwarden init.- Earlier tutorial pages (see the tutorial index), or equivalent familiarity with managed rows, derived values, validations, and transitions.
Why artifacts, not declarations¶
Applying a migration never re-imports your Python declarations. Generation compiles them once into an immutable bundle; apply consumes only that bundle. So the review step is the whole point: whatever you see here is exactly what runs. If you change a declaration later, you generate a new migration rather than editing an old one.
Step 1: Generate the bundle¶
From an example project, generate one unified schema and data migration:
One command writes three paired files under migrations/primary/:
primary__0001_seed_countries_currencies_and_slugs.sql
primary__0001_seed_countries_currencies_and_slugs.plan.json
primary__0001_seed_countries_currencies_and_slugs.data.py
.sqlis the reviewable-- upgradeand-- rollbackoutput..plan.jsonis the machine-readable contract: typed operations, canonical data spec, execution steps, safety information, and the integrity manifest..data.pyis the frozen canonical declaration data, written as a literal. It is audit data, never an execution input.
The .plan.json carries the manifest that binds the members together:
"data_bundle": {
"format_version": 1,
"migration_id": "primary__0001_seed_countries_currencies_and_slugs",
"semantic_checksum": "88e1bdbe…",
"checksum": "853e6b59…",
"components": {
"sql": {"sha256": "9564da88…"},
"plan": {"sha256": "76f213f4…"},
"frozen": {"sha256": "2ddf9e0d…"}
},
"members": [
{"name": "…sql", "media_type": "application/sql", "byte_length": 6930, "sha256": "…"},
{"name": "…plan.json","media_type": "application/json", "byte_length": 103058, "sha256": "…"},
{"name": "…data.py", "media_type": "text/x-python", "byte_length": 13171, "sha256": "…"}
]
}
Apply verifies every member before it writes anything, so a hand-edited SQL file or a stale plan fails closed. The manifest records ordered names, media types, byte lengths, and SHA-256 checksums for all three members.
Values are redacted by default. --show-managed-values reveals declaration
literals in plans and rendered output. SQL output intentionally shows literals
because it is meant for review — that is the .sql file's job.
Step 2: Render the compiled operations¶
data render prints the compiled data specification. Run it before generating
to see the pending operations, because after generation the live declarations are
no longer a pending diff:
c9e6163b4a6d166b3d72e4b226b52ecd8a33168171345c4249c27f4306a76d62 managed_rows countries INFO
529c1f3392406ba8ebb6500c66b2facfa5f2ee40e9a10f48e17c5cc1f32ff349 managed_rows currencies INFO
c1715070192f11c3668e4a432ecf71811fa7d4bbe16de86cad8310a330d4c812 managed_rows users INFO
2f376fed733af0d98ecfc77936819138f273177a8dba84fe1dd2ede6a61955ff transformations users WARN
f4011c0dbcedd56c02da823ba4f07cb7388d74bee53fcc3bb4dc4c9ad89ca289 validations users INFO
Each line is <operation id> <kind> <table> <severity>. The other render formats
are text, json, mapping, and sql; --show-managed-values reveals literals,
and --include / --model / --only-with-data narrow the view. Use --format sql
to see exactly the statements the bundle will emit.
Step 3: Describe and document¶
data describe summarizes models, constraints, and declared data in a
human-readable form:
# DBWarden data declarations
Database: `primary`
## Models
### countries
| Column | Type | Null | Default |
|---|---|---|---|
| code | VARCHAR(2) | no | |
| name | VARCHAR(100) | no | |
Describe formats are text, json, and markdown (add --output <path> to write
a file). For a full documentation tree, generate pages and a manifest:
This writes index.md, model and transition pages under models/ and
transitions/, and checksums.json. Generated documentation is never a
discovery source; it is for humans.
Step 4: Validate, plan, and probe a transition¶
Transitions get their own review commands. Validation checks the live semantics; planning renders the dependency-ordered operations; probes read the intended database without writing:
dbwarden data transition validate SplitLegacyPeople --database primary
dbwarden data transition plan SplitLegacyPeople --database primary --format sql
dbwarden data transition dry-run SplitLegacyPeople --database primary --probes
plan --format json gives the machine contract and --format sql gives the
statements. dry-run --probes executes only read-only guards and reports the
observed counts, scope checksums, database identity, isolation level, thresholds,
and evidence checksums. It does not predict post-write values when the target
schema or rows do not exist yet — you may see:
That is expected before the first apply: probes that require the new schema are reported as unavailable rather than guessed.
Step 5: Choose the generator¶
make-data-migration uses the same generator with schema changes excluded. It is
for data-only releases where target tables already exist:
Use make-migrations when target tables or columns must change with the data. It
unifies schema and data operations into one dependency-ordered migration, and it
removes a duplicate schema drop_table when a transition already owns that source
removal.
Step 6: Parameters and offline generation¶
Expressions may reference parameters. Freeze them with repeated --param, parsing
each value as JSON when possible and as a string otherwise:
Missing or duplicate names fail, and the resolved canonical values are stored in the frozen artifacts — execution never rereads parameters.
Offline generation reads saved model state, declarations, and pinned snapshots without a target database connection:
A historical source without a valid snapshot fails before SQL generation, so offline mode is safe for CI but still requires pinned lineage.
Recap¶
- Generation compiles declarations into
.sql,.plan.json, and.data.py, bound by a manifest of names, media types, lengths, and SHA-256 checksums. - Apply consumes only the frozen bundle, so review the bundle, not the declarations.
data render/data describe/data docsinspect semantics;data transition validate|plan|dry-runreview a transition; probes are read-only.- Use
make-data-migrationfor data-only releases andmake-migrationswhen schema changes travel with the data. --paramfreezes values and--offlinegenerates without a database connection.
What's next¶
Apply, roll back, and reapply the bundle you just reviewed: 12. Apply, roll back, and reapply.