Skip to content

Commit 81eaaa2

Browse files
committed
Make "directory" setting work with extension_control_path
The extension_control_path setting (commit 4f7f7b0) 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 <mths.dev@pm.me> Reviewed-by: Christoph Berg <myon@debian.org> Reviewed-by: David E. Wheeler <david@justatheory.com> Discussion: https://www.postgresql.org/message-id/flat/aAi1VACxhjMhjFnb%40msg.df7cb.de#0cdf7b7d727cc593b029650daa3c4fbc
1 parent a724c78 commit 81eaaa2

File tree

4 files changed

+148
-59
lines changed

4 files changed

+148
-59
lines changed

doc/src/sgml/config.sgml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11030,16 +11030,17 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
1103011030
(Use <literal>pg_config --sharedir</literal> to find out the name of
1103111031
this directory.) For example:
1103211032
<programlisting>
11033-
extension_control_path = '/usr/local/share/postgresql/extension:/home/my_project/share/extension:$system'
11033+
extension_control_path = '/usr/local/share/postgresql:/home/my_project/share:$system'
1103411034
</programlisting>
1103511035
or, in a Windows environment:
1103611036
<programlisting>
11037-
extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\extension;$system'
11037+
extension_control_path = 'C:\tools\postgresql;H:\my_project\share;$system'
1103811038
</programlisting>
11039-
Note that the path elements should typically end in
11040-
<literal>extension</literal> if the normal installation layouts are
11041-
followed. (The value for <literal>$system</literal> already includes
11042-
the <literal>extension</literal> suffix.)
11039+
Note that the specified paths elements are expected to have a
11040+
subdirectory <filename>extension</filename> which will contain the
11041+
<filename>.control</filename> and <filename>.sql</filename> files; the
11042+
<filename>extension</filename> suffix is automatically appended to
11043+
each path element.
1104311044
</para>
1104411045

1104511046
<para>
@@ -11064,7 +11065,7 @@ extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\exte
1106411065
linkend="guc-dynamic-library-path"/> to a correspondent location, for
1106511066
example,
1106611067
<programlisting>
11067-
extension_control_path = '/usr/local/share/postgresql/extension:$system'
11068+
extension_control_path = '/usr/local/share/postgresql:$system'
1106811069
dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
1106911070
</programlisting>
1107011071
</para>

doc/src/sgml/extend.sgml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ RETURNS anycompatible AS ...
674674
<para>
675675
The directory containing the extension's <acronym>SQL</acronym> script
676676
file(s). Unless an absolute path is given, the name is relative to
677-
the installation's <literal>SHAREDIR</literal> directory. By default,
677+
the directory where the control file was found. By default,
678678
the script files are looked for in the same directory where the
679679
control file was found.
680680
</para>
@@ -1836,7 +1836,7 @@ make install prefix=/usr/local/extras
18361836
linkend="guc-dynamic-library-path"/> to enable the
18371837
<productname>PostgreSQL</productname> server to find the files:
18381838
<programlisting>
1839-
extension_control_path = '/usr/local/extras/share/postgresql/extension:$system'
1839+
extension_control_path = '/usr/local/extras/share/postgresql:$system'
18401840
dynamic_library_path = '/usr/local/extras/lib/postgresql:$libdir'
18411841
</programlisting>
18421842
</para>

