Hi Zsolt,

> > servers before v19 skip 0 only when the next multixid is assigned.
>
> It doesn't seem to be 19 specific to me?

You're right, it isn't 19-specific, and that line in the commit message was
misleading. The bug is in set_frozenxids() and dates back to 2014; what
changed is only whether you can reach it:

- pg_upgrade to 14 still fails earlier, in pg_resetwal ("multitransaction ID
  (-m) must not be 0"), because 8747b969fd8 was not backpatched to 14, so 14
  is not affected.
- pg_upgrade to 15..18 has been able to store the 0 since 8747b969fd8 made
  pg_resetwal accept -m 0.
- 19 and master reach the same code through the multixact conversion path.

The only part that is really about 19 is the trigger: a pre-19 server leaves
NextMultiXactId = 0 in the control file after assigning 0xFFFFFFFF, while
since 87a350e1f28 a 19 server never stores 0. I checked by recompiling:
14..18 clusters record 0, 19 and master record 1, so only a pre-19 old
cluster reaches it. And the 0 is not only template0's datminmxid -- it is the
relminmxid of the initdb-created catalogs in every database.

Attached is v3 for master, with the commit message rewritten and
Backpatch-through: 15, plus per-branch versions for 19 down to 15. The
back-branch files are attached as .txt so the cfbot does not try to apply
them to master. 007_multixact_conversion.pl only exists in 19+, so the
15..18 versions add a small same-version test. On every branch the test
fails without the fix and passes with it, and every old-version ->
target-version combination is clean with it.

Regards,
Manu
From 4cdefe1c99a6c57b98753efd5ad84abd25309bad Mon Sep 17 00:00:00 2001
From: Manuel Reyes Bravo <[email protected]>
Date: Sat, 17 Oct 2026 06:00:00 -0300
Subject: [PATCH v3] pg_upgrade: Don't store an invalid datminmxid/relminmxid

If the old cluster's newest multixid is the last one before multixid
wraparound (0xFFFFFFFF), the next multixid in its control file is 0:
before v19, the server skips 0 only when it assigns the next multixid.
set_frozenxids() copied that 0 into pg_database.datminmxid and into
pg_class.relminmxid of the relations that pg_upgrade does not restore
from the old cluster, i.e. the catalogs created by initdb, in every
database.  0 is InvalidMultiXactId.  On assert-enabled builds, the next
VACUUM fails an assertion.  Otherwise template0 keeps datminmxid = 0,
for which mxid_age() reports 2147483647, until autovacuum processes it;
with autovacuum = off, it stays that way.

Use FirstMultiXactId instead.  In v19, copy_xact_xlog_xid() already
does the same for the next multixid it passes to pg_resetwal when
converting multixacts.

This is not new in v19.  Before commit 8747b969fd8, pg_upgrade to
v15-v18 failed on such a cluster, because pg_resetwal refused "-m 0";
since then, the upgrade succeeds and stores the invalid values.  v14
did not get that commit, so pg_upgrade to v14 still fails on such a
cluster and is not affected.  Since commit 87a350e1f28, v19 servers
never store 0 as the next multixid, so only pre-v19 old clusters are
affected.

Backpatch-through: 15
---
 src/bin/pg_upgrade/pg_upgrade.c               | 15 ++++-
 .../pg_upgrade/t/007_multixact_conversion.pl  | 59 +++++++++++++++++++
 2 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 6d01084e4b0..1eaa2d03ba1 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -913,9 +913,20 @@ set_frozenxids(void)
 	int			ntups;
 	int			i_datname;
 	int			i_datallowconn;
+	MultiXactId minmxid;
 
 	prep_status("Setting frozenxid and minmxid counters in new cluster");
 
+	/*
+	 * If the old cluster's newest multixid was the last one before
+	 * wraparound, its next multixid is 0: servers before v19 skip 0 only
+	 * when they assign the next multixid.  0 is not a valid datminmxid or
+	 * relminmxid, so use FirstMultiXactId, as copy_xact_xlog_xid() does.
+	 */
+	minmxid = old_cluster.controldata.chkpnt_nxtmulti;
+	if (minmxid < FirstMultiXactId)
+		minmxid = FirstMultiXactId;
+
 	conn_template1 = connectToServer(&new_cluster, "template1");
 
 	/* set pg_database.datfrozenxid */
@@ -928,7 +939,7 @@ set_frozenxids(void)
 	PQclear(executeQueryOrDie(conn_template1,
 							  "UPDATE pg_catalog.pg_database "
 							  "SET	datminmxid = '%u'",
-							  old_cluster.controldata.chkpnt_nxtmulti));
+							  minmxid));
 
 	/* get database names */
 	dbres = executeQueryOrDie(conn_template1,
@@ -978,7 +989,7 @@ set_frozenxids(void)
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
-								  old_cluster.controldata.chkpnt_nxtmulti));
+								  minmxid));
 		PQfinish(conn);
 
 		/* Reset datallowconn flag */
diff --git a/src/bin/pg_upgrade/t/007_multixact_conversion.pl b/src/bin/pg_upgrade/t/007_multixact_conversion.pl
index 5c87c2527ee..4cc7e1bc9ad 100644
--- a/src/bin/pg_upgrade/t/007_multixact_conversion.pl
+++ b/src/bin/pg_upgrade/t/007_multixact_conversion.pl
@@ -484,4 +484,63 @@ SKIP:
 	upgrade_and_compare($tag, $old, $new);
 }
 
