CommitFest 7262 Review: pg_upgrade --invalid-databases=skip|error

report · generated from canonical Markdown

Type
report
Source
postgresql-master
Slug
commitfest-7262-review
Tags
commitfest-7262invalid-databasesneeds-reviewpg-review-harnesspg_upgradereview-report-v2
Updated
2026-09-08T15:39:20.890924+00:00
Verdict / Summaryneeds-review
Front matter
schema: review-report-v2
target: CommitFest 7262: pg_upgrade --invalid-databases=skip|error option for handling invalid databases
base: 140fdfcdf12d524541ff26491b8e2076a690e434
head: 140fdfcdf12d524541ff26491b8e2076a690e434
verdict: needs-review
blocker_counts: verified=0 unverified=0
scope_identity: d956c637efb0a14c784e5c8024811af435c81409e61e9f268501e06474ba8c7b
model: glm-5.2:cloud
source: postgresql-master
slug: commitfest-7262-review
commitfest: "7262"
diff_sha: d956c637efb0a14c784e5c8024811af435c81409e61e9f268501e06474ba8c7b
diff_type: staged-uncommitted
artifact_sha256: 9de79ec59e85af5028b7ed825cbab7e8d94e3e9a2c3f7aab8b4a6ad053d51a2a
router_policy_version: 1
router_policy_hash: 997c035512fa1b35aa17149ea386a861af531ec05839ce06810f38268a3caebe

CommitFest 7262 Review: pg_upgrade --invalid-databases=skip|error

Verdict: needs-review — 0 blockers, 1 test MAJOR (PATCH-1: full-upgrade skip path untested), 1 design MAJOR (THREAD-1: unresolved default-skip-vs-default-error debate on pgsql-hackers), 6 MINORs (PATCH-2/3/4/5, SEC-1, COV-1), 2 NITs (PATCH-6/7), 1 refuted (SEC-2). Changed-code coverage measured: 17/19 lines (89.5%), 14/20 branches (70.0%). All findings verified with concrete repair patches; code patch fully validated (apply+compile+smoke); test patch apply-validated + perl syntax OK (TAP two-run delegated to pg-regress-expert).

Target

Pipeline and provenance

SeatAgentHandoff slugStatus
Patch reviewpg-patch-reviewercommitfest-7262-review-handoff-patch-reviewerverified (sha256 83caabdf…)
Security reviewpg-security-reviewercommitfest-7262-review-handoff-security-reviewerverified (sha256 7c8be18c…)
Test authorpg-test-authorcommitfest-7262-review-handoff-test-authorverified (sha256 5cf521da…)
Thread reconciliationcross-reference-review-with-threadcommitfest-7262-review-thread-ledgerverified (sha256 559be1c1…)
Finding verificationpg-finding-verifiercommitfest-7262-review-handoff-verifierverified (sha256 14cd0b6b…)
Changed-code coveragepg-change-coverage-reviewercommitfest-7262-review-handoff-coverageverified (sha256 87be0a26…)
COV-1 repair verificationpg-finding-verifiercommitfest-7262-review-handoff-cov1-verifierverified (sha256 82fea4d7…)

Model routing: route-review-models policy v1 (hash 997c0355…). User-fixed model glm-5.2:cloud bound as session default for all seats.

Patch summary

The patch adds a --invalid-databases=skip|error option to pg_upgrade. Invalid databases (whose DROP DATABASE was interrupted, leaving datconnlimit=-2) are skipped by default and reported with a warning; with --invalid-databases=error they abort the upgrade (preserving the historical behavior). The get_db_infos query now filters AND d.datconnlimit <> -2 so invalid DBs are excluded from the dbarr list — they are neither dumped, restored, nor file-transferred. check_for_connection_status gains a new else if SKIP branch (before the catch-all abort branch) that logs a per-DB warning and sets skipped_invalid; the final check_ok() is suppressed when invalid DBs were skipped. The TAP test 002_pg_upgrade.pl adds --invalid-databases=error (abort) and default-skip (--check) sub-tests. Docs, typedefs.list, and option parsing are updated.

Findings summary

