This commit is contained in:
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/bin/sh
|
||||
# schema.sql <-> migration-chain convergence check (review MIG-03).
|
||||
#
|
||||
# A fresh volume is built from db/schema.sql (initdb) and then runs every
|
||||
# migration on top; an existing volume only runs migrations. Both paths must
|
||||
# land on the same structure — i.e. schema.sql has to be kept current with
|
||||
# the chain, so each migration is a structural no-op against it. This builds
|
||||
# two scratch databases, compares columns/indexes/constraints, and fails on
|
||||
# any drift. Runs inside the postgres image against the compose db; needs
|
||||
# CREATEDB rights for DB_USER (the compose superuser has them).
|
||||
#
|
||||
# Environment: PGPASSWORD (required), DB_HOST, DB_USER.
|
||||
set -eu
|
||||
|
||||
DB_HOST="${DB_HOST:-db}"
|
||||
DB_USER="${DB_USER:-computer}"
|
||||
[ -n "${PGPASSWORD:-}" ] || { echo "error: PGPASSWORD is required" >&2; exit 1; }
|
||||
|
||||
BASE_DB=computer_conv_schema
|
||||
CHAIN_DB=computer_conv_chain
|
||||
|
||||
psql() { command psql -h "$DB_HOST" -U "$DB_USER" -v ON_ERROR_STOP=1 "$@"; }
|
||||
|
||||
drop() { psql -q -d postgres -c "DROP DATABASE IF EXISTS $1 WITH (FORCE)" >/dev/null 2>&1 || true; }
|
||||
drop "$BASE_DB"; drop "$CHAIN_DB"
|
||||
trap 'drop "$BASE_DB"; drop "$CHAIN_DB"' EXIT INT TERM
|
||||
|
||||
psql -q -d postgres -c "CREATE DATABASE $BASE_DB"
|
||||
psql -q -d postgres -c "CREATE DATABASE $CHAIN_DB"
|
||||
|
||||
# Path 1: schema.sql alone. Path 2: schema.sql + the full ledger chain
|
||||
# (exactly what initdb + migrate.sh do on a fresh install). schema.sql
|
||||
# carries its own BEGIN/COMMIT, so no -1 wrapper here.
|
||||
psql -q -d "$BASE_DB" -f /db/schema.sql
|
||||
psql -q -d "$CHAIN_DB" -f /db/schema.sql
|
||||
DB_NAME="$CHAIN_DB" sh /db/migrate.sh >/dev/null
|
||||
|
||||
# Structural catalog, minus the ledger itself (chain path only).
|
||||
catalog() {
|
||||
psql -Atq -d "$1" -c "
|
||||
SELECT 'column '||table_name||'.'||column_name||' '||data_type||' '||is_nullable||' '||COALESCE(column_default,'-')
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema='public' AND table_name<>'_migration'
|
||||
UNION ALL
|
||||
SELECT 'index '||indexname||': '||indexdef FROM pg_indexes
|
||||
WHERE schemaname='public' AND tablename<>'_migration'
|
||||
UNION ALL
|
||||
SELECT 'constraint '||c.relname||' '||con.conname||': '||pg_get_constraintdef(con.oid)
|
||||
FROM pg_constraint con JOIN pg_class c ON c.oid=con.conrelid
|
||||
WHERE con.connamespace='public'::regnamespace AND c.relname<>'_migration'
|
||||
UNION ALL
|
||||
SELECT 'sequence '||sequence_name FROM information_schema.sequences
|
||||
WHERE sequence_schema='public'
|
||||
ORDER BY 1"
|
||||
}
|
||||
|
||||
catalog "$BASE_DB" > /tmp/base.catalog
|
||||
catalog "$CHAIN_DB" > /tmp/chain.catalog
|
||||
|
||||
fail() {
|
||||
echo "error: db/schema.sql and db/migrations disagree (diff above)." >&2
|
||||
echo " Update schema.sql (fresh installs) AND ship a new numbered migration (existing installs)." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Pass 1: the chain is a structural no-op against the current schema.sql.
|
||||
diff -u /tmp/base.catalog /tmp/chain.catalog || fail
|
||||
|
||||
# Pass 2 — upgraded-install simulation: pass 1 alone cannot see the DDL
|
||||
# inside CREATE TABLE IF NOT EXISTS blocks (schema.sql always wins there).
|
||||
# Drop every migration-created table, wipe the ledger, and re-run the chain
|
||||
# so those definitions materialize for real, then compare again. This is what
|
||||
# an existing install upgraded through the ledger ends up with. Caveat: if a
|
||||
# schema.sql table ever gains an FK to a migration-created table, the CASCADE
|
||||
# drop removes that constraint and this check fails conservatively.
|
||||
tables=$(sed -n 's/.*CREATE TABLE IF NOT EXISTS "\([^"]*\)".*/\1/p' /db/migrations/*.sql | sort -u)
|
||||
if [ -n "$tables" ]; then
|
||||
for tb in $tables; do
|
||||
psql -q -d "$CHAIN_DB" -c "DROP TABLE IF EXISTS \"$tb\" CASCADE"
|
||||
done
|
||||
psql -q -d "$CHAIN_DB" -c "DELETE FROM _migration"
|
||||
DB_NAME="$CHAIN_DB" sh /db/migrate.sh >/dev/null
|
||||
catalog "$CHAIN_DB" > /tmp/chain.catalog
|
||||
diff -u /tmp/base.catalog /tmp/chain.catalog || fail
|
||||
fi
|
||||
|
||||
echo "schema.sql and the migration chain converge (fresh and upgraded paths)"
|
||||
Reference in New Issue
Block a user