+# Multixid wraparound at the last multixid: The newest multixid is
+# 0xFFFFFFFF, so the old cluster's next multixid is 0.  That must not
+# end up as datminmxid or relminmxid in the new cluster.
+SKIP:
+{
+	skip
+	  "skipping mxid wraparound tests because upgrading from the old version does not require conversion"
+	  if ($old_version >= '19devel');
+
+	my $tag = 'last_mxid';
+	my $old =
+	  PostgreSQL::Test::Cluster->new("${tag}_oldnode",
+		install_path => $ENV{oldinstall});
+	my $new = PostgreSQL::Test::Cluster->new("${tag}_newnode");
+
+	$old->init(extra => ['-k']);
+
+	reset_mxid_mxoffset_pre_v19($old, 0xFFFFFFFF, 0x10000);
+
+	# Create one multixid with two members, a locker and an updater
+	$old->start;
+	$old->safe_psql(
+		'postgres', qq[
+		CREATE TABLE mxofftest (id INT PRIMARY KEY, n_updated INT)
+		  WITH (AUTOVACUUM_ENABLED=FALSE);
+		INSERT INTO mxofftest SELECT G, 0 FROM GENERATE_SERIES(1, 2) G;
+	]);
+	my $locker =
+	  $new->background_psql('', connstr => $old->connstr('postgres'));
+	$locker->query_safe("BEGIN");
+	$locker->query_safe("SELECT * FROM mxofftest WHERE id = 1 FOR KEY SHARE");
+	$old->safe_psql('postgres',
+		"UPDATE mxofftest SET n_updated = n_updated + 1 WHERE id = 1");
+	$locker->query_safe("COMMIT");
+	$locker->quit;
+	$old->stop;
+
+	my (undef, $next_mxid, undef) = read_multixid_fields($old);
+	is($next_mxid, 0, "next multixid wrapped around to 0 in old cluster");
+
+	$new->init;
+	upgrade_and_compare($tag, $old, $new);
+
+	$new->start;
+	is( $new->safe_psql(
+			'postgres',
+			"SELECT count(*) FROM pg_database WHERE datminmxid = '0'"),
+		'0',
+		'no database has an invalid datminmxid after upgrade');
+	is( $new->safe_psql(
+			'postgres',
+			"SELECT count(*) FROM pg_class WHERE relminmxid = '0' AND relkind IN ('r', 'm', 't')"
+		),
+		'0',
+		'no table has an invalid relminmxid after upgrade');
+	$new->safe_psql('postgres', 'VACUUM');
+	$new->stop;
+}
+
 done_testing();
-- 
2.56.0

From 02c84ee03e49bfd07b3be4ff23719c4eb3b8c461 Mon Sep 17 00:00:00 2001
From: Manuel Reyes Bravo <[email protected]>
Date: Sat, 17 Oct 2026 06:00:00 -0300
Subject: [PATCH v3] pg_upgrade: Don't store an invalid datminmxid/relminmxid

If the old cluster's newest multixid is the last one before multixid
wraparound (0xFFFFFFFF), the next multixid in its control file is 0:
before v19, the server skips 0 only when it assigns the next multixid.
set_frozenxids() copied that 0 into pg_database.datminmxid and into
pg_class.relminmxid of the relations that pg_upgrade does not restore
from the old cluster, i.e. the catalogs created by initdb, in every
database.  0 is InvalidMultiXactId.  On assert-enabled builds, the next
VACUUM fails an assertion.  Otherwise template0 keeps datminmxid = 0,
for which mxid_age() reports 2147483647, until autovacuum processes it;
with autovacuum = off, it stays that way.

Use FirstMultiXactId instead.  In v19, copy_xact_xlog_xid() already
does the same for the next multixid it passes to pg_resetwal when
converting multixacts.

This is not new in v19.  Before commit 8747b969fd8, pg_upgrade to
v15-v18 failed on such a cluster, because pg_resetwal refused "-m 0";
since then, the upgrade succeeds and stores the invalid values.  v14
did not get that commit, so pg_upgrade to v14 still fails on such a
cluster and is not affected.  Since commit 87a350e1f28, v19 servers
never store 0 as the next multixid, so only pre-v19 old clusters are
affected.

Backpatch-through: 15
---
 src/bin/pg_upgrade/pg_upgrade.c               | 15 ++++-
 .../pg_upgrade/t/007_multixact_conversion.pl  | 59 +++++++++++++++++++
 2 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 2127d297bfe..2dc30515c1c 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -934,12 +934,23 @@ set_frozenxids(bool minmxid_only)
        int                     ntups;
        int                     i_datname;
        int                     i_datallowconn;
+       MultiXactId minmxid;
 
        if (!minmxid_only)
                prep_status("Setting frozenxid and minmxid counters in new 
cluster");
        else
                prep_status("Setting minmxid counter in new cluster");
 
+       /*
+        * If the old cluster's newest multixid was the last one before
+        * wraparound, its next multixid is 0: servers before v19 skip 0 only
+        * when they assign the next multixid.  0 is not a valid datminmxid or
+        * relminmxid, so use FirstMultiXactId, as copy_xact_xlog_xid() does.
+        */
+       minmxid = old_cluster.controldata.chkpnt_nxtmulti;
+       if (minmxid < FirstMultiXactId)
+               minmxid = FirstMultiXactId;
+
        conn_template1 = connectToServer(&new_cluster, "template1");
 
        if (!minmxid_only)
@@ -953,7 +964,7 @@ set_frozenxids(bool minmxid_only)
        PQclear(executeQueryOrDie(conn_template1,
                                                          "UPDATE 
pg_catalog.pg_database "
                                                          "SET  datminmxid = 
'%u'",
-                                                         
old_cluster.controldata.chkpnt_nxtmulti));
+                                                         minmxid));
 
        /* get database names */
        dbres = executeQueryOrDie(conn_template1,
@@ -1004,7 +1015,7 @@ set_frozenxids(bool minmxid_only)
                                                                  
CppAsString2(RELKIND_RELATION) ", "
                                                                  
CppAsString2(RELKIND_MATVIEW) ", "
                                                                  
CppAsString2(RELKIND_TOASTVALUE) ")",
-                                                                 
old_cluster.controldata.chkpnt_nxtmulti));
+                                                                 minmxid));
                PQfinish(conn);
 
                /* Reset datallowconn flag */
diff --git a/src/bin/pg_upgrade/t/007_multixact_conversion.pl 
b/src/bin/pg_upgrade/t/007_multixact_conversion.pl
index 5c87c2527ee..4cc7e1bc9ad 100644
--- a/src/bin/pg_upgrade/t/007_multixact_conversion.pl
+++ b/src/bin/pg_upgrade/t/007_multixact_conversion.pl
@@ -484,4 +484,63 @@ SKIP:
        upgrade_and_compare($tag, $old, $new);
 }
 