IDSeverityClassVerificationRepair validation
PATCH-1MAJORtestsverifiedpartial (apply + diff + perl syntax; TAP run delegated)
PATCH-2MINORcorrectnessverifiedpassed (apply + diff + compile + smoke)
PATCH-3MINORmaintainabilityverifiedpassed (apply + diff + compile)
PATCH-4MINORdocsverifiedpassed (apply + diff + compile + smoke)
PATCH-5MINORtestsverifiedpartial (consolidated with PATCH-1)
PATCH-6NITmaintainabilityverifiedpassed (apply + diff + compile)
PATCH-7NITmaintainabilityverifiedpassed (apply + diff + compile + smoke)
SEC-1MINORsecurityverifiedpassed (consolidated with PATCH-3)
SEC-2NITsecurityrefutednot-applicable-refuted (correct idiom)
COV-1MINORtestsverified (verified-by-coverage)partial (apply + diff + perl syntax; TAP run delegated)
THREAD-1MAJORcorrectnesscannot-verifywaived (design-choice; unresolved thread)

Blockers: 0 verified, 0 unverified. Majors: 2 (PATCH-1 tests, THREAD-1 design). Minors: 6 (PATCH-2/3/4/5, SEC-1, COV-1). Nits: 2 (PATCH-6/7). Refuted: 1 (SEC-2).

Verification Summary

All 11 findings were verified by the pg-finding-verifier seat against source in the read-only worktree and disposable scratch copies at HEAD 140fdfcdf12d524541ff26491b8e2076a690e434 (scope identity d956c637efb0a14c…). Validation methods: apply-check, diff-check, compile (make -s -C src/bin/pg_upgrade clean pg_upgrade, exit 0, 0 warnings after repair), perl syntax check (perl -c), and binary smoke test (./pg_upgrade --invalid-databases=bogus --check → exit 1 with repaired message).

The combined code repair patch (PATCH-2/3/4/6/7 + SEC-1, SHA256 731c3027626d2e57…) compiles clean with 0 warnings (down from 1 -Wmisleading-indentation warning on the un-repaired staged tree) and the binary emits the repaired error message. The test repair patch (PATCH-1 + PATCH-5, SHA256 ee1bddab48350e41…) applies cleanly with perl -c syntax OK; the targeted TAP two-run validation (FAILS on pre-patch, PASSES on post-patch) was delegated to pg-regress-expert as it requires a cross-version upgrade harness with --enable-tap-tests and a compatible older PostgreSQL install. The COV-1 test repair (SHA256 5c542d1e9cbd1849…) similarly applies cleanly with perl syntax OK.

Repair Validation Summary

PatchSHA256FindingsApplyDiffCompileSmokePerlTAPStatus
code repair731c3027626d2e57…PATCH-2,3,4,6,7 + SEC-1OKOKclean (0 warnings)OKn/an/apassed
test repairee1bddab48350e41…PATCH-1, PATCH-5OKOKn/an/aOKdelegatedpartial
COV-1 test5c542d1e9cbd1849…COV-1OKOKn/an/aOKdelegatedpartial
(SEC-2)n/aSEC-2refutedrefutedrefutedrefutedrefutedrefutednot-applicable-refuted
(THREAD-1)waivedTHREAD-1waivedwaivedwaivedwaivedwaivedwaivedwaived (design-choice)

All patches apply cleanly on top of the staged postimage tree with patch -p1 --dry-run (no offsets, no fuzz, no rejects). All validation performed in disposable scratch copies; the reviewed worktree was never modified.

Changed-Code Coverage

Raw metrics

MetricCount
Changed executable lines19
Covered changed lines17
Compiler-uncovered changed lines2
Structural uncovered changed lines0
Changed lines without coverage coordinates2
Changed branches20
Covered changed branches14

Line coverage: 17/19 = 89.5%. Branch coverage: 14/20 = 70.0%.

Touched functions

FunctionFileLinesBranchesEntered
check_for_connection_statussrc/bin/pg_upgrade/check.c:1077-117235/37 (94.6%)19/24 (79.2%)yes
get_db_infossrc/bin/pg_upgrade/info.c:397-44925/28 (89.3%)10/12 (83.3%)yes
parseCommandLinesrc/bin/pg_upgrade/option.c:39-31390/141 (63.8%)63/71 (88.7%)yes
usagesrc/bin/pg_upgrade/option.c:326-36338/38 (100%)0/0yes

Actionable uncovered regions

