Releases: dbwarden-org/dbwarden
Release list
DBWarden v0.19.0: v2 migration locking, merge handling, and documentation overhaul
TL;DR
DBWarden v0.19.0 introduces v2 migration locking with per-engine strategies, heartbeat-based stale lock detection, and a new merge handling pipeline for multi-developer workflows. The release adds dbwarden merge, rebase, and reconcile commands, ClickHouse cluster configuration and idempotency, comprehensive lock and merge test suites, and a full documentation overhaul across all pages.
Test suite: 2509 passed, 77 skipped.
V2 migration locking
Migration locking now uses per-engine strategies: PostgreSQL advisory locks, MySQL named locks, SQLite BEGIN IMMEDIATE, and ClickHouse lease-based locking with configurable TTL. A heartbeat background task updates last_heartbeat_at on native-lock engines, enabling stale (STUCK) and dead worker detection. Fallback engines (ClickHouse CH-0) treat heartbeat failure as fatal.
dbwarden lock-status and dbwarden unlock commands are enhanced with detailed holder diagnostics including host, PID, execution ID, migration version, and health status. Lock exceptions are added for structured error handling. ClickHouse cluster configuration and CH-1 idempotency are implemented with clickhouse_lock_ttl and lock_namespace config keys.
Comprehensive test suites cover all lock strategies, heartbeat behavior, stale lock detection, and unlock operations. SQLite lock handling is fixed to reuse the BEGIN IMMEDIATE connection for migration execution, and the heartbeat task is correctly skipped on SQLite where staleness is inferred from acquired_at and process liveness.
Merge handling
A new merge handling pipeline supports multi-developer workflows. dbwarden merge detects schema conflicts between branches and generates merge plans. dbwarden rebase replays migrations onto a new base. dbwarden reconcile resolves dirty environments with unreconciled merge changes.
Merge detection is integrated with dbwarden make-migrations and dbwarden status, so pending merge conflicts are surfaced during normal migration workflows. Merge handling integrates with dbwarden migrate and dbwarden rollback for coordinated execution. An --all-environments flag enables cross-environment merge operations.
MariaDB merge limitations are documented. Semantic conflict detection identifies overlapping schema changes. Model state reconstruction from database snapshots enables diff-based merge analysis.
Documentation overhaul
All documentation is comprehensively revised across every page: commands, configuration, database setup, advanced topics, correctness guarantees, plugins, and the getting-started guide. ClickHouse documentation receives dedicated setup guides for CH-0 through CH-4, lock architecture details, ON CLUSTER expansion, and class-based API examples as the default.
Plugin documentation is updated to use public plugin_api equivalents. List formatting is corrected across all docs. Redis locking references use dbwarden-redis. ClickHouse index examples are converted to class-based API. All fictional config keys are removed.
Bug fixes
Fixed a logger bug in dbwarden unlock where the audit log call referenced an undefined variable. Fixed PostgreSQL advisory lock key size and type casting. Fixed critical audit findings in the lock implementation including heartbeat timestamp generation for SQLite.
Test Coverage
Full suite: 2509 passed, 77 skipped.
DBWarden v0.18.0: module split, connection rename, exception hierarchy, and typed snapshots
TL;DR
DBWarden v0.18.0 refactors the snapshot engine into a maintainable module structure, renames the connection layer for clarity, introduces a formal exception hierarchy and typed snapshot structures, and closes four open issues. The release also hardens ClickHouse and PostgreSQL reverse-engineering, adds per-component CLI log filtering, and brings compiler framing to all documentation.
Test suite: 2450 tests collected.
extract.py module split
The 1,500-line extract.py God Function is split into focused modules: extract_common (shared column, index, and constraint introspection), extract_pg (PostgreSQL enrichment and database objects), extract_mysql (MySQL and MariaDB enrichment), and extract_sqlite (SQLite enrichment with FK action fixups). extract.py is now a thin ~200-line dispatch module. All backends retain identical behavior.
Connection layer rename
dbwarden.database is renamed to dbwarden.connection to better describe its role as the infrastructure layer. A backward-compatibility shim at dbwarden.database.__init__ re-exports everything with a DeprecationWarning, so existing plugins and imports continue to work. Approximately 50 import sites across core and tests are updated.
Exception hierarchy and typed snapshots
A new dbwarden.exceptions package organises all exceptions under DBWardenError: core.py (13 exceptions for configuration, database, migration, and seed errors), plugin.py (5 plugin exceptions reparented under DBWardenError), and engine.py (OrderingError and RollbackContractError with dual inheritance for catch flexibility).
dbwarden.engine.snapshot.types introduces 26 TypedDicts covering the full snapshot hierarchy: Snapshot, SnapshotTable, SnapshotColumn, indexes, constraints, PG/MySQL/SQLite metadata, roles, grants, policies, sequences, functions, and event triggers.
Issue resolution
-h is now accepted as a shorthand for --help across all CLI commands (issue #44). Per-component log filtering via --log-level COMPONENT:LEVEL replaces the all-or-nothing debug flag (issue #34). ClickHouse immutable option detection is fixed, and 142 parametrised CLI option combination tests cover every flag permutation (issue #43). The issue tracker summary is reframed with accurate scope (issue #39).
Reverse-engineering hardening
ClickHouse materialised view extraction and PostgreSQL enum, identity, and foreign key reverse-engineering gaps are closed. PostgreSQL column statistics, storage, and compression metadata are now captured during snapshot extraction. SQLite foreign key action fixups handle edge cases that previously produced incorrect DDL.
Documentation
All documentation is reframed around the compiler metaphor: source (SQLAlchemy models) compiles to target (SQL DDL) across multiple backends. The plugin template gains ruff linting and object-handler examples. Private plugin imports are replaced with public plugin_api equivalents across pgsql-extensions, pgsql-rbac, pgsql-types, and fastapi plugins.
Test Coverage
Full suite: 2450 tests collected.
DBWarden v0.17.1: declarative configuration, convergence hardening, and safer persistence
TL;DR
DBWarden v0.17.1 makes declarative database configuration the default scaffold, strengthens migration convergence and replay, and hardens generated output and runtime boundaries. Generated files now use atomic replacement, SQL identifiers are protected, credentials are masked through parsed URLs, and plugin and workspace inputs receive stricter validation.
Declarative configuration
dbwarden init now generates DbwardenDatabase subclasses by default. Declarative configuration supports inherited fields, plugin-owned settings, aliases, indirect imports, equivalent handles, and validation parity with database_config(...). The existing function-based API remains supported.
Configuration discovery and impact analysis reject symlink escapes, so scans stay inside the intended workspace. Runtime configuration paths and plugin boundaries now receive stricter validation as well.
Migration convergence and replay
Migration generation and execution now share stronger convergence checks and replay handling. Connection cleanup is more reliable, and PostgreSQL column behavior has additional coverage across generation and application.
Generated files use atomic replacement for migration files, model state, generated models, rollback files, plugin state, and exported configuration. Interrupted writes no longer leave truncated artifacts behind.
SQL and output hardening
Backend-generated schema, table, column, statistics, and drop-object SQL now quotes safe identifiers or rejects unsafe ones. SQL splitting respects semicolons inside quoted strings and comments.
Settings and configuration output mask URLs through SQLAlchemy URL parsing, including encoded credentials and IPv6 hosts. Live check and snapshot commands now propagate connection failures instead of reporting success after retries are exhausted.
Plugin and provenance hardening
Plugin provenance and installation inputs are HTTPS-only and size-bounded. Lock and consent TOML serialization escapes structural characters, and installer and provenance inputs are validated before use.
Test Coverage
Full suite: 1930 passed, 44 skipped.
Import-boundary checks passed. The P0 convergence audit passed with 9 tests. PostgreSQL convergence passed. ClickHouse convergence passed with 3 tests and skipped 1 unavailable replicated-engine case.
DBWarden v0.17.0: CLI observability, safer multi-database operations, and hardened SQL generation
DBWarden v0.17.0: CLI observability, safer multi-database operations, and hardened SQL generation
TL;DR
DBWarden now provides structured JSON CLI output, TRACE logging, migration performance timing, and a stable top-level error boundary. Multi-database operations can skip configured optional databases after connection retries, report dedicated partial success with exit code 3, and be forced to fail with --disable-skip. Database configuration now supports automatically registered declarative classes through DbwardenDatabase while preserving database_config(...). Migration locking, backups, migration ordering, path validation, SQL identifier quoting, and generated SQL tests were hardened across supported backends.
Test suite: 1863 passed, 36 skipped.
CLI observability and automation
The CLI now supports a global --json flag for machine-readable command output and JSON-formatted logs. Commands with existing output formats honor the global flag, while logs are kept separate from structured output.
TRACE logging is available through --debug-level trace or level 5. Migration commands can emit per-statement SQL diagnostics at TRACE level.
The --perf flag adds timing breakdowns for migration and SQL-generation phases, including lock acquisition, snapshot writes, model state writes, and individual SQL statements.
A top-level CLI error boundary now provides stable error codes and structured JSON error documents for automation.
Optional databases and partial success
Database configurations support skip_if_missing=True. Optional databases are skipped only after normal connection retries fail during supported multi-database operations.
The global --disable-skip flag overrides configured skip behavior and forces connection failures to remain hard failures.
Multi-database migration, status, and seed operations now report succeeded, skipped, and failed databases consistently. Operations that complete while skipping optional databases return exit code 3 for dedicated partial-success handling.
Declarative database configuration
Concrete subclasses of DbwardenDatabase are automatically registered when defined:
class ProductionDatabase(DbwardenDatabase):
__abstract__ = True
database_type = "postgresql"
model_paths = ["models"]
skip_if_missing = True
class Primary(ProductionDatabase):
database_name = "primary"
database_url_sync = DATABASE_URL
default = TrueConfiguration inheritance, overrides, abstract bases, mutable-value isolation, and mixed declarative and function-style configuration are validated through the same registry pipeline. The existing database_config(...) API remains supported.
Migration safety and database hardening
Migration locks now use owner-aware acquisition and release, preventing one process from releasing another process's lock. Force unlock behavior and lock schema upgrades are covered by regression tests.
SQLite backups use online backup, atomic replacement, restrictive file permissions, and symlink rejection.
Migration execution records metadata only after autocommit statements succeed. Multi-database migration failures are aggregated instead of stopping after the first database.
Migration discovery validates path containment, rejects symlink and non-regular migration files, and uses dependency-aware migration ordering.
SQL generation and test coverage
Generated identifiers are quoted for SQLite, MySQL, MariaDB, and ClickHouse paths where required. Offline SQL generation has deterministic assertions and shared scenario helpers.
The release adds focused coverage for:
- CLI JSON output and error handling
- TRACE and performance logging
- Optional database availability
--disable-skip- Partial-success result contracts
- Declarative database configuration
- Backup safety
- Migration lock ownership
- Migration ordering
- SQLite failure injection
- Backend SQL generation and identifier quoting
Test Coverage
Full suite: 1863 passed, 36 skipped.
DBWarden v0.16.5: plugin-declared config keys, masked settings output, MySQL new-table diff cleanup, hardened publishing
DBWarden v0.16.5: plugin-declared config keys, masked settings output, MySQL new-table diff cleanup, hardened publishing
TL;DR
database_config(...) no longer hardcodes the backend object keys; each plugin declares the keys it consumes and core validates them, so a config key whose plugin is missing now fails with an install hint instead of being silently ignored. dbwarden settings prints resolved database values with secrets masked. The MySQL diff stage stops emitting a redundant ALTER TABLE for brand-new tables. The publish pipeline now verifies the release tag against pyproject.toml, gates publishing on the full CI suite at the exact tag ref, and adds a manual retry path.
Test suite: 1786 passed, 44 skipped.
Plugin-declared config keys
database_config(...) previously carried 17 hardcoded keyword arguments for plugin-owned objects (pg_roles, pg_domains, ch_grants, and so on). Those are gone: the signature now accepts **plugin_config, and each official plugin registers the keys it consumes through registrar.register_config_key (guarded, so plugins keep working against older cores).
Core keeps the PLUGIN_CONFIG_KEY_OWNERS table mapping every key to the distribution that owns it. At database_config(...) time _validate_plugin_config now rejects:
- a key owned by a plugin that is not installed, with a message telling you to run
dbwarden plugin add <owner> - any other unknown keyword argument, with a clear error instead of a silent drop
Empty values for declared keys are allowed without the plugin installed, so an empty list does not force an install.
DatabaseEntry stores these as a plugin_config dict and keeps attribute access working through __getattr__, so entry.pg_roles still returns the list for consumers across the engine.
dbwarden settings: resolved, secret-masked output
The settings command now resolves each database through get_database() before printing, so it shows the effective values after configuration resolution rather than the raw entry. URLs and other sensitive fields are passed through display_value, which masks secrets, so database URLs no longer leak credentials into terminal output.
MySQL: no redundant ALTER for new tables
The MySQL backend emitted an ALTER TABLE for table options even when the table was brand new, duplicating options the CREATE TABLE already carries inline. The snapshot diff now skips alter_my_table ops for tables being created in the same migration, and emits those options only once.
Publish pipeline hardening
publishing.yml now verifies the release tag against the pyproject.toml version before any artifact is built (a mismatch fails the run with a fix hint), and gates the build on a full CI re-run at the exact tag ref through the new callable ci.yml. A workflow_dispatch retry path lets a publish be re-triggered after a flaky infrastructure failure without deleting and recreating the GitHub release. The deploy-docs workflow bumps seoslug to 2.1.0 for zensical compatibility.
Test Coverage
New tests cover the config-key validation (install hints, unknown keys, empty-value exemption), attribute access on plugin_config, and the MySQL new-table diff. Full suite: 1786 passed, 44 skipped.
DBWarden v0.16.4
DBWarden v0.16.4: --debug CLI logging flags + offline rollback/fwd-ref fixes + config scan-caching fix
TL;DR
New global --debug / --debug-level flags let you set the exact logging severity, orthogonal to the per-command --verbose/-v flag. Model discovery now logs every scanned file (and failed loads) at DEBUG level. Offline cascading-ch_raw() migrations get a rollback-ordering fix and string forward-reference resolution, so agg targets and plain SQLAlchemy models in separate files resolve correctly. And repeated config access no longer re-runs the expensive full-workspace scan, fixing a hang in large repositories.
Test suite: 1775 passed, 44 skipped.
New: --debug / --debug-level CLI flags
DBWardenLogger now carries an exact debug_level (stdlib severity) separate from the legacy boolean debug_enabled:
--debugswitches the logger to DEBUG level.--debug-level <LEVEL>accepts a named level (debug,info,warning,error,critical) or a numeric level (10/20/30/40/50); it takes precedence over the bare--debugswitch.- Both are orthogonal to
--verbose: use--debugplus-vfor DEBUG diagnostics with verbose INFO output. --debug-levelhas no verbosity effect; the per-command-vflag still controls INFO output.
Model discovery (path_discovery.py) now emits DEBUG messages for every file scanned and every file that fails to load, so dbwarden --debug make-migrations ... shows exactly which model files were discovered. make-migrations also forwards --verbose to the logger, so it no longer silently ignores the flag.
resolve_debug_level() centralizes CLI parsing and raises a ValueError for invalid values (surfaced as a typer BadParameter).
Fix: offline rollback ordering and string forward-references
_run_offline_migrations was double-reversing the rollback list; _assemble_migration already reverses it, so the pipeline wrote it back in forward order. A migration dropping a ClickHouse MV and its target therefore recreated the MV before the target table. The pipeline now writes the rollback verbatim.
Separately, load_model_from_path now loads each file under a per-path unique module name registered in sys.modules, so string forward-references to plain SQLAlchemy models resolve instead of {}.
Fix: config scan-caching
get_multi_db_config() moved reset_registry() ahead of the multi-db cache check, so repeated get_database() / get_multi_db_config() calls reuse the cached resolved source instead of re-running the expensive full-repository AST scan on every cache miss. Regression tests assert the scan runs exactly once across repeated calls.
v0.16.3
DBWarden v0.16.3: MV drop ordering fix and cascade ch_raw group-by type resolution
TL;DR
Offline migrations for cascading AggregatingViews now assign StatementOrder.DROP_VIEW (11) to materialized view drops instead of always using DROP_TABLE (14). Upgrade SQL drops MVs before their target tables, and rollback SQL recreates target tables before their MVs. ch_raw() group-by expressions on aggregating views now resolve their ClickHouse types through the cascade chain by extracting column references from the raw SQL and walking up to the base SA table; group-by keys no longer fall back to "String". Two regression tests cover the drop-ordering and the cascade group-by type resolution.
MV Drop Order in Offline Migrations
TableHandler.emit() for drop_table ops always assigned StatementOrder.DROP_TABLE (14), even when the dropped object was a materialized view. With equal order values the stable sort kept each target table before its MV, so upgrade SQL dropped tables first (failing while the MV still depended on them) and rollback recreated tables before their depending MVs. The op now checks drop_table.object_type and uses DROP_VIEW (11) for views and materialized views:
- Upgrade order: DROP_VIEW (11) before DROP_TABLE (14), so MVs drop before their targets.
- Rollback order (reversed): target tables are recreated before the MVs that depend on them.
ch_raw Group-By Column Type Resolution
_resolve_source_column_types (materialized_view.py) now includes group-by key types for AggregatingView sources by walking the cascade chain to the base SA table:
- _resolve_aggregating_view_group_by_types: for an AggregatingView source, resolves each group-by key. Plain column-name keys are looked up against the next source up the chain; ch_raw() keys with an AS alias use the alias, otherwise the bare expression is matched by identifier extraction.
- _match_column_type_from_raw: extracts column-name-like identifiers from a raw CH expression (identifiers only, not the function or the alias) and returns the type of the first matching column in the resolved source types.
- Recursion is bounded at depth 20 to guard against cyclic source graphs.
As a result, group-by keys such as ch_raw("toStartOfHour(toTimeZone(closed_at, 'America/Montevideo')) AS hour") resolve hour to DATETIME from the base table, and a cascade like toDate(hour) AS day resolves day to DATETIME from the hourly view's group-by types.
Test Coverage
2 new regression tests in TestCHViewDiscovery; 283 targeted tests pass (test_type_mapping, test_queries, test_schema_meta, test_model_discovery).
- test_drop_table_uses_drop_view_order_for_materialized_views: DROP_VIEW (11) sorts before DROP_TABLE (14), so MVs drop before target tables in upgrade and are recreated after them in rollback.
- test_ch_raw_group_by_types_resolve_through_cascade: ch_raw() group-by keys resolve their CH types by extracting column references from the raw SQL and traversing the cascade chain (hourly -> daily -> fact_order).
Full Commit Log
3a66954 Bump version to 0.16.3
b8c7c65 test: add regression tests for MV drop ordering and ch_raw group-by types
2b5f102 fix(ch): resolve ch_raw group-by column types through cascade chain
8e9e6d2 fix: use DROP_VIEW order for materialized view drops
v0.16.2...v0.16.3: 4 commits, 4 files changed, 186 insertions(+), 1 deletion(-).
DBWarden v0.16.2: Registry fallback for cascade MV resolution and config fallback warnings
DBWarden v0.16.2: Registry fallback for cascade MV resolution and config fallback warnings
TL;DR
Cascading AggregatingViews with string forward references (e.g. source="HourlyRollup") now correctly resolve class names through ChView._ch_view_registry when the class is loaded via load_model_from_path (which registers modules under _dbwarden_loaded_model, invisible to sys.modules scans). Group-by keys on agg targets resolve source column types instead of always falling back to "String". ch_meta.ch_type is populated unconditionally regardless of backend detection outcome. Backend and schema config resolution failures now log warnings instead of silently falling back to "sqlite" and "public". Three regression tests cover cascade dependency ordering, ch_type fallback, and backend delegation consistency.
Cascade MV String Forward-Ref Resolution
load_model_from_path (path_discovery.py:23) always uses a hardcoded module name; classes loaded this way are invisible to sys.modules scans. Three sites now fall back to ChView._ch_view_registry:
- _resolve_source (materialized_view.py:456) resolves string forward refs to tablename for MV SELECT generation.
- _resolve_source_column_types (materialized_view.py:487) resolves cascade source column types for correct MergeState combinator detection.
- ch_view_tables_from_models dependency ordering (views.py:263) resolves string forward refs to build the topological sort DAG for multi-level cascades (hourly -> daily -> weekly).
Group-By Key Type Resolution
_expand_agg_target (views.py:395) now calls _resolve_source_column_types and passes the result as _source_column_types to _make_target_columns (views.py:430). Non-aggregate columns (group-by keys) use the resolved source column type instead of always falling back to "String". Bare-table-name sources still return {} from _resolve_source_column_types, so group-by keys on those sources continue to default to "String".
Unconditional ch_meta.ch_type Population
extract_column_info (extraction.py:467) populates ch_meta["ch_type"] unconditionally after the if backend == "clickhouse" block. When _get_backend_name falls back to "sqlite" (config resolution failure), CH column metadata is no longer silently dropped.
Config Fallback Warnings
Two functions that silently returned fallback values when get_database() raised now log warnings:
- _get_backend_name (type_mapping.py:19) warns on fallback to "sqlite".
- get_schema_name (database/queries/init.py:38) warns on fallback to "public".
_get_backend in snapshot/utils.py now delegates to the canonical _get_backend_name from type_mapping.py, removing an identical duplicate.
Test Coverage
3 new regression tests; 281 targeted tests pass (test_type_mapping, test_queries, test_schema_meta, test_model_discovery).
- test_cascade_dependency_ordering: 3-level string-forward-ref cascade loaded via get_all_model_tables; verifies topological sort produces hourly < daily < weekly and each target precedes its MV.
- test_get_backend_fallback_sqlite_populates_ch_type: extract_column_info with backend="sqlite" returns ch_meta with "ch_type":"Int32".
- test_snapshot_get_backend_delegates_to_type_mapping: both _get_backend and _get_backend_name return "sqlite" for nonexistent db_name.
Full Commit Log
6e869371 Bump version to 0.16.2
4785eb31 test: add regression tests for cascade ordering, ch_type fallback, backend delegation
2eb2ae77 fix: warn on backend/schema config fallback
8fced558 fix: populate ch_meta.ch_type regardless of backend
282fe4dc fix(ch): cascade MVs resolve string forward-refs via registry; fix group-by key types
v0.16.1...v0.16.2: 5 commits, 9 files changed, 481 insertions(+), 13 deletions(-).
DBWarden v0.16.1: Agg-target DDL fixes and cascade combinator fix
TL;DR
Auto-schema extracted to dbwarden-fastapi plugin, removing 537 lines from core. Cascading aggregate views fixed with per-column combinator resolution and topological ordering. Three agg-target DDL bugs fixed (wrong column types, corrupted MV syntax, duplicate DDL, bad rollback order). Approved tier renamed to Verified. Cookbook entries updated with plugin install steps. New test coverage for schema, safety, CH utils, sqlite_translation, and cascade combinator correctness. 10 commits, 73 files changed, 3,151 insertions, 1,106 deletions.
Auto-Schema Extracted to Plugin
dbwarden/schema/_auto_schema.py and the schemap dependency removed from core. Auto-schema functionality now lives in the dbwarden-fastapi plugin. All docs updated: import paths, cookbook prerequisites, and examples merged into fastapi-app. Users of auto-schema must dbwarden plugin add dbwarden-fastapi.
Cascading Aggregate View Combinator Fix
Per-column combinator resolution (_classify_column_type, resolve_combinator) in dbwarden/databases/clickhouse/agg.py. Source column type resolution in AggregatingViewSpec.to_dict. Import-time validation of function/inner-type/SimpleAggregateFunction mismatches. Topological ordering in ch_view_tables_from_models for target-before-MV emission.
Container Test
tests/integration/test_agg_cascade.py: Two-level cascade (events -> event_hourly -> event_daily) against live ClickHouse 24.3 asserting exact numeric values from sumMerge(amount_sum). With the wrong combinator (sumState on AggregateFunction), ClickHouse rejects the MV with ILLEGAL_TYPE_OF_ARGUMENT. With the correct combinator (sumMergeState), the cascade produces exact rolled-up values.
Agg-Target DDL Bug Fixes
Bug #1: _make_target_columns Always Returned String
dbwarden/databases/clickhouse/views.py:419-461: AggregatingView subclasses lack __table__ (enforced by _validate_view_class), so the SA-column loop was always skipped and every column fell back to "String". Now reads aggregate types from target_info["aggregates"] via target_type() and falls back to String only for group-by keys without known types.
Bug #2a: MV DDL Parens on TO-Clause MVs
dbwarden/engine/backends/clickhouse/render.py:117: _generate_clickhouse_materialized_view_sql always appended column parens, even for TO-clause MVs where ClickHouse syntax forbids them. Now only emits (columns_sql) when columns_sql is non-empty and TO is not set.
Bug #2b: Duplicate DDL Between Handlers
TableHandler and ChAggTargetHandler both emitted CREATE TABLE for the same agg target table. TableHandler.model_spec_from_tables now skips AggregatingMergeTree tables so only ChAggTargetHandler owns their lifecycle.
Bug #3: Wrong Rollback Order for Agg Targets
ChAggTargetHandler used ALTER_TABLE_OPTIONS (order 17), making target drops run before MV drops in rollback (reversed sort). Changed to CREATE_TABLE (order 7) so rollback correctly drops MVs first, then target tables.
Approved → Verified Tier Rename
dbwarden/_approved.py renamed to dbwarden/_verified.py with all internal references updated (APPROVED_PLUGINS → VERIFIED_PLUGINS, approved_allows → verified_allows). Documentation and approved-standard.md renamed to verified-standard.md. All imports, tier strings, CLI help text, and scripts updated.
Documentation Updates
- README/docs: Removed seeds section, added official plugins list, plugin system mentions
- Cookbook: Added
dbwarden plugin add <name>install steps to 07-seeds, 09-fastapi, and 11-observability entries - Contributors: Added Ahmet Cetin with accurate PR references; shell script style fixes
- Plugin docs: Updated for Verified tier, publisher guidelines, and plugin system references
CI Fix
Ecosystem workflow: Added missing uv sync --dev step. The ecosystem-check.py script imports dbwarden.config_schema which requires cattrs, but dependencies were never installed before running the script.
Test Coverage
New test modules:
tests/test_type_mapping.py(127 lines)tests/test_metrics.py(53 lines)tests/test_safety.py(123 lines)tests/test_sqlite_translation.py(107 lines)tests/test_offline_migrations.py(69 lines)tests/test_version_unit.py(155 lines)tests/test_schema_meta.py(229 lines)tests/engine/snapshot/test_ch_utils.py(242 lines)tests/engine/snapshot/test_core.py(199 lines)tests/engine/snapshot/test_diff.py(56 lines)tests/engine/snapshot/test_extract_ch.py(640 lines)tests/engine/snapshot/test_index_utils.py(231 lines)tests/integration/test_agg_cascade.py(262 lines)tests/engine/backends/clickhouse/test_ch_handlers.py(82 lines, 2 cascade combinator regression tests)
1,725 tests pass, 36 skipped, 11 warnings.
Full Commit Log
96bf8c55 fix: three agg-target DDL bugs and CI dependency step
2c94d556 test(ch): add container test for cascade combinator correctness
45bbe180 fix(ch): cascading aggregate views use MergeState combinator
90b0b38b test: add coverage for schema, safety, CH utils, and sqlite_translation
fae211c0 Move schemap/@auto_schema from core to dbwarden-fastapi plugin
f1d60c1f Fix Ahmet Cetin contribution description with accurate PR references
75427feb Replace em dashes with colons/semicolons in shell scripts; add contributors to README
d7fa5f19 Add plugin install steps to cookbook entries that use plugins
23e8d9c3 Rename Approved tier to Verified across code and docs
e550878b Update README and docs: remove seed section, add official plugins list and plugin system mention
v0.16.0...v0.16.1: 10 commits, 73 files changed, 3,151 insertions(+), 1,106 deletions(-).
DBWarden v0.16.0: Plugin Architecture
TL;DR
Full plugin architecture with three trust tiers (official, approved, community), consent-based loading, and PEP 740 provenance attestation. 7 official plugins extracted from core, reducing core footprint by ~35,000 lines. Repository migrated to dbwarden-org GitHub organization. Nine commits, 141 files changed, 12,886 insertions, 9,554 deletions.
Plugin Architecture
dbwarden/plugin.py (758 lines): Entry-point discovery via importlib.metadata, HookRegistry for value hooks, ObjectPluginRegistry for schema object handlers, PluginRegistrar passed to plugin setup() functions. Topological ordering via Anchor/OrderingConstraint.
Three trust tiers:
- Official: Signed with PEP 740 attestation, verified against each repo's
publish.ymlworkflow fingerprint - Approved: Community-reviewed, cataloged in
dbwarden/_approved.py - Community: Consent-gated at first load
CLI commands: plugin list, plugin info, plugin add, plugin remove, plugin trust, plugin untrust. Auto-loaded for non-plugin commands. --format, --uv, --version, --dry-run flags throughout.
Plugin conformance harness (dbwarden/plugin_conformance.py): signature validation, idempotency checks, no-core-internals guard, entry-point verification. Used by every official plugin's CI.
15 documentation files covering quickstart, using, developing, and reference. docs/plugins/ replaces docs/extensions/.
Hook Integration Points
Session factories (dbwarden.engine.sandbox), health routes (dbwarden.extensions.fastapi.health), migration routes (dbwarden.extensions.fastapi.routes), model/config module loading (dbwarden.config.resolve), seed commands (dbwarden.commands.seeds), sandbox lifecycle (dbwarden.commands.migrate, dbwarden.commands.recover_model_state), FastAPI lifespan and engine dependencies (dbwarden.extensions.fastapi), database state operations.
7 Official Plugins
All hosted under github.com/dbwarden-org/:
| Plugin | Description |
|---|---|
dbwarden-ch-rbac |
ClickHouse RBAC: roles, users, grants, row policies, quotas, settings profiles, named collections |
dbwarden-fastapi |
FastAPI session dependencies, health endpoints, migration routes, lifespan management |
dbwarden-pgsql-extensions |
PostgreSQL extensions, event triggers, extended statistics, functions, triggers, storage parameters |
dbwarden-pgsql-rbac |
PostgreSQL RBAC: roles, grants, default privileges, policies |
dbwarden-pgsql-types |
PostgreSQL custom types: ENUMs, domains, composite types, sequences |
dbwarden-sandbox |
SQLAlchemy and testcontainers sandbox providers for safe migration replay |
dbwarden-seeds |
Seed data management: apply, rollback, and export across environments |
Official spec in dbwarden/_official.py maps each plugin to its PyPI name, repo slug, and attestation workflow (publish.yml). Provenance verification uses PEP 740 attestation records from PyPI to confirm the publisher identity matches.
Core Shrink
86 files, 17,078 deletions in the first extraction pass (commit 5271dbd); 72 files, 9,554 additional deletions in the second pass (commit a947921). Total: ~35,000 lines removed from core.
Removed from core:
dbwarden/extensions/fastapi/(12 files) replaced bydbwarden-fastapidbwarden/extensions/sandbox.pyreplaced bydbwarden-sandboxdbwarden/engine/seeds.py,dbwarden/engine/code_seeds.pyreplaced bydbwarden-seeds- 7 CH RBAC handler files replaced by
dbwarden-ch-rbac - 8 PG handler files (roles, grants, default_privileges, policies, enum, domain, sequence, composite_type) replaced by
dbwarden-pgsql-rbac,dbwarden-pgsql-types, anddbwarden-pgsql-extensions - 5 PG extension handler files (event_trigger, extended_statistics, function, trigger, storage_params) replaced by
dbwarden-pgsql-extensions docs/fastapi/(7 files, 5,396 lines) moved to plugin repo- 13 test files for removed modules
All existing handler imports rewritten to use importlib fallback or HookRegistry calls. Seed/export/migrate commands simplified to plugin hooks. snapshot/diff.py, snapshot/sql_gen.py, offline/diff.py, pipeline.py, sql_build.py updated to use ObjectPluginRegistry instead of hard-coded handler references.
Repository Migration
Full repository moved to github.com/dbwarden-org/dbwarden. All official plugin repositories created under the same organization. GPG-signed commits now required per CONTRIBUTING.md.
Bug Fixes
- plugin_api.py: Added as a stable re-export surface for backend SQL helpers (CH cluster/secrets, PG quoting) object plugins reference these without importing backend internals.
- Import boundary checker: Exempts
plugin_api.pyfrom theengine/core/no-backend-import rule since re-exporting backend helpers to plugins is its intended purpose. - Conformance signature snapshot: Normalizes
typing.prefix across Python versions so the CI snapshot matches both 3.12 (typing.Any) and 3.14 (Any).
Test Coverage
1,503 tests pass, 43 skipped, 10 warnings (in previous release: 1,804 pass, 30 skipped the difference is from removed plugin-specific tests that moved to plugin repos).
Full Commit Log
c77a6c8 fix: preserve generated model metadata
70a2ba0 fix: stabilize PostgreSQL round trip diff
3208a41 fix: normalize live snapshot diff
2969c4b Bump up version
23c51e5 feat: add plugin system with trust tiers, consent, and provenance verification
5271dbd Strip plugin-duplicated code from core
a947921 Extract remaining sandbox and 5 PG extension handlers to plugins
fcf67f1 Require GPG signed commits in CONTRIBUTING.md
9772b6f Fix CI: exempt plugin_api.py from import-boundary check and normalize typing. prefix in signature snapshot
v0.15.0...v0.16.0: 9 commits, 141 files changed, 12,886 insertions(+), 9,554 deletions(-).