+# Multixid wraparound at the last multixid: The newest multixid is
+# 0xFFFFFFFF, so the old cluster's next multixid is 0.  That must not
+# end up as datminmxid or relminmxid in the new cluster.
+SKIP:
+{
+       skip
+         "skipping mxid wraparound tests because upgrading from the old 
version does not require conversion"
+         if ($old_version >= '19devel');
+
+       my $tag = 'last_mxid';
+       my $old =
+         PostgreSQL::Test::Cluster->new("${tag}_oldnode",
+               install_path => $ENV{oldinstall});
+       my $new = PostgreSQL::Test::Cluster->new("${tag}_newnode");
+
+       $old->init(extra => ['-k']);
+
+       reset_mxid_mxoffset_pre_v19($old, 0xFFFFFFFF, 0x10000);
+
+       # Create one multixid with two members, a locker and an updater
+       $old->start;
+       $old->safe_psql(
+               'postgres', qq[
+               CREATE TABLE mxofftest (id INT PRIMARY KEY, n_updated INT)
+                 WITH (AUTOVACUUM_ENABLED=FALSE);
+               INSERT INTO mxofftest SELECT G, 0 FROM GENERATE_SERIES(1, 2) G;
+       ]);
+       my $locker =
+         $new->background_psql('', connstr => $old->connstr('postgres'));
+       $locker->query_safe("BEGIN");
+       $locker->query_safe("SELECT * FROM mxofftest WHERE id = 1 FOR KEY 
SHARE");
+       $old->safe_psql('postgres',
+               "UPDATE mxofftest SET n_updated = n_updated + 1 WHERE id = 1");
+       $locker->query_safe("COMMIT");
+       $locker->quit;
+       $old->stop;
+
+       my (undef, $next_mxid, undef) = read_multixid_fields($old);
+       is($next_mxid, 0, "next multixid wrapped around to 0 in old cluster");
+
+       $new->init;
+       upgrade_and_compare($tag, $old, $new);
+
+       $new->start;
+       is( $new->safe_psql(
+                       'postgres',
+                       "SELECT count(*) FROM pg_database WHERE datminmxid = 
'0'"),
+               '0',
+               'no database has an invalid datminmxid after upgrade');
+       is( $new->safe_psql(
+                       'postgres',
+                       "SELECT count(*) FROM pg_class WHERE relminmxid = '0' 
AND relkind IN ('r', 'm', 't')"
+               ),
+               '0',
+               'no table has an invalid relminmxid after upgrade');
+       $new->safe_psql('postgres', 'VACUUM');
+       $new->stop;
+}
+
 done_testing();
-- 
2.56.0

From 653ff59756aaae62ff15f611e7de5e4c3867d24c Mon Sep 17 00:00:00 2001
From: Manuel Reyes Bravo <[email protected]>
Date: Sat, 17 Oct 2026 06:00:00 -0300
Subject: [PATCH v3] pg_upgrade: Don't store an invalid datminmxid/relminmxid

If the old cluster's newest multixid is the last one before multixid
wraparound (0xFFFFFFFF), the next multixid in its control file is 0:
before v19, the server skips 0 only when it assigns the next multixid.
set_frozenxids() copied that 0 into pg_database.datminmxid and into
pg_class.relminmxid of the relations that pg_upgrade does not restore
from the old cluster, i.e. the catalogs created by initdb, in every
database.  0 is InvalidMultiXactId.  On assert-enabled builds, the next
VACUUM fails an assertion.  Otherwise template0 keeps datminmxid = 0,
for which mxid_age() reports 2147483647, until autovacuum processes it;
with autovacuum = off, it stays that way.

Use FirstMultiXactId instead.  In v19, copy_xact_xlog_xid() already
does the same for the next multixid it passes to pg_resetwal when
converting multixacts.

This is not new in v19.  Before commit 8747b969fd8, pg_upgrade to
v15-v18 failed on such a cluster, because pg_resetwal refused "-m 0";
since then, the upgrade succeeds and stores the invalid values.  v14
did not get that commit, so pg_upgrade to v14 still fails on such a
cluster and is not affected.  Since commit 87a350e1f28, v19 servers
never store 0 as the next multixid, so only pre-v19 old clusters are
affected.

Backpatch-through: 15
---
 src/bin/pg_upgrade/meson.build                |  1 +
 src/bin/pg_upgrade/pg_upgrade.c               | 16 +++-
 .../pg_upgrade/t/007_multixid_wraparound.pl   | 91 +++++++++++++++++++
 3 files changed, 106 insertions(+), 2 deletions(-)
 create mode 100644 src/bin/pg_upgrade/t/007_multixid_wraparound.pl

diff --git a/src/bin/pg_upgrade/meson.build b/src/bin/pg_upgrade/meson.build
index ac992f0d14b..7bb74be45f4 100644
--- a/src/bin/pg_upgrade/meson.build
+++ b/src/bin/pg_upgrade/meson.build
@@ -47,6 +47,7 @@ tests += {
       't/004_subscription.pl',
       't/005_char_signedness.pl',
       't/006_transfer_modes.pl',
+      't/007_multixid_wraparound.pl',
     ],
     'test_kwargs': {'priority': 40}, # pg_upgrade tests are slow
   },
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 536e49d2616..62113c1551e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -43,6 +43,7 @@
 
 #include <time.h>
 
+#include "access/multixact.h"
 #include "catalog/pg_class_d.h"
 #include "common/file_perm.h"
 #include "common/logging.h"
@@ -881,12 +882,23 @@ set_frozenxids(bool minmxid_only)
        int                     ntups;
        int                     i_datname;
        int                     i_datallowconn;
+       MultiXactId minmxid;
 
        if (!minmxid_only)
                prep_status("Setting frozenxid and minmxid counters in new 
cluster");
        else
                prep_status("Setting minmxid counter in new cluster");
 
+       /*
+        * If the old cluster's newest multixid was the last one before
+        * wraparound, its next multixid is 0: the server skips 0 only when it
+        * assigns the next multixid.  0 is not a valid datminmxid or
+        * relminmxid, so use FirstMultiXactId, the next multixid assigned.
+        */
+       minmxid = old_cluster.controldata.chkpnt_nxtmulti;
+       if (minmxid < FirstMultiXactId)
+               minmxid = FirstMultiXactId;
+
        conn_template1 = connectToServer(&new_cluster, "template1");
 
        if (!minmxid_only)
@@ -900,7 +912,7 @@ set_frozenxids(bool minmxid_only)
        PQclear(executeQueryOrDie(conn_template1,
                                                          "UPDATE 
pg_catalog.pg_database "
                                                          "SET  datminmxid = 
'%u'",
-                                                         
old_cluster.controldata.chkpnt_nxtmulti));
+                                                         minmxid));
 
        /* get database names */
        dbres = executeQueryOrDie(conn_template1,
@@ -951,7 +963,7 @@ set_frozenxids(bool minmxid_only)
                                                                  
CppAsString2(RELKIND_RELATION) ", "
                                                                  
CppAsString2(RELKIND_MATVIEW) ", "
                                                                  
CppAsString2(RELKIND_TOASTVALUE) ")",
-                                                                 
old_cluster.controldata.chkpnt_nxtmulti));
+                                                                 minmxid));
                PQfinish(conn);
 
                /* Reset datallowconn flag */