UR-1: src/bin/pg_upgrade/option.c:241 — explicit --invalid-databases=skip parse (coverable-now)

c
241: 				user_opts.invalid_db_mode = INVALID_DB_SKIP;

Why missed: the default init at option.c:79 already sets INVALID_DB_SKIP, so only an explicit --invalid-databases=skip reaches line 241. No existing or currently-proposed test passes the option explicitly (the default-skip TAP test omits it). Meaningful untested parse path of the new option feature.

Test example: command_checks_all([ 'pg_upgrade', ... '--check', '--invalid-databases' => 'skip' ], 0, [qr/invalid database "regression_invalid" will not be upgraded/], [qr/^$/], 'invalid database is skipped with --invalid-databases=skip');

COV-1 finding issued (MINOR tests, verified-by-coverage) — explicit --invalid-databases=skip parse path is untested. Repair patch SHA256: 5c542d1e9cbd1849….

UR-2: src/bin/pg_upgrade/option.c:245 — invalid argument rejection (coverable-now)

c
245: 				pg_fatal("invalid argument for option %s", "--invalid-databases");

Why missed: no existing test passes a bogus --invalid-databases value. Addressed by the pg-test-author's proposed PATCH-1 test patch (adds --invalid-databases=bogus test). No separate COV finding — gap is known and addressed by a proposed repair.

Uncovered branches

LocationBranchCoverabilityReason
check.c:1131if (!skipped_invalid) else-arc (2nd+ invalid DB)requires-special-harnessRequires 2+ invalid DBs in skip mode; existing fixture has one
check.c:1145datallowconn=="f" true-arcrequires-special-harnessRequires a non-template0 datallowconn=f DB (aborts upgrade); pre-existing condition
check.c:1147script==NULL false-arc (2nd+ abort-worthy DB)requires-special-harnessRequires 2+ DBs hitting the abort path
check.c:1147fopen_priv==NULL arc (fs failure)infeasible-with-reasonNo TAP fault-injection seam for fopen_priv; pre-existing branch
option.c:240pg_strcasecmp("skip")==0 true-arccoverable-nowExplicit --invalid-databases=skip; covered by COV-1 test repair
option.c:242pg_strcasecmp("error")==0 false-arccoverable-nowBogus value → pg_fatal; covered by PATCH-1 test repair

Excluded changed coordinates

KindPathCountReason
compiler-no-coverage-coordinatesrc/bin/pg_upgrade/info.c2String-literal continuation lines of the get_db_infos snprintf query (AND d.datconnlimit <> -2 filter, lines 426-427). gcov reports -: (no executable coordinate). The containing snprintf is executed 48 times.

Non-instrumented changes

PathReason
doc/src/sgml/ref/pgupgrade.sgmlDocumentation (SGML), not instrumentable C
src/bin/pg_upgrade/pg_upgrade.hHeader file: adds invalidDbMode enum and UserOpts.invalid_db_mode field. No executable code
src/bin/pg_upgrade/t/002_pg_upgrade.plTAP test file (Perl), not C
src/tools/pgindent/typedefs.listIndenter typedef list, not code

Residual limitations

Findings

PATCH-1

— MAJOR — tests — src/bin/pg_upgrade/t/002_pg_upgrade.pl:590-606

The skip path is only tested in --check mode. The existing test DROPS the invalid DB at line 611 before the full upgrade command_ok at line 632, so the full pg_upgrade never observes an invalid database. The patch's most behaviorally novel claim — that a full upgrade with default skip (a) exits 0, (b) leaves the invalid DB absent from the new cluster's pg_database, and (c) leaves its OID directory absent from base/ — is never tested.

Verification: verified (static-read + coverage-confirmed). The coverage run exercised check_for_connection_status 21 times and get_db_infos 48 times, but the full-upgrade-with-invalid-DB scenario was not in the test set. Thread disposition: new.

Repair: Restructure the test so the invalid DB survives into the full upgrade on PG>=11 (version-guard the DROP), add absence assertions on the new node, and add a bogus-argument rejection test. Consolidated with PATCH-5 (skip-count fix). Repair patch SHA256: ee1bddab48350e41d64e0083e069de702dd1902f17ec695011a65ed33dbd5eb2. Validation: partial (apply + diff + perl syntax OK; TAP two-run delegated).