src/backend/commands/extension.c

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ Oid CurrentExtensionObject = InvalidOid;
8383
typedef struct ExtensionControlFile
8484
{
8585
char *name; /* name of the extension */
86+
char *basedir; /* base directory where control and script
87+
* files are located */
8688
char *control_dir; /* directory where control file was found */
8789
char *directory; /* directory for script files */
8890
char *default_version; /* default install target version, if any */
@@ -153,6 +155,7 @@ static void ExecAlterExtensionContentsRecurse(AlterExtensionContentsStmt *stmt,
153155
static char *read_whole_file(const char *filename, int *length);
154156
static ExtensionControlFile *new_ExtensionControlFile(const char *extname);
155157

158+
char *find_in_paths(const char *basename, List *paths);
156159

157160
/*
158161
* get_extension_oid - given an extension name, look up the OID
@@ -374,8 +377,15 @@ get_extension_control_directories(void)
374377
piece = palloc(len + 1);
375378
strlcpy(piece, ecp, len + 1);
376379

377-
/* Substitute the path macro if needed */
378-
mangled = substitute_path_macro(piece, "$system", system_dir);
380+
/*
381+
* Substitute the path macro if needed or append "extension"
382+
* suffix if it is a custom extension control path.
383+
*/
384+
if (strcmp(piece, "$system") == 0)
385+
mangled = substitute_path_macro(piece, "$system", system_dir);
386+
else
387+
mangled = psprintf("%s/extension", piece);
388+
379389
pfree(piece);
380390

381391
/* Canonicalize the path based on the OS and add to the list */
@@ -401,28 +411,16 @@ get_extension_control_directories(void)
401411
static char *
402412
find_extension_control_filename(ExtensionControlFile *control)
403413
{
404-
char sharepath[MAXPGPATH];
405-
char *system_dir;
406414
char *basename;
407-
char *ecp;
408415
char *result;
416+
List *paths;
409417

410418
Assert(control->name);
411419

412-
get_share_path(my_exec_path, sharepath);
413-
system_dir = psprintf("%s/extension", sharepath);
414-
415420
basename = psprintf("%s.control", control->name);
416421

417-
/*
418-
* find_in_path() does nothing if the path value is empty. This is the
419-
* historical behavior for dynamic_library_path, but it makes no sense for
420-
* extensions. So in that case, substitute a default value.
421-
*/
422-
ecp = Extension_control_path;
423-
if (strlen(ecp) == 0)
424-
ecp = "$system";
425-
result = find_in_path(basename, ecp, "extension_control_path", "$system", system_dir);
422+
paths = get_extension_control_directories();
423+
result = find_in_paths(basename, paths);
426424

427425
if (result)
428426
{
@@ -439,24 +437,20 @@ find_extension_control_filename(ExtensionControlFile *control)
439437
static char *
440438
get_extension_script_directory(ExtensionControlFile *control)
441439
{
442-
char sharepath[MAXPGPATH];
443-
char *result;
444-
445440
/*
446441
* The directory parameter can be omitted, absolute, or relative to the
447-
* installation's share directory.
442+
* installation's base directory, which can be the sharedir or a custom
443+
* path that it was set extension_control_path. It depends where the
444+
* .control file was found.
448445
*/
449446
if (!control->directory)
450447
return pstrdup(control->control_dir);
451448

452449
if (is_absolute_path(control->directory))
453450
return pstrdup(control->directory);
454451

455-
get_share_path(my_exec_path, sharepath);
456-
result = (char *) palloc(MAXPGPATH);
457-
snprintf(result, MAXPGPATH, "%s/%s", sharepath, control->directory);
458-
459-
return result;
452+
Assert(control->basedir != NULL);
453+
return psprintf("%s/%s", control->basedir, control->directory);
460454
}
461455

462456
static char *
@@ -550,6 +544,14 @@ parse_extension_control_file(ExtensionControlFile *control,
550544
errhint("The extension must first be installed on the system where PostgreSQL is running.")));
551545
}
552546

547+
/* Assert that the control_dir ends with /extension */
548+
Assert(control->control_dir != NULL);
549+
Assert(strcmp(control->control_dir + strlen(control->control_dir) - strlen("/extension"), "/extension") == 0);
550+
551+
control->basedir = pnstrdup(
552+
control->control_dir,
553+
strlen(control->control_dir) - strlen("/extension"));
554+
553555
if ((file = AllocateFile(filename, "r")) == NULL)
554556
{
555557
/* no complaint for missing auxiliary file */
@@ -3863,3 +3865,44 @@ new_ExtensionControlFile(const char *extname)
38633865

38643866
return control;
38653867
}
3868+
3869+
/*
3870+
* Work in a very similar way with find_in_path but it receives an already
3871+
* parsed List of paths to search the basename and it do not support macro
3872+
* replacement or custom error messages (for simplicity).
3873+
*
3874+
* By "already parsed List of paths" this function expected that paths already
3875+
* have all macros replaced.
3876+
*/
3877+
char *
3878+
find_in_paths(const char *basename, List *paths)
3879+
{
3880+
ListCell *cell;
3881+
3882+
foreach(cell, paths)
3883+
{
3884+
char *path = lfirst(cell);
3885+
char *full;
3886+
3887+
Assert(path != NULL);
3888+
3889+
path = pstrdup(path);
3890+
canonicalize_path(path);
3891+
3892+
/* only absolute paths */
3893+
if (!is_absolute_path(path))
3894+
ereport(ERROR,
3895+
errcode(ERRCODE_INVALID_NAME),
3896+
errmsg("component in parameter \"%s\" is not an absolute path", "extension_control_path"));
3897+
3898+
full = psprintf("%s/%s", path, basename);
3899+
3900+
if (pg_file_exists(full))
3901+
return full;
3902+
3903+
pfree(path);
3904+
pfree(full);
3905+
}
3906+
3907+
return NULL;
3908+
}

src/test/modules/test_extensions/t/001_extension_control_path.pl

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use strict;
44
use warnings FATAL => 'all';
5+
use File::Path qw(mkpath);
56
use PostgreSQL::Test::Cluster;
67
use PostgreSQL::Test::Utils;
78
use Test::More;
@@ -12,25 +13,14 @@
1213

1314
# Create a temporary directory for the extension control file
1415
my $ext_dir = PostgreSQL::Test::Utils::tempdir();
16+
mkpath("$ext_dir/extension");
17+
1518
my $ext_name = "test_custom_ext_paths";
16-
my $control_file = "$ext_dir/$ext_name.control";
17-
my $sql_file = "$ext_dir/$ext_name--1.0.sql";
18-
19-
# Create .control .sql file
20-
open my $cf, '>', $control_file or die "Could not create control file: $!";
21-
print $cf "comment = 'Test extension_control_path'\n";
22-
print $cf "default_version = '1.0'\n";
23-
print $cf "relocatable = true\n";
24-
close $cf;
25-
26-
# Create --1.0.sql file
27-
open my $sqlf, '>', $sql_file or die "Could not create sql file: $!";
28-
print $sqlf "/* $sql_file */\n";
29-
print $sqlf
30-
"-- complain if script is sourced in psql, rather than via CREATE EXTENSION\n";
31-
print $sqlf
32-
qq'\\echo Use "CREATE EXTENSION $ext_name" to load this file. \\quit\n';
33-
close $sqlf;
19+
create_extension($ext_name, $ext_dir);
20+
21+
my $ext_name2 = "test_custom_ext_paths_using_directory";
22+
mkpath("$ext_dir/$ext_name2");
23+
create_extension($ext_name2, $ext_dir, $ext_name2);
3424

3525
# Use the correct separator and escape \ when running on Windows.
3626
my $sep = $windows_os ? ";" : ":";
@@ -48,33 +38,88 @@
4838
"custom extension control directory path configured");
4939

5040
$node->safe_psql('postgres', "CREATE EXTENSION $ext_name");
41+
$node->safe_psql('postgres', "CREATE EXTENSION $ext_name2");
5142

5243
my $ret = $node->safe_psql('postgres',
5344
"select * from pg_available_extensions where name = '$ext_name'");
5445
is( $ret,
5546
"test_custom_ext_paths|1.0|1.0|Test extension_control_path",
5647
"extension is installed correctly on pg_available_extensions");
5748

58-
my $ret2 = $node->safe_psql('postgres',
49+
$ret = $node->safe_psql('postgres',
5950
"select * from pg_available_extension_versions where name = '$ext_name'");
60-
is( $ret2,
51+
is( $ret,
6152
"test_custom_ext_paths|1.0|t|t|f|t|||Test extension_control_path",
6253
"extension is installed correctly on pg_available_extension_versions");
6354

55+
$ret = $node->safe_psql('postgres',
56+
"select * from pg_available_extensions where name = '$ext_name2'");
57+
is( $ret,
58+
"test_custom_ext_paths_using_directory|1.0|1.0|Test extension_control_path",
59+
"extension is installed correctly on pg_available_extensions");
60+
61+
$ret = $node->safe_psql('postgres',
62+
"select * from pg_available_extension_versions where name = '$ext_name2'"
63+
);
64+
is( $ret,
65+
"test_custom_ext_paths_using_directory|1.0|t|t|f|t|||Test extension_control_path",
66+
"extension is installed correctly on pg_available_extension_versions");
67+
6468
# Ensure that extensions installed on $system is still visible when using with
6569
# custom extension control path.
66-
my $ret3 = $node->safe_psql('postgres',
70+
$ret = $node->safe_psql('postgres',
6771
"select count(*) > 0 as ok from pg_available_extensions where name = 'plpgsql'"
6872
);
69-
is($ret3, "t",
73+
is($ret, "t",
7074
"\$system extension is installed correctly on pg_available_extensions");
7175

7276

73-
my $ret4 = $node->safe_psql('postgres',
77+
$ret = $node->safe_psql('postgres',
7478
"set extension_control_path = ''; select count(*) > 0 as ok from pg_available_extensions where name = 'plpgsql'"
7579
);
76-
is($ret4, "t",
80+
is($ret, "t",
7781
"\$system extension is installed correctly on pg_available_extensions with empty extension_control_path"
7882
);
7983

84+
# Test with an extension that does not exists
85+
my ($code, $stdout, $stderr) =
86+
$node->psql('postgres', "CREATE EXTENSION invalid");
87+
is($code, 3, 'error to create an extension that does not exists');
88+
like($stderr, qr/ERROR: extension "invalid" is not available/);
89+
90+
sub create_extension
91+
{
92+
my ($ext_name, $ext_dir, $directory) = @_;
93+
94+
my $control_file = "$ext_dir/extension/$ext_name.control";
95+
my $sql_file;
96+
97+
if (defined $directory)
98+
{
99+
$sql_file = "$ext_dir/$directory/$ext_name--1.0.sql";
100+
}
101+
else
102+
{
103+
$sql_file = "$ext_dir/extension/$ext_name--1.0.sql";
104+
}
105+
106+
# Create .control .sql file
107+
open my $cf, '>', $control_file
108+
or die "Could not create control file: $!";
109+
print $cf "comment = 'Test extension_control_path'\n";
110+
print $cf "default_version = '1.0'\n";
111+
print $cf "relocatable = true\n";
112+
print $cf "directory = $directory" if defined $directory;
113+
close $cf;
114+
115+
# Create --1.0.sql file
116+
open my $sqlf, '>', $sql_file or die "Could not create sql file: $!";
117+
print $sqlf "/* $sql_file */\n";
118+
print $sqlf
119+
"-- complain if script is sourced in psql, rather than via CREATE EXTENSION\n";
120+
print $sqlf
121+
qq'\\echo Use "CREATE EXTENSION $ext_name" to load this file. \\quit\n';
122+
close $sqlf;
123+
}
124+
80125
done_testing();

0 commit comments

Comments
 (0)