diff --git a/src/bin/pg_upgrade/t/007_multixid_wraparound.pl 
b/src/bin/pg_upgrade/t/007_multixid_wraparound.pl
new file mode 100644
index 00000000000..4ebd45722ed
--- /dev/null
+++ b/src/bin/pg_upgrade/t/007_multixid_wraparound.pl
@@ -0,0 +1,91 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Upgrade a cluster whose newest multixid is the last one before
+# multixid wraparound (0xFFFFFFFF), so that the next multixid in its
+# control file is 0.  That must not end up as datminmxid or relminmxid
+# in the new cluster.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Run pg_upgrade in the build directory, see 002_pg_upgrade.pl.
+chdir ${PostgreSQL::Test::Utils::tmp_check};
+
+my $old = PostgreSQL::Test::Cluster->new('old_node');
+my $new = PostgreSQL::Test::Cluster->new('new_node');
+
+$old->init;
+$old->append_conf('postgresql.conf', 'max_prepared_transactions = 2');
+
+$old->start;
+my $blcksz = $old->safe_psql('postgres', 'SHOW block_size');
+$old->stop;
+
+# Set the next multixid to 0xFFFFFFFF.  The server refuses to start if
+# the 'offsets' SLRU segment covering it doesn't exist, so replace the
+# segments with one that covers it, filled with zeros.
+command_ok(
+       [ 'pg_resetwal', '-m', '0xFFFFFFFF,0xFFFFFFFF', $old->data_dir ],
+       'set next multixid to 0xFFFFFFFF');
+unlink glob $old->data_dir . "/pg_multixact/offsets/*";
+my $segsize = 32 * $blcksz;
+my $path = sprintf('%s/pg_multixact/offsets/%04X',
+       $old->data_dir, int(0xFFFFFFFF / ($blcksz / 4) / 32));
+open my $fh, '>', $path or die "could not open \"$path\": $!";
+binmode $fh;
+syswrite($fh, "\0" x $segsize) == $segsize
+  or die "could not write to \"$path\": $!";
+close $fh;
+
+# Create one multixid: two prepared transactions holding KEY SHARE locks
+# on the same row.
+$old->start;
+$old->safe_psql(
+       'postgres', q[
+       CREATE TABLE mxtest (id int PRIMARY KEY) WITH (autovacuum_enabled = 
false);
+       INSERT INTO mxtest VALUES (1);
+       BEGIN; SELECT * FROM mxtest FOR KEY SHARE; PREPARE TRANSACTION 'mx1';
+       BEGIN; SELECT * FROM mxtest FOR KEY SHARE; PREPARE TRANSACTION 'mx2';
+       COMMIT PREPARED 'mx1';
+       COMMIT PREPARED 'mx2';
+]);
+$old->stop;
+
+command_like(
+       [ 'pg_controldata', $old->data_dir ],
+       qr/^Latest checkpoint's NextMultiXactId:\s*0$/m,
+       'next multixid wrapped around to 0 in old cluster');
+
+$new->init;
+command_ok(
+       [
+               'pg_upgrade', '--no-sync',
+               '-d', $old->data_dir,
+               '-D', $new->data_dir,
+               '-b', $old->config_data('--bindir'),
+               '-B', $new->config_data('--bindir'),
+               '-s', $new->host,
+               '-p', $old->port,
+               '-P', $new->port
+       ],
+       'run of pg_upgrade');
+
+$new->start;
+is( $new->safe_psql(
+               'postgres', "SELECT count(*) FROM pg_database WHERE datminmxid 
= '0'"),
+       '0',
+       'no database has an invalid datminmxid after upgrade');
+is( $new->safe_psql(
+               'postgres',
+               "SELECT count(*) FROM pg_class WHERE relminmxid = '0' AND 
relkind IN ('r', 'm', 't')"
+       ),
+       '0',
+       'no table has an invalid relminmxid after upgrade');
+$new->safe_psql('postgres', 'VACUUM');
+$new->stop;
+
+done_testing();
-- 
2.56.0

From c3b7960974ad20dd4d041cf8ffc7ac690bfe53b6 Mon Sep 17 00:00:00 2001
From: Manuel Reyes Bravo <[email protected]>
Date: Sat, 17 Oct 2026 06:00:00 -0300
Subject: [PATCH v3] pg_upgrade: Don't store an invalid datminmxid/relminmxid

If the old cluster's newest multixid is the last one before multixid
wraparound (0xFFFFFFFF), the next multixid in its control file is 0:
before v19, the server skips 0 only when it assigns the next multixid.
set_frozenxids() copied that 0 into pg_database.datminmxid and into
pg_class.relminmxid of the relations that pg_upgrade does not restore
from the old cluster, i.e. the catalogs created by initdb, in every
database.  0 is InvalidMultiXactId.  On assert-enabled builds, the next
VACUUM fails an assertion.  Otherwise template0 keeps datminmxid = 0,
for which mxid_age() reports 2147483647, until autovacuum processes it;
with autovacuum = off, it stays that way.

Use FirstMultiXactId instead.  In v19, copy_xact_xlog_xid() already
does the same for the next multixid it passes to pg_resetwal when
converting multixacts.

This is not new in v19.  Before commit 8747b969fd8, pg_upgrade to
v15-v18 failed on such a cluster, because pg_resetwal refused "-m 0";
since then, the upgrade succeeds and stores the invalid values.  v14
did not get that commit, so pg_upgrade to v14 still fails on such a
cluster and is not affected.  Since commit 87a350e1f28, v19 servers
never store 0 as the next multixid, so only pre-v19 old clusters are
affected.

Backpatch-through: 15
---
 src/bin/pg_upgrade/meson.build                |  1 +
 src/bin/pg_upgrade/pg_upgrade.c               | 16 +++-
 .../pg_upgrade/t/005_multixid_wraparound.pl   | 91 +++++++++++++++++++
 3 files changed, 106 insertions(+), 2 deletions(-)
 create mode 100644 src/bin/pg_upgrade/t/005_multixid_wraparound.pl

diff --git a/src/bin/pg_upgrade/meson.build b/src/bin/pg_upgrade/meson.build
index 9825fa33054..0406812ab0e 100644
--- a/src/bin/pg_upgrade/meson.build
+++ b/src/bin/pg_upgrade/meson.build
@@ -44,6 +44,7 @@ tests += {
       't/002_pg_upgrade.pl',
       't/003_logical_slots.pl',
       't/004_subscription.pl',
+      't/005_multixid_wraparound.pl',
     ],
     'test_kwargs': {'priority': 40}, # pg_upgrade tests are slow
   },
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index bb5a223e1c6..3991ab966e3 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -44,6 +44,7 @@
 #include <langinfo.h>
 #endif
 