PATCH-2

— MINOR — correctness — src/bin/pg_upgrade/info.c:425-427

The AND d.datconnlimit <> -2 filter in get_db_infos is unconditional (applies in both skip and error mode). In error mode check_for_connection_status aborts first so the filter is unreachable, but the two defenses are coupled only by call order.

Verification: verified (static-read). Thread disposition: new. Repair: clarifying block comment documenting the call-order coupling. Validation: passed (apply + compile + smoke).

PATCH-3

— MINOR — maintainability — src/bin/pg_upgrade/check.c:1128-1151

The else if precedence (SKIP branch before catch-all abort) is load-bearing — a database that is both invalid and datallowconn=f must hit SKIP first. Reordering would silently break skip behavior.

Verification: verified (static-read). Thread disposition: new. Repair: comment documenting the ordering requirement. Consolidated with SEC-1. Validation: passed.

PATCH-4

— MINOR — docs — src/bin/pg_upgrade/check.c:1162-1168

The pg_fatal abort message does not hint at --invalid-databases=skip as a workaround. Nathan Bossart on the thread (Message-ID ZwQ-xtmKbNAz_HK8@nathan) explicitly suggested the report mention the skip option.

Verification: verified (static-read + thread-corroboration). Thread disposition: already-raised (Nathan Bossart, 2024). Repair: extend the message to mention --invalid-databases=skip. Validation: passed (apply + compile + smoke; binary emits the hint).

PATCH-5

— MINOR — tests — src/bin/pg_upgrade/t/002_pg_upgrade.pl:566-567

skip "database invalidation not implemented", 2 declares a skip count of 2 but the block contains two command_checks_all calls (6 ok calls when not skipped). The skip count understates the skipped tests.

Verification: verified (static-read). Thread disposition: new. Repair: bump skip count from 2 to 6. Consolidated with PATCH-1. Validation: partial.

PATCH-6

— NIT — maintainability — src/bin/pg_upgrade/check.c:1131-1135

report_status(PG_WARNING, "warning") is called in-loop, diverging from the sibling check_for_unicode pattern which calls it after the loop. Behavior-preserving stylistic alignment.

Verification: verified (static-read). Thread disposition: new. Repair: move report_status to after the loop. Validation: passed.

PATCH-7

— NIT — maintainability — src/bin/pg_upgrade/option.c:245

The error message for a bad --invalid-databases argument doesn't list accepted values. The staged patch's de-indented else also introduced a -Wmisleading-indentation warning.

Verification: verified (static-read + compile). Thread disposition: new. Repair: list accepted values and fix indentation (eliminates the warning). Validation: passed (0 warnings after repair).

SEC-1

— MINOR — security — src/bin/pg_upgrade/check.c:1128-1138

Operator-reporting precedence: a DB that is both invalid and datallowconn=f gets the skip warning instead of the cannot-connect report. NOT a vulnerability — invalid DBs are excluded from dbarr by get_db_infos and refused connections by the backend.

Verification: verified (static-read + data-flow-trace). Thread disposition: new. Repair: comment clarifying the precedence is intentional and not a security boundary. Consolidated with PATCH-3. Validation: passed.

SEC-2

— NIT — security — src/bin/pg_upgrade/check.c:1170 — REFUTED

Status-line lifecycle when skipped_invalid is true — correct idiom (same as check_for_unicode). Refuted as a finding — verified-clean observation, not a defect.

COV-1

— MINOR — tests — src/bin/pg_upgrade/option.c:241

Explicit --invalid-databases=skip parse path untested. gcov confirms count=0 for line 241 and branch 240:1 taken 0%. The default init at line 79 masks this in the default-skip test.

Verification: verified (verified-by-coverage). Thread disposition: new. Repair: add explicit --invalid-databases=skip TAP test. Repair patch SHA256: 5c542d1e9cbd18492bf5e27a6b58fad4f090b6bf123491151d7e448f212990e5. Validation: partial (apply + diff + perl syntax; TAP run delegated).

THREAD-1

— MAJOR — correctness — src/bin/pg_upgrade/check.c:1129 — CANNOT-VERIFY

