Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 210 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@
# own job would only duplicate this job's own
# per-PG-version container/checkout setup for
# no added confidence.
# pg-upgrade-test -- BINARY pg_upgrade: install 0.9.6 on the OLD
# PostgreSQL floor (the same META.json-derived
# floor the `changes` job's supported_pg uses -
# see its floor_pg output), then binary-upgrade
# the cluster to the newest supported major
# (changes job's newest_pg). A single leg (not
# the full PG matrix - by far the most
# expensive job here, installing two full
# PostgreSQL majors and running the real
# pg_upgrade binary), which via twin databases
# migrated by the SAME binary pg_upgrade call
# compares BOTH update-vs-pg_upgrade orderings
# a real user could hit - see the job's own
# comment for why one floor is enough to cover
# both.
# pg-tle-test -- pg_tle DEPLOYMENT: fresh install registered
# through AWS pg_tle's database-backed catalog
# instead of a filesystem .control file.
Expand Down Expand Up @@ -68,23 +83,27 @@ jobs:
# would never report on doc-only pushes and get stuck Pending in branch
# protection.
#
# Also derives the supported-PostgreSQL-major list the test job consumes,
# from a single NEWEST constant plus META.json's own declared minimum
# (not a second hand-maintained floor constant): every job that cares
# which majors are supported reads the SAME list, so they can't silently
# drift onto different sets, and adding a new major is a one-line change
# here instead of an edit in several jobs. `newest_pg` is the same
# NEWEST constant emitted again as a bare scalar (not wrapped in the
# JSON-array `supported_pg`), consumed only by the `test` job's draft-PR
# matrix reduction (see the top-of-file comment and that job's own
# comment) - so NEWEST still only needs to change in one place.
# Also derives the supported-PostgreSQL-major list every other job reads,
# from a single NEWEST constant plus META.json's own declared minimum (not
# a second hand-maintained floor constant) - every job that cares which
# majors are supported reads the SAME values, so they can't silently drift
# onto different sets, and adding a new major is a one-line change here
# instead of an edit in several jobs.
# - supported_pg: the full descending list (NEWEST down to the
# META.json-derived floor), as a JSON array, for jobs that need every
# supported major.
# - newest_pg: just the single newest major, as a bare scalar, for
# anything that needs one value instead of the full array.
# - floor_pg: just the single oldest (META.json-derived) major, as a
# bare scalar, for the same reason.
changes:
name: 🔍 Detect docs-only changes & derive PG matrix
runs-on: ubuntu-latest
outputs:
docs_only: ${{ steps.diff.outputs.docs_only }}
supported_pg: ${{ steps.pg.outputs.supported_pg }}
newest_pg: ${{ steps.pg.outputs.newest_pg }}
floor_pg: ${{ steps.pg.outputs.floor_pg }}
steps:
- name: Check out the repo
uses: actions/checkout@v4
Expand Down Expand Up @@ -154,9 +173,9 @@ jobs:
id: pg
run: |
# A dozen-odd lines to replace what looks like a handful of version
# references, but it buys CONSISTENCY: the fresh-install/update
# `test` matrix derives its PostgreSQL set from this ONE source, so
# it cannot silently drift onto a different list. Adding a new
# references, but it buys CONSISTENCY: every job that needs the
# supported-major list derives it from this ONE source, so none of
# them can silently drift onto a different list. Adding a new
# major is a one-line NEWEST bump here, not an edit in N places.
#
# The floor itself is NOT a second hardcoded constant here: it's
Expand Down Expand Up @@ -202,12 +221,18 @@ jobs:

echo "supported_pg=$(json)" >> "$GITHUB_OUTPUT"

# Also emitted as a bare scalar (not a JSON array) so the `test`
# job's draft-PR matrix reduction (see its own comment) can build a
# single-element list from it via fromJSON(format(...)) without a
# second hardcoded "18" anywhere in this file.
# Also emitted as a bare scalar (not a JSON array), for anything
# that needs just the single newest major instead of the full
# array - so NEWEST still only needs to change in one place.
echo "newest_pg=$NEWEST" >> "$GITHUB_OUTPUT"