+#include "access/multixact.h"
 #include "catalog/pg_class_d.h"
 #include "common/file_perm.h"
 #include "common/logging.h"
@@ -833,12 +834,23 @@ set_frozenxids(bool minmxid_only)
        int                     ntups;
        int                     i_datname;
        int                     i_datallowconn;
+       MultiXactId minmxid;
 
        if (!minmxid_only)
                prep_status("Setting frozenxid and minmxid counters in new 
cluster");
        else
                prep_status("Setting minmxid counter in new cluster");
 
+       /*
+        * If the old cluster's newest multixid was the last one before
+        * wraparound, its next multixid is 0: the server skips 0 only when it
+        * assigns the next multixid.  0 is not a valid datminmxid or
+        * relminmxid, so use FirstMultiXactId, the next multixid assigned.
+        */
+       minmxid = old_cluster.controldata.chkpnt_nxtmulti;
+       if (minmxid < FirstMultiXactId)
+               minmxid = FirstMultiXactId;
+
        conn_template1 = connectToServer(&new_cluster, "template1");
 
        if (!minmxid_only)
@@ -852,7 +864,7 @@ set_frozenxids(bool minmxid_only)
        PQclear(executeQueryOrDie(conn_template1,
                                                          "UPDATE 
pg_catalog.pg_database "
                                                          "SET  datminmxid = 
'%u'",
-                                                         
old_cluster.controldata.chkpnt_nxtmulti));
+                                                         minmxid));
 
        /* get database names */
        dbres = executeQueryOrDie(conn_template1,
@@ -903,7 +915,7 @@ set_frozenxids(bool minmxid_only)
                                                                  
CppAsString2(RELKIND_RELATION) ", "
                                                                  
CppAsString2(RELKIND_MATVIEW) ", "
                                                                  
CppAsString2(RELKIND_TOASTVALUE) ")",
-                                                                 
old_cluster.controldata.chkpnt_nxtmulti));
+                                                                 minmxid));
                PQfinish(conn);
 
                /* Reset datallowconn flag */