Live design objection: Nathan Bossart (Message-ID apsFv3w7VCBM7Cub@nathan) preferred default=off (error) to preserve existing fail behavior; Bharath Rupireddy (Message-ID CALj2ACVEraTKM7WV+ubnFPeD4WnH+KvrvrtyJx9jVQSaG-mYbQ@mail.gmail.com) defended default=skip. The thread has not reached consensus. The patch flips the historical default from hard-fail to skip.

Verification: cannot-verify (thread-reconciliation). This is a design-policy judgment, not a code defect verifiable by execution. Requires committer/community consensus. Thread disposition: author-deferred. Repair: waived (design-choice) — if default=error is preferred, change the default init at option.c:79; if default=skip is confirmed, no change needed. Surface the objection to the committer.

pgsql-hackers thread ledger

Key thread finding links:

Aggregated Test Plan

TestVehicleFileStatus
pg_upgrade rejects unknown --invalid-databases valueTAPt/002_pg_upgrade.plproposed (PATCH-1 test patch)
Full upgrade with invalid DB present (default skip)TAPt/002_pg_upgrade.plproposed (PATCH-1; re-purposes existing command_ok)
invalid database absent from pg_database on new clusterTAPt/002_pg_upgrade.plproposed (PATCH-1)
invalid database OID directory absent from new cluster base/TAPt/002_pg_upgrade.plproposed (PATCH-1)
invalid database is skipped with --invalid-databases=skipTAPt/002_pg_upgrade.plproposed (COV-1 repair)
Skip-count fix (2→6)TAP infrat/002_pg_upgrade.plproposed (PATCH-5, consolidated with PATCH-1)

Two-run oracle: baseline (pre-patch) — new tests FAIL (unknown option → stderr; invalid DB aborts full upgrade); post-patch — all PASS.

Consolidated repair patches

Code repair (PATCH-2, 3, 4, 6, 7 + SEC-1)

SHA256: 731c3027626d2e57de62271a22256d81090a421c0ae91e9c0faa66413866916d. Files: src/bin/pg_upgrade/check.c, src/bin/pg_upgrade/info.c, src/bin/pg_upgrade/option.c. Validation: passed (apply + diff + compile 0 warnings + smoke).

diff
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ef9e34f..de55dcf 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -1124,15 +1124,20 @@ check_for_connection_status(ClusterInfo *cluster)
 		 * error. They are left out of the upgrade, and their files are
 		 * removed along with the rest of the old cluster by the generated
 		 * delete_old_cluster script.
+		 *
+		 * This else-if must precede the catch-all abort branch below: a
+		 * database that is both invalid (datconnlimit == -2) and
+		 * datallowconn == f must reach the skip path, not the abort path.
+		 * The order is load-bearing; reordering would silently turn skip
+		 * mode into an abort for such databases.  (Invalid databases are
+		 * also excluded from dbarr by get_db_infos() and refused
+		 * connections by the backend, so this precedence is a reporting
+		 * choice, not a security boundary.)
 		 */
 		else if (strcmp(datconnlimit, "-2") == 0 &&
 				 user_opts.invalid_db_mode == INVALID_DB_SKIP)
 		{
-			if (!skipped_invalid)
-			{
-				report_status(PG_WARNING, "warning");
-				skipped_invalid = true;
-			}
+			skipped_invalid = true;
 			pg_log(PG_WARNING,
 				   "invalid database \"%s\" will not be upgraded", datname);
 		}
@@ -1165,9 +1170,16 @@ check_for_connection_status(ClusterInfo *cluster)
 				 "which cannot be connected to.  Consider allowing connection for all\n"
 				 "non-template0 databases or drop the databases which do not allow\n"
 				 "connections.  A list of databases with the problem is in the file:\n"
-				 "    %s", output_path);
+				 "    %s\n"
+				 "If the databases with datconnlimit -2 are invalid (their DROP\n"
+				 "DATABASE was interrupted), running pg_upgrade with\n"
+				 "--invalid-databases=skip will skip them and proceed.",
+				 output_path);
 	}
-	else if (!skipped_invalid)
+	else if (skipped_invalid)
+		/* report once after the loop, like check_for_unicode() */
+		report_status(PG_WARNING, "warning");
+	else
 		check_ok();
 }
 
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 09f0b73..408d793 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -423,7 +423,16 @@ get_db_infos(ClusterInfo *cluster)
 			 " LEFT OUTER JOIN pg_catalog.pg_tablespace t "
 			 " ON d.dattablespace = t.oid "
 			 "WHERE d.datallowconn = true "
