Skip to content

Commit d203d88

Browse files
Fix pg_upgrade compatibility: omit removed catalog columns from views (re-release 0.2.2) (#18)
Re-release of 0.2.2 (originally released 2026-04-27) fixing `pg_upgrade` compatibility with PostgreSQL 12 and 17+. `pg_upgrade` physically copies data files and re-applies schema definitions on the new cluster. Any view referencing a catalog column removed in the new PostgreSQL version will cause the upgrade to fail. The initial 0.2.2 release missed several such columns: - `relhasoids` — removed from `pg_catalog.pg_class` in PG12 - `relhaspkey` — removed from `pg_catalog.pg_class` in PG17 - `attcacheoff` — removed from `pg_catalog.pg_attribute` in PG17 Those columns are removed in this release. Also, the existing `pg-upgrade-test` CI job used `pg_upgradecluster` (dump/restore), which reinstalls extensions at the current version and masks exactly this class of problem. Replaced with binary `pg_upgrade`, which physically copies data files and correctly catches view/column incompatibilities across major versions. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7fea3f0 commit d203d88

7 files changed

Lines changed: 150 additions & 31 deletions

File tree

.github/workflows/ci.yml

Lines changed: 103 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
name: CI
2-
on: [push, pull_request]
2+
on:
3+
push:
4+
branches:
5+
- master
6+
paths-ignore:
7+
- '**.md'
8+
- '**.asc'
9+
pull_request:
10+
paths-ignore:
11+
- '**.md'
12+
- '**.asc'
13+
env:
14+
PGUSER: postgres
315
jobs:
416
test:
517
strategy:
@@ -16,7 +28,7 @@ jobs:
1628
- name: Install rsync
1729
run: apt-get install -y rsync
1830
- name: Test on PostgreSQL ${{ matrix.pg }}
19-
run: make test PGUSER=postgres
31+
run: make test
2032

2133
pg-upgrade-test:
2234
strategy:
@@ -34,37 +46,71 @@ jobs:
3446
new_pg: "13"
3547
- old_pg: "12"
3648
new_pg: "18"
37-
name: 🔄 pg_upgrade ${{ matrix.old_pg }} → ${{ matrix.new_pg }}
49+
name: 🔄 Binary pg_upgrade ${{ matrix.old_pg }} → ${{ matrix.new_pg }}
3850
runs-on: ubuntu-latest
3951
container: pgxn/pgxn-tools
52+
env:
53+
# Both clusters must use the same initdb options so pg_upgrade sees
54+
# consistent settings (checksums, auth) on old and new clusters.
55+
INITDB_OPTS: --data-checksums --auth trust
4056
steps:
4157
- name: Start PostgreSQL ${{ matrix.old_pg }}
4258
run: pg-start ${{ matrix.old_pg }}
59+
- name: Recreate old cluster with data checksums enabled
60+
run: |
61+
pg_ctlcluster ${{ matrix.old_pg }} test stop
62+
pg_dropcluster ${{ matrix.old_pg }} test
63+
# -p 5432: pg_createcluster assigns the next available port, which
64+
# may not be 5432 after pg-start has claimed and released it. Force
65+
# 5432 so subsequent psql/createdb calls connect without -p.
66+
pg_createcluster -p 5432 ${{ matrix.old_pg }} test -- $INITDB_OPTS
67+
pg_ctlcluster ${{ matrix.old_pg }} test start
68+
pg_isready -t 30
4369
- name: Check out the repo
4470
uses: actions/checkout@v4
4571
- name: Install rsync
4672
run: apt-get install -y rsync
4773
- name: Install cat_tools into old cluster
48-
run: make install PGUSER=postgres
49-
- name: Test cat_tools on old cluster
50-
run: make test PGUSER=postgres
51-
- name: Prepare upgrade test database
52-
run: |
53-
createdb -U postgres cat_tools_upgrade_test
54-
psql -U postgres -d cat_tools_upgrade_test -c "CREATE EXTENSION cat_tools"
74+
run: make install
75+
- name: Install cat_tools extension into upgrade test database
76+
run: psql -c "CREATE EXTENSION cat_tools"
5577
- name: Install PostgreSQL ${{ matrix.new_pg }}
5678
run: apt-get install -y postgresql-${{ matrix.new_pg }} postgresql-server-dev-${{ matrix.new_pg }}
5779
- name: Install cat_tools into new cluster
58-
run: make install PGUSER=postgres PG_CONFIG=/usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_config
59-
- name: Upgrade cluster to PostgreSQL ${{ matrix.new_pg }}
60-
run: pg_upgradecluster -v ${{ matrix.new_pg }} ${{ matrix.old_pg }} test
80+
# PG_CONFIG must be specified explicitly: at this point both old and new
81+
# PostgreSQL are installed, and the default pg_config on PATH may not be
82+
# the new version's.
83+
run: make install PG_CONFIG=/usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_config
84+
- name: Stop old cluster, binary pg_upgrade to PostgreSQL ${{ matrix.new_pg }}, start new cluster
85+
run: |
86+
pg_ctlcluster ${{ matrix.old_pg }} test stop
87+
pg_createcluster -p 5432 ${{ matrix.new_pg }} test -- $INITDB_OPTS
88+
# PG17+ writes logs to $new_datadir/pg_upgrade_output.d/; older
89+
# versions write to CWD. Search both on failure.
90+
mkdir -p /tmp/pg_upgrade_logs
91+
chown postgres:postgres /tmp/pg_upgrade_logs
92+
su -c "cd /tmp/pg_upgrade_logs && /usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_upgrade \
93+
-b /usr/lib/postgresql/${{ matrix.old_pg }}/bin \
94+
-B /usr/lib/postgresql/${{ matrix.new_pg }}/bin \
95+
-d /var/lib/postgresql/${{ matrix.old_pg }}/test \
96+
-D /var/lib/postgresql/${{ matrix.new_pg }}/test \
97+
-o '-c config_file=/etc/postgresql/${{ matrix.old_pg }}/test/postgresql.conf' \
98+
-O '-c config_file=/etc/postgresql/${{ matrix.new_pg }}/test/postgresql.conf'" postgres \
99+
|| { find /tmp/pg_upgrade_logs \
100+
/var/lib/postgresql/${{ matrix.new_pg }}/test/pg_upgrade_output.d \
101+
-name '*.log' 2>/dev/null | sort | xargs -r tail -n +1; exit 1; }
102+
pg_ctlcluster ${{ matrix.new_pg }} test start
61103
- name: Verify extension version after upgrade
62104
run: |
63-
VERSION=$(psql -U postgres -d cat_tools_upgrade_test -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'")
105+
VERSION=$(psql -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'")
64106
echo "Extension version: ${VERSION:-<not found>}"
65107
echo "$VERSION" | grep -q "0.2.2"
66-
- name: Run test suite on new cluster
67-
run: make test PGUSER=postgres
108+
- name: Run test suite on upgraded cluster
109+
run: make test
110+
# TODO: also test ALTER EXTENSION cat_tools UPDATE here, once the
111+
# pg_upgrade source versions (e.g. 0.2.0) can install on the new_pg
112+
# version. Currently the pre-0.2.2 install scripts fail on PG11+ so
113+
# we cannot pg_upgrade from a cluster that has them installed.
68114

69115
extension-update-test:
70116
strategy:
@@ -91,21 +137,53 @@ jobs:
91137
- name: Install rsync
92138
run: apt-get install -y rsync
93139
- name: Install cat_tools (all versions)
94-
run: make install PGUSER=postgres
140+
run: make install
95141
- name: Test upgrade from 0.2.0 (direct 0.2.0→0.2.2 path)
96142
run: |
97-
psql -U postgres -c "CREATE EXTENSION cat_tools VERSION '0.2.0'"
98-
psql -U postgres -c "ALTER EXTENSION cat_tools UPDATE"
99-
VERSION=$(psql -U postgres -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'")
143+
psql -c "CREATE EXTENSION cat_tools VERSION '0.2.0'"
144+
psql -c "ALTER EXTENSION cat_tools UPDATE"
145+
VERSION=$(psql -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'")
100146
echo "Version after 0.2.0 upgrade: ${VERSION:-<not found>}"
101147
echo "$VERSION" | grep -q "0.2.2"
102148
- name: Test upgrade from 0.2.1 (0.2.1→0.2.2 path)
103149
run: |
104-
createdb -U postgres cat_tools_from_021
105-
psql -U postgres -d cat_tools_from_021 -c "CREATE EXTENSION cat_tools VERSION '0.2.1'"
106-
psql -U postgres -d cat_tools_from_021 -c "ALTER EXTENSION cat_tools UPDATE"
107-
VERSION=$(psql -U postgres -d cat_tools_from_021 -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'")
150+
createdb cat_tools_from_021
151+
psql -d cat_tools_from_021 -c "CREATE EXTENSION cat_tools VERSION '0.2.1'"
152+
psql -d cat_tools_from_021 -c "ALTER EXTENSION cat_tools UPDATE"
153+
VERSION=$(psql -d cat_tools_from_021 -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'")
108154
echo "Version after 0.2.1 upgrade: ${VERSION:-<not found>}"
109155
echo "$VERSION" | grep -q "0.2.2"
110156
- name: Run test suite on updated extension
111-
run: make test PGUSER=postgres
157+
run: make test
158+
159+
all-checks-passed:
160+
needs: [test, pg-upgrade-test, extension-update-test]
161+
if: always()
162+
runs-on: ubuntu-latest
163+
steps:
164+
- uses: actions/checkout@v4
165+
- name: Verify all jobs are listed in needs
166+
# Ensures this job won't silently ignore a newly-added job that was
167+
# omitted from the needs list above.
168+
run: |
169+
DEFINED=$(python3 -c "
170+
import yaml
171+
with open('.github/workflows/ci.yml') as f:
172+
w = yaml.safe_load(f)
173+
print('\n'.join(sorted(j for j in w['jobs'] if j != 'all-checks-passed')))
174+
")
175+
NEEDED=$(echo '${{ toJson(needs) }}' | python3 -c "
176+
import json, sys
177+
print('\n'.join(sorted(json.load(sys.stdin))))
178+
")
179+
if [ "$DEFINED" != "$NEEDED" ]; then
180+
echo "Some jobs are missing from all-checks-passed needs:"
181+
diff <(echo "$DEFINED") <(echo "$NEEDED")
182+
exit 1
183+
fi
184+
- name: Check all jobs passed or were skipped
185+
run: |
186+
if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
187+
echo "One or more jobs failed or were cancelled"
188+
exit 1
189+
fi

CLAUDE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Claude Code Instructions for cat_tools
22

3+
## GitHub CI
4+
5+
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.
6+
7+
## Bug Fixes
8+
9+
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.
10+
311
## Git
412

513
**Never delete a branch without explicit user approval.** This includes `git push origin --delete`, `git branch -d`, and `git branch -D`. Always ask first.

HISTORY.asc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Compatibility release: fixes broken installs on PostgreSQL 11 and 12+, and
44
provides an upgrade path from 0.2.0 and 0.2.1.
55

6+
67
### PostgreSQL Version Support
78

89
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
3536
exist — this is intentional, to avoid silently breaking user-defined objects.
3637
After dropping your dependent objects, run `ALTER EXTENSION cat_tools UPDATE` again.
3738

39+
### `pg_upgrade` Compatibility (Re-release)
40+
41+
`pg_upgrade` physically copies data files and re-applies schema definitions on
42+
the new cluster. Any view that references a catalog column removed in the new
43+
PostgreSQL version will cause the upgrade to fail. The initial 0.2.2 release
44+
omitted `oid` and `attmissingval` from the catalog views, but missed several
45+
columns that were later removed from PostgreSQL:
46+
47+
* `relhasoids` was removed from `pg_catalog.pg_class` in PG12.
48+
* `relhaspkey` was removed from `pg_catalog.pg_class` in PG17.
49+
* `attcacheoff` was removed from `pg_catalog.pg_attribute` in PG17.
50+
51+
Without this fix, running `pg_upgrade` across any of these version boundaries
52+
with cat_tools installed would fail.
53+
54+
**You must have the re-release of 0.2.2 installed before running `pg_upgrade`
55+
to PostgreSQL 12 or later.**
56+
3857
### Changes
3958

4059
* `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
4867
which also applies all 0.2.1 function additions in a single step.
4968
* `GRANT SELECT ON cat_tools.pg_extension_v TO cat_tools__usage` is now applied on the
5069
upgrade path from 0.2.0 (it was absent in 0.2.0 and only added via the 0.2.1 upgrade).
70+
* (Re-release) `_cat_tools.pg_class_v` now explicitly omits `relhasoids` (removed in PG12)
71+
and `relhaspkey` (removed in PG17) to prevent `pg_upgrade` failures.
72+
* (Re-release) `_cat_tools.pg_attribute_v` now explicitly omits `attcacheoff` (removed in
73+
PG17) to prevent `pg_upgrade` failures.
5174
5275
0.2.1
5376
-----

README.asc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ tables/views/functions. They are meant for use by code, not by people.
44

55
To make use of them, you need to grant `cat_tools__usage` to any roles that need access.
66

7+
[WARNING]
8+
====
9+
Any function or view in this extension that exposes raw PostgreSQL catalog
10+
information does *not* provide a stable API. The PostgreSQL system catalogs
11+
change between major versions — columns are added, removed, and change type.
12+
If your code depends on the specific columns returned by objects such as
13+
`cat_tools.pg_class_v`, `cat_tools.column`, or `cat_tools.pg_attribute_v`,
14+
it may break when you upgrade PostgreSQL.
15+
====
16+
717
== Current Status
818

919
image:https://badge.fury.io/pg/cat_tools.svg["PGXN version",link="https://badge.fury.io/pg/cat_tools"]

sql/cat_tools--0.2.0--0.2.2.sql.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS
498498
LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace )
499499
;
500500
$fmt$
501-
, __cat_tools.omit_column('pg_catalog.pg_class')
501+
, __cat_tools.omit_column('pg_catalog.pg_class', array['oid', 'relhasoids', 'relhaspkey'])
502502
));
503503
REVOKE ALL ON _cat_tools.pg_class_v FROM public;
504504

@@ -531,7 +531,7 @@ $fmt$
531531
* attmissingval is explicitly included above (cast to text[] via SED markers).
532532
* Omit it here so it doesn't appear twice, and omit oid to avoid conflicts on PG12+.
533533
*/
534-
, __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval'])
534+
, __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval', 'attcacheoff'])
535535
, __cat_tools.omit_column('pg_catalog.pg_type')
536536
));
537537
REVOKE ALL ON _cat_tools.pg_attribute_v FROM public;

sql/cat_tools--0.2.1--0.2.2.sql.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS
4242
LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace )
4343
;
4444
$fmt$
45-
, __cat_tools.omit_column('pg_catalog.pg_class')
45+
, __cat_tools.omit_column('pg_catalog.pg_class', array['oid', 'relhasoids', 'relhaspkey'])
4646
));
4747
REVOKE ALL ON _cat_tools.pg_class_v FROM public;
4848

@@ -75,7 +75,7 @@ $fmt$
7575
* attmissingval is explicitly included above (cast to text[] via SED markers).
7676
* Omit it here so it doesn't appear twice, and omit oid to avoid conflicts on PG12+.
7777
*/
78-
, __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval'])
78+
, __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval', 'attcacheoff'])
7979
, __cat_tools.omit_column('pg_catalog.pg_type')
8080
));
8181
REVOKE ALL ON _cat_tools.pg_attribute_v FROM public;

sql/cat_tools--0.2.2.sql.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS
6767
LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace )
6868
;
6969
$fmt$
70-
, __cat_tools.omit_column('pg_catalog.pg_class')
70+
, __cat_tools.omit_column('pg_catalog.pg_class', array['oid', 'relhasoids', 'relhaspkey'])
7171
));
7272
REVOKE ALL ON _cat_tools.pg_class_v FROM public;
7373

@@ -766,7 +766,7 @@ $fmt$
766766
* attmissingval is explicitly included above (cast to text[] via SED markers).
767767
* Omit it here so it doesn't appear twice, and omit oid to avoid conflicts on PG12+.
768768
*/
769-
, __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval'])
769+
, __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval', 'attcacheoff'])
770770
, __cat_tools.omit_column('pg_catalog.pg_type')
771771
));
772772
REVOKE ALL ON _cat_tools.pg_attribute_v FROM public;

0 commit comments

Comments
 (0)