summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Eisentraut2025-05-02 14:25:40 +0000
committerPeter Eisentraut2025-05-02 14:35:48 +0000
commit81eaaa2c41dd29b06edf8b234753debbd1a581d5 (patch)
treedcaff46d93c9bce7ef21a16aec711957b0f9c91e
parenta724c7889f74cc4e76e1979f90808decbc744c79 (diff)
Make "directory" setting work with extension_control_path
The extension_control_path setting (commit 4f7f7b03758) did not support extensions that set a custom "directory" setting in their control file. Very few extensions use that and during the discussion on the previous commit it was suggested to maybe remove that functionality. But a fix was easier than initially thought, so this just adds that support. The fix is to use the control->control_dir as a share dir to return the path of the extension script files. To make this work more sensibly overall, the directory suffix "extension" is no longer to be included in the extension_control_path value. To quote the patch, it would be -extension_control_path = '/usr/local/share/postgresql/extension:/home/my_project/share/extension:$system' +extension_control_path = '/usr/local/share/postgresql:/home/my_project/share:$system' During the initial patch, there was some discussion on which of these two approaches would be better, and the committed patch was a 50/50 decision. But the support for the "directory" setting pushed it the other way, and also it seems like many people didn't like the previous behavior much. Author: Matheus Alcantara <[email protected]> Reviewed-by: Christoph Berg <[email protected]> Reviewed-by: David E. Wheeler <[email protected]> Discussion: https://www.postgresql.org/message-id/flat/aAi1VACxhjMhjFnb%40msg.df7cb.de#0cdf7b7d727cc593b029650daa3c4fbc
-rw-r--r--doc/src/sgml/config.sgml15
-rw-r--r--doc/src/sgml/extend.sgml4
-rw-r--r--src/backend/commands/extension.c95
-rw-r--r--src/test/modules/test_extensions/t/001_extension_control_path.pl93
4 files changed, 148 insertions, 59 deletions
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index fd6e3e02890..23d2b1be424 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -11030,16 +11030,17 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
(Use <literal>pg_config --sharedir</literal> to find out the name of
this directory.) For example:
<programlisting>
-extension_control_path = '/usr/local/share/postgresql/extension:/home/my_project/share/extension:$system'
+extension_control_path = '/usr/local/share/postgresql:/home/my_project/share:$system'
</programlisting>
or, in a Windows environment:
<programlisting>
-extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\extension;$system'
+extension_control_path = 'C:\tools\postgresql;H:\my_project\share;$system'
</programlisting>
- Note that the path elements should typically end in
- <literal>extension</literal> if the normal installation layouts are
- followed. (The value for <literal>$system</literal> already includes
- the <literal>extension</literal> suffix.)
+ Note that the specified paths elements are expected to have a
+ subdirectory <filename>extension</filename> which will contain the
+ <filename>.control</filename> and <filename>.sql</filename> files; the
+ <filename>extension</filename> suffix is automatically appended to
+ each path element.
</para>
<para>
@@ -11064,7 +11065,7 @@ extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\exte
linkend="guc-dynamic-library-path"/> to a correspondent location, for
example,
<programlisting>
-extension_control_path = '/usr/local/share/postgresql/extension:$system'
+extension_control_path = '/usr/local/share/postgresql:$system'
dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
</programlisting>
</para>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 065bc49c973..63c5ec6d1eb 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -674,7 +674,7 @@ RETURNS anycompatible AS ...
<para>
The directory containing the extension's <acronym>SQL</acronym> script
file(s). Unless an absolute path is given, the name is relative to
- the installation's <literal>SHAREDIR</literal> directory. By default,
+ the directory where the control file was found. By default,
the script files are looked for in the same directory where the
control file was found.
</para>
@@ -1836,7 +1836,7 @@ make install prefix=/usr/local/extras
linkend="guc-dynamic-library-path"/> to enable the
<productname>PostgreSQL</productname> server to find the files:
<programlisting>
-extension_control_path = '/usr/local/extras/share/postgresql/extension:$system'
+extension_control_path = '/usr/local/extras/share/postgresql:$system'
dynamic_library_path = '/usr/local/extras/lib/postgresql:$libdir'
</programlisting>
</para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 180f4af9be3..73c52e970f6 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -83,6 +83,8 @@ Oid CurrentExtensionObject = InvalidOid;
typedef struct ExtensionControlFile
{
char *name; /* name of the extension */
+ char *basedir; /* base directory where control and script
+ * files are located */
char *control_dir; /* directory where control file was found */
char *directory; /* directory for script files */
char *default_version; /* default install target version, if any */
@@ -153,6 +155,7 @@ static void ExecAlterExtensionContentsRecurse(AlterExtensionContentsStmt *stmt,
static char *read_whole_file(const char *filename, int *length);
static ExtensionControlFile *new_ExtensionControlFile(const char *extname);
+char *find_in_paths(const char *basename, List *paths);
/*
* get_extension_oid - given an extension name, look up the OID
@@ -374,8 +377,15 @@ get_extension_control_directories(void)
piece = palloc(len + 1);
strlcpy(piece, ecp, len + 1);
- /* Substitute the path macro if needed */
- mangled = substitute_path_macro(piece, "$system", system_dir);
+ /*
+ * 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);
+
pfree(piece);
/* Canonicalize the path based on the OS and add to the list */
@@ -401,28 +411,16 @@ get_extension_control_directories(void)
static char *
find_extension_control_filename(ExtensionControlFile *control)
{
- char sharepath[MAXPGPATH];
- char *system_dir;
char *basename;
- char *ecp;
char *result;
+ List *paths;
Assert(control->name);
- get_share_path(my_exec_path, sharepath);
- system_dir = psprintf("%s/extension", sharepath);
-
basename = psprintf("%s.control", control->name);
- /*
- * find_in_path() does nothing if the path value is empty. This is the
- * historical behavior for dynamic_library_path, but it makes no sense for
- * extensions. So in that case, substitute a default value.
- */
- ecp = Extension_control_path;
- if (strlen(ecp) == 0)
- ecp = "$system";
- result = find_in_path(basename, ecp, "extension_control_path", "$system", system_dir);
+ paths = get_extension_control_directories();
+ result = find_in_paths(basename, paths);
if (result)
{
@@ -439,12 +437,11 @@ find_extension_control_filename(ExtensionControlFile *control)
static char *
get_extension_script_directory(ExtensionControlFile *control)
{
- char sharepath[MAXPGPATH];
- char *result;
-
/*
* The directory parameter can be omitted, absolute, or relative to the
- * installation's share directory.
+ * installation's base directory, which can be the sharedir or a custom
+ * path that it was set extension_control_path. It depends where the
+ * .control file was found.
*/
if (!control->directory)
return pstrdup(control->control_dir);
@@ -452,11 +449,8 @@ get_extension_script_directory(ExtensionControlFile *control)
if (is_absolute_path(control->directory))
return pstrdup(control->directory);
- get_share_path(my_exec_path, sharepath);
- result = (char *) palloc(MAXPGPATH);
- snprintf(result, MAXPGPATH, "%s/%s", sharepath, control->directory);
-
- return result;
+ Assert(control->basedir != NULL);
+ return psprintf("%s/%s", control->basedir, control->directory);
}
static char *
@@ -550,6 +544,14 @@ parse_extension_control_file(ExtensionControlFile *control,
errhint("The extension must first be installed on the system where PostgreSQL is running.")));
}
+ /* Assert that the control_dir ends with /extension */
+ Assert(control->control_dir != NULL);
+ Assert(strcmp(control->control_dir + strlen(control->control_dir) - strlen("/extension"), "/extension") == 0);
+
+ control->basedir = pnstrdup(
+ control->control_dir,
+ strlen(control->control_dir) - strlen("/extension"));
+
if ((file = AllocateFile(filename, "r")) == NULL)
{
/* no complaint for missing auxiliary file */
@@ -3863,3 +3865,44 @@ new_ExtensionControlFile(const char *extname)
return control;
}
+
+/*
+ * Work in a very similar way with find_in_path but it receives an already
+ * parsed List of paths to search the basename and it do not support macro
+ * replacement or custom error messages (for simplicity).
+ *
+ * By "already parsed List of paths" this function expected that paths already
+ * have all macros replaced.
+ */
+char *
+find_in_paths(const char *basename, List *paths)
+{
+ ListCell *cell;
+
+ foreach(cell, paths)
+ {
+ char *path = lfirst(cell);
+ char *full;
+
+ Assert(path != NULL);
+
+ path = pstrdup(path);
+ canonicalize_path(path);
+
+ /* only absolute paths */
+ if (!is_absolute_path(path))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("component in parameter \"%s\" is not an absolute path", "extension_control_path"));
+
+ full = psprintf("%s/%s", path, basename);
+
+ if (pg_file_exists(full))
+ return full;
+
+ pfree(path);
+ pfree(full);
+ }
+
+ return NULL;
+}
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 c186c1470f7..1a9c97bbf4d 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
@@ -2,6 +2,7 @@
use strict;
use warnings FATAL => 'all';
+use File::Path qw(mkpath);
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
@@ -12,25 +13,14 @@ $node->init;
# Create a temporary directory for the extension control file
my $ext_dir = PostgreSQL::Test::Utils::tempdir();
+mkpath("$ext_dir/extension");
+
my $ext_name = "test_custom_ext_paths";
-my $control_file = "$ext_dir/$ext_name.control";
-my $sql_file = "$ext_dir/$ext_name--1.0.sql";
-
-# Create .control .sql file
-open my $cf, '>', $control_file or die "Could not create control file: $!";
-print $cf "comment = 'Test extension_control_path'\n";
-print $cf "default_version = '1.0'\n";
-print $cf "relocatable = true\n";
-close $cf;
-
-# Create --1.0.sql file
-open my $sqlf, '>', $sql_file or die "Could not create sql file: $!";
-print $sqlf "/* $sql_file */\n";
-print $sqlf
- "-- complain if script is sourced in psql, rather than via CREATE EXTENSION\n";
-print $sqlf
- qq'\\echo Use "CREATE EXTENSION $ext_name" to load this file. \\quit\n';
-close $sqlf;
+create_extension($ext_name, $ext_dir);
+
+my $ext_name2 = "test_custom_ext_paths_using_directory";
+mkpath("$ext_dir/$ext_name2");
+create_extension($ext_name2, $ext_dir, $ext_name2);
# Use the correct separator and escape \ when running on Windows.
my $sep = $windows_os ? ";" : ":";
@@ -48,6 +38,7 @@ is($ecp, "\$system$sep$ext_dir",
"custom extension control directory path configured");
$node->safe_psql('postgres', "CREATE EXTENSION $ext_name");
+$node->safe_psql('postgres', "CREATE EXTENSION $ext_name2");
my $ret = $node->safe_psql('postgres',
"select * from pg_available_extensions where name = '$ext_name'");
@@ -55,26 +46,80 @@ is( $ret,
"test_custom_ext_paths|1.0|1.0|Test extension_control_path",
"extension is installed correctly on pg_available_extensions");
-my $ret2 = $node->safe_psql('postgres',
+$ret = $node->safe_psql('postgres',
"select * from pg_available_extension_versions where name = '$ext_name'");
-is( $ret2,
+is( $ret,
"test_custom_ext_paths|1.0|t|t|f|t|||Test extension_control_path",
"extension is installed correctly on pg_available_extension_versions");
+$ret = $node->safe_psql('postgres',
+ "select * from pg_available_extensions where name = '$ext_name2'");
+is( $ret,
+ "test_custom_ext_paths_using_directory|1.0|1.0|Test extension_control_path",
+ "extension is installed correctly on pg_available_extensions");
+
+$ret = $node->safe_psql('postgres',
+ "select * from pg_available_extension_versions where name = '$ext_name2'"
+);
+is( $ret,
+ "test_custom_ext_paths_using_directory|1.0|t|t|f|t|||Test extension_control_path",
+ "extension is installed correctly on pg_available_extension_versions");
+
# Ensure that extensions installed on $system is still visible when using with
# custom extension control path.
-my $ret3 = $node->safe_psql('postgres',
+$ret = $node->safe_psql('postgres',
"select count(*) > 0 as ok from pg_available_extensions where name = 'plpgsql'"
);
-is($ret3, "t",
+is($ret, "t",
"\$system extension is installed correctly on pg_available_extensions");
-my $ret4 = $node->safe_psql('postgres',
+$ret = $node->safe_psql('postgres',
"set extension_control_path = ''; select count(*) > 0 as ok from pg_available_extensions where name = 'plpgsql'"
);
-is($ret4, "t",
+is($ret, "t",
"\$system extension is installed correctly on pg_available_extensions with empty extension_control_path"
);
+# Test with an extension that does not exists
+my ($code, $stdout, $stderr) =
+ $node->psql('postgres', "CREATE EXTENSION invalid");
+is($code, 3, 'error to create an extension that does not exists');
+like($stderr, qr/ERROR: extension "invalid" is not available/);
+
+sub create_extension
+{
+ my ($ext_name, $ext_dir, $directory) = @_;
+
+ my $control_file = "$ext_dir/extension/$ext_name.control";
+ my $sql_file;
+
+ if (defined $directory)
+ {
+ $sql_file = "$ext_dir/$directory/$ext_name--1.0.sql";
+ }
+ else
+ {
+ $sql_file = "$ext_dir/extension/$ext_name--1.0.sql";
+ }
+
+ # Create .control .sql file
+ open my $cf, '>', $control_file
+ or die "Could not create control file: $!";
+ print $cf "comment = 'Test extension_control_path'\n";
+ print $cf "default_version = '1.0'\n";
+ print $cf "relocatable = true\n";
+ print $cf "directory = $directory" if defined $directory;
+ close $cf;
+
+ # Create --1.0.sql file
+ open my $sqlf, '>', $sql_file or die "Could not create sql file: $!";
+ print $sqlf "/* $sql_file */\n";
+ print $sqlf
+ "-- complain if script is sourced in psql, rather than via CREATE EXTENSION\n";
+ print $sqlf
+ qq'\\echo Use "CREATE EXTENSION $ext_name" to load this file. \\quit\n';
+ close $sqlf;
+}
+
done_testing();