From bb12060e42c2b62bdd71d87099d3919d25f0c5da Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
Date: Tue, 3 Nov 2026 09:55:18 +0800
Subject: [PATCH v1 1/2] Ignore empty elements in extension_control_path

Skip empty elements before appending the extension directory suffix,
so leading, trailing, and repeated separators do not add /extension
to the search path. Preserve the documented system-directory default
for an entirely empty setting.

Document the behavior and cover empty elements in the extension
control path TAP tests.

Author: Chao Li <lic@highgo.com>
---
 doc/src/sgml/config.sgml                      |  2 ++
 src/backend/commands/extension.c              | 16 ++++++++++++-
 .../t/001_extension_control_path.pl           | 24 +++++++++++++++++++
 3 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f7db2995c3e..eb9574e3dbc 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -11736,6 +11736,8 @@ extension_control_path = 'C:\tools\postgresql;H:\my_project\share;$system'
         The default value for this parameter is
         <literal>'$system'</literal>. If the value is set to an empty
         string, the default <literal>'$system'</literal> is also assumed.
+        Empty elements within a nonempty value are ignored. A value consisting
+        only of separators searches no directories.
        </para>
 
        <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 5855668c5ca..6ef4bdb3faf 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -541,7 +541,7 @@ get_extension_control_directories(void)
 			int			len;
 			char	   *mangled;
 			char	   *piece = first_path_var_separator(ecp);
-			ExtensionLocation *location = palloc_object(ExtensionLocation);
+			ExtensionLocation *location;
 
 			/* Get the length of the next path on ecp */
 			if (piece == NULL)
@@ -549,6 +549,17 @@ get_extension_control_directories(void)
 			else
 				len = piece - ecp;
 
+			/* Ignore empty path elements, including a trailing one. */
+			if (len == 0)
+			{
+				if (ecp[len] == '\0')
+					break;
+				ecp += len + 1;
+				continue;
+			}
+
+			location = palloc_object(ExtensionLocation);
+
 			/* Copy the next path found on ecp */
 			piece = palloc(len + 1);
 			strlcpy(piece, ecp, len + 1);
@@ -582,6 +593,9 @@ get_extension_control_directories(void)
 		}
 	}
 
+	foreach_ptr(ExtensionLocation, location, paths)
+		elog(DEBUG3, "%s: path \"%s\"", __func__, location->loc);
+
 	return paths;
 #undef EXTENSION_SYSTEM_MACRO
 }
diff --git a/src/test/modules/test_extensions/t/001_extension_control_path.pl b/src/test/modules/test_extensions/t/001_extension_control_path.pl
index 4a013a7da4b..7c42561f5bc 100644
--- a/src/test/modules/test_extensions/t/001_extension_control_path.pl
+++ b/src/test/modules/test_extensions/t/001_extension_control_path.pl
@@ -115,6 +115,30 @@ is($ret, "\$system",
 	"\$system location is shown correctly in pg_available_extensions with empty extension_control_path"
 );
 
+# Empty path elements must not add /extension to the search path.
+my $path_message = qr/get_extension_control_directories: path/;
+for my $path (
+	"$sep$ext_dir_canonicalized",
+	"$ext_dir_canonicalized$sep",
+	$sep)
+{
+	my $stderr;
+	$node->psql(
+		'postgres',
+		"set client_min_messages = debug3;
+		 set extension_control_path = '$path';
+		 select count(*) from pg_available_extensions",
+		stderr => \$stderr,
+		on_error_die => 1);
+	if ($path ne $sep)
+	{
+		like($stderr, qr{$path_message "\Q$ext_dir_canonicalized\E/extension"},
+			"custom directory is searched with path '$path'");
+	}
+	unlike($stderr, qr{$path_message "/extension"},
+		"empty elements should not search /extension with path '$path'");
+}
+
 # Test with an extension that does not exists
 my ($code, $stdout, $stderr) =
   $node->psql('postgres', "CREATE EXTENSION invalid");
-- 
2.50.1 (Apple Git-155)