diff --git a/src/bin/pg_upgrade/t/005_multixid_wraparound.pl 
b/src/bin/pg_upgrade/t/005_multixid_wraparound.pl
new file mode 100644
index 00000000000..4ebd45722ed
--- /dev/null
+++ b/src/bin/pg_upgrade/t/005_multixid_wraparound.pl
@@ -0,0 +1,91 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Upgrade a cluster whose newest multixid is the last one before
+# multixid wraparound (0xFFFFFFFF), so that the next multixid in its
+# control file is 0.  That must not end up as datminmxid or relminmxid
+# in the new cluster.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Run pg_upgrade in the build directory, see 002_pg_upgrade.pl.
+chdir ${PostgreSQL::Test::Utils::tmp_check};
+
+my $old = PostgreSQL::Test::Cluster->new('old_node');
+my $new = PostgreSQL::Test::Cluster->new('new_node');
+
+$old->init;
+$old->append_conf('postgresql.conf', 'max_prepared_transactions = 2');
+
+$old->start;
+my $blcksz = $old->safe_psql('postgres', 'SHOW block_size');
+$old->stop;
+
+# Set the next multixid to 0xFFFFFFFF.  The server refuses to start if
+# the 'offsets' SLRU segment covering it doesn't exist, so replace the
+# segments with one that covers it, filled with zeros.
+command_ok(
+       [ 'pg_resetwal', '-m', '0xFFFFFFFF,0xFFFFFFFF', $old->data_dir ],
+       'set next multixid to 0xFFFFFFFF');
+unlink glob $old->data_dir . "/pg_multixact/offsets/*";
+my $segsize = 32 * $blcksz;
+my $path = sprintf('%s/pg_multixact/offsets/%04X',
+       $old->data_dir, int(0xFFFFFFFF / ($blcksz / 4) / 32));
+open my $fh, '>', $path or die "could not open \"$path\": $!";
+binmode $fh;
+syswrite($fh, "\0" x $segsize) == $segsize
+  or die "could not write to \"$path\": $!";
+close $fh;
+
+# Create one multixid: two prepared transactions holding KEY SHARE locks
+# on the same row.
+$old->start;
+$old->safe_psql(
+       'postgres', q[
+       CREATE TABLE mxtest (id int PRIMARY KEY) WITH (autovacuum_enabled = 
false);
+       INSERT INTO mxtest VALUES (1);
+       BEGIN; SELECT * FROM mxtest FOR KEY SHARE; PREPARE TRANSACTION 'mx1';
+       BEGIN; SELECT * FROM mxtest FOR KEY SHARE; PREPARE TRANSACTION 'mx2';
+       COMMIT PREPARED 'mx1';
+       COMMIT PREPARED 'mx2';
+]);
+$old->stop;
+
+command_like(
+       [ 'pg_controldata', $old->data_dir ],
+       qr/^Latest checkpoint's NextMultiXactId:\s*0$/m,
+       'next multixid wrapped around to 0 in old cluster');
+
+$new->init;
+command_ok(
+       [
+               'pg_upgrade', '--no-sync',
+               '-d', $old->data_dir,
+               '-D', $new->data_dir,
+               '-b', $old->config_data('--bindir'),
+               '-B', $new->config_data('--bindir'),
+               '-s', $new->host,
+               '-p', $old->port,
+               '-P', $new->port
+       ],
+       'run of pg_upgrade');
+
+$new->start;
+is( $new->safe_psql(
+               'postgres', "SELECT count(*) FROM pg_database WHERE datminmxid 
= '0'"),
+       '0',
+       'no database has an invalid datminmxid after upgrade');
+is( $new->safe_psql(
+               'postgres',
+               "SELECT count(*) FROM pg_class WHERE relminmxid = '0' AND 
relkind IN ('r', 'm', 't')"
+       ),
+       '0',
+       'no table has an invalid relminmxid after upgrade');
+$new->safe_psql('postgres', 'VACUUM');
+$new->stop;
+
+done_testing();
-- 
2.56.0

From a06c94c70ba686f1ecb11917930d29cac2937bed Mon Sep 17 00:00:00 2001
From: Manuel Reyes Bravo <[email protected]>
Date: Sat, 17 Oct 2026 06:00:00 -0300
Subject: [PATCH v3] pg_upgrade: Don't store an invalid datminmxid/relminmxid

If the old cluster's newest multixid is the last one before multixid
wraparound (0xFFFFFFFF), the next multixid in its control file is 0:
before v19, the server skips 0 only when it assigns the next multixid.
set_frozenxids() copied that 0 into pg_database.datminmxid and into
pg_class.relminmxid of the relations that pg_upgrade does not restore
from the old cluster, i.e. the catalogs created by initdb, in every
database.  0 is InvalidMultiXactId.  On assert-enabled builds, the next
VACUUM fails an assertion.  Otherwise template0 keeps datminmxid = 0,
for which mxid_age() reports 2147483647, until autovacuum processes it;
with autovacuum = off, it stays that way.

Use FirstMultiXactId instead.  In v19, copy_xact_xlog_xid() already
does the same for the next multixid it passes to pg_resetwal when
converting multixacts.

This is not new in v19.  Before commit 8747b969fd8, pg_upgrade to
v15-v18 failed on such a cluster, because pg_resetwal refused "-m 0";
since then, the upgrade succeeds and stores the invalid values.  v14
did not get that commit, so pg_upgrade to v14 still fails on such a
cluster and is not affected.  Since commit 87a350e1f28, v19 servers
never store 0 as the next multixid, so only pre-v19 old clusters are
affected.

Backpatch-through: 15
---
 src/bin/pg_upgrade/meson.build                |  1 +
 src/bin/pg_upgrade/pg_upgrade.c               | 16 +++-
 .../pg_upgrade/t/003_multixid_wraparound.pl   | 91 +++++++++++++++++++
 3 files changed, 106 insertions(+), 2 deletions(-)
 create mode 100644 src/bin/pg_upgrade/t/003_multixid_wraparound.pl

diff --git a/src/bin/pg_upgrade/meson.build b/src/bin/pg_upgrade/meson.build
index 12a97f84e29..a798259b517 100644
--- a/src/bin/pg_upgrade/meson.build
+++ b/src/bin/pg_upgrade/meson.build
@@ -42,6 +42,7 @@ tests += {
     'tests': [
       't/001_basic.pl',
       't/002_pg_upgrade.pl',
+      't/003_multixid_wraparound.pl',
     ],
     'test_kwargs': {'priority': 40}, # pg_upgrade tests are slow
   },
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 6359109c721..a7cd33febbe 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -44,6 +44,7 @@
 #include <langinfo.h>
 #endif
 
+#include "access/multixact.h"
 #include "catalog/pg_class_d.h"
 #include "common/file_perm.h"
 #include "common/logging.h"
@@ -776,12 +777,23 @@ set_frozenxids(bool minmxid_only)
        int                     ntups;
        int                     i_datname;
        int                     i_datallowconn;
+       MultiXactId minmxid;
 
        if (!minmxid_only)
                prep_status("Setting frozenxid and minmxid counters in new 
cluster");
        else
                prep_status("Setting minmxid counter in new cluster");
 
+       /*
+        * If the old cluster's newest multixid was the last one before
+        * wraparound, its next multixid is 0: the server skips 0 only when it
+        * assigns the next multixid.  0 is not a valid datminmxid or
+        * relminmxid, so use FirstMultiXactId, the next multixid assigned.
+        */
+       minmxid = old_cluster.controldata.chkpnt_nxtmulti;
+       if (minmxid < FirstMultiXactId)
+               minmxid = FirstMultiXactId;
+
        conn_template1 = connectToServer(&new_cluster, "template1");
 
        if (!minmxid_only)
@@ -795,7 +807,7 @@ set_frozenxids(bool minmxid_only)
        PQclear(executeQueryOrDie(conn_template1,
                                                          "UPDATE 
pg_catalog.pg_database "
                                                          "SET  datminmxid = 
'%u'",
-                                                         
old_cluster.controldata.chkpnt_nxtmulti));
+                                                         minmxid));
 
        /* get database names */
        dbres = executeQueryOrDie(conn_template1,
@@ -846,7 +858,7 @@ set_frozenxids(bool minmxid_only)
                                                                  
CppAsString2(RELKIND_RELATION) ", "
                                                                  
CppAsString2(RELKIND_MATVIEW) ", "
                                                                  
CppAsString2(RELKIND_TOASTVALUE) ")",
-                                                                 
old_cluster.controldata.chkpnt_nxtmulti));
+                                                                 minmxid));
                PQfinish(conn);
 
                /* Reset datallowconn flag */
