Hello,

I'm attaching a v2 with rebase and adding Niccolo Fei as one of the
reviewers.

Thank you!


-- 
Jonathan Gonzalez V. <[email protected]>
EnterpriseDB
From fef92b2baa38edc0d8c545e3fe60a5803268864a Mon Sep 17 00:00:00 2001
From: "Jonathan Gonzalez V." <[email protected]>
Date: Mon, 23 Feb 2026 22:27:51 +0100
Subject: [PATCH v2 1/1] Strip `$libdir` during pg_upgrade starting on 19.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

With the commit 4f7f7b03758 extension_control_path GUC was included,
while some test were added, is missing to handle the hardcoded `$libdir/`
path during the execution of `pg_upgrade` for the the installed
extensions using the extension_control_path GUC

An aditional test for `pg_upgrade` is added to test the upgrade with
the extension_control_path in use with a C extension using the
hardcoded `$libdir/` string in the `module_pathname`

Reviewed-by: Niccolò Fei <[email protected]>
Signed-off-by: Jonathan Gonzalez V. <[email protected]>
---
 src/bin/pg_upgrade/Makefile                   |   6 +-
 src/bin/pg_upgrade/function.c                 |   9 ++
 src/bin/pg_upgrade/meson.build                |  21 ++-
 .../t/008_extension_control_path.pl           | 126 ++++++++++++++++++
 src/test/modules/test_extensions/Makefile     |   3 +
 src/test/modules/test_extensions/meson.build  |  13 ++
 src/test/modules/test_extensions/test_ext.c   |  22 +++
 7 files changed, 198 insertions(+), 2 deletions(-)
 create mode 100644 src/bin/pg_upgrade/t/008_extension_control_path.pl
 create mode 100644 src/test/modules/test_extensions/test_ext.c

diff --git a/src/bin/pg_upgrade/Makefile b/src/bin/pg_upgrade/Makefile
index 726df4b7525..771addb675a 100644
--- a/src/bin/pg_upgrade/Makefile
+++ b/src/bin/pg_upgrade/Makefile
@@ -3,7 +3,7 @@
 PGFILEDESC = "pg_upgrade - an in-place binary upgrade utility"
 PGAPPICON = win32
 
-EXTRA_INSTALL=contrib/test_decoding src/test/modules/dummy_seclabel
+EXTRA_INSTALL=contrib/test_decoding src/test/modules/dummy_seclabel src/test/modules/test_extensions
 
 subdir = src/bin/pg_upgrade
 top_builddir = ../../..
@@ -38,6 +38,10 @@ LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
 REGRESS_SHLIB=$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)
 export REGRESS_SHLIB
 
