Changelog¶
All notable changes to dbwarden, newest first. Versions follow semantic versioning and are tagged in the repository.
Unreleased¶
Added¶
DbwardenConfigfor project-wide migration policy. A singleton class (pre_migrate_safety,pre_migrate_impact,missing_plan,impact_paths) that controls built-in safety and impact gates. Defaults preserve current behavior.- Six lifecycle hooks.
pre_migration_run,pre_migration,migration_progress,post_migration,on_migration_failure,post_migration_run— all multi-value, all command-aware (migrate,rollback,downgrade). register_migration_hooks()API. Register project-wide app hooks with signature validation and deduplication.validate_migration_hooks()API. Pre-command validation of all registered hooks (global, plugin, per-database).- Per-database
migration_hooks.database_config()acceptsmigration_hooksdict; per-database hooks run after global hooks. - Structured preflight runner.
run_preflight()validates files, plans, dependencies, and impact before lock acquisition. validate_dependencies()for migration deps. Checks missing targets, circular deps, unmet applied deps, and superseded deps. Always blocks (correctness, not policy).DependencyErrordataclass. Structured error type for dependency validation results.--forceacknowledgement for safety warnings. Whenpre_migrate_safety="block", WARNING safety ops block unless--forceis used.progress_callbackinrun_migration(). Per-statement progress hooks fire via callback parameter.- Operation severity classification. Canonical SAFE/INFO/WARN/CRITICAL/UNKNOWN levels via
engine.safety.classifiers, derived per operation and backend. - Plugin migration groups through
register_migration_category. Operation/statementcategoryandsafetyfields,plugin categoriesinspection with--load. Custom groups retain dependency closure, state checksums, and existing ceilings. - Safety-scoped generation and execution. Dependency-closed split files, recorded severity, strict-prefix ceilings, exit 3 deferrals, and static plan adoption with exit 4 for unresolved files.
- Schema 1.1 plans with full typed operations, normalized content hashes, state checksums, split pairing, and required acknowledgements.
- Shared effective-state generation for online, offline, merge, and reconciliation workflows; pending plans compose without SQL replay.
- Static SQL parsing through SQLGlot and native PostgreSQL grammar validation through pglast. Unmapped statements remain UNKNOWN; neither parser executes SQL.
- Severity status, structured deferral/skip events, and optional deferral metrics.
- Declarative data migrations.
dbwarden datacommand group (render,describe,docs,data transitionworkflows), frozen.data.pybundles, snapshot registry, and guarded rollback of created targets. - Data-aware CLI surface.
make-data-migration,diff --data,check --write-plan/--all,migrate --max-severity/--data/--reapply-data, andmake-migrations --split-at-severity/--strict-pending/--dry-run/--param/--show-managed-values. - Data path configuration.
data_paths,data_snapshot_dir, andsnapshot_registryondatabase_config(). - DB-scoped settings in resolved state.
recovery_policy,tcp_keepalive,rename_policy, etc. now flow throughDatabaseConfig. - Python 3.10+ support. Lowered from 3.12.7+ with
tomliandtyping-extensionsfallbacks.
Changed¶
- Python version requirement lowered from 3.12.7+ to 3.10+.
impact_pathsdefault changed from[]to["."].ProjectConfigEntryenforcespre_migrate_impact="block"requiresmissing_plan="block".impact_pathsvalidates absolute paths, traversal, and symlink escapes.- Merge dirty-environment check fails closed on parsing failures.
resolve_migration_order()error messages include per-migration dependency details.- Backup runs after lock acquisition (Section 7.1 ordering).
- Preflight checks run before lock acquisition (Section 7.1 ordering).
- Dry-run mode runs preflight, merge checks, and pre-hooks for all migration types (including RA/ROC).
post_migration_rundoes not fire in dry-run mode.migration_progressfires per-statement viaprogress_callback.
Fixed¶
- Plan provenance is anchored and keyed. Trusted plans require their typed-operations anchor, and content hashes are HMAC-SHA256 keyed by a project-local
.dbwarden/plan.secret— fabricated or recomputed plan files are rejected instead of authorizing severity ceilings. Legacy unkeyed plans fail closed with a regenerate hint. - Frozen data bundles seal their execution steps.
data_executionis integrity-bound into the frozen artifact; tampered batch rows with a recomputed manifest are refused before any write. Bundles generated before the seal need one regeneration. - UNKNOWN severity exceeds every ceiling, including the CRITICAL default. Plan-less files keep their
missing_planpolicy behavior; explicitly-UNKNOWN files now stop with exit 3 under any ceiling. - Model discovery extracts
__table_args__constraints. Table-levelUniqueConstraint/CheckConstraint(named or unnamed) now reach model state, diffs, and CREATE TABLE output on every backend; previously SQLite rebuilds silently dropped them. - SQLite rebuilds absorb companion column operations. A type change combined with add/drop column no longer leaves a stray op that breaks the rollback with
duplicate column name. - Data-losing rollbacks are announced. Dropping a column (or a rebuild that drops one) warns at generation time, records
rollback_warningsin the plan artifact, and prints pre-apply — rollback restores schema with NULL values, not row data. - The static classifier fails closed on parser recursion and the docs checker returns exit 4 instead of crashing on deeply nested input.
- Deferred-file races no longer crash. Deleted deferred files are tolerated by stop reports and status age metrics; missing
data_executionraises the designed guard instead of a bareKeyError; tamper reasons propagate with specifics instead of "malformed plan". - Offline safe type changes accept SQLAlchemy's non-key
autoincrement="auto". - PostgreSQL diffs no longer emit ClickHouse SQL from cached column type metadata.
- Type-change
--planoutput remains valid JSON. - Merge refreshes both state JSON paths.
- Windows repeatables use matching history keys and preserve legacy records.
- Unknown emitters and failed diffs are rejected instead of silently omitting operations.
- Colliding branch files are preserved during merge and all reconciliation versions are recorded.
- Persistent-environment convergence is verified before marking repair complete.
- Baseline records history without executing migration SQL. PostgreSQL history lookup includes every non-null version, including baseline and reconciliation rows.
- Severity levels are canonical. Drop table/column is CRITICAL; SET NOT NULL and non-concurrent PostgreSQL indexes are WARN. Legacy plan values retain their INFO/WARNING/ERROR spellings.
- A PostgreSQL identity column is no longer rendered as
SERIALtoo. A model declaringpg.field(identity=...)on an integer primary key had the base type rewritten toSERIALby the primary-key mapper and also rendered asGENERATED ... AS IDENTITY, and PostgreSQL rejected the column with both default and identity specified. - PostgreSQL index sort options are captured. The snapshot extractor and
generate-modelscalledpg_index_column_has_propertywith the 0-based series index where the function is 1-based, so no index ever recorded itsASC/DESCorNULLS FIRST/NULLS LAST. NULLS NOT DISTINCTis emitted in the right position. It was appended afterWHERE, which PostgreSQL cannot parse; it now precedesINCLUDEandWHEREin both the upgrade and the rollback.- PostgreSQL storage parameters, column
COLLATE, andCOMPRESSIONare emitted.pg_storage_paramswas dropped from the model spec,CREATE TABLEnever rendered aWITH (...)storage clause, and neither collation nor compression reached the DDL. - Reverse-engineered PostgreSQL models keep their index list.
generate-modelsnow writespg_indexesentries asPgIndexSpecobjects, emittingcolumns=[]for expression indexes. - MySQL and MariaDB snapshot types carry their length and precision. A reverse-engineered
varcharreached the column-definition builder as the bare base type and raised Incomplete MySQL column type, sodiffnever converged. Inherited charset and collation are also no longer treated as model differences. - The last migration of a MySQL or MariaDB run is recorded. Each DDL statement implicitly commits, but the bookkeeping
INSERTopened a transaction nothing committed; the next migration's DDL committed it implicitly, so the final migration was silently dropped from the history and re-applying it failed with Duplicate column name. - MariaDB uses the MySQL lock DDL instead of the SQLite fallback. The v2 lock templates are keyed by database type and
mariadbwas not a key, soCREATE TABLEusedTEXTfor the namespace primary key and MariaDB rejected it with BLOB/TEXT column 'namespace' used in key specification without a key length. - The configured PostgreSQL schema is created before the bookkeeping tables. Bookkeeping queries reference
<schema>._dbwarden_migrations, but the schema may only be created by a user migration; on a fresh database the firstCREATEfailed withInvalidSchemaName. _parse_plan_safetyno longer crashes onoperations: nullin plan files.ProjectConfig.impact_pathsdefault now matches spec (["."]).- Plugin-registered lifecycle hooks are now collected and executed (previously silently ignored).
- Per-database hooks receive signature validation at pre-command time.
- Two-class error message now names both conflicting classes.
_collect_hooks()ordering: plugins first, then app hooks, then per-database hooks.check_dirty_environment()fails closed on merge-record parsing failures.- SQLite is a first-class backend. Changes SQLite's
ALTER TABLEcannot express - column type, nullability, default, table constraints,WITHOUT ROWID,STRICT, generated expressions and collation - are emitted as a table rebuild (create, copy, drop, rename, recreate indexes) with the reverse rebuild as the rollback, instead of a comment. All changes to one table collapse into a single rebuild. - SQLite table and column metadata.
SqTableMetasupportssq_without_rowid,sq_strictandsq_indexes; the newSqColumnMetaplussq.field(generated=..., generated_mode=..., collate=...)cover generated columns and per-column collation. Both are emitted inCREATE TABLE, captured by schema snapshots, and written back bygenerate-models. - SQLite impact analysis.
check-impactreports table-option, generated-column and collation changes as warnings, because each rebuilds the table. - PostgreSQL reverse-engineering now works with mixed-case and quoted identifiers.
generate-modelsand snapshot queries use quotedregclassreferences, so tables and columns such as"MyTable"and"weird-col"no longer silently fail metadata extraction. generate-modelsno longer crashes against PostgreSQL. The command now opens a raw SQLAlchemy connection instead of a transactional one, avoiding the closed-transaction error during SQLAlchemy reflection.- Generated model imports are complete. Models that only have column-level metadata now correctly import the required table-meta class (
PGTableMeta,MyTableMeta,SqTableMeta). - Python identifier sanitization. SQL column names containing hyphens or other non-identifier characters are converted into valid Python attribute and nested-class names while preserving the original SQL name in
Column(...). - PostgreSQL array columns render correctly. Array types are emitted as
text[],integer[], etc. instead of barearray. - ClickHouse codec extraction is parenthesis-aware. Multi-codec chains like
CODEC(Delta(8), ZSTD(1))are preserved in order, and the implicit defaultLZ4codec is omitted. - ClickHouse
LowCardinality(Nullable(T))is preserved. Reverse-engineering now correctly captures bothlow_cardinality=Trueandnullable=Truefor nested wrappers. - PostgreSQL
SERIAL/BIGSERIALand identity columns round-trip cleanly.generate-modelsonly marks columns asautoincrement=Truewhen the live default is anextval(...)sequence; identity columns are left asautoincrement=Falseand rely onpg_meta.make-migrationsnow treats bothSERIALtypes and identity columns as autoincrement-equivalent. - PostgreSQL identity column ALTER syntax is valid. Identity changes now emit
ALTER TABLE ... ALTER COLUMN ... SET START WITH ...,SET INCREMENT BY ...,SET MINVALUE ...,SET MAXVALUE ...instead of the invalidSET (...)form. - ClickHouse default changes generate valid DDL.
_build_alter_default_sqlnow emitsALTER TABLE ... MODIFY COLUMN ... DEFAULT ...and... REMOVE DEFAULTinstead of falling through to PostgreSQLALTER COLUMN SET DEFAULTsyntax. - ClickHouse nullability changes are type-safe. The shared nullable builder guards against generic SQLAlchemy type strings and requires a ClickHouse type such as
Int64orString. - ClickHouse materialized view DDL is complete.
CREATE MATERIALIZED VIEWnow includesPOPULATE,REFRESH ..., andSETTINGS ...when declared, and omits engine/ORDER BY/PARTITION BYclauses when aTOtarget is set. - ClickHouse
ChEngineSpecengines converge with snapshot engines. Model-sideChEngineSpec('MergeTree')and similar specs are serialized to the same string/tuple representation stored in snapshots, somake-migrationsno longer emits spuriousalter_ch_optionsoperations for engine-only differences. - ClickHouse implicit MV backing tables are excluded from diffs. Internal
.inner_id.*tables created by ClickHouse for materialized views with implicit storage are filtered out of model-to-snapshot diffs. - ClickHouse default
MergeTreesettings no longer cause drift. Snapshot extraction records the server's default setting values; both sides strip never-declared defaults so hand-written models that omit settings converge with snapshots. - ClickHouse materialized view
SELECTdatabase qualifier is stripped during reverse-engineering.FROM dbwarden_test.eventsbecomesFROM eventsin the snapshot, preventing spuriousMODIFY QUERYdiffs when models are written without qualifiers. - ClickHouse materialized views reverse-engineer to
MaterializedViewmodels.generate-modelsnow emitsMaterializedViewbase classes withCHViewMetaandmaterialized_view(...)specs for both implicit-storage andTO-target MVs, including correct imports for single-file and per-file output. - PostgreSQL enum types are created before tables in migrations.
make-migrationsnow emitsCREATE TYPE ... AS ENUM (...)before anyCREATE TABLEthat references the enum, both when diffing against a snapshot and when generating the very first migration. - PostgreSQL foreign keys round-trip when the referenced table has no constraints. The constraint handler now recognizes the referenced table from the full model set rather than only from tables that declare their own constraints.
- PostgreSQL migration parsing no longer invents a
constraintcolumn.ALTER TABLE ... ADD CONSTRAINT ...statements in pending migrations are ignored by snapshot merging; previously they were misread asADD COLUMN constraint. - PostgreSQL identity and storage metadata no longer drift after reverse-engineering.
make-migrationsonly emitsALTER TABLE ... SET STORAGE ...or identity-sequence parameter changes when the model explicitly requests them, so models produced bygenerate-modelsconverge cleanly with the database. - PostgreSQL identity values are normalized to lowercase.
Identity(always=True)andpg.field(identity="ALWAYS")both normalize topg_identity="always", eliminating spurious identity diffs. - Official plugin provenance recognizes renamed publisher workflows.
dbwarden plugin addnow verifies the trusted-publishing attestations for the ClickHouse RBAC, PostgreSQL extensions, PostgreSQL RBAC, and PostgreSQL types plugins against theirpublishing.ymlworkflows. - A migration generated without a snapshot no longer drops every table constraint.
make-migrationsfalls back to generating from the models alone when there is no schema snapshot, the database is unreachable, or the snapshot diff raises. That path emitted columns, indexes and foreign keys but silently omittedUNIQUEandCHECKconstraints, so the schema applied cleanly and then failed at runtime with there is no unique or exclusion constraint matching the ON CONFLICT specification. EXCLUDEconstraints survive model-only generation. They are rendered by the PostgreSQL table handler, which the fallback path does not run.- A single-column foreign key is emitted once on MySQL and MariaDB. It was rendered inline as
REFERENCESin the column definition and as a namedADD CONSTRAINT, which MariaDB honours as two constraints. - An unnamed
CHECKconstraint keeps the name the database gave it. PostgreSQL names an inlineCHECK (age >= 0)itself while dbwarden's generated name is index-based, so every unnamed check was dropped and recreated on every run - and reordering a model's checks renamed them. Checks the model does not name are now matched by expression, with casts and parentheses normalized away; a changed expression is still detected. - An aggregating view's target table is emitted as ClickHouse DDL. When the model declared its dimension columns, the target was built from the SQLAlchemy types, producing
VARCHAR(50)andINTEGERinstead ofStringandInt32, and giving the aggregate column its declared type instead of theAggregateFunction(...)signature it needs to accept-Staterows. - PostgreSQL roles are no longer lost when a table has a row-level security policy. The policy loop bound its list of policy roles to the same name as the snapshot's role accumulator, so every later role lookup raised
TypeErrorand the snapshot recorded no roles at all. - An aggregating view keeps a declared ClickHouse aggregate type. Only the default
Float64thatagg.sum()/agg.avg()fall back to is replaced by the source column's resolved type; an explicit type such asagg.raw("quantile(0.95)", col, "UInt32")was being rewritten to the source column'sInt32, changing theAggregateFunctionsignature. - Falling back to model-only generation is announced. The snapshot diff failing used to be swallowed, leaving no way to tell a degraded migration from a correct one; it now logs the cause and warns.
- A column-level
unique=Trueproduces one constraint, not two. The column was renderedUNIQUEinline and emitted again as a named table constraint, leaving two unique constraints - and two indexes - on the same column. - A unique constraint dbwarden named itself no longer renames the database's. When a model declares a unique constraint without a name, an existing constraint on the same columns -
users_email_key, say - is now left alone instead of being renamed touq_users_emailand, in some orderings, dropped. - Column defaults are compared without their cast. PostgreSQL reports a default as
'queued'::character varyingwhile the model spells it'queued', so every run re-emitted the default and the rollback re-quoted the cast into'''queued''::character varying'. - Default index sort options are no longer recorded.
ASC NULLS LASTandDESC NULLS FIRSTare what PostgreSQL implies; recording them made plain indexes differ from the models and be dropped and recreated on every run. - A column-level
unique=Trueis read from every model. The constraint was only collected when the model also declared aclass Meta, so a diff against a database that had the constraint emitted aDROP CONSTRAINTfor it. - Column storage is compared against the type's own default.
NUMERICdefaults toMAINandTEXTtoEXTENDED; recording those made untouched columns differ from the models. - Foreign keys are matched regardless of how the action is spelled.
ondelete="cascade"in a model andCASCADEin the database no longer read as different constraints. - Constraint canonicalization no longer edits the snapshot it was given. The diff rewrote the caller's snapshot in place, so anything reading the snapshot after a diff saw constraints it could no longer classify.
- Unnamed
CHECKconstraints get a stable key. The generated name usedhash(), which is salted per process, so the same schema produced a different snapshot on every run. - Configuration-declared objects are no longer recreated on every migration. Roles, domains, sequences, functions, triggers, composite types, extended statistics, event triggers and default privileges were diffed against a hardcoded empty snapshot, so each generation emitted
CREATEfor objects that already existed - the secondmigratefailed with role "..." already exists, and an attribute change never became anALTER. The preamble now diffs against the same snapshot as the rest of the migration; offline generation, which cannot see the server, still creates. - A SQLite table rebuild no longer leaves its staging table in the snapshot. Pending-migration merging read every
CREATE TABLEout of the migration text but ignored theDROP/RENAMEthat followed, sot__dbw_newsurvived as a table nothing declared and every later run generated a migration to drop it, in SQL built from columns typedunknown. - MySQL and MariaDB integer primary keys are
AUTO_INCREMENT. The same model producedSERIALon PostgreSQL andAUTOINCREMENTon SQLite but a plainid INTEGER NOT NULL PRIMARY KEYhere, so the migration applied and the first insert that omitted the key failed with Field 'id' doesn't have a default value. - Reverse-engineered models import
func. A column defaulting tonow()was written asdefault=func.now()whilefuncwas filtered out of the generated import line, so the artifact raisedNameErrorthe moment dbwarden loaded it. generate-modelsno longer writes an empty class body for SQLite. Column metadata with nothing renderable emittedclass id(SqColumnMeta):followed by nothing, and the generated module failed to import with expected an indented block after class definition.- SQLite migrations no longer fail the rollback contract. A column type change or foreign key change on SQLite previously raised
RollbackContractErrorbecause the generated rollback was a placeholder. - SQLite primary keys no longer churn. A primary key column is recorded as
NOT NULLwhen reading a SQLite schema, which SQLite itself reports as nullable, and autoincrement changes are no longer generated for SQLite primary keys whereAUTOINCREMENTis either implicit or illegal. - Generated SQLite models import cleanly.
generate-modelswritessq = sq.field(...)specs instead of flatsq_*attributes, which the metadata reader rejects. - Rebuilt tables keep their column order. Reconstructing a table from model state preserved sorted order rather than declaration order.
- A SQLite rebuild preserves what reflection cannot see. Declared types keep their length and case (
VARCHAR(255), notvarchar), andAUTOINCREMENT, foreign keyON DELETE/ON UPDATE, unnamedUNIQUE (...)constraints, partial indexes,DESC/COLLATEinside an index, and expression indexes all survive the rebuild. - SQLite partial indexes are recorded. The index predicate was read only under PostgreSQL's dialect key, so
WHEREwas dropped from every SQLite index.
[0.19.0] - 2026-09-03¶
Added¶
- V2 migration locking with per-engine strategies. PostgreSQL advisory locks, MySQL named locks, SQLite
BEGIN IMMEDIATE, and ClickHouse lease-based locking with configurable TTL. A heartbeat background task updateslast_heartbeat_aton native-lock engines, enabling stale (STUCK) and dead worker detection. dbwarden lock-statusanddbwarden unlockcommands. Enhanced with detailed holder diagnostics including host, PID, execution ID, migration version, and health status.- Lock exceptions for structured error handling.
LockAcquireTimeout,LockStuck, andRecoveryRequiredexceptions. - ClickHouse cluster configuration and CH-1 idempotency.
clickhouse_lock_ttlandlock_namespaceconfig keys. - Merge handling pipeline.
dbwarden mergedetects schema conflicts between branches and generates merge plans.dbwarden rebasereplays migrations onto a new base.dbwarden reconcileresolves dirty environments with unreconciled merge changes. - Merge detection integration.
dbwarden make-migrationsanddbwarden statussurface pending merge conflicts during normal workflows. --all-environmentsflag for cross-environment merge operations.- MariaDB merge documentation. Semantic conflict detection and model state reconstruction documented.
- Documentation overhaul. All documentation comprehensively revised: commands, configuration, database setup, advanced topics, correctness guarantees, plugins, and getting-started guide. ClickHouse documentation receives dedicated setup guides for CH-0 through CH-4.
Fixed¶
- Logger bug in
dbwarden unlock. Audit log call referenced an undefined variable. - PostgreSQL advisory lock key size and type casting.
- Critical audit findings in lock implementation. Heartbeat timestamp generation for SQLite.
- SQLite lock handling. Reuses the
BEGIN IMMEDIATEconnection for migration execution. - Heartbeat task correctly skipped on SQLite. Staleness is inferred from
acquired_atand process liveness.
[0.18.0] - 2026-09-01¶
Added¶
- Exception hierarchy. New
dbwarden.exceptionspackage organizes all exceptions underDBWardenError:core.py(13 exceptions),plugin.py(5 plugin exceptions), andengine.py(OrderingErrorandRollbackContractError). - Typed snapshot structures.
dbwarden.engine.snapshot.typesintroduces 26 TypedDicts covering the full snapshot hierarchy:Snapshot,SnapshotTable,SnapshotColumn, indexes, constraints, PG/MySQL/SQLite metadata, roles, grants, policies, sequences, functions, and event triggers. - Per-component CLI log filtering.
--log-level COMPONENT:LEVELreplaces the all-or-nothing debug flag. -hshorthand for--helpacross all CLI commands.
Changed¶
extract.pymodule split. The 1,500-lineextract.pyGod Function is split into focused modules:extract_common,extract_pg,extract_mysql, andextract_sqlite.extract.pyis now a thin dispatch module.- Connection layer rename.
dbwarden.databaseis renamed todbwarden.connection. A backward-compatibility shim withDeprecationWarningpreserves existing imports. - 142 parametrised CLI option combination tests covering every flag permutation.
Fixed¶
- ClickHouse immutable option detection.
- PostgreSQL enum, identity, and foreign key reverse-engineering gaps.
- PostgreSQL column statistics, storage, and compression metadata now captured during snapshot extraction.
- SQLite foreign key action fixups handle edge cases that previously produced incorrect DDL.
[0.17.1] - 2026-08-15¶
Added¶
- Declarative configuration as the default scaffold.
dbwarden initnow generatesDbwardenDatabasesubclasses, while discovery supports inherited configuration, aliases, indirect imports, and the existing function-based API. - Migration convergence and replay hardening. Migration generation and execution now share stronger convergence checks, replay handling, connection cleanup, and PostgreSQL column behavior coverage.
Changed¶
- Declarative configuration parity.
DbwardenDatabasenow supports the completedatabase_config(...)field and default surface, inherited and direct plugin configuration, equivalent handles and validation, and aliased or indirect class discovery. - Generated files use atomic replacement. Migration files, model state, generated models, rollback files, plugin state, and exported configuration now avoid leaving truncated files after an interrupted write.
- Database settings mask URLs through SQLAlchemy URL parsing. Settings and config output now handle encoded credentials and IPv6 hosts without exposing passwords.
Fixed¶
- Unsafe SQL identifiers are quoted or rejected. Backend-generated schema, table, column, statistics, and drop-object SQL now protects reserved words and embedded identifier quotes.
- Live command disconnects no longer report success. Explicit
checkandsnapshotoperations propagate connection failures after retries. - Configuration and impact scans reject symlink escapes. Workspace discovery no longer imports or scans symlinked files and directories outside the intended root.
- Plugin provenance is HTTPS-only and size-bounded. Plugin lock and consent TOML serialization escapes structural characters, and installer/provenance inputs are validated.
- Migration SQL splitting respects quoted strings and comments. Semicolons inside SQL literals and comments no longer split statements incorrectly.
[0.17.0] - 2026-08-13¶
Added¶
- Global
--jsonflag.dbwarden --json <command>switches display commands (status,history,database list,settings show,config,version,lock-status) to structured JSON output and also routes the command's log output through JSON formatting (the same effect asDBWARDEN_LOG_JSON=true). Commands that already support JSON output (check,check-db,check-impact,diff,plugin list,plugin info) honor the global flag too. - TRACE log level. A
tracelevel (numeric5) is available via--debug-level traceand logs per-statement SQL duringmigrate,rollback, anddowngrade. --perfflag for migration commands.migrate,rollback,downgrade, andmake-migrationsaccept--perfto add per-SQL-statement timing breakdowns on top of the always-on phase timings (lock acquisition, snapshot write, model state write, and SQL statement durations).- Optional database availability handling. Database entries support
skip_if_missing=Truefor optional databases in multi-database operations. Use the global--disable-skipflag to force configured optional databases to fail normally. - Declarative database configuration. Concrete subclasses of
DbwardenDatabaseare automatically registered and support inherited configuration, while the existingdatabase_config(...)API remains supported. - Partial-success results. Multi-database migration, status, and seed commands report skipped databases and use exit code
3when the operation completes with optional databases unavailable.
Fixed¶
- ClickHouse DDL generation for fresh migrations.
make-migrationsnow emits ClickHouse DDL that applies cleanly to a fresh ClickHouse 24.x database: column comments are emitted beforeCODEC(...),Nullableis nested insideLowCardinality(...)rather than the invalid reverse, and columns used inORDER BY/PRIMARY KEY/PARTITION BY/SAMPLE BYare rendered as non-nullable. - Plugin list table rendering. The
plugin listtable no longer wraps or truncates plugin distribution names in narrow terminals. - Rollback warning test stability. The irreversible-rollback warning test now asserts against the log record instead of Rich console wrapping.
[0.16.5] - 2026-08-05¶
Added¶
- Plugin-declared configuration keys. Plugins can now register their own config keys and have them surface through the standard configuration path, so an installed plugin ships its settings surface instead of relying on loose user-side keys.
Changed¶
settingsoutput masks secret values. Thesettingscommand now redacts values it recognises as credentials, so dumping configuration for a support ticket no longer prints a database password or API token.
Fixed¶
- MySQL diff for newly created tables. A newly added table in a MySQL database no longer produces an empty or broken diff; the new-table path now emits the full create statement.
- Publish and CI workflows hardened. Release publishing and the CI pipeline were tightened to fail fast on the conditions that previously produced partial artifacts.
[0.16.4] - 2026-08-05¶
Added¶
--debugand--debug-levelCLI flags. Both flags enable per-file scan logging, so a slow or misbehavingmake-migrationsrun can be traced down to the individual model file that caused it.
Fixed¶
- Config cache miss no longer rescans the whole workspace. A config cache miss previously triggered a repeated full-workspace rescan; the cache now reloads in place.
- Offline rollback no longer double-reverses. The offline rollback path was reversing the same operation twice; the reversal is now applied exactly once, and agg-target key types resolve through the cascade chain.
[0.16.3] - 2026-08-03¶
Fixed¶
- Materialized view drops use
DROP_VIEWordering. Drops for cascading materialized views are emitted inDROP_VIEWorder, matching how the views depend on one another. ch_rawgroup-by column types resolve through the cascade chain. Ach_rawview whose group-by keys come from an upstream view now resolves the types transitively instead of falling back to an unresolved state.
[0.16.2] - 2026-07-29¶
Fixed¶
- Cascade materialized views resolve string forward references. Cascading MVs that reference an upstream view by a forward string now resolve through the registry rather than failing at snapshot time.
- Group-by key types fixed in cascading views.
ch_meta.ch_typepopulated regardless of backend. Thech_typemetadata is now filled in even when the diff is running under a non-ClickHouse backend.- Config fallback now warns. Falling back to a default backend or schema when a config value is missing now emits a warning instead of silently proceeding.
[0.16.1] - 2026-07-28¶
Added¶
- Container test for cascade combinator correctness. A live-ClickHouse test now verifies that cascading aggregate views emit the correct combinator.
Changed¶
- Cascading aggregate views use the
MergeStatecombinator. Cascades no longer emitSumfor upstream aggregate state; they useMergeStateso the aggregate survives the cascade correctly. schemapand@auto_schemamoved to thedbwarden-fastapiplugin. The schema-map integration is no longer part of core; installdbwarden-fastapito keep using@auto_schema.
Fixed¶
- Three agg-target DDL bugs. Aggregation target handling in the DDL layer emitted incorrect SQL in three edge cases.
[0.16.0] - 2026-07-24¶
Added¶
- Plugin system with trust tiers, consent, and provenance verification. dbwarden now discovers plugins through the
dbwarden.pluginsentry point group and classifies each distribution into official, verified, or community tiers. Plugin code from unverified sources is not imported until the operator explicitly consents to that exact version. See the Plugins section for the trust model. recover-model-statecommand. Model state is now stored in the database as well as on disk, anddbwarden recover-model-staterestores it when the on-disk file is lost or corrupt.- GPG-signed commit requirement. Contribution guidelines now require GPG-signed commits.
Changed¶
- Remaining sandbox and five PostgreSQL extension handlers extracted to plugins. Core no longer ships the sandbox provider or the PostgreSQL extensions, event triggers, functions, triggers, and storage-parameter handlers; they live in
dbwarden-sandboxanddbwarden-pgsql-extensions. - Plugin-duplicated code stripped from core. Every handler that now lives in a plugin was removed from core so there is a single implementation.
- Normalized live snapshot diff. Live-database snapshots now diff against the same normalized representation as stored snapshots.
- PostgreSQL round-trip diff stabilized.
Fixed¶
- Generated model metadata preserved. Reverse-engineered models keep their detected backend metadata instead of dropping it during regeneration.
[0.15.0] - 2026-07-21¶
Added¶
- Strict rollback contracts. Every generated migration now has a declared rollback contract: executable rollback when it is safe, placeholder refusal by default, and an explicit irreversible declaration when rollback cannot be produced. See Rollback Generation.
- Irreversible rollback annotations. An operator can annotate a migration as irreversible, which dbwarden records and respects.
- Rollback round-trip integration harness. A test harness applies upgrade and rollback in sequence and verifies the schema lands back where it started. See Round Trip Verification.
- ObjectHandler protocol documented.
Changed¶
- Rollback metadata preserved across diff pipelines. Rollback information survives the snapshot, diff, and emission stages instead of being regenerated per stage.
- Rollback state restored for ClickHouse and PostgreSQL. RBAC, collection, profile, and policy rollback are restored for ClickHouse; PostgreSQL rollback state is restored as well.
[0.14.3] - 2026-07-21¶
Added¶
recover-model-statecommand. Restores model state from the database when the on-disk state is missing or stale.
[0.14.2] - 2026-07-21¶
Fixed¶
clickhouse_optionsnormalized toch_options. The snapshot pipeline now uses a single dict key,ch_options, so diffs no longer churn on naming alone.- TTL expression query conditional for ClickHouse before 24.4. The
ttl_expressionquery only runs against versions that expose the column, preventing failures on older servers. - Dead
ChRbacHandlerremoved. - Mobile sidebar drawer fixed. The Zensical modern theme now uses a class-based drawer instead of
:has(), and the viewport meta tag is present.
[0.14.1] - 2026-07-20¶
Changed¶
- Docs aligned to the API. Reference pages now match the exported surface, including the missing aggregation methods and RBAC class exports.
[0.14.0] - 2026-07-20¶
Changed¶
- Core refactored to a single registry-driven protocol. The twin registries were collapsed into one, the five backends were extracted into
dbwarden/databases/, and every handler now speaks one protocol with**kwargspropagation and acluster_ctxon the driver. - Config, commands, and FastAPI extracted into packages.
config/,commands/, and the FastAPI extension now live as packages rather than modules, which is the groundwork for the plugin system. - ClickHouse view API rewritten.
ChView,MaterializedView, andAggregatingVieware now registry-backed non-SQLAlchemy base classes, with builder signatures (toinstead ofto_table),_resolve_source, andAggregatingViewSpecfor correctness. - ClickHouse RBAC, named collections, and data operations. New handlers and spec classes cover roles, users, grants, policies, quotas, settings profiles, named collections, partition operations, mutations, and
OPTIMIZEvia aDataOptype. render_expr()and compiled-expression acceptance. Expression sites now accept SQLAlchemyColumnElementobjects and render them throughrender_expr().
Added¶
p0andintegrationpytest markers.p0marks the two-cycle convergence gate that must never regress;integrationmarks tests that need a live ClickHouse container.- Convergence-audit and ClickHouse integration CI jobs.
- ClickHouse documentation split into a multi-page reference with dedicated pages for RBAC, views, dictionaries, and data operations.
[0.13.0] - 2026-07-06¶
Added¶
- PyPI publishing workflow. A
v*tag now publishes the package to PyPI automatically. - SEO integration via seoslug 2.0.1. The docs site generates canonical URLs, OG images, and schema-aware frontmatter through the inline Zensical extension.
- Favicon, OG image, and robots.txt content-signal directives.
Fixed¶
- Nine SQL generation bugs resolved across the PostgreSQL expansion.
- Schema package re-exports removed so imports resolve against the single canonical path.
[0.12.5] - 2026-07-01¶
Changed¶
- README version badge updated and the publishing workflow prepared for the PyPI release.
[0.12.4] - 2026-06-24¶
Changed¶
_write_model_stateskipped when no migrations applied. Startup no longer burns CPU rewriting model state that did not change; this removes a multi-minute stall on every startup with nothing pending.
Fixed¶
- MySQL metadata preserved in generated models.
- Config and model caches refreshed when the underlying files change.
- Docs lint issues fixed.
[0.12.3] - 2026-06-23¶
Fixed¶
make-migrationsoutput deduplicated.- MySQL DDL savepoint destruction handled so a failed statement does not invalidate the transaction.
- Primary key inferred for tables missing one, and MySQL DDL translation improved.
- Generated SQLite databases ignored by version control.
[0.12.1] - 2026-06-17¶
Added¶
--baseflag documented.dbwarden generate-models --base app.database:Baseimports a project base instead of declaring a new one.- Graceful database disconnection. Connection failures now trigger retry logic and a clear error instead of a bare traceback.
Changed¶
- Schema backends moved to
dbwarden/databases/. - Safe SQL quoting and config cache fixes. Duplicate model loads are avoided and default serialization is stable.
Fixed¶
- Live snapshot taken when no cached snapshot exists for
make-migrations. - Unique module name computed per file in the sandbox loader, so two files with the same basename no longer collide.
[0.12.0] - 2026-06-13¶
Added¶
- MySQL round-trip support. MySQL schema classes, model discovery, a round-trip engine, and comprehensive test coverage, alongside the MySQL dependency group. See MySQL.
- Seed export command.
dbwarden exportwrites code seeds to ROC SQL files.
Changed¶
- ClickHouse
ForeignKeyprohibited. A foreign key on a ClickHouse table is now an error rather than silently unsupported SQL. - Snapshots moved to
.dbwarden/, and model state is synced online. - Auto-generated SQL for previously manual operations. ClickHouse rename, nullable,
LowCardinality, and projection changes, plus the PostgreSQLUSINGclause, are now emitted automatically. - Generated models emit
pg.field()andch.field()spec objects instead of flat backend attributes.
[0.11.2] - 2026-06-12¶
Added¶
- In-code seed engine.
@seed_data,SeedRow, andDBWardenSeeddefine seeds in Python; a tracking table records what has been applied. auto_apply_seedsconfig wired intomigrate.- Checksum drift detection and seed pruning.
Fixed¶
- Offline migration first run generates SQL for all tables, not an empty set.
- Seed types moved to the
dbwarden.seedmodule so the CLI and engine share one definition.
[0.11.0] - 2026-06-11¶
Added¶
- Typed Meta system. A
_MetaValidatormetaclass validates every attribute onclass Metaat import time; a typo now raisesDBWardenConfigErrorinstead of silently producing wrong DDL. Per-backend field factories produce spec objects. - Offline v2 state format. ClickHouse engine recreate and column rename flags are captured in the offline state.
Changed¶
requires-pythonrelaxed to>=3.12and the upper bound removed, so Python 3.14 is supported.
[0.10.2] - 2026-06-11¶
Added¶
model_tablesper-database filter with overlap validation, so one model file set can be partitioned across databases with ownership checks.
[0.10.1] - 2026-06-11¶
Fixed¶
- Config sandbox classification for in-package and
src/layout projects. Model files discovered inside the package directory or undersrc/are now classified correctly.
[0.10.0] - 2026-06-09¶
Added¶
- PostgreSQL auto-increment lifecycle support. Serial, identity, and sequence lifecycle are handled end to end, including rename and rollback.
--typeflag for repeatable migrations.
Changed¶
- Real
diffimplemented, replacing the placeholder, and obsolete CLI commands removed. - Lock system overhauled. Migration locking, connection safety, and sandbox fixes landed as a mass bug-fix pass (38+ fixes).
Fixed¶
alter_column_typerollback anddrop_tableplaceholder fixed, with the full rollback cycle verified.- Rollback SQL for ClickHouse indexes and foreign key constraints fixed, and the
make-rollbackregex corrected. - System tables excluded from diffs and rollback counts corrected.
- Offline migration engine fixed for compatible operations and a wrong import, with 35 comprehensive tests; 26 edge-case tests added, and the crash on corrupted state resolved.
[0.9.5] - 2026-06-09¶
Added¶
--typeflag for repeatable migrations.
Changed¶
- Documentation updated for the new flag and related CLI changes.
[0.9.4] - 2026-06-09¶
Added¶
- Cookbook docs, examples, and an integration test suite.
Fixed¶
- Six bugs fixed.
- Missing import in
rollback.pyfixed (get_database). - Comments added to every example.
[0.9.0] - 2026-06-09¶
Fixed¶
- Offline migration engine fixed for compatible operations and the wrong import path, with 35 comprehensive tests.
Changed¶
- Docs restructured. Index and introduction merged, models/modeling split into separate references, squashing folded into the squash page, and the navigation rebuilt.
[0.8.0] - 2026-04-26 through 2026-06-09¶
This window aggregated the 0.8 and 0.9 development lines into core; the version was not bumped per feature.
Added¶
- Schema snapshots for rename detection. A checksummed JSON snapshot is written after every migration and powers rename detection without querying the live database. See Deterministic Diff.
- Column-level diff with
StatementOrderand--safe-type-change. Type, nullability, default, and comment changes produce preciseALTER COLUMNstatements, and type changes require the flag. - Table and column rename detection with an interactive prompt and a rename flag.
- Richer index metadata. Partial and covering indexes,
USINGaccess methods,WITHstorage parameters,WHERE,TABLESPACE,NULLS NOT DISTINCT, per-column sort order, and ClickHouse skip indexes, plus a--concurrentflag. DatabaseHandlereturned bydatabase_config(), withdatabase_url_syncanddatabase_url_asyncsplit into separate settings and a unified async engine dispatcher.- Sandbox providers and
--dry-run.dbwarden migrate --dry-run --sandboxreplays migrations against SQLite or a testcontainers provider. - ClickHouse materialized views, projections, and a safety analyzer, wired into the new
checkcommand. - ClickHouse replicated engines and external dictionaries.
- Versioned seed management with CLI commands and a configurable tracking table.
- Prometheus metrics and JSON logging, plus FastAPI Redis lock, status, and migrate endpoints and a
dbwarden_lifespancontext manager. See Observability. - Migration impact analysis with the
check-impactcommand, using AST analysis with a grep fallback. - Offline migrations.
export-modelsdumps model state;make-migrations --offlinegenerates SQL with no database connection. See Offline Integrity. - In-code seed definitions (
@seed_data,SeedRow,DBWardenSeed). - Class Meta metadata foundation, with
IndexSpec,CheckSpec,UniqueSpecdataclasses and factory functions. - PostgreSQL first-class support with zero-diff round-trip, including unlogged tables,
no_inherit, deferred uniques,tsvector,enum ADD VALUE, partitioning, and the typed@auto_schemawrapper. - ClickHouse first-class Meta with
ChEngineSpec,ChIndexSpec, and spec serialization.
[0.7] - 2026-04-26¶
Added¶
- Auto-generated migration names when no description is provided, with descriptive names derived from the change.
- Migration plan output and configurable migration tracking tables.
- Health router hardening. Authentication via the
DBWARDEN_HEALTH_AUTHenvironment variable, sanitized error responses, and validation of identifiers and paths against strict patterns.
Changed¶
- Migrations now use savepoints. Any statement failure rolls back all statements in the migration.
- Atomic writes with backups. Config and snapshot writes are atomic and validated before replace, with the backup directory checked first.
Fixed¶
- No spurious backup on initial
init. RestrictedFileLoaderadded so sandboxed config loads cannot escape the project root.
[0.6] - 2026-04-24¶
Added¶
- FastAPI integration. Session dependencies, health router, and startup helpers.
settingscommand group and mutators.- Rich colorized CLI output with command accents.
Changed¶
- Python-based
database_configruntime replaced the TOML-only config surface. - Docs restructured into Getting Started, Tutorial, Advanced, and Reference tiers.
[0.5] - 2026-04-23¶
Changed¶
- Documentation updates across the site.
[0.4] - 2026-04-23¶
Added¶
- SQLite translation layer with strict mode support. PostgreSQL-flavored models run against SQLite locally, with a
--strictflag enforcing translation correctness. - Global
--devmode and database URL and target uniqueness validation.
[0.3.6] - 2026-04-14¶
Changed¶
- Checksum-based migration tracking. Migrations are checked by checksum instead of version, so deployments are idempotent.
- Migrations table keyed by filename (auto-increment id removed) with a UNIQUE constraint on the version column for PostgreSQL
ON CONFLICTsupport. - ClickHouse-specific SQL queries for migration tracking.
[0.3.2] - 2026-04-10¶
Fixed¶
- Migration filename uses the config section name instead of the URL.
- Logging and type mapping improved across the command layer.
[0.3.1] - 2026-04-10¶
Fixed¶
- Logging and type mapping improved across the command layer.
[0.3.0] - 2026-04-10¶
Added¶
- ClickHouse support. URL conversion and the
clickhouse-connectdependency. - Multi-database support. An explicit
database_typefield, a multi-database config structure,--databaseand--allCLI options, and adatabasemanagement command. See Multi-Database.
Changed¶
- Backend-specific internal migration SQL for PostgreSQL, MySQL, and SQLite.
Fixed¶
- Duplicate migrations fixed.
[0.2.0] - 2026-03-04¶
Changed¶
- Icon and banner added, and tests and docs updated for the rebrand.
[0.1.5] - 2026-02-27¶
Added¶
- ALTER TABLE support.
Changed¶
.envalternatives and async mode removed in favour of a single sync path.- Diffs compare against the database schema instead of the migrations table.
Fixed¶
- Async mode detection and duplicate connection logs in the connection layer.
[0.1.3] - 2026-02-16¶
Added¶
warden.tomldocumented.
[0.1.2] - 2026-02-10¶
Added¶
- Issue templates and CONTRIBUTING.md.
Changed¶
- Config moved from
.envtowarden.toml.
Fixed¶
CallableColumnDefaulthandled so SQLite no longer emits syntax errors for callable defaults.
[0.1.1] - 2026-02-08¶
Added¶
- Baseline migrations and automatic backup before migration.
- Migration dependency support.
- Colored output with SQL syntax highlighting.
Changed¶
- Checksum-based deduplication for
RA__andROC__migrations. - Auto-discovery scans all subdirectories for model files.
Fixed¶
- "Table already exists" errors fixed by adding
IF NOT EXISTStoCREATE TABLEstatements. - Migration deduplication and pending-migration detection fixed.