-	/* skip invalid databases, whose DROP DATABASE was interrupted */
+	/*
+	 * Skip invalid databases, whose DROP DATABASE was interrupted.  This
+	 * filter is unconditional: it applies in both --invalid-databases modes.
+	 * In --invalid-databases=error, check_for_connection_status() aborts
+	 * before get_db_infos() is ever reached, so this filter never observes
+	 * an invalid database in that mode; the two defenses are coupled only
+	 * by call order.  Keeping the filter here in skip mode ensures invalid
+	 * databases never enter dbarr, so they are neither dumped, restored,
+	 * nor file-copied.
+	 */
 			 "  AND d.datconnlimit <> -2 "
 			 "ORDER BY 1");
 
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 5ef08e2..a35c270 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -242,7 +242,8 @@ parseCommandLine(int argc, char *argv[])
 				else if (pg_strcasecmp(optarg, "error") == 0)
 					user_opts.invalid_db_mode = INVALID_DB_ERROR;
 				else
-					pg_fatal("invalid argument for option %s", "--invalid-databases");
+					pg_fatal("invalid argument for option %s: must be \"skip\" or \"error\"",
+					 "--invalid-databases");
 				break;
 
 			default:

Test repair (PATCH-1 + PATCH-5)

SHA256: ee1bddab48350e41d64e0083e069de702dd1902f17ec695011a65ed33dbd5eb2. Files: src/bin/pg_upgrade/t/002_pg_upgrade.pl. Validation: partial (apply + diff + perl syntax OK; TAP two-run delegated).

diff
diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl
index ca903bc..0ddb377 100644
--- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl
+++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl
@@ -530,6 +530,12 @@ $oldnode->safe_psql(
   UPDATE pg_database SET datconnlimit = -2 WHERE datname = 'regression_invalid';
 ));
 
+# Capture the OID of the invalid database for later filesystem checks on the
+# new cluster.  Read while the old node is still running (it is stopped below).
+my $invalid_db_oid = $oldnode->safe_psql('postgres',
+	"SELECT oid FROM pg_database WHERE datname = 'regression_invalid'");
+chomp $invalid_db_oid;
+
 # Upgrade the instance.
 $oldnode->stop;
 
@@ -556,6 +562,26 @@ ok(-d $newnode->data_dir . "/pg_upgrade_output.d",
 	"pg_upgrade_output.d/ not removed after pg_upgrade failure");
 rmtree($newnode->data_dir . "/pg_upgrade_output.d");
 
+# An unknown --invalid-databases value is rejected at option-parsing time,
+# before any output directory is created.  pg_fatal() reports to stdout, so
+# assert the message on stdout and empty stderr.
+command_checks_all(
+	[
+		'pg_upgrade', '--no-sync',
+		'--old-datadir' => $oldnode->data_dir,
+		'--new-datadir' => $newnode->data_dir,
+		'--old-bindir' => $oldbindir,
+		'--new-bindir' => $newbindir,
+		'--socketdir' => $newnode->host,
+		'--old-port' => $oldnode->port,
+		'--new-port' => $newnode->port,
+		$mode, '--check', '--invalid-databases' => 'bogus',
+	],
+	1,
+	[qr/invalid argument for option --invalid-databases/],
+	[qr/^$/],
+	'pg_upgrade rejects unknown --invalid-databases value');
+
 # Check how pg_upgrade handles an invalid database. With
 # --invalid-databases=error it is reported and the upgrade aborts; by default
 # it is skipped and the upgrade proceeds. (Versions that were out of support
@@ -563,7 +589,7 @@ rmtree($newnode->data_dir . "/pg_upgrade_output.d");
 # there.)
 SKIP:
 {
-	skip "database invalidation not implemented", 2
+	skip "database invalidation not implemented", 6
 	  if $oldnode->pg_version < 11;
 
 	# --invalid-databases=error preserves the historical hard failure.
@@ -606,10 +632,21 @@ SKIP:
 	rmtree($newnode->data_dir . "/pg_upgrade_output.d");
 }
 