diff --git a/src/bin/pg_upgrade/t/003_multixid_wraparound.pl 
b/src/bin/pg_upgrade/t/003_multixid_wraparound.pl
new file mode 100644
index 00000000000..4ebd45722ed
--- /dev/null
+++ b/src/bin/pg_upgrade/t/003_multixid_wraparound.pl
@@ -0,0 +1,91 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Upgrade a cluster whose newest multixid is the last one before
+# multixid wraparound (0xFFFFFFFF), so that the next multixid in its
+# control file is 0.  That must not end up as datminmxid or relminmxid
+# in the new cluster.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Run pg_upgrade in the build directory, see 002_pg_upgrade.pl.
+chdir ${PostgreSQL::Test::Utils::tmp_check};
+
+my $old = PostgreSQL::Test::Cluster->new('old_node');
+my $new = PostgreSQL::Test::Cluster->new('new_node');
+
+$old->init;
+$old->append_conf('postgresql.conf', 'max_prepared_transactions = 2');
+
+$old->start;
+my $blcksz = $old->safe_psql('postgres', 'SHOW block_size');
+$old->stop;
+
+# Set the next multixid to 0xFFFFFFFF.  The server refuses to start if
+# the 'offsets' SLRU segment covering it doesn't exist, so replace the
+# segments with one that covers it, filled with zeros.
+command_ok(
+       [ 'pg_resetwal', '-m', '0xFFFFFFFF,0xFFFFFFFF', $old->data_dir ],
+       'set next multixid to 0xFFFFFFFF');
+unlink glob $old->data_dir . "/pg_multixact/offsets/*";
+my $segsize = 32 * $blcksz;
+my $path = sprintf('%s/pg_multixact/offsets/%04X',
+       $old->data_dir, int(0xFFFFFFFF / ($blcksz / 4) / 32));
+open my $fh, '>', $path or die "could not open \"$path\": $!";
+binmode $fh;
+syswrite($fh, "\0" x $segsize) == $segsize
+  or die "could not write to \"$path\": $!";
+close $fh;
+
+# Create one multixid: two prepared transactions holding KEY SHARE locks
+# on the same row.
+$old->start;
+$old->safe_psql(
+       'postgres', q[
+       CREATE TABLE mxtest (id int PRIMARY KEY) WITH (autovacuum_enabled = 
false);
+       INSERT INTO mxtest VALUES (1);
+       BEGIN; SELECT * FROM mxtest FOR KEY SHARE; PREPARE TRANSACTION 'mx1';
+       BEGIN; SELECT * FROM mxtest FOR KEY SHARE; PREPARE TRANSACTION 'mx2';
+       COMMIT PREPARED 'mx1';
+       COMMIT PREPARED 'mx2';
+]);
+$old->stop;
+
+command_like(
+       [ 'pg_controldata', $old->data_dir ],
+       qr/^Latest checkpoint's NextMultiXactId:\s*0$/m,
+       'next multixid wrapped around to 0 in old cluster');
+
+$new->init;
+command_ok(
+       [
+               'pg_upgrade', '--no-sync',
+               '-d', $old->data_dir,
+               '-D', $new->data_dir,
+               '-b', $old->config_data('--bindir'),
+               '-B', $new->config_data('--bindir'),
+               '-s', $new->host,
+               '-p', $old->port,
+               '-P', $new->port
+       ],
+       'run of pg_upgrade');
+
+$new->start;
+is( $new->safe_psql(
+               'postgres', "SELECT count(*) FROM pg_database WHERE datminmxid 
= '0'"),
+       '0',
+       'no database has an invalid datminmxid after upgrade');
+is( $new->safe_psql(
+               'postgres',
+               "SELECT count(*) FROM pg_class WHERE relminmxid = '0' AND 
relkind IN ('r', 'm', 't')"
+       ),
+       '0',
+       'no table has an invalid relminmxid after upgrade');
+$new->safe_psql('postgres', 'VACUUM');
+$new->stop;
+
+done_testing();
-- 
2.56.0

From e8e794b0ef1c2165081cc0ad87edfe16c40badd1 Mon Sep 17 00:00:00 2001
From: Manuel Reyes Bravo <[email protected]>
Date: Sat, 17 Oct 2026 06:00:00 -0300
Subject: [PATCH v3] pg_upgrade: Don't store an invalid datminmxid/relminmxid

If the old cluster's newest multixid is the last one before multixid
wraparound (0xFFFFFFFF), the next multixid in its control file is 0:
before v19, the server skips 0 only when it assigns the next multixid.
set_frozenxids() copied that 0 into pg_database.datminmxid and into
pg_class.relminmxid of the relations that pg_upgrade does not restore
from the old cluster, i.e. the catalogs created by initdb, in every
database.  0 is InvalidMultiXactId.  On assert-enabled builds, the next
VACUUM fails an assertion.  Otherwise template0 keeps datminmxid = 0,
for which mxid_age() reports 2147483647, until autovacuum processes it;
with autovacuum = off, it stays that way.

Use FirstMultiXactId instead.  In v19, copy_xact_xlog_xid() already
does the same for the next multixid it passes to pg_resetwal when
converting multixacts.

This is not new in v19.  Before commit 8747b969fd8, pg_upgrade to
v15-v18 failed on such a cluster, because pg_resetwal refused "-m 0";
since then, the upgrade succeeds and stores the invalid values.  v14
did not get that commit, so pg_upgrade to v14 still fails on such a
cluster and is not affected.  Since commit 87a350e1f28, v19 servers
never store 0 as the next multixid, so only pre-v19 old clusters are
affected.

Backpatch-through: 15
---
 src/bin/pg_upgrade/pg_upgrade.c               | 16 +++-
 .../pg_upgrade/t/003_multixid_wraparound.pl   | 91 +++++++++++++++++++
 2 files changed, 105 insertions(+), 2 deletions(-)
 create mode 100644 src/bin/pg_upgrade/t/003_multixid_wraparound.pl

diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 265d8294906..15a33e5af1e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -44,6 +44,7 @@
 #include <langinfo.h>
 #endif
 
+#include "access/multixact.h"
 #include "catalog/pg_class_d.h"
 #include "common/file_perm.h"
 #include "common/logging.h"
@@ -697,12 +698,23 @@ set_frozenxids(bool minmxid_only)
        int                     ntups;
        int                     i_datname;
        int                     i_datallowconn;
+       MultiXactId minmxid;
 
        if (!minmxid_only)
                prep_status("Setting frozenxid and minmxid counters in new 
cluster");
        else
                prep_status("Setting minmxid counter in new cluster");
 
+       /*
+        * If the old cluster's newest multixid was the last one before
+        * wraparound, its next multixid is 0: the server skips 0 only when it
+        * assigns the next multixid.  0 is not a valid datminmxid or
+        * relminmxid, so use FirstMultiXactId, the next multixid assigned.
+        */
+       minmxid = old_cluster.controldata.chkpnt_nxtmulti;
+       if (minmxid < FirstMultiXactId)
+               minmxid = FirstMultiXactId;
+
        conn_template1 = connectToServer(&new_cluster, "template1");
 
        if (!minmxid_only)
@@ -716,7 +728,7 @@ set_frozenxids(bool minmxid_only)
        PQclear(executeQueryOrDie(conn_template1,
                                                          "UPDATE 
pg_catalog.pg_database "
                                                          "SET  datminmxid = 
'%u'",
-                                                         
old_cluster.controldata.chkpnt_nxtmulti));
+                                                         minmxid));
 
        /* get database names */
        dbres = executeQueryOrDie(conn_template1,
@@ -767,7 +779,7 @@ set_frozenxids(bool minmxid_only)
                                                                  
CppAsString2(RELKIND_RELATION) ", "
                                                                  
CppAsString2(RELKIND_MATVIEW) ", "
                                                                  
CppAsString2(RELKIND_TOASTVALUE) ")",
-                                                                 
old_cluster.controldata.chkpnt_nxtmulti));
+                                                                 minmxid));
                PQfinish(conn);
 
                /* Reset datallowconn flag */
