diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aef0efe..ba85fd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,17 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - master + paths-ignore: + - '**.md' + - '**.asc' + pull_request: + paths-ignore: + - '**.md' + - '**.asc' +env: + PGUSER: postgres jobs: test: strategy: @@ -16,7 +28,7 @@ jobs: - name: Install rsync run: apt-get install -y rsync - name: Test on PostgreSQL ${{ matrix.pg }} - run: make test PGUSER=postgres + run: make test pg-upgrade-test: strategy: @@ -34,37 +46,71 @@ jobs: new_pg: "13" - old_pg: "12" new_pg: "18" - name: 🔄 pg_upgrade ${{ matrix.old_pg }} → ${{ matrix.new_pg }} + name: 🔄 Binary pg_upgrade ${{ matrix.old_pg }} → ${{ matrix.new_pg }} runs-on: ubuntu-latest container: pgxn/pgxn-tools + env: + # Both clusters must use the same initdb options so pg_upgrade sees + # consistent settings (checksums, auth) on old and new clusters. + INITDB_OPTS: --data-checksums --auth trust steps: - name: Start PostgreSQL ${{ matrix.old_pg }} run: pg-start ${{ matrix.old_pg }} + - name: Recreate old cluster with data checksums enabled + run: | + pg_ctlcluster ${{ matrix.old_pg }} test stop + pg_dropcluster ${{ matrix.old_pg }} test + # -p 5432: pg_createcluster assigns the next available port, which + # may not be 5432 after pg-start has claimed and released it. Force + # 5432 so subsequent psql/createdb calls connect without -p. + pg_createcluster -p 5432 ${{ matrix.old_pg }} test -- $INITDB_OPTS + pg_ctlcluster ${{ matrix.old_pg }} test start + pg_isready -t 30 - name: Check out the repo uses: actions/checkout@v4 - name: Install rsync run: apt-get install -y rsync - name: Install cat_tools into old cluster - run: make install PGUSER=postgres - - name: Test cat_tools on old cluster - run: make test PGUSER=postgres - - name: Prepare upgrade test database - run: | - createdb -U postgres cat_tools_upgrade_test - psql -U postgres -d cat_tools_upgrade_test -c "CREATE EXTENSION cat_tools" + run: make install + - name: Install cat_tools extension into upgrade test database + run: psql -c "CREATE EXTENSION cat_tools" - name: Install PostgreSQL ${{ matrix.new_pg }} run: apt-get install -y postgresql-${{ matrix.new_pg }} postgresql-server-dev-${{ matrix.new_pg }} - name: Install cat_tools into new cluster - run: make install PGUSER=postgres PG_CONFIG=/usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_config - - name: Upgrade cluster to PostgreSQL ${{ matrix.new_pg }} - run: pg_upgradecluster -v ${{ matrix.new_pg }} ${{ matrix.old_pg }} test + # PG_CONFIG must be specified explicitly: at this point both old and new + # PostgreSQL are installed, and the default pg_config on PATH may not be + # the new version's. + run: make install PG_CONFIG=/usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_config + - name: Stop old cluster, binary pg_upgrade to PostgreSQL ${{ matrix.new_pg }}, start new cluster + run: | + pg_ctlcluster ${{ matrix.old_pg }} test stop + pg_createcluster -p 5432 ${{ matrix.new_pg }} test -- $INITDB_OPTS + # PG17+ writes logs to $new_datadir/pg_upgrade_output.d/; older + # versions write to CWD. Search both on failure. + mkdir -p /tmp/pg_upgrade_logs + chown postgres:postgres /tmp/pg_upgrade_logs + su -c "cd /tmp/pg_upgrade_logs && /usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_upgrade \ + -b /usr/lib/postgresql/${{ matrix.old_pg }}/bin \ + -B /usr/lib/postgresql/${{ matrix.new_pg }}/bin \ + -d /var/lib/postgresql/${{ matrix.old_pg }}/test \ + -D /var/lib/postgresql/${{ matrix.new_pg }}/test \ + -o '-c config_file=/etc/postgresql/${{ matrix.old_pg }}/test/postgresql.conf' \ + -O '-c config_file=/etc/postgresql/${{ matrix.new_pg }}/test/postgresql.conf'" postgres \ + || { find /tmp/pg_upgrade_logs \ + /var/lib/postgresql/${{ matrix.new_pg }}/test/pg_upgrade_output.d \ + -name '*.log' 2>/dev/null | sort | xargs -r tail -n +1; exit 1; } + pg_ctlcluster ${{ matrix.new_pg }} test start - name: Verify extension version after upgrade run: | - VERSION=$(psql -U postgres -d cat_tools_upgrade_test -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") + VERSION=$(psql -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") echo "Extension version: ${VERSION:-}" echo "$VERSION" | grep -q "0.2.2" - - name: Run test suite on new cluster - run: make test PGUSER=postgres + - name: Run test suite on upgraded cluster + run: make test + # TODO: also test ALTER EXTENSION cat_tools UPDATE here, once the + # pg_upgrade source versions (e.g. 0.2.0) can install on the new_pg + # version. Currently the pre-0.2.2 install scripts fail on PG11+ so + # we cannot pg_upgrade from a cluster that has them installed. extension-update-test: strategy: @@ -91,21 +137,53 @@ jobs: - name: Install rsync run: apt-get install -y rsync - name: Install cat_tools (all versions) - run: make install PGUSER=postgres + run: make install - name: Test upgrade from 0.2.0 (direct 0.2.0→0.2.2 path) run: | - psql -U postgres -c "CREATE EXTENSION cat_tools VERSION '0.2.0'" - psql -U postgres -c "ALTER EXTENSION cat_tools UPDATE" - VERSION=$(psql -U postgres -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") + psql -c "CREATE EXTENSION cat_tools VERSION '0.2.0'" + psql -c "ALTER EXTENSION cat_tools UPDATE" + VERSION=$(psql -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") echo "Version after 0.2.0 upgrade: ${VERSION:-}" echo "$VERSION" | grep -q "0.2.2" - name: Test upgrade from 0.2.1 (0.2.1→0.2.2 path) run: | - createdb -U postgres cat_tools_from_021 - psql -U postgres -d cat_tools_from_021 -c "CREATE EXTENSION cat_tools VERSION '0.2.1'" - psql -U postgres -d cat_tools_from_021 -c "ALTER EXTENSION cat_tools UPDATE" - VERSION=$(psql -U postgres -d cat_tools_from_021 -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") + createdb cat_tools_from_021 + psql -d cat_tools_from_021 -c "CREATE EXTENSION cat_tools VERSION '0.2.1'" + psql -d cat_tools_from_021 -c "ALTER EXTENSION cat_tools UPDATE" + VERSION=$(psql -d cat_tools_from_021 -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") echo "Version after 0.2.1 upgrade: ${VERSION:-}" echo "$VERSION" | grep -q "0.2.2" - name: Run test suite on updated extension - run: make test PGUSER=postgres + run: make test + + all-checks-passed: + needs: [test, pg-upgrade-test, extension-update-test] + if: always() + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Verify all jobs are listed in needs + # Ensures this job won't silently ignore a newly-added job that was + # omitted from the needs list above. + run: | + DEFINED=$(python3 -c " + import yaml + with open('.github/workflows/ci.yml') as f: + w = yaml.safe_load(f) + print('\n'.join(sorted(j for j in w['jobs'] if j != 'all-checks-passed'))) + ") + NEEDED=$(echo '${{ toJson(needs) }}' | python3 -c " + import json, sys + print('\n'.join(sorted(json.load(sys.stdin)))) + ") + if [ "$DEFINED" != "$NEEDED" ]; then + echo "Some jobs are missing from all-checks-passed needs:" + diff <(echo "$DEFINED") <(echo "$NEEDED") + exit 1 + fi + - name: Check all jobs passed or were skipped + run: | + if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then + echo "One or more jobs failed or were cancelled" + exit 1 + fi diff --git a/CLAUDE.md b/CLAUDE.md index f8f07bd..44f3caa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,13 @@ # Claude Code Instructions for cat_tools +## GitHub CI + +After pushing to a branch with an open PR, monitor CI using `gh pr checks --watch` in a background subagent until all jobs pass or a failure is confirmed. Investigate and fix failures immediately rather than leaving them for the user to notice. + +## Bug Fixes + +When fixing a bug, add a comment at the fix site explaining what the bug was and why the fix works. The goal is to prevent re-introducing the bug later. + ## Git **Never delete a branch without explicit user approval.** This includes `git push origin --delete`, `git branch -d`, and `git branch -D`. Always ask first. diff --git a/HISTORY.asc b/HISTORY.asc index 8740ed8..b25e350 100644 --- a/HISTORY.asc +++ b/HISTORY.asc @@ -3,6 +3,7 @@ Compatibility release: fixes broken installs on PostgreSQL 11 and 12+, and provides an upgrade path from 0.2.0 and 0.2.1. + ### PostgreSQL Version Support cat_tools 0.2.1 (and earlier) only installs correctly on **PostgreSQL 9.2 – 10**. @@ -35,6 +36,24 @@ afterward.** The upgrade will fail with an error if any such dependent objects exist — this is intentional, to avoid silently breaking user-defined objects. After dropping your dependent objects, run `ALTER EXTENSION cat_tools UPDATE` again. +### `pg_upgrade` Compatibility (Re-release) + +`pg_upgrade` physically copies data files and re-applies schema definitions on +the new cluster. Any view that references a catalog column removed in the new +PostgreSQL version will cause the upgrade to fail. The initial 0.2.2 release +omitted `oid` and `attmissingval` from the catalog views, but missed several +columns that were later removed from PostgreSQL: + +* `relhasoids` was removed from `pg_catalog.pg_class` in PG12. +* `relhaspkey` was removed from `pg_catalog.pg_class` in PG17. +* `attcacheoff` was removed from `pg_catalog.pg_attribute` in PG17. + +Without this fix, running `pg_upgrade` across any of these version boundaries +with cat_tools installed would fail. + +**You must have the re-release of 0.2.2 installed before running `pg_upgrade` +to PostgreSQL 12 or later.** + ### Changes * `sql/cat_tools--0.1.4--0.1.5.sql` was empty; added `-- empty upgrade` placeholder so @@ -48,6 +67,10 @@ After dropping your dependent objects, run `ALTER EXTENSION cat_tools UPDATE` ag which also applies all 0.2.1 function additions in a single step. * `GRANT SELECT ON cat_tools.pg_extension_v TO cat_tools__usage` is now applied on the upgrade path from 0.2.0 (it was absent in 0.2.0 and only added via the 0.2.1 upgrade). +* (Re-release) `_cat_tools.pg_class_v` now explicitly omits `relhasoids` (removed in PG12) + and `relhaspkey` (removed in PG17) to prevent `pg_upgrade` failures. +* (Re-release) `_cat_tools.pg_attribute_v` now explicitly omits `attcacheoff` (removed in + PG17) to prevent `pg_upgrade` failures. 0.2.1 ----- diff --git a/README.asc b/README.asc index 9fa8761..cbd36d8 100644 --- a/README.asc +++ b/README.asc @@ -4,6 +4,16 @@ tables/views/functions. They are meant for use by code, not by people. To make use of them, you need to grant `cat_tools__usage` to any roles that need access. +[WARNING] +==== +Any function or view in this extension that exposes raw PostgreSQL catalog +information does *not* provide a stable API. The PostgreSQL system catalogs +change between major versions — columns are added, removed, and change type. +If your code depends on the specific columns returned by objects such as +`cat_tools.pg_class_v`, `cat_tools.column`, or `cat_tools.pg_attribute_v`, +it may break when you upgrade PostgreSQL. +==== + == Current Status image:https://badge.fury.io/pg/cat_tools.svg["PGXN version",link="https://badge.fury.io/pg/cat_tools"] diff --git a/sql/cat_tools--0.2.0--0.2.2.sql.in b/sql/cat_tools--0.2.0--0.2.2.sql.in index ecc068d..c8b632c 100644 --- a/sql/cat_tools--0.2.0--0.2.2.sql.in +++ b/sql/cat_tools--0.2.0--0.2.2.sql.in @@ -498,7 +498,7 @@ CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace ) ; $fmt$ - , __cat_tools.omit_column('pg_catalog.pg_class') + , __cat_tools.omit_column('pg_catalog.pg_class', array['oid', 'relhasoids', 'relhaspkey']) )); REVOKE ALL ON _cat_tools.pg_class_v FROM public; @@ -531,7 +531,7 @@ $fmt$ * attmissingval is explicitly included above (cast to text[] via SED markers). * Omit it here so it doesn't appear twice, and omit oid to avoid conflicts on PG12+. */ - , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval']) + , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval', 'attcacheoff']) , __cat_tools.omit_column('pg_catalog.pg_type') )); REVOKE ALL ON _cat_tools.pg_attribute_v FROM public; diff --git a/sql/cat_tools--0.2.1--0.2.2.sql.in b/sql/cat_tools--0.2.1--0.2.2.sql.in index fef880c..a1cdbe9 100644 --- a/sql/cat_tools--0.2.1--0.2.2.sql.in +++ b/sql/cat_tools--0.2.1--0.2.2.sql.in @@ -42,7 +42,7 @@ CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace ) ; $fmt$ - , __cat_tools.omit_column('pg_catalog.pg_class') + , __cat_tools.omit_column('pg_catalog.pg_class', array['oid', 'relhasoids', 'relhaspkey']) )); REVOKE ALL ON _cat_tools.pg_class_v FROM public; @@ -75,7 +75,7 @@ $fmt$ * attmissingval is explicitly included above (cast to text[] via SED markers). * Omit it here so it doesn't appear twice, and omit oid to avoid conflicts on PG12+. */ - , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval']) + , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval', 'attcacheoff']) , __cat_tools.omit_column('pg_catalog.pg_type') )); REVOKE ALL ON _cat_tools.pg_attribute_v FROM public; diff --git a/sql/cat_tools--0.2.2.sql.in b/sql/cat_tools--0.2.2.sql.in index bf119eb..6cdbdca 100644 --- a/sql/cat_tools--0.2.2.sql.in +++ b/sql/cat_tools--0.2.2.sql.in @@ -67,7 +67,7 @@ CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace ) ; $fmt$ - , __cat_tools.omit_column('pg_catalog.pg_class') + , __cat_tools.omit_column('pg_catalog.pg_class', array['oid', 'relhasoids', 'relhaspkey']) )); REVOKE ALL ON _cat_tools.pg_class_v FROM public; @@ -766,7 +766,7 @@ $fmt$ * attmissingval is explicitly included above (cast to text[] via SED markers). * Omit it here so it doesn't appear twice, and omit oid to avoid conflicts on PG12+. */ - , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval']) + , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval', 'attcacheoff']) , __cat_tools.omit_column('pg_catalog.pg_type') )); REVOKE ALL ON _cat_tools.pg_attribute_v FROM public;