# Same idea for the OLDEST supported major: emit the exact $FLOOR
# value supported_pg above already derived from META.json, as a
# bare scalar, for anything that needs just the single oldest
# major instead of the full array - rather than hand-maintaining a
# THIRD copy of this number.
echo "floor_pg=$FLOOR" >> "$GITHUB_OUTPUT"

lint:
name: 🧹 SQL lint
runs-on: ubuntu-latest
Expand Down Expand Up @@ -266,6 +291,173 @@ jobs:
- name: Update 0.9.6 -> current and run the suite
run: make verify-results TEST_LOAD_SOURCE=update

# Proves count_nulls survives a BINARY pg_upgrade (in-place catalog
# migration to a newer PostgreSQL major). Installs 0.9.6 on the oldest
# supported PostgreSQL floor, plants a dependency guard, binary-
# pg_upgrades to the newest supported major, then runs the suite against
# the REAL migrated objects in existing mode.
#
# A single old_pg/new_pg leg (old_pg from the changes job's floor_pg
# output, new_pg from its newest_pg output - see that job's comment; NOT
# a second/third hardcoded copy of either number), which via TWIN
# databases in the SAME cluster, migrated by a SINGLE pg_upgrade call
# (the same trick this file uses to cross TEST_SCHEMA without doubling
# matrix legs elsewhere), compares BOTH update-vs-pg_upgrade orderings a
# real user could hit:
# upgrade_oldest_first -- stays at 0.9.6 through the
# binary pg_upgrade, updated to current AFTER it (on the NEW
# cluster). Proves pg_upgrade itself correctly preserves objects
# that are STILL at the oldest supported version at the moment of
# the upgrade - a real-world scenario (a user upgrading their
# PostgreSQL major while still running an old extension version).
# upgrade_current_first -- updated to current BEFORE the
# binary pg_upgrade (on the OLD cluster). Proves pg_upgrade
# correctly migrates the objects count_nulls' CURRENT code actually
# creates - migrating 0.9.6's objects and updating afterward would
# instead test whether pg_upgrade can migrate a legacy structure
# frozen in the past, which isn't actionable on its own (that
# version already shipped; nothing to fix if it turned out
# fragile) - which is exactly why this leg exists alongside
# oldest_first rather than instead of it.
# The ordering comparison is a property of pg_upgrade itself, not of
# which PG floor it runs from, so a single floor is enough to cover
# both orderings - there was never a technical reason for a second
# old_pg floor here (the previous old_pg=12 leg predated this ordering
# comparison and had no purpose once it existed).
#
# Both databases still end up at the current version by the time
# run-suite runs, so nothing downstream (the pgTAP suite, the
# dependency guard check) needs to differ - only which side(s) of the
# pg_upgrade call each database's update happens on. No bridge-update
# step first: count_nulls has always been pure SQL functions with no
# SELECT-*-over-catalog views, so it has no known pg_upgrade-unsafe old
# version to bridge past.
#
# Deliberately not doing a stepwise every-major-in-sequence climb (one
# cluster walking 10->11->12->...->newest, vs. the single big jump
# here): that would catch a regression specific to one particular
# major-to-major boundary, which would matter if count_nulls had views/
# functions touching catalog internals, but it doesn't - pure SQL
# functions over anyarray/json/jsonb, nothing version-sensitive to
# break at a specific boundary. Revisit if count_nulls ever grows
# something catalog-touching.
#
# Not yet crossed with TEST_SCHEMA (a later phase adds that, once it can
# do so for both this job and the test job's update leg together).
pg-upgrade-test:
needs: [changes]
# Skipped outright (not just matrix-reduced like `test`) on a draft PR:
# this is a heavy job, and a draft author doesn't need a real binary
# pg_upgrade re-proven on every push while still iterating.
if: needs.changes.outputs.docs_only != 'true' && github.event.pull_request.draft != true
strategy:
matrix:
# Single-element lists built from the changes job's floor_pg/
# newest_pg scalar outputs (see that job's comment), NOT a second/
# third hardcoded copy of either number - keeping this a `matrix:`
# (rather than plain `env:`) preserves the ${{ matrix.old_pg }}/
# ${{ matrix.new_pg }} interpolations used throughout the steps
# below unchanged.
old_pg: ["${{ needs.changes.outputs.floor_pg }}"]
new_pg: ["${{ needs.changes.outputs.newest_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 or pg_upgrade
# refuses to run.
INITDB_OPTS: --data-checksums --auth trust
steps:
- name: Install PostgreSQL ${{ matrix.old_pg }} (cluster created next, with the right initdb options from the start)
run: NO_CLUSTER=1 pg-start ${{ matrix.old_pg }}
- name: Create old cluster with data checksums enabled
run: |
# -p 5432: pg_createcluster would otherwise assign the next
# available port; 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 count_nulls into old cluster
run: make install
- name: Prepare the old cluster (install + dependency guard), twin databases
# Two separate databases (distinct names, one per ordering) so
# both exist in the SAME cluster ahead of the single pg_upgrade
# call below - that one binary upgrade migrates both at once.
# prepare-old installs count_nulls at 0.9.6 in each, then plants +
# proves the dependency guard, so a later accidental CASCADE drop
# anywhere in this job cannot silently make the eventual
# existing-mode run test a fresh install instead.
run: |
bin/test_existing prepare-old upgrade_oldest_first "" 0.9.6
bin/test_existing prepare-old upgrade_current_first "" 0.9.6
- name: Update to current before pg_upgrade (proves pg_upgrade preserves CURRENT objects)
# Updates ONLY upgrade_current_first
# (upgrade_oldest_first must NOT be touched here - it
# stays at 0.9.6 until AFTER pg_upgrade, below). Exercises ALTER
# EXTENSION UPDATE on the OLD cluster, BEFORE the binary pg_upgrade
# below, running the 0.9.6->stable update script, so pg_upgrade
# runs against already-current objects, not 0.9.6 ones. `make
# install` above already installed the current version's update
# scripts/control file into this (old) cluster's sharedir, so
# they're in place for this ALTER EXTENSION UPDATE to use.
run: bin/test_existing update upgrade_current_first
- name: Install PostgreSQL ${{ matrix.new_pg }}
run: apt-get install -y postgresql-${{ matrix.new_pg }} postgresql-server-dev-${{ matrix.new_pg }}
- name: Install count_nulls into new cluster
# 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: Update to current after pg_upgrade (proves pg_upgrade preserves OLDEST-version objects)
# Exercises ALTER EXTENSION UPDATE AFTER the binary pg_upgrade
# above, against the NEW cluster (already started by the previous
# step, and the only cluster listening on 5432 at this point,
# since the old one was stopped in that same step), on
# upgrade_oldest_first ONLY (upgrade_current_first
# was already updated before the upgrade, above, and must not be
# updated again here) - the 0.9.6 objects prepare-old planted into
# that database are thus still at 0.9.6 at the moment pg_upgrade
# itself runs, so this proves pg_upgrade correctly preserves
# objects still frozen at the oldest supported version, not just
# already-current ones. `make install` into the new cluster above
# already installed the current version's update scripts/control
# file into ITS sharedir, so they're in place for this ALTER
# EXTENSION UPDATE to use.
run: bin/test_existing update upgrade_oldest_first
- name: Run the suite against the pg_upgraded database(s) (existing mode), twin databases
# run-suite asserts the version, re-proves the dependency guard
# still blocks a non-CASCADE drop (i.e. it survived both the update
# and pg_upgrade), drops the guard, then runs the suite against the
# REAL pg_upgraded database via --use-existing (so pg_regress does
# not drop/recreate it) - a plain fresh `make test` would silently
# test a fresh install instead of the migrated objects. Once per
# database, since each holds an independent ordering's result.
run: |
bin/test_existing run-suite upgrade_oldest_first ""
bin/test_existing run-suite upgrade_current_first ""

pg-tle-test:
needs: [changes]
# Skipped outright (not just matrix-reduced like `test` above) on a
Expand Down Expand Up @@ -399,7 +591,7 @@ jobs:
# `changes` job on a docs-only push), and fails if any failed or were
# cancelled.
all-checks-passed:
needs: [changes, lint, test, pg-tle-test]
needs: [changes, lint, test, pg-upgrade-test, pg-tle-test]
if: always()
runs-on: ubuntu-latest
steps:
Expand Down
Loading
Loading