Hide duplicate names from extension views
authorPeter Eisentraut <peter@eisentraut.org>
Mon, 15 Sep 2025 05:25:22 +0000 (07:25 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Mon, 15 Sep 2025 05:30:31 +0000 (07:30 +0200)
If extensions of equal names were installed in different directories
in the path, the views pg_available_extensions and
pg_available_extension_versions would show all of them, even though
only the first one was actually reachable by CREATE EXTENSION.  To
fix, have those views skip extensions found later in the path if they
have names already found earlier.

Also add a bit of documentation that only the first extension in the
path can be used.

Reported-by: Pierrick <pierrick.chovelon@dalibo.com>
Discussion: https://www.postgresql.org/message-id/flat/8f5a0517-1cb8-4085-ae89-77e7454e27ba%40dalibo.com

doc/src/sgml/config.sgml
src/backend/commands/extension.c
src/test/modules/test_extensions/t/001_extension_control_path.pl

index 3c33d5d0fbcae47126e1361d23305317b006c3b5..e9b420f3ddbeff0c63f16f6c59eb9bbcba304f2a 100644 (file)
@@ -11065,6 +11065,12 @@ extension_control_path = 'C:\tools\postgresql;H:\my_project\share;$system'
         string, the default <literal>'$system'</literal> is also assumed.
        </para>
 
+       <para>
+        If extensions with equal names are present in multiple directories in
+        the configured path, only the instance found first in the path will be
+        used.
+       </para>
+
        <para>
         This parameter can be changed at run time by superusers and users
         with the appropriate <literal>SET</literal> privilege, but a
index e6f9ab6dfd66b008b9f41c0795c83f55d12db454..93ef1ad106fdbd8ae5824f41a7d8dfb20e8791ff 100644 (file)
@@ -2208,6 +2208,7 @@ pg_available_extensions(PG_FUNCTION_ARGS)
    List       *locations;
    DIR        *dir;
    struct dirent *de;
+   List       *found_ext = NIL;
 
    /* Build tuplestore to hold the result rows */
    InitMaterializedSRF(fcinfo, 0);
@@ -2232,6 +2233,7 @@ pg_available_extensions(PG_FUNCTION_ARGS)
            {
                ExtensionControlFile *control;
                char       *extname;
+               String     *extname_str;
                Datum       values[3];
                bool        nulls[3];
 
@@ -2246,6 +2248,16 @@ pg_available_extensions(PG_FUNCTION_ARGS)
                if (strstr(extname, "--"))
                    continue;
 
+               /*
+                * Ignore already-found names.  They are not reachable by the
+                * path search, so don't shown them.
+                */
+               extname_str = makeString(extname);
+               if (list_member(found_ext, extname_str))
+                   continue;
+               else
+                   found_ext = lappend(found_ext, extname_str);
+
                control = new_ExtensionControlFile(extname);
                control->control_dir = pstrdup(location);
                parse_extension_control_file(control, NULL);
@@ -2294,6 +2306,7 @@ pg_available_extension_versions(PG_FUNCTION_ARGS)
    List       *locations;
    DIR        *dir;
    struct dirent *de;
+   List       *found_ext = NIL;
 
    /* Build tuplestore to hold the result rows */
    InitMaterializedSRF(fcinfo, 0);
@@ -2318,6 +2331,7 @@ pg_available_extension_versions(PG_FUNCTION_ARGS)
            {
                ExtensionControlFile *control;
                char       *extname;
+               String     *extname_str;
 
                if (!is_extension_control_filename(de->d_name))
                    continue;
@@ -2330,6 +2344,16 @@ pg_available_extension_versions(PG_FUNCTION_ARGS)
                if (strstr(extname, "--"))
                    continue;
 
+               /*
+                * Ignore already-found names.  They are not reachable by the
+                * path search, so don't shown them.
+                */
+               extname_str = makeString(extname);
+               if (list_member(found_ext, extname_str))
+                   continue;
+               else
+                   found_ext = lappend(found_ext, extname_str);
+
                /* read the control file */
                control = new_ExtensionControlFile(extname);
                control->control_dir = pstrdup(location);
index 1a9c97bbf4dcca59294e80cda7d82bbff7c3e511..7fbe5bde3322746d76cea6021e324b88430859da 100644 (file)
@@ -11,12 +11,15 @@ my $node = PostgreSQL::Test::Cluster->new('node');
 
 $node->init;
 
-# Create a temporary directory for the extension control file
+# Create temporary directories for the extension control files
 my $ext_dir = PostgreSQL::Test::Utils::tempdir();
 mkpath("$ext_dir/extension");
+my $ext_dir2 = PostgreSQL::Test::Utils::tempdir();
+mkpath("$ext_dir2/extension");
 
 my $ext_name = "test_custom_ext_paths";
 create_extension($ext_name, $ext_dir);
+create_extension($ext_name, $ext_dir2);
 
 my $ext_name2 = "test_custom_ext_paths_using_directory";
 mkpath("$ext_dir/$ext_name2");
@@ -26,7 +29,7 @@ create_extension($ext_name2, $ext_dir, $ext_name2);
 my $sep = $windows_os ? ";" : ":";
 $node->append_conf(
    'postgresql.conf', qq{
-extension_control_path = '\$system$sep@{[ $windows_os ? ($ext_dir =~ s/\\/\\\\/gr) : $ext_dir ]}'
+extension_control_path = '\$system$sep@{[ $windows_os ? ($ext_dir =~ s/\\/\\\\/gr) : $ext_dir ]}$sep@{[ $windows_os ? ($ext_dir2 =~ s/\\/\\\\/gr) : $ext_dir2 ]}'
 });
 
 # Start node
@@ -34,7 +37,7 @@ $node->start;
 
 my $ecp = $node->safe_psql('postgres', 'show extension_control_path;');
 
-is($ecp, "\$system$sep$ext_dir",
+is($ecp, "\$system$sep$ext_dir$sep$ext_dir2",
    "custom extension control directory path configured");
 
 $node->safe_psql('postgres', "CREATE EXTENSION $ext_name");