44# An object_reference install can be arrived at more than one way, each of
55# which can break differently, so each is exercised by its own job below:
66#
7- # - `lint`: the cheapest possible check (no database, no container beyond
8- # a plain checkout, seconds to run) -- gates everything else so a broken
9- # style baseline never ties up runner slots on the heavier jobs below.
10- #
117# - `changes`: cheap docs-only gate, PLUS the single source of truth for
128# the supported-PostgreSQL-major list every other job's matrix derives
139# from (see its own "Derive ..." step).
3430# crossing this axis against every major would just multiply job count
3531# for no added coverage.
3632#
37- # - No binary pg_upgrade job (the cat_tools reference this effort is
38- # modeled on has `pg-upgrade-test` / `pg-upgrade-stepwise`) exists yet.
39- # Deliberate, not an oversight: that job exists to catch a view/function
40- # that breaks across a PostgreSQL major specifically because it touches
41- # catalog internals (SELECT * over a system catalog whose columns get
42- # added/exposed/removed between majors). object_reference has no such
43- # construct in either its current or 0.1.0 install script (checked
44- # directly -- no view or function selects * from a system catalog; every
45- # object_reference table/view is an ordinary user object), so the risk
46- # that job protects against is correspondingly low here. Left as
47- # explicitly-noted future work rather than built preemptively; revisit
48- # if/when object_reference grows a catalog-touching view or function.
33+ # - `pg-upgrade-stepwise`: BINARY pg_upgrade coverage. ONE cluster starts
34+ # at the oldest supported PostgreSQL major with the one real historical
35+ # PGXN release (0.1.0) installed, updates straight to the current
36+ # version, then climbs every later supported major in sequence
37+ # (e.g. 12→13→...→18) via a REAL binary pg_upgrade per step, running the
38+ # full suite (existing mode) and re-proving the dependency guard after
39+ # EVERY step. This catches a regression specific to one particular
40+ # major-to-major boundary that a single before/after snapshot would
41+ # never exercise -- a view/function that breaks because it touches
42+ # catalog internals (columns added/exposed/removed between majors) is
43+ # the concrete risk. No such construct has been found in object_reference
44+ # today (checked directly -- no view or function selects * from a system
45+ # catalog; every object_reference table/view is an ordinary user
46+ # object), but that is a fact about the code today, not a permanent
47+ # property of it, and this job is cheap (the same install+pg_upgrade
48+ # shape as any other pg_upgrade leg, just run once per step) -- so
49+ # "unlikely to catch anything today" is not treated as a reason to skip
50+ # it; only genuine cost would be. There is no separate "single big jump"
51+ # pg_upgrade job (cat_tools's `pg-upgrade-test`) -- with only one
52+ # historical extension version and no identified PostgreSQL-version
53+ # floor for it, a big-jump leg would add a second job with no coverage
54+ # the stepwise climb doesn't already provide.
4955#
5056# - `all-checks-passed`: single stable required-status-check name; see its
5157# own comment below.
@@ -66,15 +72,17 @@ jobs:
6672 # all-checks-passed check would then never report and get stuck Pending in
6773 # branch protection.
6874 #
69- # Also derives the supported-PostgreSQL-major list the test job's matrix
70- # consumes, from a single pair of constants below, so adding or dropping a
71- # major is a one-line edit here instead of touching the matrix directly.
75+ # Also derives the supported-PostgreSQL-major lists the test job's matrix
76+ # and the pg-upgrade-stepwise job's climb consume, from a single pair of
77+ # constants below, so adding or dropping a major is a one-line edit here
78+ # instead of touching either job directly.
7279 changes :
7380 name : 🔍 Detect changes & derive PG matrix
7481 runs-on : ubuntu-latest
7582 outputs :
7683 docs_only : ${{ steps.diff.outputs.docs_only }}
7784 supported_pg : ${{ steps.pg.outputs.supported_pg }}
85+ climb_pg : ${{ steps.pg.outputs.climb_pg }}
7886 steps :
7987 - name : Check out the repo
8088 uses : actions/checkout@v4
@@ -123,30 +131,46 @@ jobs:
123131 echo "changed files:"
124132 echo "$CHANGED"
125133 echo "docs_only=$DOCS_ONLY" >> "$GITHUB_OUTPUT"
126- - name : Derive the supported-PostgreSQL-major list
134+ - name : Derive the supported-PostgreSQL-major lists
127135 id : pg
128136 run : |
129137 # SINGLE SOURCE OF TRUTH for the supported PostgreSQL majors. To
130- # add or drop a major, edit only the two constants below; the test
131- # job's matrix derives its version list from them. Do NOT hardcode
132- # a supported major directly in a job matrix.
138+ # add or drop a major, edit only the two constants below; every
139+ # job's matrix/climb derives its version list from them. Do NOT
140+ # hardcode a supported major directly in a job matrix or loop .
133141 #
134142 # NEWEST -- highest PostgreSQL major tested.
135143 # CURRENT_FLOOR -- oldest major supported. object_reference
136144 # requires cat_tools at both build and runtime,
137145 # and cat_tools's own current release declares
138146 # PostgreSQL 12 as its build floor, so
139147 # object_reference can't usefully claim support
140- # for anything older either.
148+ # for anything older either. 0.1.0 (the one
149+ # real historical PGXN release) has no
150+ # PostgreSQL-version floor of its own (no
151+ # SELECT * over a system catalog, no ALTER
152+ # TYPE ... ADD VALUE in its update script), so
153+ # unlike cat_tools's reference implementation
154+ # there is no separate, older LEGACY_FLOOR --
155+ # 0.1.0 installs cleanly across the whole
156+ # CURRENT_FLOOR..NEWEST range, and the stepwise
157+ # climb (pg-upgrade-stepwise) starts right at
158+ # CURRENT_FLOOR too.
141159 NEWEST=18
142160 CURRENT_FLOOR=12
143161
144162 supported=$(seq "$NEWEST" -1 "$CURRENT_FLOOR")
163+ # Ascending (ties floor-to-newest, opposite order from $supported
164+ # above): pg-upgrade-stepwise reads the first element as its
165+ # starting major and binary-pg_upgrades through the rest in turn.
166+ climb=$(seq "$CURRENT_FLOOR" "$NEWEST")
145167
146168 # Emit a JSON array for the test job's matrix to consume via
147169 # fromJSON.
148170 json=$(printf '%s\n' $supported | paste -sd, - | sed 's/^/[/; s/$/]/')
149171 echo "supported_pg=$json" >> "$GITHUB_OUTPUT"
172+ # Space-separated for direct iteration in the stepwise bash loop.
173+ echo "climb_pg=$(echo $climb)" >> "$GITHUB_OUTPUT"
150174
151175 test :
152176 needs : [changes]
@@ -213,14 +237,126 @@ jobs:
213237 - name : Update 0.1.0 -> stable, structurally compare, run the suite (existing mode)
214238 run : bin/test_existing update-scenario object_reference_update 0.1.0
215239
240+ # Proves object_reference survives EVERY individual major-to-major binary
241+ # pg_upgrade transition, not just the single newest-major snapshot the
242+ # `test`/`extension-update-test` jobs above cover: ONE cluster that starts
243+ # on the floor PostgreSQL major and climbs through every later supported
244+ # major in sequence via a REAL binary pg_upgrade. See the "Test strategy"
245+ # comment at the top of this file for why this is included even though no
246+ # catalog-touching view/function has been found in object_reference today.
247+ #
248+ # Installs 0.1.0 on the floor major, updates it straight to the current
249+ # version (0.1.0's update script has no PostgreSQL-version floor of its
250+ # own -- no ALTER TYPE ... ADD VALUE -- so, unlike cat_tools's reference
251+ # implementation, there is no reason to hold the extension at an old
252+ # version through any step of the climb), then binary-pg_upgrades one
253+ # major at a time through the rest of the range, running the full suite
254+ # (existing mode) and re-proving the dependency guard after EVERY step.
255+ pg-upgrade-stepwise :
256+ # Gated behind test, not just changes -- see extension-update-test's
257+ # needs comment above: every leg here would fail anyway against an
258+ # already-broken baseline. success() must be written explicitly --
259+ # GitHub only assumes success() as a job's default when it has no if: at
260+ # all.
261+ needs : [changes, test]
262+ if : success() && needs.changes.outputs.docs_only != 'true'
263+ name : 🪜 Stepwise pg_upgrade (0.1.0 → stable)
264+ runs-on : ubuntu-latest
265+ container : pgxn/pgxn-tools
266+ env :
267+ # Every pg_upgrade step pairs two clusters that must share initdb
268+ # options (checksums, auth) or pg_upgrade refuses to run.
269+ INITDB_OPTS : --data-checksums --auth trust
270+ DB : object_reference_stepwise
271+ # Ascending list of every supported PostgreSQL major, from the single
272+ # source in the changes job -- so a new major joins the climb with no
273+ # edit here. The first element is the climb's starting (floor) major.
274+ CLIMB_PG : ${{ needs.changes.outputs.climb_pg }}
275+ steps :
276+ - name : Read the climb's starting (floor) PostgreSQL major
277+ id : floor
278+ run : echo "pg=$(set -- $CLIMB_PG; echo "$1")" >> "$GITHUB_OUTPUT"
279+ - name : Start PostgreSQL ${{ steps.floor.outputs.pg }}
280+ run : pg-start ${{ steps.floor.outputs.pg }}
281+ - name : Recreate the floor cluster with data checksums enabled
282+ # pg-start's default "test" cluster doesn't enable data checksums,
283+ # but binary pg_upgrade requires the old and new clusters to have
284+ # MATCHING checksum/auth settings.
285+ run : |
286+ pg_ctlcluster ${{ steps.floor.outputs.pg }} test stop
287+ pg_dropcluster ${{ steps.floor.outputs.pg }} test
288+ # -p 5432: pg_createcluster assigns the next available port, which
289+ # may not be 5432 after pg-start has claimed and released it.
290+ # Force 5432 so later psql/createdb calls connect without -p.
291+ pg_createcluster -p 5432 ${{ steps.floor.outputs.pg }} test -- $INITDB_OPTS
292+ pg_ctlcluster ${{ steps.floor.outputs.pg }} test start
293+ pg_isready -t 30
294+ - name : Check out the repo
295+ uses : actions/checkout@v4
296+ - name : Install object_reference + its 0.1.0-only test dependency (count_nulls) into the floor cluster
297+ # TEST_LOAD_SOURCE=update activates the Makefile's conditional
298+ # `install: count_nulls` prerequisite -- 0.1.0's install script
299+ # needs count_nulls even though current object_reference.control no
300+ # longer declares it. count_nulls stays installed as its own
301+ # extension in the database even after updating object_reference
302+ # past 0.1.0 (nothing drops it), so every later `make install` in
303+ # this job's climb loop keeps passing TEST_LOAD_SOURCE=update too --
304+ # pg_upgrade needs count_nulls's files present in each new cluster
305+ # for as long as it remains a real extension in the test database.
306+ run : make install TEST_LOAD_SOURCE=update
307+ - name : Prepare the floor cluster (install 0.1.0, plant guard, update to current, run suite)
308+ run : |
309+ bin/test_existing prepare-old "$DB" 0.1.0
310+ bin/test_existing update "$DB"
311+ bin/test_existing run-suite "$DB"
312+ - name : Climb every later major via binary pg_upgrade
313+ # One sequential loop; each iteration binary-pg_upgrades the cluster
314+ # from $old to $new (a single major step), re-installs
315+ # object_reference + its dependencies into the new cluster first
316+ # (pg_upgrade needs their files present in the NEW cluster's
317+ # sharedir -- default pg_config on PATH may not be $new's once
318+ # several majors are installed, hence PG_CONFIG explicit), then
319+ # re-runs the full suite in existing mode -- re-proving the
320+ # dependency guard survived, and that the same suite/expected-output
321+ # still passes against the objects that just crossed a pg_upgrade.
322+ run : |
323+ set -- $CLIMB_PG
324+ old=$1
325+ shift
326+ for new in "$@"; do
327+ echo "=== binary pg_upgrade PostgreSQL $old -> $new ==="
328+ apt-get install -y postgresql-$new postgresql-server-dev-$new
329+ make install PG_CONFIG=/usr/lib/postgresql/$new/bin/pg_config TEST_LOAD_SOURCE=update
330+ pg_ctlcluster $old test stop
331+ pg_createcluster -p 5432 $new test -- $INITDB_OPTS
332+ # PG17+ writes pg_upgrade logs under the new datadir; older
333+ # versions write to CWD. Dump both on failure.
334+ mkdir -p /tmp/pg_upgrade_logs
335+ chown postgres:postgres /tmp/pg_upgrade_logs
336+ su -c "cd /tmp/pg_upgrade_logs && /usr/lib/postgresql/$new/bin/pg_upgrade \
337+ -b /usr/lib/postgresql/$old/bin \
338+ -B /usr/lib/postgresql/$new/bin \
339+ -d /var/lib/postgresql/$old/test \
340+ -D /var/lib/postgresql/$new/test \
341+ -o '-c config_file=/etc/postgresql/$old/test/postgresql.conf' \
342+ -O '-c config_file=/etc/postgresql/$new/test/postgresql.conf'" postgres \
343+ || { find /tmp/pg_upgrade_logs \
344+ /var/lib/postgresql/$new/test/pg_upgrade_output.d \
345+ -name '*.log' 2>/dev/null | sort | xargs -r tail -n +1; exit 1; }
346+ pg_ctlcluster $new test start
347+ pg_isready -t 30
348+ bin/test_existing run-suite "$DB"
349+ old=$new
350+ done
351+
216352 # A single stable check name for use as a required status check in branch
217353 # protection rules. Matrix jobs produce check names like "🐘 PostgreSQL 14"
218354 # which would all need to be listed individually and updated whenever the
219355 # matrix changes. This job passes if all others passed or were skipped
220356 # (e.g. test, on a docs-only push), and fails if any failed or were
221357 # cancelled.
222358 all-checks-passed :
223- needs : [changes, test, extension-update-test]
359+ needs : [changes, test, extension-update-test, pg-upgrade-stepwise ]
224360 if : always()
225361 runs-on : ubuntu-latest
226362 steps :
0 commit comments