+# required for 008_extension_control_path.pl
+TEST_EXT_LIB=$(abs_top_builddir)/src/test/modules/test_extensions/test_ext$(DLSUFFIX)
+export TEST_EXT_LIB
+
 all: pg_upgrade
 
 pg_upgrade: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index a3184f95665..461b7b2c20c 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -120,6 +120,15 @@ get_loadable_libraries(void)
 		{
 			char	   *lib = PQgetvalue(res, rowno, 0);
 
+			/*
+			 * Starting on version 18, the extension may be loaded using
+			 * extension_control_path, some extensions have the `$libdir/`
+			 * hardcoded, we should remove it to allow the upgrade to version
+			 * 19 or above.
+			 */
+			if (strncmp(lib, "$libdir/", 8) == 0)
+				lib += 8;
+
 			os_info.libraries[totaltups].name = pg_strdup(lib);
 			os_info.libraries[totaltups].dbnum = dbnum;
 
diff --git a/src/bin/pg_upgrade/meson.build b/src/bin/pg_upgrade/meson.build
index 49b1b624f25..ffbf6ae8d75 100644
--- a/src/bin/pg_upgrade/meson.build
+++ b/src/bin/pg_upgrade/meson.build
@@ -36,13 +36,30 @@ pg_upgrade = executable('pg_upgrade',
 )
 bin_targets += pg_upgrade
 
+test_ext_sources = files(
+    '../../test/modules/test_extensions/test_ext.c'
+)
+
+if host_system == 'windows'
+  test_ext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ext',
+    '--FILEDESC', 'test_ext - test C extension for pg_upgrade',])
+endif
+
+test_ext = shared_module('test_ext',
+  test_ext_sources,
+  kwargs: pg_test_mod_args,
+)
 
 tests += {
   'name': 'pg_upgrade',
   'sd': meson.current_source_dir(),
   'bd': meson.current_build_dir(),
   'tap': {
-    'env': {'with_icu': icu.found() ? 'yes' : 'no'},
+    'env': {
+      'with_icu': icu.found() ? 'yes' : 'no',
+      'TEST_EXT_LIB': test_ext.full_path(),
+    },
     'tests': [
       't/001_basic.pl',
       't/002_pg_upgrade.pl',
@@ -51,7 +68,9 @@ tests += {
       't/005_char_signedness.pl',
       't/006_transfer_modes.pl',
       't/007_multixact_conversion.pl',
+      't/008_extension_control_path.pl',
     ],
+    'deps': [test_ext],
     'test_kwargs': {'priority': 40}, # pg_upgrade tests are slow
   },
 }
diff --git a/src/bin/pg_upgrade/t/008_extension_control_path.pl b/src/bin/pg_upgrade/t/008_extension_control_path.pl
new file mode 100644
index 00000000000..b50e92326ca
--- /dev/null
+++ b/src/bin/pg_upgrade/t/008_extension_control_path.pl
@@ -0,0 +1,126 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test pg_upgrade with the extension_control_path GUC active.
+
+use strict;
+use warnings FATAL => 'all';
+
+use File::Copy;
+use File::Path;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Make sure the extension file .so path is provided
+my $ext_lib_so = $ENV{TEST_EXT_LIB}
+	or die "couldn't get the extension so path";
+
+# Create the custom extension directory layout:
+#   $ext_dir/extension/  -- .control and .sql files
+#   $ext_dir/lib/        -- .so file
+my $ext_dir = PostgreSQL::Test::Utils::tempdir();
+mkpath("$ext_dir/extension");
+mkpath("$ext_dir/lib");
+my $ext_lib = $ext_dir . '/lib';
+
+# Copy the .so file into the lib/ subdirectory.
+copy($ext_lib_so, $ext_lib)
+  or die "could not copy '$ext_lib_so' to '$ext_lib': $!";
+
+create_extension_files('test_ext', $ext_dir);
+
+my $sep = $windows_os ? ";" : ":";
+my $ext_path = $windows_os ? ($ext_dir =~ s/\\/\\\\/gr) : $ext_dir;
+my $ext_lib_path = $windows_os ? ($ext_lib =~ s/\\/\\\\/gr) : $ext_lib;
+
+my $extension_control_path_conf = qq(
+extension_control_path = '\$system$sep$ext_path'
+dynamic_library_path = '\$libdir$sep$ext_lib_path'
+);
+
+my $old =
+  PostgreSQL::Test::Cluster->new('old', install_path => $ENV{oldinstall});
+$old->init;
+
+# Configure extension_control_path so the .control file is found in our
+# extension/ directory, and dynamic_library_path so the .so is found in lib/.
+$old->append_conf('postgresql.conf', $extension_control_path_conf);
+
+$old->start;
+
+# CREATE EXTENSION 'test_ext'
+$old->safe_psql('postgres', 'CREATE EXTENSION test_ext');
+
+# Verify the extension works before the upgrade.
+my ($code, $stdout, $stderr) = $old->psql('postgres', 'SELECT test_ext()');
+is($code, 0, 'extension works before upgrade');
+like($stderr, qr/NOTICE:  running successful/, 'extension working');
+
+$old->stop;
+
+my $new = PostgreSQL::Test::Cluster->new('new');
+$new->init;
+
+# Pre-configure the new cluster with dynamic_library_path and
+# extension_control_path before running pg_upgrade.
+$new->append_conf('postgresql.conf', $extension_control_path_conf);
+
+# In a VPATH build, we'll be started in the source directory, but we want
+# to run pg_upgrade in the build directory so that any files generated finish
+# in it, like delete_old_cluster.{sh,bat}.
+chdir ${PostgreSQL::Test::Utils::tmp_check};
+
+command_ok(
+	[
+		'pg_upgrade', '--no-sync',
+		'--old-datadir' => $old->data_dir,
+		'--new-datadir' => $new->data_dir,
+		'--old-bindir' => $old->config_data('--bindir'),
+		'--new-bindir' => $new->config_data('--bindir'),
+		'--socketdir' => $new->host,
+		'--old-port' => $old->port,
+		'--new-port' => $new->port,
+		'--copy',
+	],
+	'pg_upgrade succeeds with extension installed via extension_control_path'
+);
+
+$new->start;
+
+# Verify the extension still works after the upgrade.
+($code, $stdout, $stderr) = $new->psql('postgres', 'SELECT test_ext()');
+is($code, 0, 'extension works after upgrade');
+like($stderr, qr/NOTICE:  running successful/, 'extension working');
+
+$new->stop;
+
+# Write .control and .sql files into $ext_dir/extension/
+# `module_pathname` contains the `$libdir/` to simulate most of the extensions
+# that use it as a prefix in the `module_pathname` by default
+sub create_extension_files
+{
+	my ($ext_name, $ext_dir) = @_;
+
+	open my $cf, '>', "$ext_dir/extension/$ext_name.control"
+	  or die "could not create control file: $!";
+	print $cf
+	  "comment = 'Test C extension for pg_upgrade + extension_control_path'\n";
+	print $cf "default_version = '1.0'\n";
+	print $cf "module_pathname = '\$libdir/$ext_name'\n";
+	print $cf "relocatable = true\n";
+	close $cf;
+
+	open my $sqlf, '>', "$ext_dir/extension/$ext_name--1.0.sql"
+	  or die "could not create SQL file: $!";
+	print $sqlf "/* $ext_name--1.0.sql */\n";
+	print $sqlf
+	  "-- complain if script is sourced in psql, rather than via CREATE EXTENSION\n";
+	print $sqlf
+	  qq'\\echo Use "CREATE EXTENSION $ext_name" to load this file. \\quit\n';
+	print $sqlf "CREATE FUNCTION test_ext()\n";
+	print $sqlf "RETURNS void AS 'MODULE_PATHNAME'\n";
+	print $sqlf "LANGUAGE C;\n";
+	close $sqlf;
+}
+
+done_testing();
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index a3591bf3d2f..d1b0b81e5fd 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -1,6 +1,7 @@
 # src/test/modules/test_extensions/Makefile
 
 MODULE = test_extensions
+MODULE_big = test_ext
 PGFILEDESC = "test_extensions - regression testing for EXTENSION support"
 
 EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
@@ -11,6 +12,8 @@ EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
             test_ext_set_schema \
             test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3
 
+OBJS = test_ext.o
+
 DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
        test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
        test_ext7--1.0.sql test_ext7--1.0--2.0.sql \
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index be9c9ae593f..2c7cea189e2 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -46,6 +46,19 @@ test_install_data += files(
   'test_ext_set_schema.control',
 )
 
+test_ext_sources = files('test_ext.c')
+
+if host_system == 'windows'
+  test_ext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ext',
+    '--FILEDESC', 'test_ext - test C extension for pg_upgrade',])
+endif
+
+test_ext = shared_module('test_ext',
+  test_ext_sources,
+  kwargs: pg_test_mod_args,
+)
+
 tests += {
   'name': 'test_extensions',
   'sd': meson.current_source_dir(),
diff --git a/src/test/modules/test_extensions/test_ext.c b/src/test/modules/test_extensions/test_ext.c
new file mode 100644
index 00000000000..a23165ba67a
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext.c
@@ -0,0 +1,22 @@
+/*
+ * test_ext.c
+ *
+ * Dummy C extension for testing extension_control_path with pg_upgrade
+ *
+ * Portions Copyright (c) 2026, PostgreSQL Global Development Group
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_ext);
+
+Datum
+test_ext(PG_FUNCTION_ARGS)
+{
+	ereport(NOTICE,
+			(errmsg("running successful")));
+	PG_RETURN_VOID();
+}
-- 
2.51.0

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to