ch-migrate-cli 0.7.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ch-migrate-cli might be problematic. Click here for more details.
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/.gitignore +2 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/PKG-INFO +103 -5
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/README.md +101 -4
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/__init__.py +1 -1
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/cli.py +177 -107
- ch_migrate_cli-0.8.0/ch_migrate/json_output.py +152 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/lint.py +3 -1
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/mv_validate.py +3 -3
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/skills/ch-migrate/SKILL.md +13 -5
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/pyproject.toml +2 -0
- ch_migrate_cli-0.8.0/tests/integration/test_drift_workflow.py +94 -0
- ch_migrate_cli-0.8.0/tests/integration/test_json_commands.py +105 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_cli_output.py +17 -2
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_introspect.py +25 -25
- ch_migrate_cli-0.8.0/tests/test_json_output.py +198 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_mv_validate.py +14 -14
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_snapshot.py +5 -5
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/LICENSE +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/alembic_env.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/authoring.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/baseline.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/bootstrap.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/config.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/connection.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/deps.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/diff.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/display.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/downgrade.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/env.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/gate.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/helpers.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/hooks.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/idempotency.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/introspect.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/migration_runner.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/py.typed +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/rebase.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/runner.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/scaffold.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/secrets.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/sql.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/statements.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/templates/bootstrap/init_users.sql +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/templates/project/alembic.ini.template +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/templates/project/config.yaml.template +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/templates/project/env.local.example.template +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/templates/project/script.py.mako.template +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/ui.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/version_table.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/waiting/__init__.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/waiting/budget.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/waiting/display.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/waiting/errors.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/waiting/mutations.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/waiting/reruns.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/waiting/sql.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/ch_migrate/waiting/waiter.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/clickhouse_alembic/__init__.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/corpus/idempotency.yaml +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/fixtures/env_v0_4_1.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/conftest.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/dialect_spike_probe.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_cloud.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_cluster_harness.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_gate.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_idempotency_advice.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_irreversible.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_lint_pending.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_mutation_waiting.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_on_cluster.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_readme_quickstart.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_run_sql.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_session_env.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_smoke.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_sql_first.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_standalone_set.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/integration/test_version_table.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_bootstrap.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_compat_import.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_config.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_deps.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_diff.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_display.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_downgrade.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_helpers.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_hooks.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_idempotency.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_lint.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_new.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_package.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_readme.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_rebase.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_reruns.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_scaffold.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_secrets.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_sql.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_statements.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_status_command.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_version_table_policy.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_waiting.py +0 -0
- {ch_migrate_cli-0.7.0 → ch_migrate_cli-0.8.0}/tests/test_waiting_display.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ch-migrate-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: SQL-first ClickHouse schema migrations across environments: the ch-migrate command
|
|
5
5
|
Project-URL: Homepage, https://github.com/DRYCodeWorks/ch-migrate
|
|
6
6
|
Project-URL: Repository, https://github.com/DRYCodeWorks/ch-migrate
|
|
@@ -32,6 +32,7 @@ Requires-Dist: ruamel-yaml<0.20,>=0.19.1
|
|
|
32
32
|
Provides-Extra: dev
|
|
33
33
|
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
34
34
|
Requires-Dist: isort>=5.12.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: jsonschema>=4.18; extra == 'dev'
|
|
35
36
|
Requires-Dist: mypy>=1.5.0; extra == 'dev'
|
|
36
37
|
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
37
38
|
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
@@ -65,6 +66,10 @@ This README describes the source checkout, which may be ahead of PyPI. To try an
|
|
|
65
66
|
|
|
66
67
|
## Upgrade from 0.x
|
|
67
68
|
|
|
69
|
+
**From 0.7:** 0.8 needs no `upgrade-env`, and nothing changes unless you pass
|
|
70
|
+
`--json` (see [Drift detection in CI](#drift-detection-in-ci)). A loose requirement
|
|
71
|
+
such as `ch-migrate-cli` upgrades to 0.8; pin `ch-migrate-cli==0.7.0` to stay on 0.7.
|
|
72
|
+
|
|
68
73
|
**From 0.6:** 0.7 needs no `upgrade-env`. `up` now waits for the mutations and
|
|
69
74
|
`ON CLUSTER` DDL its statements start (see [Migrations that wait](#migrations-that-wait)),
|
|
70
75
|
so give CI jobs time to finish or pass `--timeout`. Where `bootstrap` couldn't grant
|
|
@@ -515,12 +520,68 @@ def upgrade():
|
|
|
515
520
|
create_dictionary("history/dictionaries/dict_users/001_create.sql")
|
|
516
521
|
```
|
|
517
522
|
|
|
523
|
+
## Drift detection in CI
|
|
524
|
+
|
|
525
|
+
After applying and verifying migrations in an environment, capture its expected
|
|
526
|
+
schema:
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
ch-migrate snapshot dev
|
|
530
|
+
mv migrations/sql/snapshots/20261002_120000 migrations/sql/snapshots/dev
|
|
531
|
+
ch-migrate diff dev --snapshot-dir migrations/sql/snapshots/dev --json
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
`snapshot` writes a new timestamped directory every time; replace the example
|
|
535
|
+
timestamp with the one it reports. Give each environment's snapshot a fixed name,
|
|
536
|
+
such as `snapshots/dev`, so `CH_MIGRATE_SNAPSHOT_DIR` never changes and the next
|
|
537
|
+
snapshot shows up in review as a diff against this one. Review and commit that
|
|
538
|
+
directory alongside the migration head. Do not regenerate the snapshot
|
|
539
|
+
automatically in the drift job: doing so would accept an out-of-band change as the
|
|
540
|
+
new expected schema. Re-snapshot after an intentional migration or an explicitly
|
|
541
|
+
reviewed reconciliation, not merely to turn a failed check green. To re-snapshot,
|
|
542
|
+
run `snapshot` again, delete the old `snapshots/dev`, and move the new directory
|
|
543
|
+
into its place, so the commit also shows objects that were dropped.
|
|
544
|
+
|
|
545
|
+
Copy [the GitHub Actions example](docs/examples/github-actions-drift.yml) into the
|
|
546
|
+
consumer repository's `.github/workflows/`. It runs on a daily schedule and
|
|
547
|
+
same-repository pull requests; fork PRs are excluded because they have no database
|
|
548
|
+
secret. It does not use `pull_request_target`. All actions are pinned by commit.
|
|
549
|
+
The checkout must contain `config.yaml` and the committed snapshot, and the runner
|
|
550
|
+
must be able to reach the selected server.
|
|
551
|
+
|
|
552
|
+
Configure these repository variables:
|
|
553
|
+
|
|
554
|
+
| Variable | Value |
|
|
555
|
+
|---|---|
|
|
556
|
+
| `CH_MIGRATE_PACKAGE` | A pinned `ch-migrate-cli` requirement, such as `ch-migrate-cli==0.8.0`. 0.8.0 is the first release with `diff --json`; there is no fallback to an older one. |
|
|
557
|
+
| `CH_MIGRATE_ENV` | An environment name in `config.yaml`, such as `dev`. |
|
|
558
|
+
| `CH_MIGRATE_SNAPSHOT_DIR` | That environment's committed snapshot directory. |
|
|
559
|
+
|
|
560
|
+
Set the repository secret `DRIFT_MIGRATION_PASSWORD` to the password for that
|
|
561
|
+
environment's configured user. Prefer a dedicated schema-inspection account,
|
|
562
|
+
not an admin account. The job writes a private `.env.local` without printing the
|
|
563
|
+
value and removes the file even when comparison fails.
|
|
564
|
+
|
|
565
|
+
`diff --json` exits **0** for matching schemas, **1** for drift, and **2** for a
|
|
566
|
+
configuration/connection error. The workflow saves `drift.json`, uploads it even
|
|
567
|
+
on comparison failure, then fails the job for either nonzero result. The report
|
|
568
|
+
names changed tables and fields. ch-migrate's own test suite runs the example's
|
|
569
|
+
shell steps against a real ClickHouse server, installing from the source checkout,
|
|
570
|
+
for both a matching and a drifted schema.
|
|
571
|
+
|
|
572
|
+
Without `--snapshot-dir`, `diff` sorts entries in `migrations/sql/snapshots/` by
|
|
573
|
+
name and chooses the last one. It does not select a snapshot by environment or
|
|
574
|
+
recorded migration revision. Use an explicit path in CI, especially when one
|
|
575
|
+
repository holds snapshots for several environments.
|
|
576
|
+
|
|
518
577
|
## Command reference
|
|
519
578
|
|
|
520
579
|
Every command accepts `--help`. Top-level `ch-migrate --version` reports the installed package version. `ENV` below names an entry in `config.yaml`.
|
|
521
580
|
|
|
522
581
|
Output lines start with `→` for a step, `✓` for a result, `!` for a warning and `✗` for an error; warnings and errors go to stderr. Colour is dropped when output is not a terminal or `NO_COLOR` is set, and lines are never wrapped, so paths and SQL can be copied or grepped.
|
|
523
582
|
|
|
583
|
+
`status`, `history`, `lint`, and `diff` accept `--json`. JSON mode writes one document to stdout; diagnostics go to stderr. Every document includes `"schema_version": 1` and `"command"`. The format can still change before 1.0; `schema_version` says which one a document follows. Errors include an `error` string rather than inventing a successful empty result. The schemas linked below use JSON Schema draft 2020-12.
|
|
584
|
+
|
|
524
585
|
### `init`
|
|
525
586
|
|
|
526
587
|
`ch-migrate init [PATH] [-n NAME]` initializes the current directory by default. `-n/--name` sets the project name; otherwise it uses the directory name.
|
|
@@ -553,22 +614,53 @@ Example: `ch-migrate down dev --revision base`
|
|
|
553
614
|
|
|
554
615
|
### `status`
|
|
555
616
|
|
|
556
|
-
`ch-migrate status ENV` shows connection information, applied/pending counts, and head status, and names the `up` command when migrations are pending.
|
|
617
|
+
`ch-migrate status ENV [--json]` shows connection information, applied/pending counts, and head status, and names the `up` command when migrations are pending. Status is a report: human output exits 0 when the database is unreachable (with a warning) or migrations are pending, and exits 1 only when the configuration or `migrations/versions/` is missing, so CI can run it as a non-blocking check. JSON mode uses nonzero exits for unreachable or pending state.
|
|
557
618
|
|
|
558
619
|
Example: `ch-migrate status dev`
|
|
559
620
|
|
|
621
|
+
With `--json`, [the status schema](docs/schemas/status.schema.json) includes
|
|
622
|
+
`database`, `current_heads`, `script_heads`, `pending`, `applied`, and `at_head`.
|
|
623
|
+
`applied` includes ancestors resolved through the revision graph, not just stored
|
|
624
|
+
heads. JSON exits 0 when the head sets match exactly, 1 when pending or diverged,
|
|
625
|
+
and 2 when configuration or database state cannot be read. Human-mode exits are
|
|
626
|
+
unchanged.
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
ch-migrate status dev --json | jq -e .current_heads
|
|
630
|
+
# Check the actual head set, not the last element of applied:
|
|
631
|
+
ch-migrate status dev --json | jq -e '.current_heads == ["PINNED_REVISION"]'
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Replace `PINNED_REVISION` with the required revision. In a shell pipeline, enable
|
|
635
|
+
`set -o pipefail` if the caller must also preserve ch-migrate's exit code.
|
|
636
|
+
|
|
560
637
|
### `history`
|
|
561
638
|
|
|
562
|
-
`ch-migrate history ENV` displays the revision graph and applied state.
|
|
639
|
+
`ch-migrate history ENV [--json]` displays the revision graph and applied state.
|
|
563
640
|
|
|
564
641
|
Example: `ch-migrate history dev`
|
|
565
642
|
|
|
643
|
+
`ch-migrate history dev --json` follows [the history schema](docs/schemas/history.schema.json).
|
|
644
|
+
Each entry in `revisions` includes its revision, all `down_revisions` (including
|
|
645
|
+
merge parents), description, create date, path, applied status, and irreversible
|
|
646
|
+
reason. A reversible revision has `irreversible: null`. If database state is
|
|
647
|
+
unavailable, `applied` is null, the document includes `error`, and JSON exits 2.
|
|
648
|
+
An unknown database head also makes applied status unknown instead of claiming
|
|
649
|
+
that every local revision is unapplied.
|
|
650
|
+
|
|
566
651
|
### `lint`
|
|
567
652
|
|
|
568
|
-
`ch-migrate lint [ENV]` analyzes upgrade statements, not downgrade SQL. Without `ENV`, it checks revisions after the gate baseline statically without credentials or a connection. With an environment, it checks only pending revisions in that scope and adds live size and dependency checks. If it cannot determine the pending set, it fails rather than silently checking a different scope.
|
|
653
|
+
`ch-migrate lint [ENV] [--json]` analyzes upgrade statements, not downgrade SQL. Without `ENV`, it checks revisions after the gate baseline statically without credentials or a connection. With an environment, it checks only pending revisions in that scope and adds live size and dependency checks. If it cannot determine the pending set, it fails rather than silently checking a different scope. Errors exit nonzero; warnings and waiver INFO lines alone do not.
|
|
569
654
|
|
|
570
655
|
Example: `ch-migrate lint`
|
|
571
656
|
|
|
657
|
+
`ch-migrate lint --json` follows [the lint schema](docs/schemas/lint.schema.json).
|
|
658
|
+
`findings` include rule, severity, source file/line, message, `blocking`, and
|
|
659
|
+
`waived`. `blocking` means the finding currently blocks `up`; a waived finding
|
|
660
|
+
has its written reason and does not block. `counts` reports total, error, warning,
|
|
661
|
+
info, blocking, and waived findings. JSON and human output use the same exit
|
|
662
|
+
codes: 1 for lint/configuration errors, otherwise 0.
|
|
663
|
+
|
|
572
664
|
Findings name the project-relative SQL file and statement line. Inline Python SQL
|
|
573
665
|
points to its `op.execute` call. Extraction reads `run_sql`/`read_sql` file
|
|
574
666
|
references and literal or f-string `op.execute` arguments without importing
|
|
@@ -585,10 +677,16 @@ Example: `ch-migrate deps dev --validate migrations/sql/history/tables/logs/chan
|
|
|
585
677
|
|
|
586
678
|
### `diff`
|
|
587
679
|
|
|
588
|
-
`ch-migrate diff ENV [-s PATH]` compares the live schema with the latest snapshot. `-s/--snapshot-dir PATH` chooses another snapshot. Exit code 0 means no drift; 1 means drift
|
|
680
|
+
`ch-migrate diff ENV [-s PATH] [--json]` compares the live schema with the latest snapshot. `-s/--snapshot-dir PATH` chooses another snapshot. Exit code 0 means no drift; 1 means drift. JSON errors exit 2; human-mode execution errors retain exit 1.
|
|
589
681
|
|
|
590
682
|
Example: `ch-migrate diff dev --snapshot-dir migrations/sql/snapshots/20261002_120000`
|
|
591
683
|
|
|
684
|
+
`ch-migrate diff dev --json` follows [the diff schema](docs/schemas/diff.schema.json).
|
|
685
|
+
`objects` identifies each object's type, name, status, and structural `details`
|
|
686
|
+
(field, local value, remote value, and message). `in_sync` is true only when every
|
|
687
|
+
object matches. Use the exit code as the drift gate; an error document does not
|
|
688
|
+
contain a fabricated `in_sync: true`.
|
|
689
|
+
|
|
592
690
|
### `snapshot`
|
|
593
691
|
|
|
594
692
|
`ch-migrate snapshot ENV [-e GLOB] [-f GLOB]` writes CREATE statements to a timestamped snapshot directory. `-e/--exclude` and `-f/--filter` accept repeated glob patterns for excluded and included objects.
|
|
@@ -23,6 +23,10 @@ This README describes the source checkout, which may be ahead of PyPI. To try an
|
|
|
23
23
|
|
|
24
24
|
## Upgrade from 0.x
|
|
25
25
|
|
|
26
|
+
**From 0.7:** 0.8 needs no `upgrade-env`, and nothing changes unless you pass
|
|
27
|
+
`--json` (see [Drift detection in CI](#drift-detection-in-ci)). A loose requirement
|
|
28
|
+
such as `ch-migrate-cli` upgrades to 0.8; pin `ch-migrate-cli==0.7.0` to stay on 0.7.
|
|
29
|
+
|
|
26
30
|
**From 0.6:** 0.7 needs no `upgrade-env`. `up` now waits for the mutations and
|
|
27
31
|
`ON CLUSTER` DDL its statements start (see [Migrations that wait](#migrations-that-wait)),
|
|
28
32
|
so give CI jobs time to finish or pass `--timeout`. Where `bootstrap` couldn't grant
|
|
@@ -473,12 +477,68 @@ def upgrade():
|
|
|
473
477
|
create_dictionary("history/dictionaries/dict_users/001_create.sql")
|
|
474
478
|
```
|
|
475
479
|
|
|
480
|
+
## Drift detection in CI
|
|
481
|
+
|
|
482
|
+
After applying and verifying migrations in an environment, capture its expected
|
|
483
|
+
schema:
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
ch-migrate snapshot dev
|
|
487
|
+
mv migrations/sql/snapshots/20261002_120000 migrations/sql/snapshots/dev
|
|
488
|
+
ch-migrate diff dev --snapshot-dir migrations/sql/snapshots/dev --json
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
`snapshot` writes a new timestamped directory every time; replace the example
|
|
492
|
+
timestamp with the one it reports. Give each environment's snapshot a fixed name,
|
|
493
|
+
such as `snapshots/dev`, so `CH_MIGRATE_SNAPSHOT_DIR` never changes and the next
|
|
494
|
+
snapshot shows up in review as a diff against this one. Review and commit that
|
|
495
|
+
directory alongside the migration head. Do not regenerate the snapshot
|
|
496
|
+
automatically in the drift job: doing so would accept an out-of-band change as the
|
|
497
|
+
new expected schema. Re-snapshot after an intentional migration or an explicitly
|
|
498
|
+
reviewed reconciliation, not merely to turn a failed check green. To re-snapshot,
|
|
499
|
+
run `snapshot` again, delete the old `snapshots/dev`, and move the new directory
|
|
500
|
+
into its place, so the commit also shows objects that were dropped.
|
|
501
|
+
|
|
502
|
+
Copy [the GitHub Actions example](docs/examples/github-actions-drift.yml) into the
|
|
503
|
+
consumer repository's `.github/workflows/`. It runs on a daily schedule and
|
|
504
|
+
same-repository pull requests; fork PRs are excluded because they have no database
|
|
505
|
+
secret. It does not use `pull_request_target`. All actions are pinned by commit.
|
|
506
|
+
The checkout must contain `config.yaml` and the committed snapshot, and the runner
|
|
507
|
+
must be able to reach the selected server.
|
|
508
|
+
|
|
509
|
+
Configure these repository variables:
|
|
510
|
+
|
|
511
|
+
| Variable | Value |
|
|
512
|
+
|---|---|
|
|
513
|
+
| `CH_MIGRATE_PACKAGE` | A pinned `ch-migrate-cli` requirement, such as `ch-migrate-cli==0.8.0`. 0.8.0 is the first release with `diff --json`; there is no fallback to an older one. |
|
|
514
|
+
| `CH_MIGRATE_ENV` | An environment name in `config.yaml`, such as `dev`. |
|
|
515
|
+
| `CH_MIGRATE_SNAPSHOT_DIR` | That environment's committed snapshot directory. |
|
|
516
|
+
|
|
517
|
+
Set the repository secret `DRIFT_MIGRATION_PASSWORD` to the password for that
|
|
518
|
+
environment's configured user. Prefer a dedicated schema-inspection account,
|
|
519
|
+
not an admin account. The job writes a private `.env.local` without printing the
|
|
520
|
+
value and removes the file even when comparison fails.
|
|
521
|
+
|
|
522
|
+
`diff --json` exits **0** for matching schemas, **1** for drift, and **2** for a
|
|
523
|
+
configuration/connection error. The workflow saves `drift.json`, uploads it even
|
|
524
|
+
on comparison failure, then fails the job for either nonzero result. The report
|
|
525
|
+
names changed tables and fields. ch-migrate's own test suite runs the example's
|
|
526
|
+
shell steps against a real ClickHouse server, installing from the source checkout,
|
|
527
|
+
for both a matching and a drifted schema.
|
|
528
|
+
|
|
529
|
+
Without `--snapshot-dir`, `diff` sorts entries in `migrations/sql/snapshots/` by
|
|
530
|
+
name and chooses the last one. It does not select a snapshot by environment or
|
|
531
|
+
recorded migration revision. Use an explicit path in CI, especially when one
|
|
532
|
+
repository holds snapshots for several environments.
|
|
533
|
+
|
|
476
534
|
## Command reference
|
|
477
535
|
|
|
478
536
|
Every command accepts `--help`. Top-level `ch-migrate --version` reports the installed package version. `ENV` below names an entry in `config.yaml`.
|
|
479
537
|
|
|
480
538
|
Output lines start with `→` for a step, `✓` for a result, `!` for a warning and `✗` for an error; warnings and errors go to stderr. Colour is dropped when output is not a terminal or `NO_COLOR` is set, and lines are never wrapped, so paths and SQL can be copied or grepped.
|
|
481
539
|
|
|
540
|
+
`status`, `history`, `lint`, and `diff` accept `--json`. JSON mode writes one document to stdout; diagnostics go to stderr. Every document includes `"schema_version": 1` and `"command"`. The format can still change before 1.0; `schema_version` says which one a document follows. Errors include an `error` string rather than inventing a successful empty result. The schemas linked below use JSON Schema draft 2020-12.
|
|
541
|
+
|
|
482
542
|
### `init`
|
|
483
543
|
|
|
484
544
|
`ch-migrate init [PATH] [-n NAME]` initializes the current directory by default. `-n/--name` sets the project name; otherwise it uses the directory name.
|
|
@@ -511,22 +571,53 @@ Example: `ch-migrate down dev --revision base`
|
|
|
511
571
|
|
|
512
572
|
### `status`
|
|
513
573
|
|
|
514
|
-
`ch-migrate status ENV` shows connection information, applied/pending counts, and head status, and names the `up` command when migrations are pending.
|
|
574
|
+
`ch-migrate status ENV [--json]` shows connection information, applied/pending counts, and head status, and names the `up` command when migrations are pending. Status is a report: human output exits 0 when the database is unreachable (with a warning) or migrations are pending, and exits 1 only when the configuration or `migrations/versions/` is missing, so CI can run it as a non-blocking check. JSON mode uses nonzero exits for unreachable or pending state.
|
|
515
575
|
|
|
516
576
|
Example: `ch-migrate status dev`
|
|
517
577
|
|
|
578
|
+
With `--json`, [the status schema](docs/schemas/status.schema.json) includes
|
|
579
|
+
`database`, `current_heads`, `script_heads`, `pending`, `applied`, and `at_head`.
|
|
580
|
+
`applied` includes ancestors resolved through the revision graph, not just stored
|
|
581
|
+
heads. JSON exits 0 when the head sets match exactly, 1 when pending or diverged,
|
|
582
|
+
and 2 when configuration or database state cannot be read. Human-mode exits are
|
|
583
|
+
unchanged.
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
ch-migrate status dev --json | jq -e .current_heads
|
|
587
|
+
# Check the actual head set, not the last element of applied:
|
|
588
|
+
ch-migrate status dev --json | jq -e '.current_heads == ["PINNED_REVISION"]'
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Replace `PINNED_REVISION` with the required revision. In a shell pipeline, enable
|
|
592
|
+
`set -o pipefail` if the caller must also preserve ch-migrate's exit code.
|
|
593
|
+
|
|
518
594
|
### `history`
|
|
519
595
|
|
|
520
|
-
`ch-migrate history ENV` displays the revision graph and applied state.
|
|
596
|
+
`ch-migrate history ENV [--json]` displays the revision graph and applied state.
|
|
521
597
|
|
|
522
598
|
Example: `ch-migrate history dev`
|
|
523
599
|
|
|
600
|
+
`ch-migrate history dev --json` follows [the history schema](docs/schemas/history.schema.json).
|
|
601
|
+
Each entry in `revisions` includes its revision, all `down_revisions` (including
|
|
602
|
+
merge parents), description, create date, path, applied status, and irreversible
|
|
603
|
+
reason. A reversible revision has `irreversible: null`. If database state is
|
|
604
|
+
unavailable, `applied` is null, the document includes `error`, and JSON exits 2.
|
|
605
|
+
An unknown database head also makes applied status unknown instead of claiming
|
|
606
|
+
that every local revision is unapplied.
|
|
607
|
+
|
|
524
608
|
### `lint`
|
|
525
609
|
|
|
526
|
-
`ch-migrate lint [ENV]` analyzes upgrade statements, not downgrade SQL. Without `ENV`, it checks revisions after the gate baseline statically without credentials or a connection. With an environment, it checks only pending revisions in that scope and adds live size and dependency checks. If it cannot determine the pending set, it fails rather than silently checking a different scope.
|
|
610
|
+
`ch-migrate lint [ENV] [--json]` analyzes upgrade statements, not downgrade SQL. Without `ENV`, it checks revisions after the gate baseline statically without credentials or a connection. With an environment, it checks only pending revisions in that scope and adds live size and dependency checks. If it cannot determine the pending set, it fails rather than silently checking a different scope. Errors exit nonzero; warnings and waiver INFO lines alone do not.
|
|
527
611
|
|
|
528
612
|
Example: `ch-migrate lint`
|
|
529
613
|
|
|
614
|
+
`ch-migrate lint --json` follows [the lint schema](docs/schemas/lint.schema.json).
|
|
615
|
+
`findings` include rule, severity, source file/line, message, `blocking`, and
|
|
616
|
+
`waived`. `blocking` means the finding currently blocks `up`; a waived finding
|
|
617
|
+
has its written reason and does not block. `counts` reports total, error, warning,
|
|
618
|
+
info, blocking, and waived findings. JSON and human output use the same exit
|
|
619
|
+
codes: 1 for lint/configuration errors, otherwise 0.
|
|
620
|
+
|
|
530
621
|
Findings name the project-relative SQL file and statement line. Inline Python SQL
|
|
531
622
|
points to its `op.execute` call. Extraction reads `run_sql`/`read_sql` file
|
|
532
623
|
references and literal or f-string `op.execute` arguments without importing
|
|
@@ -543,10 +634,16 @@ Example: `ch-migrate deps dev --validate migrations/sql/history/tables/logs/chan
|
|
|
543
634
|
|
|
544
635
|
### `diff`
|
|
545
636
|
|
|
546
|
-
`ch-migrate diff ENV [-s PATH]` compares the live schema with the latest snapshot. `-s/--snapshot-dir PATH` chooses another snapshot. Exit code 0 means no drift; 1 means drift
|
|
637
|
+
`ch-migrate diff ENV [-s PATH] [--json]` compares the live schema with the latest snapshot. `-s/--snapshot-dir PATH` chooses another snapshot. Exit code 0 means no drift; 1 means drift. JSON errors exit 2; human-mode execution errors retain exit 1.
|
|
547
638
|
|
|
548
639
|
Example: `ch-migrate diff dev --snapshot-dir migrations/sql/snapshots/20261002_120000`
|
|
549
640
|
|
|
641
|
+
`ch-migrate diff dev --json` follows [the diff schema](docs/schemas/diff.schema.json).
|
|
642
|
+
`objects` identifies each object's type, name, status, and structural `details`
|
|
643
|
+
(field, local value, remote value, and message). `in_sync` is true only when every
|
|
644
|
+
object matches. Use the exit code as the drift gate; an error document does not
|
|
645
|
+
contain a fabricated `in_sync: true`.
|
|
646
|
+
|
|
550
647
|
### `snapshot`
|
|
551
648
|
|
|
552
649
|
`ch-migrate snapshot ENV [-e GLOB] [-f GLOB]` writes CREATE statements to a timestamped snapshot directory. `-e/--exclude` and `-f/--filter` accept repeated glob patterns for excluded and included objects.
|