diff --git a/src/bin/pg_upgrade/t/003_multixid_wraparound.pl 
b/src/bin/pg_upgrade/t/003_multixid_wraparound.pl
new file mode 100644
index 00000000000..4ebd45722ed
--- /dev/null
+++ b/src/bin/pg_upgrade/t/003_multixid_wraparound.pl
@@ -0,0 +1,91 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Upgrade a cluster whose newest multixid is the last one before
+# multixid wraparound (0xFFFFFFFF), so that the next multixid in its
+# control file is 0.  That must not end up as datminmxid or relminmxid
+# in the new cluster.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Run pg_upgrade in the build directory, see 002_pg_upgrade.pl.
+chdir ${PostgreSQL::Test::Utils::tmp_check};
+
+my $old = PostgreSQL::Test::Cluster->new('old_node');
+my $new = PostgreSQL::Test::Cluster->new('new_node');
+
+$old->init;
+$old->append_conf('postgresql.conf', 'max_prepared_transactions = 2');
+
+$old->start;
+my $blcksz = $old->safe_psql('postgres', 'SHOW block_size');
+$old->stop;
+
+# Set the next multixid to 0xFFFFFFFF.  The server refuses to start if
+# the 'offsets' SLRU segment covering it doesn't exist, so replace the
+# segments with one that covers it, filled with zeros.
+command_ok(
+       [ 'pg_resetwal', '-m', '0xFFFFFFFF,0xFFFFFFFF', $old->data_dir ],
+       'set next multixid to 0xFFFFFFFF');
+unlink glob $old->data_dir . "/pg_multixact/offsets/*";
+my $segsize = 32 * $blcksz;
+my $path = sprintf('%s/pg_multixact/offsets/%04X',
+       $old->data_dir, int(0xFFFFFFFF / ($blcksz / 4) / 32));
+open my $fh, '>', $path or die "could not open \"$path\": $!";
+binmode $fh;
+syswrite($fh, "\0" x $segsize) == $segsize
+  or die "could not write to \"$path\": $!";
+close $fh;
+
+# Create one multixid: two prepared transactions holding KEY SHARE locks
+# on the same row.
+$old->start;
+$old->safe_psql(
+       'postgres', q[
+       CREATE TABLE mxtest (id int PRIMARY KEY) WITH (autovacuum_enabled = 
false);
+       INSERT INTO mxtest VALUES (1);
+       BEGIN; SELECT * FROM mxtest FOR KEY SHARE; PREPARE TRANSACTION 'mx1';
+       BEGIN; SELECT * FROM mxtest FOR KEY SHARE; PREPARE TRANSACTION 'mx2';
+       COMMIT PREPARED 'mx1';
+       COMMIT PREPARED 'mx2';
+]);
+$old->stop;
+
+command_like(
+       [ 'pg_controldata', $old->data_dir ],
+       qr/^Latest checkpoint's NextMultiXactId:\s*0$/m,
+       'next multixid wrapped around to 0 in old cluster');
+
+$new->init;
+command_ok(
+       [
+               'pg_upgrade', '--no-sync',
+               '-d', $old->data_dir,
+               '-D', $new->data_dir,
+               '-b', $old->config_data('--bindir'),
+               '-B', $new->config_data('--bindir'),
+               '-s', $new->host,
+               '-p', $old->port,
+               '-P', $new->port
+       ],
+       'run of pg_upgrade');
+
+$new->start;
+is( $new->safe_psql(
+               'postgres', "SELECT count(*) FROM pg_database WHERE datminmxid 
= '0'"),
+       '0',
+       'no database has an invalid datminmxid after upgrade');
+is( $new->safe_psql(
+               'postgres',
+               "SELECT count(*) FROM pg_class WHERE relminmxid = '0' AND 
relkind IN ('r', 'm', 't')"
+       ),
+       '0',
+       'no table has an invalid relminmxid after upgrade');
+$new->safe_psql('postgres', 'VACUUM');
+$new->stop;
+
+done_testing();
-- 
2.56.0

Reply via email to