From 3cfb811beb69e4f418af0c4334c2e59d70ddcb66 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Tue, 3 Nov 2026 09:55:18 +0800 Subject: [PATCH v6 REL_18 1/2] Ignore empty elements in extension_control_path Empty path elements were expanded to /extension by the automatic suffix addition, causing an unintended search of that directory. Silently discard empty elements before appending the suffix, while continuing to search the remaining directories. This avoids introducing errors for existing settings with leading or trailing separators. Preserve the documented $system default for an entirely empty setting. A nonempty setting containing only separators provides no search directories. Document the behavior and add TAP coverage for empty elements, separator-only settings, and unknown-language errors and hints. Author: Chao Li Reviewed-by: Prashant Chinnam Reviewed-by: Surya Poondla Reviewed-by: Matheus Alcantara Discussion: https://postgr.es/m/DC2D9FA5-45C4-494F-99FC-F9B95AD4FF55@gmail.com --- doc/src/sgml/config.sgml | 1 + src/backend/commands/extension.c | 38 +++++++++++-------- .../t/001_extension_control_path.pl | 35 +++++++++++++++++ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index ee151b15dc5..9a20dcf5422 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -11220,6 +11220,7 @@ extension_control_path = 'C:\tools\postgresql;H:\my_project\share;$system' The default value for this parameter is '$system'. If the value is set to an empty string, the default '$system' is also assumed. + Empty elements within a nonempty value are ignored. diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 94383d6473f..7bd5b3ab181 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -470,6 +470,7 @@ is_extension_script_filename(const char *filename) /* * Return a list of directories declared on extension_control_path GUC. + * Empty elements are ignored. */ static List * get_extension_control_directories(void) @@ -495,7 +496,6 @@ get_extension_control_directories(void) for (;;) { int len; - char *mangled; char *piece = first_path_var_separator(ecp); /* Get the length of the next path on ecp */ @@ -504,24 +504,30 @@ get_extension_control_directories(void) else len = piece - ecp; - /* Copy the next path found on ecp */ - piece = palloc(len + 1); - strlcpy(piece, ecp, len + 1); + /* Ignore empty elements instead of expanding them to /extension */ + if (len > 0) + { + char *mangled; - /* - * Substitute the path macro if needed or append "extension" - * suffix if it is a custom extension control path. - */ - if (strcmp(piece, "$system") == 0) - mangled = substitute_path_macro(piece, "$system", system_dir); - else - mangled = psprintf("%s/extension", piece); + /* Copy the next path found on ecp */ + piece = palloc(len + 1); + strlcpy(piece, ecp, len + 1); - pfree(piece); + /* + * Substitute the path macro if needed or append "extension" + * suffix if it is a custom extension control path. + */ + if (strcmp(piece, "$system") == 0) + mangled = substitute_path_macro(piece, "$system", system_dir); + else + mangled = psprintf("%s/extension", piece); - /* Canonicalize the path based on the OS and add to the list */ - canonicalize_path(mangled); - paths = lappend(paths, mangled); + pfree(piece); + + /* Canonicalize the path based on the OS and add to the list */ + canonicalize_path(mangled); + paths = lappend(paths, mangled); + } /* Break if ecp is empty or move to the next path on ecp */ if (ecp[len] == '\0') 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 7fbe5bde332..8a2e3222059 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 @@ -84,6 +84,41 @@ is($ret, "t", "\$system extension is installed correctly on pg_available_extensions with empty extension_control_path" ); +# Empty path elements do not prevent searching the remaining directories. +for my $path ("$sep$ext_dir", "$ext_dir$sep") +{ + $ret = $node->safe_psql( + 'postgres', + "set extension_control_path = '$path'; + select count(*) from pg_available_extensions where name = '$ext_name'"); + is($ret, '1', + "extension is found with an empty element in path '$path'"); +} + +# A separator-only value provides no directories, unlike an empty value. +$ret = $node->safe_psql( + 'postgres', + "set extension_control_path = '$sep'; + select count(*) from pg_available_extensions"); +is($ret, '0', 'separator-only path lists no extensions'); + +# Empty elements must not replace an unknown-language error or prevent a hint +# from being found using the remaining directories. +for my $sql ( + "create function test_invalid_language() returns int language $ext_name as 'x'", + "do language $ext_name 'x'") +{ + my ($code, $stdout, $stderr) = $node->psql( + 'postgres', + "set extension_control_path = '$sep$ext_dir$sep'; $sql"); + is($code, 3, "unknown language is rejected by $sql"); + like($stderr, qr/ERROR: language "\Q$ext_name\E" does not exist/, + "invalid path does not replace the unknown-language error"); + like($stderr, + qr/HINT: Use CREATE EXTENSION to load the language into the database\./, + "remaining directory provides the extension hint"); +} + # Test with an extension that does not exists my ($code, $stdout, $stderr) = $node->psql('postgres', "CREATE EXTENSION invalid"); -- 2.50.1 (Apple Git-155)