From 7c9bfb1c5d91b3ffb7d879da7788549b674d3477 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Tue, 3 Nov 2026 09:55:18 +0800 Subject: [PATCH v5 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 | 54 ++++++++++--------- .../t/001_extension_control_path.pl | 37 +++++++++++++ 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 7018ca1e3ea..0c0510d306c 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -11783,6 +11783,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 35443dabecf..6fdaf6aa963 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -511,6 +511,7 @@ is_extension_script_filename(const char *filename) /* * Return a list of directories declared in the extension_control_path GUC. + * Empty elements are ignored. */ static List * get_extension_control_directories(void) @@ -541,9 +542,7 @@ get_extension_control_directories(void) for (;;) { int len; - char *mangled; char *piece = first_path_var_separator(ecp); - ExtensionLocation *location = palloc_object(ExtensionLocation); /* Get the length of the next path on ecp */ if (piece == NULL) @@ -551,30 +550,37 @@ 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); - - /* - * Substitute the path macro if needed or append "extension" - * suffix if it is a custom extension control path. - */ - if (strcmp(piece, EXTENSION_SYSTEM_MACRO) == 0) + /* Ignore empty elements instead of expanding them to /extension */ + if (len > 0) { - location->macro = pstrdup(piece); - mangled = substitute_path_macro(piece, EXTENSION_SYSTEM_MACRO, system_dir); - } - else - { - location->macro = NULL; - mangled = psprintf("%s/extension", piece); - } - pfree(piece); + char *mangled; + ExtensionLocation *location = palloc_object(ExtensionLocation); - /* Canonicalize the path based on the OS and add to the list */ - canonicalize_path(mangled); - location->loc = mangled; - paths = lappend(paths, location); + /* Copy the next path found on ecp */ + piece = palloc(len + 1); + strlcpy(piece, ecp, len + 1); + + /* + * Substitute the path macro if needed or append "extension" + * suffix if it is a custom extension control path. + */ + if (strcmp(piece, EXTENSION_SYSTEM_MACRO) == 0) + { + location->macro = pstrdup(piece); + mangled = substitute_path_macro(piece, EXTENSION_SYSTEM_MACRO, system_dir); + } + else + { + location->macro = NULL; + mangled = psprintf("%s/extension", piece); + } + pfree(piece); + + /* Canonicalize the path based on the OS and add to the list */ + canonicalize_path(mangled); + location->loc = mangled; + paths = lappend(paths, location); + } /* 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 4a013a7da4b..5fb811b8e8d 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,43 @@ is($ret, "\$system", "\$system location is shown correctly in pg_available_extensions with empty extension_control_path" ); +# Empty path elements do not prevent searching the remaining directories. +for my $path ( + "$sep$ext_dir_canonicalized", + "$ext_dir_canonicalized$sep") +{ + $ret = $node->safe_psql( + 'postgres', + "set extension_control_path = '$path'; + select location from pg_available_extensions where name = '$ext_name'"); + is($ret, "$ext_dir_canonicalized/extension", + "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_canonicalized$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)