Skip to content

Real pg_upgrade support via a reduced bin/test_existing - #31

Open
jnasbyupgrade wants to merge 5 commits into
masterfrom
phase4-pg-upgrade
Open

Real pg_upgrade support via a reduced bin/test_existing#31
jnasbyupgrade wants to merge 5 commits into
masterfrom
phase4-pg-upgrade

Conversation

@jnasbyupgrade

Copy link
Copy Markdown
Contributor

Stacked on #30 (phase 3.5: CI hygiene). Adds binary `pg_upgrade` coverage - the one U&U scenario that genuinely can't live inside a single `pg_regress` invocation, since `pg_upgrade` is an external binary process.

What changed

  • New `pg-upgrade-test` CI job: install 0.9.6 on an old PostgreSQL major, plant + prove the dependency guard, binary `pg_upgrade` to a newer major, `ALTER EXTENSION UPDATE` the migrated objects, then run the suite against the real upgraded database in existing mode. Matrix: 10→18 and 12→18 (no bridge-update leg - 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). No stepwise every-major climb either, for the same reason (nothing catalog-touching to break at a specific boundary).
  • `bin/test_existing`: notably smaller than it would have needed to be before phase 3's `test/install` work. Only `prepare-old` (old-cluster setup before the real `pg_upgrade` binary runs) and `run-suite` (assert + re-prove the guard + run the suite via `--use-existing` afterward) are genuinely external-to-`pg_regress` concerns, plus a small `update` subcommand for the post-upgrade `ALTER EXTENSION UPDATE` step. There's no `update-scenario` subcommand at all - that whole scenario is just `make test-update` now (phase 3's `test/install`-driven update mode), since an in-place update has no external step to drive.
  • `run_suite()` gates on plain `make test`, not the old belt-and-suspenders `make test && make verify-results` combo - pgxntool 2.3.0 (Update pgxntool to 2.3.0 #25) already made `make test` itself exit non-zero on regression failures, so the extra call is redundant now.

Not yet crossed with `TEST_SCHEMA` - that's the next phase, once both this job and `extension-update-test` can cross it together.

Verification

Locally against PG17: `prepare-old` → `update` → `run-suite` all pass end to end, without a real `pg_upgrade` (this container's PG12/PG17 clusters are persistent shared dev infra, not disposable, so - same reasoning as the earlier pg-tle-test work - the actual binary `pg_upgrade` leg is left for CI's ephemeral containers to exercise).

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c153f6f4-ec8a-4181-9298-79dbfea7e77d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@jnasbyupgrade jnasbyupgrade changed the title Phase 4: real pg_upgrade support via a reduced bin/test_existing Real pg_upgrade support via a reduced bin/test_existing Aug 4, 2026
@jnasbyupgrade
jnasbyupgrade marked this pull request as draft August 4, 2026 21:11
@jnasbyupgrade
jnasbyupgrade force-pushed the phase4-pg-upgrade branch 2 times, most recently from 6c1d36f to e64f80f Compare August 5, 2026 18:14
@jnasbyupgrade
jnasbyupgrade force-pushed the phase4-pg-upgrade branch 2 times, most recently from a9c46c2 to a791789 Compare August 5, 2026 22:49
@jnasbyupgrade
jnasbyupgrade force-pushed the phase4-pg-upgrade branch 2 times, most recently from 3b33ad8 to f37b039 Compare August 6, 2026 22:10
Base automatically changed from phase3.5-ci-hygiene to master August 6, 2026 23:52
jnasbyupgrade and others added 4 commits August 6, 2026 18:52
Adds the pg-upgrade-test CI job: install 0.9.6 on an old PostgreSQL major,
plant + prove a dependency guard, binary pg_upgrade to a newer major,
ALTER EXTENSION UPDATE the migrated objects, then run the suite against
the real upgraded database in existing mode.

bin/test_existing is much smaller than the equivalent script would have
been pre-test/install: only prepare-old and run-suite are genuinely
external-to-pg_regress concerns (a real pg_upgrade binary run isn't
something pg_regress can invoke itself), plus a small `update` subcommand
for the post-upgrade ALTER EXTENSION UPDATE step. There's no
update-scenario subcommand at all - that entire scenario is just `make
test-update` now (test/install/load.sql's own 'update' mode, added in
phase 3), since an in-place update has no external step to drive.

run_suite() gates on plain `make test`, not the old belt-and-suspenders
`make test && make verify-results` - pgxntool 2.3.0 (this repo's phase 0)
already made `make test` itself exit non-zero on regression failures.

Not yet crossed with TEST_SCHEMA - that's the next phase, once both this
job and extension-update-test can cross it together.

Verified locally against PG17 (prepare-old -> update -> run-suite, without
a real pg_upgrade - this container's clusters are persistent shared
infra, so the actual binary pg_upgrade leg is left for CI's ephemeral
containers, same reasoning as the pg-tle-test work).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… not after

Reorders prepare-old -> update -> pg_upgrade -> run-suite (was prepare-old
-> pg_upgrade -> update -> run-suite). The old order proved pg_upgrade
could migrate 0.9.6's frozen objects, then updated afterward - not
actionable, since that version already shipped. This job's whole point is
proving pg_upgrade correctly migrates the objects count_nulls' CURRENT code
creates, which requires updating BEFORE the binary upgrade runs. make
install (into the old cluster) already happens earlier in the job, so the
current version's update scripts are on disk in time for the moved step.

Updates the job's step names/comments and bin/test_existing's own
file-header sequence description to match the new order.
Propagates the draft-PR gating from phase3.5-ci-hygiene to the
pg-upgrade-test job introduced by this branch: same needs:[changes]/
if: docs_only pattern as pg-tle-test, so it gets the same
&& github.event.pull_request.draft != true guard.
old_pg=12 keeps updating to current before the binary pg_upgrade (proves
pg_upgrade preserves CURRENT-code objects); old_pg=10 now updates after
instead (proves pg_upgrade preserves objects still frozen at the oldest
supported version at upgrade time - a real user scenario the
update-before-only setup missed). Same 2-leg matrix, no added CI cost.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jnasbyupgrade
jnasbyupgrade marked this pull request as ready for review August 7, 2026 18:11
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Claude finished @jnasbyupgrade's task in 5m 47s —— View job


Code review

  • Gather context (PR body, changed files)
  • Review .github/workflows/ci.yml changes
  • Review bin/test_existing
  • Review bin/test_existing.sql/*.sql
  • Post review feedback

Overall this is a well-reasoned, carefully-commented PR — the two-leg matrix design (updating on opposite sides of the binary pg_upgrade) is a nice way to get both real-world orderings out of the same CI cost, and the dependency-guard self-check (plant → prove → re-prove → drop) is a solid way to make a silent CASCADE/reinstall fail loudly instead of passing green by accident. I read through ci.yml's new pg-upgrade-test job, bin/test_existing, and all three of its SQL helpers, and traced the step ordering (install-before-upgrade, update-before-vs-after-upgrade, guard survival checks) end to end — it's internally consistent and the two legs do test what the comments claim.

One real issue, otherwise minor:

bin/test_existing:125 — latent set -e trap in assert_version()

assert_version() {
  local db=$1 expected=$2 installed
  [ "$expected" = current ] && expected=$(current_version)
  ...

Under set -euo pipefail (line 56), && used as a conditional is dangerous: if $expected is ever anything other than the literal string current, the [ ] test is false, the whole command1 && command2 construct returns non-zero, and — because there's no enclosing if/||/similar to absorb that — the script aborts right there under set -e, with no FAIL: message at all.

This is exactly the failure mode this file's own author already flagged and avoided a few lines below, in update_ext() (line 138): "Use if, not &&: a false test under set -e would abort." The same fix wasn't applied here.

It's currently dormant — assert_version is only ever called as assert_version "$db" current (in run_suite, line 183) — so it can't manifest today. But the function's own signature (DB EXPECTED) implies a specific version is a supported input, and if a future caller ever passes one, the script would die silently instead of running the intended comparison. Worth the one-line fix for consistency with the pattern already established in this same file:

if [ "$expected" = current ]; then expected=$(current_version); fi

Fix this →

Nothing else stood out: the if: matrix.old_pg == '12' / '10' step gating matches the described leg semantics, the install-into-new-cluster-before-binary-pg_upgrade ordering is correct (the new cluster needs the control/update scripts on disk before pg_upgrade replays CREATE EXTENSION), the guard SQL's exception handling correctly rolls back the probe DROP EXTENSION even in the failure branch, and FLOOR=10/NEWEST=18 in the changes job confirms the "oldest supported version" claim in the old_pg=10 leg's comment.

…n databases

The old_pg=10-vs-12 split wrongly conflated two independent axes (which PG
floor is tested, and which update-order is tested). Both orderings are now
compared on the SAME floor (old_pg=10, the oldest supported) using twin
databases migrated by a single pg_upgrade call - the same trick this file
already uses to cross TEST_SCHEMA without doubling matrix legs. old_pg=12
reverts to its original single-database, single-ordering design and is now
just a second, unrelated old-PG-floor data point.
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Claude finished @jnasbyupgrade's task in 2m 20s —— View job


Code review

  • Gather context (PR body, changed files, prior review comments)
  • Check diff since last review (new commits from pull request synchronize)
  • Review .github/workflows/ci.yml changes
  • Review bin/test_existing
  • Review bin/test_existing.sql/*.sql
  • Post review feedback

This push (d714ef5, "confine the ordering comparison to old_pg=10 via twin databases") only touches .github/workflows/ci.ymlbin/test_existing and its SQL helpers are unchanged since the prior review. I traced the new twin-database flow end to end and it's internally consistent:

  • old_pg=10's two prepare-old calls (count_nulls_upgrade_oldest_first, count_nulls_upgrade_current_first, ci.yml:368-369) create both databases in the same old cluster before the single binary pg_upgrade call (ci.yml:409-415), which upgrades the whole cluster and therefore migrates both at once — matching the "one pg_upgrade call, twin databases" claim in the header comment.
  • The pre-upgrade update step (ci.yml:393) now runs unconditionally but selects the right target database via the matrix.old_pg == '10' && '...' || '...' GitHub Actions ternary — correctly touching only count_nulls_upgrade_current_first on that leg and leaving count_nulls_upgrade_oldest_first frozen at 0.9.6 for the post-upgrade update (ci.yml:438).
  • old_pg=12 cleanly reverts to the original single-database, single-ordering shape (count_nulls_upgrade, ci.yml:378/393/462), with no leftover if: matrix.old_pg == '12' guard on the now-unconditional pre-upgrade update step.
  • Both run-suite calls for the old_pg=10 leg (ci.yml:450-451) run sequentially in the same step against the two independently-migrated databases — each gets its own version/guard assertions.

No bugs found in this diff.

One item from the previous review remains open, since it's in a file this push didn't touch:

bin/test_existing:125 — latent set -e trap in assert_version()

[ "$expected" = current ] && expected=$(current_version)

Under set -euo pipefail (line 56), if $expected is ever anything other than the literal string current, this && short-circuits false and aborts the whole script with no FAIL: message — the exact pitfall this file's own update_ext() (line 138) already documents and avoids with an if. Still dormant today (assert_version is only ever called with "$db" current, run_suite:183), but worth the one-line fix for consistency:

if [ "$expected" = current ]; then expected=$(current_version); fi

Fix this →
· branch: phase4-pg-upgrade

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant