From 284a0d6f96dbc020cf2ec9ceef71bc4534af6a89 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Tue, 3 Nov 2026 09:56:50 +0800 Subject: [PATCH v2 3/3] Deduplicate extension control search directories Compare directories after macro expansion and path canonicalization, retaining the first occurrence and its display metadata. This avoids rescanning repeated directories while preserving search precedence. Keep extension-name deduplication for matching names in distinct directories, and test that repeated directories preserve the reported location. Author: Chao Li Reviewed-by: Prashant Chinnam Reviewed-by: Surya Poondla Discussion: https://postgr.es/m/DC2D9FA5-45C4-494F-99FC-F9B95AD4FF55@gmail.com --- src/backend/commands/extension.c | 38 +++++++++++++------ .../t/001_extension_control_path.pl | 27 +++++++++++++ 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index d1d43450f90..63e87096151 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -533,6 +533,8 @@ get_extension_control_directories(void) } else { + List *found_paths = NIL; + /* Duplicate the string so we can modify it */ ecp = pstrdup(Extension_control_path); @@ -541,7 +543,8 @@ get_extension_control_directories(void) int len; char *mangled; char *piece = first_path_var_separator(ecp); - ExtensionLocation *location; + String *path_str; + bool is_system; /* Get the length of the next path on ecp */ if (piece == NULL) @@ -556,8 +559,6 @@ get_extension_control_directories(void) errmsg("empty element in parameter \"%s\"", "extension_control_path")); - location = palloc_object(ExtensionLocation); - /* Copy the next path found on ecp */ piece = palloc(len + 1); strlcpy(piece, ecp, len + 1); @@ -566,16 +567,11 @@ get_extension_control_directories(void) * 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); + is_system = strcmp(piece, EXTENSION_SYSTEM_MACRO) == 0; + if (is_system) 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 */ @@ -586,8 +582,23 @@ get_extension_control_directories(void) errmsg("component in parameter \"%s\" is not an absolute path", "extension_control_path")); - location->loc = mangled; - paths = lappend(paths, location); + path_str = makeString(mangled); + + /* Keep the first occurrence, including its macro for display. */ + if (list_member(found_paths, path_str)) + { + pfree(path_str); + pfree(mangled); + } + else + { + ExtensionLocation *location = palloc_object(ExtensionLocation); + + location->macro = is_system ? pstrdup(EXTENSION_SYSTEM_MACRO) : NULL; + location->loc = mangled; + found_paths = lappend(found_paths, path_str); + paths = lappend(paths, location); + } /* Break if ecp is empty or move to the next path on ecp */ if (ecp[len] == '\0') @@ -595,6 +606,9 @@ get_extension_control_directories(void) else ecp += len + 1; } + + /* Free the String nodes, but retain the strings used by locations. */ + list_free_deep(found_paths); } return paths; 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 a1c87f38599..9db0a48d6d7 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 @@ -148,6 +148,33 @@ for my $sql ( "error identifies the relative path element"); } +# Repeated directories, including differently spelled paths that canonicalize +# to the same directory, must preserve the first location for each extension. +my $ext_dir2_canonicalized = $ext_dir2; +$ext_dir2_canonicalized =~ s!\\!/!g if $windows_os; +for my $dirs ( + [ $ext_dir_canonicalized, $ext_dir2_canonicalized ], + [ $ext_dir2_canonicalized, $ext_dir_canonicalized ]) +{ + my ($first, $second) = @$dirs; + my $path = join($sep, $first, "$first/.", $second, $first); + for my $view (qw(pg_available_extensions pg_available_extension_versions)) + { + $ret = $node->safe_psql( + 'postgres', + "set extension_control_path = '$path'; + select location from $view where name = '$ext_name'"); + is($ret, "$first/extension", + "duplicate directories preserve the first location in $view with path '$path'"); + } +} + +$ret = $node->safe_psql( + 'postgres', + "set extension_control_path = '\$system$sep\$system'; + select location from pg_available_extensions where name = 'plpgsql'"); +is($ret, '$system', 'repeated system directory preserves the macro location'); + # Test with an extension that does not exists my ($code, $stdout, $stderr) = $node->psql('postgres', "CREATE EXTENSION invalid"); -- 2.50.1 (Apple Git-155)