]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix pg_upgrade failure when extension_control_path is used
authorPeter Eisentraut <peter@eisentraut.org>
Mon, 16 Mar 2026 10:52:02 +0000 (11:52 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Mon, 16 Mar 2026 10:52:26 +0000 (11:52 +0100)
When an extension is located via extension_control_path and it has a
hardcoded $libdir/ path, this is stripped by the
extension_control_path mechanism.  But when pg_upgrade verifies the
extension using LOAD, this stripping does not happen, and so
pg_upgrade will fail because it cannot load the extension.  To work
around that, change pg_upgrade to itself strip the prefix when it runs
its checks.  A test case is also added.

Author: Jonathan Gonzalez V. <jonathan.abdiel@gmail.com>
Reviewed-by: Niccolò Fei <niccolo.fei@enterprisedb.com>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/43b3691c673a8b9158f5a09f06eacc3c63e2c02d.camel%40gmail.com

src/bin/pg_upgrade/Makefile
src/bin/pg_upgrade/function.c
src/bin/pg_upgrade/meson.build
src/bin/pg_upgrade/t/008_extension_control_path.pl [new file with mode: 0644]
src/test/modules/test_extensions/Makefile
src/test/modules/test_extensions/meson.build
src/test/modules/test_extensions/test_ext.c [new file with mode: 0644]

index 726df4b7525f6d435d7ce666b6df45144fc1e861..771addb675a6dcc52a3ac90ca1252a5e88a83d45 100644 (file)
@@ -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
index a3184f95665ccbf2523096e7f2cea4660aeb3de3..bc7e8006d825ead6fd5ff6e6c70b3c7259a7e0e8 100644 (file)
@@ -120,6 +120,14 @@ get_loadable_libraries(void)
                {
                        char       *lib = PQgetvalue(res, rowno, 0);
 
+                       /*
+                        * Starting with version 19, for extensions with hardcoded
+                        * '$libdir/' library names, we strip the prefix to allow the
+                        * library search path to be used.
+                        */
+                       if (strncmp(lib, "$libdir/", 8) == 0)
+                               lib += 8;
+
                        os_info.libraries[totaltups].name = pg_strdup(lib);
                        os_info.libraries[totaltups].dbnum = dbnum;
 
index 49b1b624f2596883ea3a6a60a501f163aa7254ed..ffbf6ae8d759bf8d95559c735d846ba1869347d3 100644 (file)
@@ -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 (file)
index 0000000..383d7dc
--- /dev/null
@@ -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();
index a3591bf3d2f357b1e117e8a414ccaff0c1f72e05..d1b0b81e5fd3d9d0a3eb88f941d5427186f2fd14 100644 (file)
@@ -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 \
index be9c9ae593fcf14e078b31121dce58c8bf853f09..2c7cea189e2860cac3aa4191c2ea6a07b427912d 100644 (file)
@@ -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 (file)
index 0000000..a23165b
--- /dev/null
@@ -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();
+}