-# And drop it, so we can continue
-$oldnode->start;
-$oldnode->safe_psql('postgres', 'DROP DATABASE regression_invalid');
-$oldnode->stop;
+# On versions that predate the invalid-database mechanism (commit
+# c66a7d75e652), datconnlimit=-2 has no "invalid" semantics and a full
+# upgrade would reject the database, so drop it before the upgrade as the
+# historical test did.  On PG >= 11 the default --invalid-databases=skip
+# excludes it from the upgrade, so we deliberately leave it in place to
+# exercise the full-upgrade skip path end to end.
+SKIP:
+{
+	skip "invalid database invalidation not implemented", 0
+	  if $oldnode->pg_version >= 11;
+
+	$oldnode->start;
+	$oldnode->safe_psql('postgres', 'DROP DATABASE regression_invalid');
+	$oldnode->stop;
+}
 
 # --check command works here, cleans up pg_upgrade_output.d.
 command_ok(
@@ -647,6 +684,23 @@ ok( !-d $newnode->data_dir . "/pg_upgrade_output.d",
 
 $newnode->start;
 
+# On PG >= 11, the full upgrade above ran with the invalid database still
+# present and the default --invalid-databases=skip.  Assert that the invalid
+# database was not carried over to the new cluster: it must be absent from
+# pg_database, and its OID directory must be absent under base/.
+SKIP:
+{
+	skip "invalid database invalidation not implemented", 2
+	  if $oldnode->pg_version < 11;
+
+	is($newnode->safe_psql('postgres',
+			"SELECT count(*) FROM pg_database WHERE datname = 'regression_invalid'"),
+		'0',
+		'invalid database absent from pg_database on new cluster');
+	ok( !-d $newnode->data_dir . "/base/$invalid_db_oid",
+		"invalid database OID directory absent from new cluster base/");
+}
+
 # Check if there are any logs coming from pg_upgrade, that would only be
 # retained on failure.
 my $log_path = $newnode->data_dir . "/pg_upgrade_output.d";
COV-1

test repair

SHA256: 5c542d1e9cbd18492bf5e27a6b58fad4f090b6bf123491151d7e448f212990e5. Files: src/bin/pg_upgrade/t/002_pg_upgrade.pl. Validation: partial (apply + diff + perl syntax OK; TAP two-run delegated).

diff
diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl
index ca903bc..6f1cd72 100644
--- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl
+++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl
@@ -563,7 +563,7 @@ rmtree($newnode->data_dir . "/pg_upgrade_output.d");
 # there.)
 SKIP:
 {
-	skip "database invalidation not implemented", 2
+	skip "database invalidation not implemented", 3
 	  if $oldnode->pg_version < 11;
 
 	# --invalid-databases=error preserves the historical hard failure.
@@ -604,6 +604,28 @@ SKIP:
 		[qr/^$/],
 		'invalid database is skipped by default');
 	rmtree($newnode->data_dir . "/pg_upgrade_output.d");
+
+	# Explicitly requesting --invalid-databases=skip must behave like the
+	# default: --check succeeds while naming the database that will not be
+	# upgraded.  This exercises the explicit "skip" parse arm of the
+	# --invalid-databases option (distinct from the default initialization).
+	command_checks_all(
+		[
+			'pg_upgrade', '--no-sync',
+			'--old-datadir' => $oldnode->data_dir,
+			'--new-datadir' => $newnode->data_dir,
+			'--old-bindir' => $oldbindir,
+			'--new-bindir' => $newbindir,
+			'--socketdir' => $newnode->host,
+			'--old-port' => $oldnode->port,
+			'--new-port' => $newnode->port,
+			$mode, '--check', '--invalid-databases' => 'skip',
+		],
+		0,
+		[qr/invalid database "regression_invalid" will not be upgraded/],
+		[qr/^$/],
+		'invalid database is skipped with --invalid-databases=skip');
+	rmtree($newnode->data_dir . "/pg_upgrade_output.d");
 }
 
 # And drop it, so we can continue

Residual risk

Verified-clean section (explicitly checked, no issue)

Deterministic verdict

Maintainer CC

Author: Bharath Rupireddy (bharath.rupireddyforpostgres@gmail.com). Reviewer: Nathan Bossart (nathandbossart@gmail.com). Predecessor: Kirill Richine (tk@postsubmeta.net), Daniel Gustafsson (daniel@yesql.se).