Database-agnostic tool for schema migration and data management.
English | Deutsch
📖 Documentation (German): Anwenderhandbuch (task-oriented user guide) · Administrationshandbuch (deployment/ops) · CLI spec · MCP server spec — full index: docs/user/README.md. See "Documentation" below for the complete list.
d-migrate is a database-agnostic tool for schema migration and data
management, usable as a CLI and as an MCP server
(mcp serve --transport stdio|http, MCP 2025-11-25). You define your
schema once in a neutral YAML format and then validate, compare,
generate DDL, and execute live diff-based migrations against
PostgreSQL, MySQL, and SQLite. d-migrate also covers reverse
engineering of existing databases, streaming-based data
export/import/transfer between databases, and export to existing
migration toolchains (Flyway, Liquibase, Django, Knex).
d-migrate targets database administrators, platform engineers, data teams, and integrators who:
- need a dialect-agnostic schema artefact (PostgreSQL / MySQL / SQLite from the same YAML source)
- want reproducible, signed migration plans with explicit rollback contracts, drift checks, and per-statement metadata
- run schema and data operations against existing databases — including reverse engineering, comparison, transfer, and incremental export — without locking into a single vendor's tooling
- need an AI-agent-callable MCP server for read-only schema discovery (validate / compare / generate / reverse) plus policy-gated job workers for data import / transfer / profile
It is not (yet) an ETL platform, a streaming-CDC pipeline, or a replacement for hand-tuned dialect-specific migrations — but it captures the schema and data work that's common across these stacks.
d-migrate is a working production tool at version 1.2.0 (stable, released 2026-09-05).
New in 1.2.0:
data seedgenerates deterministic, FK-safe test data straight from the neutral schema (an optional--rulesfile steers values per column);mcp servegains a configurable--policy-filefor*_startjob policy and aconnections/listmethod with an optional live-status check;--server-statenow persists reverse-engineered schemas and generated artifacts too, not just jobs/quota. SeeCHANGELOG.mdfor the full list.New in 1.1.0: MS SQL Server as the fourth dialect (reverse, generate, migrate, data path, profiling),
schema migratecan now run statements a database refuses inside an open transaction (SQL Server full-text indexes), and partition-set changes (add/drop a rolling partition) are applied instead of only reported. SeeCHANGELOG.mdfor the full list.New in 1.0.3: restores the native binaries (the 1.0.1 tag could not build them) and makes Parquet import work in the native binary for the first time — it had failed in every native binary released so far; both native build legs now verify a full Parquet round trip. Also carries all of 1.0.1: the shipped artefact contains no known-vulnerable dependency version (down from one critical and 43 high findings), the PostgreSQL driver moves to 42.7.12, and single members of
--split-filesParquet bundles import correctly. The CLI contract is unchanged. SeeCHANGELOG.md.
The current capabilities:
- Schema model: neutral YAML schema with 19 types + Spatial Geometry; validator with 35+ error codes.
- Schema operations:
validate,generate,compare,reverse,migrate,rollbackfor PostgreSQL, MySQL, SQLite — file/file, file/db, db/db. - Diff migrations: tables, columns, indexes, constraints incl.
CHECK/EXCLUDE with live-data preflight, foreign keys, sequences,
views, materialized views (PG), triggers, functions/procedures;
signed
migration-plan.v1artefact via--plan-artefact. - Renames for tables, columns, views, triggers, functions,
procedures, sequences — native
RENAMEDDL or Drop+Create fallback per dialect; CLI shortcuts (--rename-table,--rename-column) or file overlay (--migration-overlay). - Sequence pipeline: MySQL helper-table emulation
(
dmg_sequences) with live drift check; opt-inpreserveCurrentValuefor PG / MySQL / SQLite — probe + restore folded into a single transaction under per-dialect lock since 0.9.7 (pg_advisory_xact_lock/SELECT FOR UPDATE/BEGIN IMMEDIATE); SQLite sequence emulation via--sqlite-named-sequences helper_table. - Spatial DDL: PostGIS, MySQL native, SpatiaLite
(
--spatial-profile); view-query transformation across dialects. - Data operations: streaming
data export/import/transfer(JSON / YAML / CSV / Parquet) with named connections, UPSERT, truncate, trigger handling, reseeding, incremental export (--since-column/--since);data profilefor data statistics. - Parquet & object storage (0.9.8):
data export/import --format parquetfor bundle (multi-table +manifest.yaml) and single-file (footer-KV) layouts with checkpoint/resume and--table-order; S3-compatibleArtifactStore(artifacts.store: s3, AWS SDK v2) for server-mode artefacts. - Integrations:
d-migrate export flyway|liquibase|django|knex. - MCP server (
mcp serve --transport stdio|http, MCP 2025-11-25): read-only tool surface (schema_validate,schema_compare,schema_generate,schema_reverse_start) plus policy-gated job workers (data_import_start,data_transfer_start,data_profile_start,procedure_transform_*,testdata_*) with idempotency and JDBC-backed state; auth via JWT-JWKS / RFC-7662 introspection / stdio token registry. - CLI UX: i18n EN/DE with ICU4J, explicit time-zone / temporal
policy, CSV / BOM encoding contract, phased DDL via
--split pre-post. - OCI image at
ghcr.io/pt9912/d-migrate:<version>and:latest, mirrored to Docker Hub aspt9912/d-migrate. Each release also ships a<version>-nativevariant built from the GraalVM native binary — no JVM inside, starts in milliseconds.
The simplest way to try the tool is the published OCI image:
docker run --rm -v $(pwd):/work ghcr.io/pt9912/d-migrate:latest \
schema validate --source /work/schema.yamlSee Quick start below for more concrete recipes.
- ≥ 90 % line coverage per module, enforced by Kover
(
minBound(90)in every module'sbuild.gradle.kts). The CI build fails if any module drops below. - Doc-check gate: Markdown link targets in
docs/,spec/, and root Markdown files (including both READMEs andCHANGELOG.md) are validated against the file system on every CI run via d-check (digest-pinned container image, configured in.d-check.yml); broken internal links and anchors break the build. - Static-analysis gate: Detekt plus a SOLID-suppression-gate
(
scripts/solid-suppression-gate.sh) —@Suppress("LargeClass")and friends are tracked in a ledger and require structural fixes, not inline waivers. - Cross-dialect test matrix:
test/cross-dialect-matrixsweeps every workstream × dialect × test-kind cell, with a carve-out registry (carve-outs.yaml) that requires every non-pinned cell to declare reason + plan-doc reference; silent carve-outs are rejected at load time. - Live-DB integration tests against Testcontainers PostgreSQL
16, MySQL 8, and file-backed SQLite — every diff, rename,
sequence, and atomic-preserve pipeline runs against real engines
via
scripts/test-integration-docker.sh. - Reproducible builds:
--deterministicplusSOURCE_DATE_EPOCHemit byte-identical DDL across timestamps and OS environments. - Signed migration plans:
--plan-artefactwrites a canonical, signedmigration-plan.v1JSON with stable fingerprints, statement IDs, and rollback metadata; tampered artefacts are rejected by theMigrationPlanArtifactValidator. - Hexagonal architecture: pure-domain
hexagon:coreplushexagon:ports-{common,read,write,execute}with driving adapters (CLI, MCP) and driven adapters (drivers, formats, persistence) isolated through explicit interfaces. Architectural decisions live as ADRs indocs/adr/. - CI mirrors local: every gate
make ciruns locally also runs in GitHub Actions on every push (.github/workflows/build.yml).
The full release history lives in CHANGELOG.md.
- Current stable · 1.2.0 (2026-09-05) — what
:latest, Homebrew and an unpinneddocker pullgive you.data seedgenerates deterministic test data,mcp servegains a configurable--policy-fileand aconnections/listmethod, and--server-statenow persists reverse-engineered schemas and artefacts too. MS SQL Server is the fourth dialect (reverse, generate, migrate, data path, profiling); the container image runs as non-root (uid 10001), so writing into a bind mount needs--user "$(id -u):$(id -g)". Native binaries ship forlinux-x64andwindows-x64; on macOS use Homebrew, the JVM artefacts or the container image.
For per-milestone task tables and ADR pointers see the canonical
roadmap at
docs/planning/in-progress/roadmap.md.
ADRs live under docs/adr/; the canonical index is
docs/adr/README.md.
All releases and details:
CHANGELOG.md |
GitHub Releases.
Individual gates for fast feedback loops:
make help # list all available targets
make ci # Docker CI build + docs-check (full local gate)
make gates # Docker check, coverage, docs, and semgrep gates
make docker-build # build the runtime image
make docker-check # Gradle check inside the Dockerfile build stage
make docker-test # Gradle test inside the Dockerfile build stage
make docker-detekt # Detekt static analysis
make docker-coverage-gate # Kover ≥ 90 % per module
make docs-check # validate Markdown link targets + Kover-excludes ledger
make semgrep # hermetic semgrep scan with pinned rules
make integration # Testcontainers integration suite
make docker-full-gates # docker-gates plus Docker-backed integration tests
make docker-oci-build # build the publishable OCI image (runtime stage)
make release-assets # build ZIP, TAR, fat JAR, SHA256 release assetsTargeted module runs:
make docker-check MODULES=":hexagon:core :adapters:driven:driver-postgresql"
make docker-test MODULES=":adapters:driving:mcp"- Docker
- Optional for local development without containers: JDK 21 or newer
No local JDK required — pull the image and run it:
# Validation
docker run --rm -v $(pwd):/work ghcr.io/pt9912/d-migrate:latest \
schema validate --source /work/schema.yaml
# Compare (file/file)
docker run --rm -v $(pwd):/work ghcr.io/pt9912/d-migrate:latest \
schema compare --source file:/work/schema.yaml --target file:/work/schema-new.yaml
# Generate DDL
docker run --rm -v $(pwd):/work ghcr.io/pt9912/d-migrate:latest \
schema generate --source /work/schema.yaml --target postgresql
# Reverse engineering
docker run --rm -v $(pwd):/work ghcr.io/pt9912/d-migrate:latest \
--config /work/.d-migrate.yaml schema reverse --source mydb --output /work/reverse.yaml
# DB-to-DB data transfer
docker run --rm -v $(pwd):/work ghcr.io/pt9912/d-migrate:latest \
data transfer --source sourcedb --target targetdb --tables users,ordersThe published image runs as a non-root user (uid 10001). Read-only commands
(validate, compare) work as shown above. Commands that write into a
bind-mounted host directory (reverse --output, generate to a file, data transfer to file targets) need the mount to be writable by the container user —
add --user "$(id -u):$(id -g)" so output lands with your host ownership:
docker run --rm --user "$(id -u):$(id -g)" -v $(pwd):/work \
ghcr.io/pt9912/d-migrate:latest \
schema reverse --source mydb --output /work/reverse.yamlPublished releases ship ZIP, TAR, a fat JAR, and — from 1.0.0-RC2 on — native binaries that need no Java, on the Releases page.
# Launcher-based distribution
tar -xf d-migrate-<version>.tar
./d-migrate-<version>/bin/d-migrate --help
# Or run the fat JAR directly
java -jar d-migrate-<version>-all.jar --help
# Or the native binary — no JVM required, starts in ~15 ms
chmod +x d-migrate-<version>-linux-x64
./d-migrate-<version>-linux-x64 --helpNative binaries are published for linux-x64 and windows-x64 (each
with a .sha256); linux-x64 is guaranteed per release, windows-x64
is best-effort. There is no native macOS binary — on macOS use
Homebrew, the JVM artefacts or the container image
(ADR 0044). They are dynamically linked
against glibc — on Alpine/musl use the JVM artefacts or the container
image.
d-migrate lives in its own tap, not in homebrew-core, so brew install d-migrate alone will not find it. Recent Homebrew versions additionally
refuse to load formulae from untrusted third-party taps — you need all
three steps:
brew tap pt9912/d-migrate
brew trust pt9912/d-migrate
brew install d-migrateopenjdk@21 comes along as a dependency. The tap follows stable
releases only — release candidates never move it.
On macOS this is the recommended path, because there is no native macOS binary (ADR 0044).
The formula is maintained in this repository from 0.5.0 on and verified
per release via
.github/workflows/verify-homebrew-formula.yml.
make ci-buildCreate a file called schema.yaml:
schema_format: "1.0"
name: "My App"
version: "1.0.0"
tables:
users:
columns:
id:
type: identifier
auto_increment: true
email:
type: text
max_length: 254
required: true
unique: true
created_at:
type: datetime
default: current_timestamp
primary_key: [id]Validate it like this:
make docker-build
docker run --rm -v $(pwd):/work d-migrate:dev schema validate --source /work/schema.yamlAnd compare two versions like this:
docker run --rm -v $(pwd):/work d-migrate:dev \
schema compare --source /work/schema.yaml --target /work/schema-v2.yamlThe repository ships a multi-stage Dockerfile that
builds and tests the project inside the container and then packages
the CLI distribution into a slim JRE runtime image. This is the
simplest way to run the full build without installing a local JDK.
Dockerfile stages — overview
deps: Gradle dependency pre-warm.build: build, tests, coverage gate, distribution. Use--target build --build-arg GRADLE_TASKS="..."to scope to a specific task list.detekt: Detekt static analysis.coverage: aggregated Kover HTML report on port 8080.docker build --target coverage -t d-migrate:coverage .+docker run --rm -p 8080:8080 d-migrate:coverage.coverage-json: Kover JSON to stdout viaENTRYPOINT.coverage-verify: hardkoverVerify(≥ 90 % per module).release-assets: ZIP / TAR / fat JAR / SHA256 (target ofmake release-assets).runtime(default): the image that gets published — slimeclipse-temurin:21-jre-noble, non-root (uid 10001),mod_spatialiteincluded. Target ofmake docker-oci-build(ADR 0041).
Common Dockerfile recipes
# Full build incl. tests and coverage validation
docker build -t d-migrate:dev .
# Force a full test/coverage run (bypasses both the Docker layer cache and the Gradle cache)
docker build --no-cache --progress=plain \
--build-arg GRADLE_TASKS="build :adapters:driving:cli:installDist --rerun-tasks" \
-t d-migrate:dev .
# Skip tests — build only the CLI distribution
docker build --build-arg GRADLE_TASKS="assemble :adapters:driving:cli:installDist" \
-t d-migrate:dev .
# Run only part of the build stage without producing the final runtime image
docker build --target build \
--build-arg GRADLE_TASKS=":hexagon:core:test :adapters:driven:driver-common:test" \
-t d-migrate:phase-a .
# Run the locally built CLI
docker run --rm -v $(pwd):/work d-migrate:dev schema validate --source /work/schema.yaml
# Run the testcontainers integration suite
./scripts/test-integration-docker.sh
# Or a subset of integration tests
./scripts/test-integration-docker.sh :adapters:driven:driver-postgresql:test| Database | Status |
|---|---|
| PostgreSQL | DDL generation, reverse engineering, data export/import/transfer |
| MySQL | DDL generation, reverse engineering, data export/import/transfer |
| SQLite | DDL generation, reverse engineering, data export/import/transfer |
| Oracle | Reverse engineering, DDL generation, schema migration, tool export, data export/import/transfer (rollout, ADR 0052) |
| MSSQL | Reverse engineering, DDL generation, schema migration, tool export, data export/import/transfer (rollout, ADR 0047) |
.
├── .github/workflows/ ← GitHub Actions: build, integration, demo/sample DB, release
├── CHANGELOG.md
├── Dockerfile ← multi-stage (deps, build, detekt, coverage, runtime, release-assets)
├── Makefile ← build/test gates per Dockerfile stage
├── README.md ← English main version (this document)
├── README.de.md ← German version
├── build.gradle.kts ← root build config + module aggregation
├── settings.gradle.kts ← Gradle multi-module declaration
├── gradle.properties ← pinned dependency versions
├── config/ ← detekt and semgrep configuration
├── hexagon/ ← pure domain + ports (no driver dependencies)
│ ├── core/ ← neutral schema model, diff core, validators
│ ├── ports-common/ ← cross-cutting port contracts
│ ├── ports-read/ ← read-side ports (DDL generation, reverse, capabilities)
│ ├── ports-write/ ← write-side ports (data import / transfer)
│ ├── ports-execute/ ← atomic-execution ports (preserve, lock contracts)
│ ├── ports/ ← driver-registry port (DatabaseDriver, DatabaseDriverRegistry, PreGenerationValidator) + facade re-export of ports-{common,read,write,execute}
│ ├── application/ ← use-case orchestration + stage pipelines
│ └── profiling/ ← perf measurement infrastructure
├── adapters/
│ ├── driven/ ← outbound: driver-postgresql/-mysql/-sqlite (+ -profiling),
│ │ formats + formats-parquet, integrations
│ │ (Flyway/Liquibase/Django/Knex), persistence-jdbc,
│ │ storage-file/-s3, streaming, text-icu,
│ │ audit-logging, connection-config
│ └── driving/ ← inbound: cli, mcp
├── examples/
│ ├── bi-demo/ ← Compose demo for Parquet/S3/BI flows
│ └── sample-db/ ← on-demand sample database harness
├── test/
│ ├── consumer-read-probe/ ← read-only consumer surface verification
│ ├── cross-dialect-matrix/ ← workstream × dialect × kind sweep + carve-out registry
│ ├── integration-postgresql/ ← Testcontainers PG live-DB tests
│ ├── integration-mysql/ ← Testcontainers MySQL live-DB tests
│ ├── integration-sqlite/ ← file-backed SQLite live-DB tests
│ ├── integration-concurrency/ ← race-condition reproducers (sequence preserve, atomic locks)
│ ├── integration-integrations/ ← export integration contract tests
│ ├── integration-persistence-jdbc/ ← JDBC store + migration runner ITs
│ ├── integration-server-state/ ← MCP server state machine ITs
│ ├── integration-storage-s3/ ← S3-compatible artifact store ITs
│ ├── e2e-cli/ ← end-to-end CLI + MCP harness scenarios
│ └── perf-large-schema/ ← N = 100 / 1000 / 10000 perf scales
├── scripts/ ← verify-doc-refs.sh, solid-suppression-gate.sh,
│ test-integration-docker.sh, kover utilities
├── ledger/ ← suppression and quality ledgers
├── packaging/homebrew/ ← Homebrew formula (d-migrate.rb)
├── spec/ ← normative specs (German): lastenheft, architecture,
│ design, cli-spec, neutral-model-spec,
│ ddl-generation-rules, mcp-server, schema-reference,
│ connection-config-spec
└── docs/
├── adr/ ← Architecture Decision Records + index
├── planning/
│ ├── open/ ← trigger watch + open follow-ups
│ ├── next/ ← planned but not yet active
│ ├── in-progress/ ← active roadmap + slice plans
│ └── done/ ← completed slices + closure notes
└── user/ ← user / operator facing (guide.md, releasing.md)
Note: the linked ADRs, slice plans, and planning documents under
docs/ and spec/ are written in German. The
English README mirrors the structure and key facts; for deep-dive
content, consult README.de.md or the German source
documents.
Detailed documentation lives in docs/ and
spec/:
- Documentation overview (German)
- Quick Start Guide (German)
- Architecture
- Schema YAML reference
- Neutral model specification
- CLI specification
- MCP server
- DDL generation rules
- Connection and configuration specification
- Roadmap
- Release guide
- Requirements document (German)
Contributions are welcome! Please open an issue or a pull request on GitHub.
- Fork the repository
- Create a feature branch off
main - Write tests for your changes (≥ 90 % per-module Kover gate applies)
- Make sure the Docker CI gates pass (
make ci) - Submit a pull request against
main
This project is licensed under the MIT License.