diff --git a/README.md b/README.md index 4a9d401..bd71e93 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ rules that apply inside the core. Read it before changing anything under ## What it looks like -Every sample below is captured verbatim from a real session against the +The sample below is captured verbatim from a real session against the compose database (`make db-up`, PostgreSQL 16): `$` marks the command, everything after it is the tool's output. @@ -93,99 +93,25 @@ apply: **Improve: a blocking form is replaced with the safer online sequence.** `migrate --dry-run` shows exactly what would run, as compiler-style -diagnostics with a doc anchor per finding (exit 0 — the plan is executable): - -```console -$ pg-sprite migrate --alter 'ALTER TABLE users ADD CONSTRAINT users_email_key UNIQUE (email)' --dry-run -statement 1: - ALTER TABLE users ADD CONSTRAINT users_email_key UNIQUE (email); - -warning[safer-idiom]: - ADD CONSTRAINT users_email_key — holds a blocking lock on the table for - the whole operation — writes (and for some forms reads) wait until it - finishes - -note: - pg-sprite will run a safer online sequence instead: - 1. CREATE UNIQUE INDEX CONCURRENTLY "users_email_key" ON "users" ("email"); - 2. ALTER TABLE "users" ADD CONSTRAINT "users_email_key" UNIQUE USING INDEX "users_email_key"; - -note: - each step commits on its own — not transactionally equivalent, and the - sequence must not run inside a transaction block - -docs: - https://github.com/block/pg-sprite/blob/main/docs/postgres-online-ddl-reference.md#safer-idiom - -plan: - public.users (PostgreSQL 16.14) — 1 statement, 2 steps to run, 0 refused - -dry-run: - nothing was executed - -apply: - re-run without --dry-run -``` +diagnostics with a doc anchor per finding (exit 0 — the plan is executable). +The demo above records the whole flow — dry run, real run, catalog proof; +the machine-readable shape is in +[docs/cli-output-examples.md](docs/cli-output-examples.md). **Refuse: no safe path exists, so nothing runs.** A genuine table rewrite needs the copy-and-swap backend (a later phase); the dry run exits 2 so CI can gate on it without parsing JSON. The exit-code gate stops refusals only — a destructive-but-executable change (`DROP COLUMN`) warns and exits 0, so a gate that must stop drops checks `.statements[].destructive` in the -`--json` report: - -```console -$ pg-sprite migrate --alter 'ALTER TABLE users ALTER COLUMN id TYPE text' --dry-run -statement 1: - ALTER TABLE users ALTER COLUMN id TYPE text; - -error[backend-unavailable]: - refused — needs the copy-and-swap backend (an online shadow-table copy - with a cutover), which this build does not implement yet - -note[type-rewrite]: - ALTER COLUMN id TYPE text — the type conversion forces a full table - rewrite under an exclusive lock that blocks reads and writes - -docs: - https://github.com/block/pg-sprite/blob/main/docs/postgres-online-ddl-reference.md#backend-unavailable - https://github.com/block/pg-sprite/blob/main/docs/postgres-online-ddl-reference.md#type-rewrite - -plan: - public.users (PostgreSQL 16.14) — 1 statement, 0 steps to run, 1 refused - -dry-run: - nothing was executed -``` +`--json` report. Watch it in +[docs/demos/refuse.gif](docs/demos/refuse.gif); the machine-readable shape +is in [docs/cli-output-examples.md](docs/cli-output-examples.md). **Lint: offline, no database needed.** Flag blocking idioms in a DDL file -and suggest the safer form: - -```console -$ pg-sprite lint changes.sql -changes.sql:1:1: - CREATE INDEX users_email_idx ON users (email); - -warning[blocking-idiom]: - CREATE INDEX users_email_idx — holds a blocking lock on the table for - the whole operation — writes (and for some forms reads) wait until it - finishes - -help: - a safer online form exists — not a semantic equivalent, and running it - by hand forgoes the engine's execution-time guards: - 1. CREATE INDEX CONCURRENTLY users_email_idx ON users USING btree (email); - -note: - run each statement in its own transaction, never one block; after a - failed CONCURRENTLY build, check pg_index.indisvalid and rebuild - -docs: - https://github.com/block/pg-sprite/blob/main/docs/postgres-online-ddl-reference.md#safer-idiom - -lint: - changes.sql — 1 finding, 0 errors, 1 warning -``` +and suggest the safer form — no connection, no Docker; error-severity +findings exit non-zero, warnings alone pass. Watch it in +[docs/demos/lint.gif](docs/demos/lint.gif); the machine-readable shape is +in [docs/cli-output-examples.md](docs/cli-output-examples.md). More shapes — every disposition as JSON, destructive warnings, and exit codes — are in [docs/cli-output-examples.md](docs/cli-output-examples.md). diff --git a/docs/cli-output-examples.md b/docs/cli-output-examples.md index b1acd48..943e776 100644 --- a/docs/cli-output-examples.md +++ b/docs/cli-output-examples.md @@ -3,8 +3,9 @@ Representative, real JSON outputs for every shape the CLI produces: the plan report for each dry-run disposition, the execution verdict, the linter, and diff. The human text rendering of the same reports is display -only — see the [README](../README.md) for samples; the JSON is the machine -contract. All were captured verbatim from a real session against the +only — see the [README](../README.md)'s diff sample and the animated demos +in [demos/](demos/) for how it reads; the JSON is the machine contract. +All were captured verbatim from a real session against the compose database (`make db-up`, PostgreSQL 16) — `$` marks the command, everything after it is the tool's output — with this schema: diff --git a/docs/demos/README.md b/docs/demos/README.md index 373e0b6..5562160 100644 --- a/docs/demos/README.md +++ b/docs/demos/README.md @@ -14,12 +14,35 @@ directory). ## Tapes -| Tape | Story | Needs database | -|---|---|---| -| `diff-greenfield.tape` | Declarative diff for a table absent from the live database: the full desired schema planned as a diagnostic report with the greenfield note | yes | -| `improve.tape` | Dry-run of a blocking `ADD CONSTRAINT … UNIQUE`, the real run executing the safer online sequence, `\d users` catalog proof — then the declarative loop: `diff --desired` plans the remaining change, `migrate` executes it, `diff` confirms convergence | yes | -| `refuse.tape` | `error[rewrite-required]` refusal with typed `note`/`help` diagnostics and doc anchors, then `echo $?` showing the exit-code contract (2) | yes | -| `lint.tape` | Offline lint of a two-statement change file — `unset PGSPRITE_URL` on camera to show no database is needed | no | +### `diff-greenfield.tape` — declarative diff, greenfield (needs database) + +Declarative diff for a table absent from the live database: the full +desired schema planned as a diagnostic report with the greenfield note. + +![pg-sprite diff planning a CREATE TABLE from the full desired schema, with the greenfield note](diff-greenfield.gif) + +### `improve.tape` — the safer online sequence, then the declarative loop (needs database) + +Dry-run of a blocking `ADD CONSTRAINT … UNIQUE`, the real run executing +the safer online sequence, `\d users` catalog proof — then the declarative +loop: `diff --desired` plans the remaining change, `migrate` executes it, +`diff` confirms convergence. + +![pg-sprite replacing a blocking ADD CONSTRAINT with the safer online sequence, then converging on a desired schema via diff and migrate](improve.gif) + +### `refuse.tape` — refusal and the exit-code contract (needs database) + +`error[rewrite-required]` refusal with typed `note`/`help` diagnostics and +doc anchors, then `echo $?` showing the exit-code contract (2). + +![pg-sprite refusing a change with no safe path, with typed diagnostics and exit code 2](refuse.gif) + +### `lint.tape` — offline lint (no database) + +Offline lint of a two-statement change file — `unset PGSPRITE_URL` on +camera to show no database is needed. + +![pg-sprite lint flagging blocking idioms in a DDL file with no database connection](lint.gif) ## Re-rendering diff --git a/docs/demos/diff-greenfield.gif b/docs/demos/diff-greenfield.gif index 767f393..ca36235 100644 Binary files a/docs/demos/diff-greenfield.gif and b/docs/demos/diff-greenfield.gif differ diff --git a/docs/demos/diff-greenfield.tape b/docs/demos/diff-greenfield.tape index 37c9bed..a389241 100644 --- a/docs/demos/diff-greenfield.tape +++ b/docs/demos/diff-greenfield.tape @@ -18,7 +18,7 @@ Type "export PGSPRITE_URL='postgres://pgsprite:pgsprite@localhost:5432/pgsprite? Type "docker exec compose-postgres-1 psql -U pgsprite -d pgsprite -c 'DROP TABLE IF EXISTS widgets' >/dev/null" Enter Type `demo_dir="$(mktemp -d)" && cd "$demo_dir"` Enter Type "echo 'CREATE TABLE widgets (id bigint PRIMARY KEY, name text NOT NULL, price numeric);' > widgets.sql" Enter -Type "export PS1='\n~/pg-sprite main '" Enter +Type "export PS1='\n~/kiran01bm/github/pg-sprite main '" Enter Type "clear" Enter Sleep 1.5s Show diff --git a/docs/demos/improve.gif b/docs/demos/improve.gif index 2d206d5..294599a 100644 Binary files a/docs/demos/improve.gif and b/docs/demos/improve.gif differ diff --git a/docs/demos/improve.tape b/docs/demos/improve.tape index e8bcd7f..6f1d42c 100644 --- a/docs/demos/improve.tape +++ b/docs/demos/improve.tape @@ -25,7 +25,7 @@ Type "echo ' email text,' >> users.sql" Enter Type "echo ' nickname text,' >> users.sql" Enter Type "echo ' CONSTRAINT users_email_key UNIQUE (email)' >> users.sql" Enter Type "echo ');' >> users.sql" Enter -Type "export PS1='\n~/pg-sprite main '" Enter +Type "export PS1='\n~/kiran01bm/github/pg-sprite main '" Enter Type "clear" Enter Sleep 1.5s Show diff --git a/docs/demos/lint.gif b/docs/demos/lint.gif index b8bdb09..b6b205e 100644 Binary files a/docs/demos/lint.gif and b/docs/demos/lint.gif differ diff --git a/docs/demos/lint.tape b/docs/demos/lint.tape index f2aef1e..8b8e168 100644 --- a/docs/demos/lint.tape +++ b/docs/demos/lint.tape @@ -16,7 +16,7 @@ Type `export PATH="$(git rev-parse --show-toplevel)/bin:$PATH"` Enter Type "cd /tmp" Enter Type "echo 'CREATE INDEX users_email_idx ON users (email);' > changes.sql" Enter Type "echo 'ALTER TABLE users ADD CONSTRAINT users_email_key UNIQUE (email);' >> changes.sql" Enter -Type "export PS1='\n~/pg-sprite main '" Enter +Type "export PS1='\n~/kiran01bm/github/pg-sprite main '" Enter Type "clear" Enter Sleep 1.5s Show diff --git a/docs/demos/refuse.gif b/docs/demos/refuse.gif index 51d85f8..5c56927 100644 Binary files a/docs/demos/refuse.gif and b/docs/demos/refuse.gif differ diff --git a/docs/demos/refuse.tape b/docs/demos/refuse.tape index bd6f425..f52ee26 100644 --- a/docs/demos/refuse.tape +++ b/docs/demos/refuse.tape @@ -16,7 +16,7 @@ Hide Type `export PATH="$(git rev-parse --show-toplevel)/bin:$PATH"` Enter Type "export PGSPRITE_URL='postgres://pgsprite:pgsprite@localhost:5432/pgsprite?sslmode=disable'" Enter Type `docker exec compose-postgres-1 psql -U pgsprite -d pgsprite -c 'DROP TABLE IF EXISTS users' -c 'CREATE TABLE users (id bigint PRIMARY KEY, email text)' -c "INSERT INTO users SELECT g, 'u'||g||'@x.com' FROM generate_series(1,1000) g" >/dev/null` Enter -Type "export PS1='\n~/pg-sprite main '" Enter +Type "export PS1='\n~/kiran01bm/github/pg-sprite main '" Enter Type "clear" Enter Sleep 1.5s Show