pgsql: extension_control_path
extension_control_path
The new GUC extension_control_path specifies a path to look for
extension control files. The default value is $system, which looks in
the compiled-in location, as before.
The path search uses the same code and works in the same way as
dynamic_library_path.
Some use cases of this are: (1) testing extensions during package
builds, (2) installing extensions outside security-restricted
containers like Python.app (on macOS), (3) adding extensions to
PostgreSQL running in a Kubernetes environment using operators such as
CloudNativePG without having to rebuild the base image for each new
extension.
There is also a tweak in Makefile.global so that it is possible to
install extensions using PGXS into an different directory than the
default, using 'make install prefix=/else/where'. This previously
only worked when specifying the subdirectories, like 'make install
datadir=/else/where/share pkglibdir=/else/where/lib', for purely
implementation reasons. (Of course, without the path feature,
installing elsewhere was rarely useful.)
Author: Peter Eisentraut <peter@eisentraut.org>
Co-authored-by: Matheus Alcantara <matheusssilv97@gmail.com>
Reviewed-by: David E. Wheeler <david@justatheory.com>
Reviewed-by: Gabriele Bartolini <gabriele.bartolini@enterprisedb.com>
Reviewed-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
Reviewed-by: Niccolò Fei <niccolo.fei@enterprisedb.com>
Discussion: /messages/by-id/E7C7BFFB-8857-48D4-A71F-88B359FADCFD@justatheory.com
Branch
------
master
Details
-------
https://git.postgresql.org/pg/commitdiff/4f7f7b0375854e2f89876473405a8f21c95012af
Modified Files
--------------
doc/src/sgml/config.sgml | 68 ++++
doc/src/sgml/extend.sgml | 19 +-
doc/src/sgml/ref/create_extension.sgml | 6 +-
src/Makefile.global.in | 19 +-
src/backend/commands/extension.c | 403 +++++++++++++--------
src/backend/utils/fmgr/dfmgr.c | 77 ++--
src/backend/utils/misc/guc_tables.c | 13 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/commands/extension.h | 2 +
src/include/fmgr.h | 3 +
src/test/modules/test_extensions/Makefile | 1 +
src/test/modules/test_extensions/meson.build | 5 +
.../t/001_extension_control_path.pl | 80 ++++
13 files changed, 512 insertions(+), 185 deletions(-)
Re: Peter Eisentraut
The new GUC extension_control_path specifies a path to look for
extension control files. The default value is $system, which looks in
the compiled-in location, as before.
Is the expectation that this also works for the extension "directory"?
semver is still failing because it's shipping its .sql files in a
separate directory:
2025-04-23 09:06:24.864 UTC [422345] myon@contrib_regression ERROR: could not open directory "/usr/share/postgresql/18/semver": No such file or directory
2025-04-23 09:06:24.864 UTC [422345] myon@contrib_regression STATEMENT: CREATE EXTENSION semver;
$ cat debian/postgresql-18-semver/usr/share/postgresql/18/extension/semver.control
# semver extension
comment = 'Semantic version data type'
default_version = '0.40.0'
directory = 'semver'
module_pathname = '$libdir/semver'
relocatable = true
$ ls debian/postgresql-18-semver/usr/share/postgresql/18/semver/
semver--0.10.0--0.11.0.sql semver--0.17.0--0.20.0.sql semver--0.30.0--0.31.0.sql semver--0.32.1--0.40.0.sql
semver--0.11.0--0.12.0.sql semver--0.20.0--0.21.0.sql semver--0.3.0--0.4.0.sql semver--0.40.0.sql
semver--0.12.0--0.13.0.sql semver--0.21.0--0.22.0.sql semver--0.31.0--0.31.1.sql semver--0.5.0--0.10.0.sql
semver--0.13.0--0.15.0.sql semver--0.2.1--0.2.4.sql semver--0.31.1--0.31.2.sql semver.sql
semver--0.15.0--0.16.0.sql semver--0.22.0--0.30.0.sql semver--0.31.2--0.32.0.sql semver--unpackaged--0.2.1.sql
semver--0.16.0--0.17.0.sql semver--0.2.4--0.3.0.sql semver--0.32.0--0.32.1.sql
Christoph
On Wed, Apr 23, 2025 at 6:39 AM Christoph Berg <myon@debian.org> wrote:
Re: Peter Eisentraut
The new GUC extension_control_path specifies a path to look for
extension control files. The default value is $system, which looks in
the compiled-in location, as before.Is the expectation that this also works for the extension "directory"?
semver is still failing because it's shipping its .sql files in a
separate directory:2025-04-23 09:06:24.864 UTC [422345] myon@contrib_regression ERROR: could not open directory "/usr/share/postgresql/18/semver": No such file or directory
2025-04-23 09:06:24.864 UTC [422345] myon@contrib_regression STATEMENT: CREATE EXTENSION semver;$ cat debian/postgresql-18-semver/usr/share/postgresql/18/extension/semver.control
# semver extension
comment = 'Semantic version data type'
default_version = '0.40.0'directory = 'semver'
module_pathname = '$libdir/semver'
relocatable = true$ ls debian/postgresql-18-semver/usr/share/postgresql/18/semver/
semver--0.10.0--0.11.0.sql semver--0.17.0--0.20.0.sql semver--0.30.0--0.31.0.sql semver--0.32.1--0.40.0.sql
semver--0.11.0--0.12.0.sql semver--0.20.0--0.21.0.sql semver--0.3.0--0.4.0.sql semver--0.40.0.sql
semver--0.12.0--0.13.0.sql semver--0.21.0--0.22.0.sql semver--0.31.0--0.31.1.sql semver--0.5.0--0.10.0.sql
semver--0.13.0--0.15.0.sql semver--0.2.1--0.2.4.sql semver--0.31.1--0.31.2.sql semver.sql
semver--0.15.0--0.16.0.sql semver--0.22.0--0.30.0.sql semver--0.31.2--0.32.0.sql semver--unpackaged--0.2.1.sql
semver--0.16.0--0.17.0.sql semver--0.2.4--0.3.0.sql semver--0.32.0--0.32.1.sql
I've tried to implement some kind of "SHAREDIR search path" as we've
discussed on another thread [1]/messages/by-id/0F50D989-B82D-4F59-9F13-C08A4673322C@justatheory.com but it shows out that we need some
considerable refactoring to make it work.
Talking with Peter privately IIUC we concluded that we may document
this limitation that using extension control path with an extension that
uses a custom "directory" field on .control file will not work. I think
that we may add a note section on "extension_control_path" doc on [2]https://www.postgresql.org/docs/devel/runtime-config-client.html#GUC-EXTENSION-CONTROL-PATH,
what do you think?
[1]: /messages/by-id/0F50D989-B82D-4F59-9F13-C08A4673322C@justatheory.com
[2]: https://www.postgresql.org/docs/devel/runtime-config-client.html#GUC-EXTENSION-CONTROL-PATH
--
Matheus Alcantara
Re: Matheus Alcantara
I've tried to implement some kind of "SHAREDIR search path" as we've
discussed on another thread [1] but it shows out that we need some
considerable refactoring to make it work.
Remembering which path the .control file was found in and from there
open the extension "directory" doesn't sound too hard. Why does it
have to be more complicated?
Also, re-running a search path discovery for the directory is probably
just wrong, if there are different extension versions in the "control"
search path and the "extensions" search path, it might lead to weird
version skew problems.
Talking with Peter privately IIUC we concluded that we may document
this limitation that using extension control path with an extension that
uses a custom "directory" field on .control file will not work. I think
that we may add a note section on "extension_control_path" doc on [2],
what do you think?
Seen from Debian, this would be a regression since it worked with my
custom patch.
The number of extensions using that feature is limited, though, so it
wouldn't be a huge problem:
$ grep directory */*/*.control
pgfincore/pgfincore/pgfincore.control:directory = pgfincore
postgresql-pgmp/postgresql-pgmp/pgmp.control:directory = 'pgmp'
postgresql-semver/postgresql-semver/semver.control:directory = 'semver'
(Not including extensions generating the .control file at build time.)
Christoph
On Apr 23, 2025, at 09:50, Christoph Berg <myon@debian.org> wrote:
Remembering which path the .control file was found in and from there
open the extension "directory" doesn't sound too hard. Why does it
have to be more complicated?
This was my question, as well. Do you have a WIP patch to share, Matheus?
Also, re-running a search path discovery for the directory is probably
just wrong, if there are different extension versions in the "control"
search path and the "extensions" search path, it might lead to weird
version skew problems.
I assumed we would just have one or the other GUCs, not both.
The number of extensions using that feature is limited, though, so it
wouldn't be a huge problem:
FWIW it’s a a simple patch to make semver work, and probably also for the others. It’s just the reverse of this change[1]https://github.com/theory/pg-semver/commit/88b3abd:
```patch
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,6 @@ EXTVERSION = $(shell grep -m 1 '[[:space:]]\{8\}"version":' META.json | \
DISTVERSION = $(shell grep -m 1 '[[:space:]]\{3\}"version":' META.json | \
sed -e 's/[[:space:]]*"version":[[:space:]]*"\([^"]*\)",\{0,1\}/\1/')
-MODULEDIR = semver
DATA = $(wildcard sql/*.sql)
DOCS = $(wildcard doc/*.mmd)
TESTS = $(wildcard test/sql/*.sql)
--- a/semver.control
+++ b/semver.control
@@ -1,7 +1,5 @@
# semver extension
comment = 'Semantic version data type'
default_version = '0.32.1'
-
-directory = 'semver'
module_pathname = '$libdir/semver'
relocatable = true
```
I think I’ll write a blog post this week recommending people not use these directives, and also to remove `$lib/` from `module_pathname`.
Best,
David
On Wed, Apr 23, 2025 at 10:57 AM David E. Wheeler <david@justatheory.com> wrote:
On Apr 23, 2025, at 09:50, Christoph Berg <myon@debian.org> wrote:
Remembering which path the .control file was found in and from there
open the extension "directory" doesn't sound too hard. Why does it
have to be more complicated?This was my question, as well. Do you have a WIP patch to share, Matheus?
I spent some time trying to implement this and somehow I got lost in the
changes I thought I would need to make to the "find_in_path" function
and others it calls, but reading these messages and looking at the code
again I think that the change is much simpler than I thought.
Attached is a draft patch that uses the path that the .control file was
found to search for the script files when the "directory" is set on the
.control file.
I've tested with the semver extension and it seems to work fine with
this patch. Can you please check on your side to see if it's also
working?
I still want to make some polish on this patch and also include some
more test cases using the "directory" on the .control file but I think
that is stable to make some tests, make check and check-world is happy.
--
Matheus Alcantara
Attachments:
v1-0001-Make-directory-work-with-extension-control-path.patchapplication/octet-stream; name=v1-0001-Make-directory-work-with-extension-control-path.patchDownload
From c07bfd1556502592c1a63cceafe0542f28dfe0a4 Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <mths.dev@pm.me>
Date: Wed, 23 Apr 2025 16:11:24 -0300
Subject: [PATCH v1] Make "directory" work with extension control path
Previously extensions installed on a custom path that is available via
extension_control_path GUC that set the "directory" field on .control
file was not being able to CREATE. This was happening because on
get_extension_script_directory was hard coded to search for the script
files only on the share system dir.
This commit fix this issue by using the control->control_dir as a share
dir to return the path of the extension script files.
---
src/backend/commands/extension.c | 43 +++++++++++++++++--
.../t/001_extension_control_path.pl | 6 ++-
2 files changed, 43 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 180f4af9be3..d68efd59118 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -376,6 +376,14 @@ get_extension_control_directories(void)
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
+
+ /*
+ * Append "extension" suffix in case is a custom extension control
+ * path.
+ */
+ if (strcmp(piece, "$system") != 0)
+ mangled = psprintf("%s/extension", mangled);
+
pfree(piece);
/* Canonicalize the path based on the OS and add to the list */
@@ -422,6 +430,9 @@ find_extension_control_filename(ExtensionControlFile *control)
ecp = Extension_control_path;
if (strlen(ecp) == 0)
ecp = "$system";
+ else if (strcmp(ecp, "$system") != 0)
+ ecp = psprintf("%s/extension", ecp);
+
result = find_in_path(basename, ecp, "extension_control_path", "$system", system_dir);
if (result)
@@ -439,8 +450,11 @@ find_extension_control_filename(ExtensionControlFile *control)
static char *
get_extension_script_directory(ExtensionControlFile *control)
{
- char sharepath[MAXPGPATH];
char *result;
+ int ctrldir_len;
+ int prefix_len;
+ int dir_len;
+ int suffix_len = strlen("extension");
/*
* The directory parameter can be omitted, absolute, or relative to the
@@ -452,9 +466,30 @@ 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);
+ ctrldir_len = strlen(control->control_dir);
+
+ /*
+ * Assert that the control->control_dir end with /extension suffix so that
+ * we can replace with the value from control->directory.
+ */
+ Assert(ctrldir_len >= suffix_len &&
+ strcmp(control->control_dir + ctrldir_len - suffix_len, "extension") == 0);
+
+ /*
+ * At this point we have the control->directory and control->control_dir
+ * filled. As the extension is using a custom "directory" value we need to
+ * replace the "/extension" suffix from "control_dir" with the value of
+ * "directory" so that we can find the correct script files.
+ */
+ prefix_len = ctrldir_len - suffix_len;
+ dir_len = strlen(control->directory);
+
+ /* don't forget null terminator */
+ result = (char *) palloc(prefix_len + dir_len + 1);
+
+ memcpy(result, control->control_dir, prefix_len);
+ memcpy(result + prefix_len, control->directory, dir_len);
+ result[prefix_len + dir_len] = '\0'; /* don't forget null terminator */
return result;
}
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..7616011105f 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
@@ -5,6 +5,7 @@ use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
+use File::Path qw( make_path );
my $node = PostgreSQL::Test::Cluster->new('node');
@@ -12,9 +13,10 @@ $node->init;
# Create a temporary directory for the extension control file
my $ext_dir = PostgreSQL::Test::Utils::tempdir();
+make_path("$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";
+my $control_file = "$ext_dir/extension/$ext_name.control";
+my $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: $!";
--
2.39.5 (Apple Git-154)
Re: Matheus Alcantara
I've tested with the semver extension and it seems to work fine with
this patch. Can you please check on your side to see if it's also
working?
Hi Matheus,
thanks for the patch, it does indeed work.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 180f4af9be3..d68efd59118 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -376,6 +376,14 @@ get_extension_control_directories(void)
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
+
+ /*
+ * Append "extension" suffix in case is a custom extension control
+ * path.
+ */
+ if (strcmp(piece, "$system") != 0)
+ mangled = psprintf("%s/extension", mangled);
This would look prettier if it was something like
mangled = substitute_path_macro(piece, "$system", system_dir "/extension");
... but I'm wondering if it wouldn't be saner if the control path
should be stored without "extension" in that struct. Then opening the
control file would be path + "extension/" + filename and the extra
directory would be path + directory, without any on-the-fly stripping
of trailing components.
The extension_control_path GUC could also be adjusted to refer to the
directory one level above the extension/foo.control location.
+ /*
+ * Assert that the control->control_dir end with /extension suffix so that
+ * we can replace with the value from control->directory.
+ */
+ Assert(ctrldir_len >= suffix_len &&
+ strcmp(control->control_dir + ctrldir_len - suffix_len, "extension") == 0);
If control_dir is coming from extension_control_path, it might have a
different suffix. Replace the Assert by elog(ERROR). (Or see above.)
Christoph
On Thu, Apr 24, 2025 at 7:21 AM Christoph Berg <myon@debian.org> wrote:
Re: Matheus Alcantara
I've tested with the semver extension and it seems to work fine with
this patch. Can you please check on your side to see if it's also
working?Hi Matheus,
thanks for the patch, it does indeed work.
Thanks for testing and for reviewing.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 180f4af9be3..d68efd59118 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -376,6 +376,14 @@ get_extension_control_directories(void)/* Substitute the path macro if needed */ mangled = substitute_path_macro(piece, "$system", system_dir); + + /* + * Append "extension" suffix in case is a custom extension control + * path. + */ + if (strcmp(piece, "$system") != 0) + mangled = psprintf("%s/extension", mangled);This would look prettier if it was something like
mangled = substitute_path_macro(piece, "$system", system_dir "/extension");
... but I'm wondering if it wouldn't be saner if the control path
should be stored without "extension" in that struct. Then opening the
control file would be path + "extension/" + filename and the extra
directory would be path + directory, without any on-the-fly stripping
of trailing components.The extension_control_path GUC could also be adjusted to refer to the
directory one level above the extension/foo.control location.
Storing the control path directly without any code to remove the
/extension at the end would be more trick I think, because we would need
to change the find_in_path() function to return the path without the
suffix.
In this new version I've introduced a new "basedir" field on
ExtensionControlFile so that we can save the base directory to search
for .control files and scripts. With this new field, on
get_extension_script_directory() we just need to join control->basedir
with control->directory. Note that we still need to handle the removal
of the /extension at the end of control path but I think that on this
new version the code looks a bit better (IMHO) since we just need to
handle on find_extension_control_filename(). WYT?
+ /* + * Assert that the control->control_dir end with /extension suffix so that + * we can replace with the value from control->directory. + */ + Assert(ctrldir_len >= suffix_len && + strcmp(control->control_dir + ctrldir_len - suffix_len, "extension") == 0);If control_dir is coming from extension_control_path, it might have a
different suffix. Replace the Assert by elog(ERROR). (Or see above.)
In v2 I've moved the logic to remove the /extension to
parse_extension_control_file(), do you think that this Assert on this
function would still be wrong? IIUC we should always have /extension at
the end of "control_dir" at this place, because the
extension_control_path GUC will omit the /extension at the end and we
will force it to have the suffix on the path at
find_extension_control_filename() and
get_extension_control_directories() functions. I'm missing something
here?
I've also included some more TAP tests on this new version.
--
Matheus Alcantara
Attachments:
v2-0001-Make-directory-work-with-extension-control-path.patchapplication/octet-stream; name=v2-0001-Make-directory-work-with-extension-control-path.patchDownload
From b74590b75b0a08c74625a1731c2544ec1271fa34 Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <mths.dev@pm.me>
Date: Wed, 23 Apr 2025 16:11:24 -0300
Subject: [PATCH v2] Make "directory" work with extension control path
Previously extensions installed on a custom path that is available via
extension_control_path GUC that set the "directory" field on .control
file was not being able to CREATE. This was happening because on
get_extension_script_directory was hard coded to search for the script
files only on the share system dir.
This commit fix this issue by using the control->control_dir as a share
dir to return the path of the extension script files.
---
src/backend/commands/extension.c | 28 ++++--
.../t/001_extension_control_path.pl | 88 ++++++++++++++-----
2 files changed, 84 insertions(+), 32 deletions(-)
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 180f4af9be3..3c85c745d0a 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 */
@@ -376,6 +378,14 @@ get_extension_control_directories(void)
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
+
+ /*
+ * Append "extension" suffix in case is a custom extension control
+ * path.
+ */
+ if (strcmp(piece, "$system") != 0)
+ mangled = psprintf("%s/extension", mangled);
+
pfree(piece);
/* Canonicalize the path based on the OS and add to the list */
@@ -422,6 +432,9 @@ find_extension_control_filename(ExtensionControlFile *control)
ecp = Extension_control_path;
if (strlen(ecp) == 0)
ecp = "$system";
+ else if (strcmp(ecp, "$system") != 0)
+ ecp = psprintf("%s/extension", ecp);
+
result = find_in_path(basename, ecp, "extension_control_path", "$system", system_dir);
if (result)
@@ -439,9 +452,6 @@ 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.
@@ -452,11 +462,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 *
@@ -542,6 +549,11 @@ parse_extension_control_file(ExtensionControlFile *control,
filename = find_extension_control_filename(control);
}
+ Assert(control->control_dir != NULL);
+ control->basedir = pnstrdup(
+ control->control_dir,
+ strlen(control->control_dir) - strlen("/extension"));
+
if (!filename)
{
ereport(ERROR,
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..827f293fbc9 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
@@ -5,6 +5,7 @@ use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
+use File::Path qw( make_path );
my $node = PostgreSQL::Test::Cluster->new('node');
@@ -12,25 +13,14 @@ $node->init;
# Create a temporary directory for the extension control file
my $ext_dir = PostgreSQL::Test::Utils::tempdir();
+make_path("$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";
+make_path("$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,75 @@ 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"
);
+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";
+ if (defined $directory)
+ {
+ print $cf "directory = $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();
--
2.39.5 (Apple Git-154)
On Apr 24, 2025, at 11:18, Matheus Alcantara <matheusssilv97@gmail.com> wrote:
In v2 I've moved the logic to remove the /extension to
parse_extension_control_file(), do you think that this Assert on this
function would still be wrong? IIUC we should always have /extension at
the end of "control_dir" at this place, because the
extension_control_path GUC will omit the /extension at the end and we
will force it to have the suffix on the path at
find_extension_control_filename() and
get_extension_control_directories() functions. I'm missing something
here?
I took this patch for a spin and managed to make it core dump. How? Well I installed semver with this command:
```sh
make prefix=/Users/david/Downloads install
```
Then set the search paths and restarted:
```ini
extension_control_path = '/Users/david/Downloads/share/extension:$system'
dynamic_library_path = '/Users/david/Downloads/lib:$libdir'
```
Then I connected and ran `CREATE EXTENSION semver` and it segfaulted. I poked around for a few minutes and realized that my prefix is not what I expected. Because it doesn’t contain the string “postgres”, PGXS helpfully adds it. The actual paths are:
```ini
extension_control_path = '/Users/david/Downloads/share/postgresql/extension:$system'
dynamic_library_path = '/Users/david/Downloads/lib/postgresql:$libdir'
```
With that fix it no longer segafulted.
So I presume something crashes when a directory or file doesn’t exist.
But I am not at all sure we want this prefix behavior for installing extensions. I get that has been the behavior for setting the main sharedir and libdir for Postgres, but I don’t know that it makes sense for extension prefixes.
Best,
David
On Thu, Apr 24, 2025 at 7:27 PM David E. Wheeler <david@justatheory.com> wrote:
On Apr 24, 2025, at 11:18, Matheus Alcantara <matheusssilv97@gmail.com> wrote:
In v2 I've moved the logic to remove the /extension to
parse_extension_control_file(), do you think that this Assert on this
function would still be wrong? IIUC we should always have /extension at
the end of "control_dir" at this place, because the
extension_control_path GUC will omit the /extension at the end and we
will force it to have the suffix on the path at
find_extension_control_filename() and
get_extension_control_directories() functions. I'm missing something
here?I took this patch for a spin and managed to make it core dump. How? Well I installed semver with this command:
```sh
make prefix=/Users/david/Downloads install
```Then set the search paths and restarted:
```ini
extension_control_path = '/Users/david/Downloads/share/extension:$system'
dynamic_library_path = '/Users/david/Downloads/lib:$libdir'
```Then I connected and ran `CREATE EXTENSION semver` and it segfaulted. I poked around for a few minutes and realized that my prefix is not what I expected. Because it doesn’t contain the string “postgres”, PGXS helpfully adds it. The actual paths are:
```ini
extension_control_path = '/Users/david/Downloads/share/postgresql/extension:$system'
dynamic_library_path = '/Users/david/Downloads/lib/postgresql:$libdir'
```With that fix it no longer segafulted.
So I presume something crashes when a directory or file doesn’t exist.
Yes, you are right. The problem was where I was asserting
control->control_dir != NULL. I've moved the assert after the "if
(!filename)" check that returns an error if the extension was not found.
Attached v3 with this fix and also a TAP test for this scenario.
I'm just a bit confused how you get it working using /extension at the
end of extension_control_path since with this patch this suffix is not
necessary and since we hard coded append this it should return an error
when trying to search on something like
/Users/david/Downloads/share/postgresql/extension/extension
--
Matheus Alcantara
Attachments:
v3-0001-Make-directory-work-with-extension-control-path.patchapplication/octet-stream; name=v3-0001-Make-directory-work-with-extension-control-path.patchDownload
From 0a19076958b684fd98d65fb97f4b228d2ddd8b2c Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <mths.dev@pm.me>
Date: Wed, 23 Apr 2025 16:11:24 -0300
Subject: [PATCH v3] Make "directory" work with extension control path
Previously extensions installed on a custom path that is available via
extension_control_path GUC that set the "directory" field on .control
file was not being able to CREATE. This was happening because on
get_extension_script_directory was hard coded to search for the script
files only on the share system dir.
This commit fix this issue by using the control->control_dir as a share
dir to return the path of the extension script files.
---
src/backend/commands/extension.c | 31 +++++--
.../t/001_extension_control_path.pl | 93 ++++++++++++++-----
2 files changed, 92 insertions(+), 32 deletions(-)
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 180f4af9be3..ce53ed85401 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 */
@@ -376,6 +378,14 @@ get_extension_control_directories(void)
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
+
+ /*
+ * Append "extension" suffix in case is a custom extension control
+ * path.
+ */
+ if (strcmp(piece, "$system") != 0)
+ mangled = psprintf("%s/extension", mangled);
+
pfree(piece);
/* Canonicalize the path based on the OS and add to the list */
@@ -422,6 +432,9 @@ find_extension_control_filename(ExtensionControlFile *control)
ecp = Extension_control_path;
if (strlen(ecp) == 0)
ecp = "$system";
+ else if (strcmp(ecp, "$system") != 0)
+ ecp = psprintf("%s/extension", ecp);
+
result = find_in_path(basename, ecp, "extension_control_path", "$system", system_dir);
if (result)
@@ -439,9 +452,6 @@ 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.
@@ -452,11 +462,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 +557,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 */
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..1ef79d7574f 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
@@ -5,6 +5,7 @@ use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
+use File::Path qw( make_path );
my $node = PostgreSQL::Test::Cluster->new('node');
@@ -12,25 +13,14 @@ $node->init;
# Create a temporary directory for the extension control file
my $ext_dir = PostgreSQL::Test::Utils::tempdir();
+make_path("$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";
+make_path("$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";
+ if (defined $directory)
+ {
+ print $cf "directory = $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();
--
2.39.5 (Apple Git-154)
On Apr 25, 2025, at 09:25, Matheus Alcantara <matheusssilv97@gmail.com> wrote:
Yes, you are right. The problem was where I was asserting
control->control_dir != NULL. I've moved the assert after the "if
(!filename)" check that returns an error if the extension was not found.Attached v3 with this fix and also a TAP test for this scenario.
That fixes the segfault, thank you.
I'm just a bit confused how you get it working using /extension at the
end of extension_control_path since with this patch this suffix is not
necessary and since we hard coded append this it should return an error
when trying to search on something like
It worked with
extension_control_path = '/Users/david/Downloads/share/postgresql/extension:$system’
But not with
extension_control_path = '/Users/david/Downloads/share/postgresql:$system’
And here is where the control file actually is:
❯ ll ~/Downloads/share/postgresql/extension total 8
-rw-r--r-- 1 david staff 161B Apr 24 18:07 semver.control
So I don’t know the answer to your question, but it’d be handy to have functions that return a list of resolved paths from extension_control_path and dynamic_library_path, since they get mangled.
Best,
David
On Fri, Apr 25, 2025 at 4:13 PM David E. Wheeler <david@justatheory.com> wrote:
On Apr 25, 2025, at 09:25, Matheus Alcantara <matheusssilv97@gmail.com> wrote:
Yes, you are right. The problem was where I was asserting
control->control_dir != NULL. I've moved the assert after the "if
(!filename)" check that returns an error if the extension was not found.Attached v3 with this fix and also a TAP test for this scenario.
That fixes the segfault, thank you.
Great, thanks for testing!
I'm just a bit confused how you get it working using /extension at the
end of extension_control_path since with this patch this suffix is not
necessary and since we hard coded append this it should return an error
when trying to search on something likeIt worked with
extension_control_path = '/Users/david/Downloads/share/postgresql/extension:$system’
But not with
extension_control_path = '/Users/david/Downloads/share/postgresql:$system’
And here is where the control file actually is:
❯ ll ~/Downloads/share/postgresql/extension total 8
-rw-r--r-- 1 david staff 161B Apr 24 18:07 semver.controlSo I don’t know the answer to your question, but it’d be handy to have functions that return a list of resolved paths from extension_control_path and dynamic_library_path, since they get mangled.
Ok, I was testing using extension_control_path = '$system:/my/custom/path'
(starting with the macro) and it was working as expected, testing with
the macro at the end does not work.
The problem was on find_extension_control_filename() that was appending
the /extension at the end of the entire extension_control_path GUC value
instead of just the custom paths.
To append the /extension at each path on extension_control_path would
require some changes on find_in_path() that
find_extension_control_filename() calls, which I think that it would
make the function more complicated. I've them created a similar
find_in_paths() function that works in the same way but it receives a
List of paths instead of the string of paths separated by ":". We can
get this List of paths using get_extension_control_directories() that
also handle the macro substitution like find_in_path().
Attached v4 with these fixes. I hope that now you should be able to omit
the /extension from the GUC value.
--
Matheus Alcantara
Attachments:
v4-0001-Make-directory-work-with-extension-control-path.patchapplication/octet-stream; name=v4-0001-Make-directory-work-with-extension-control-path.patchDownload
From 6eb655fbe59083a60d3dfdb3804eeaeaa171ad2c Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <mths.dev@pm.me>
Date: Wed, 23 Apr 2025 16:11:24 -0300
Subject: [PATCH v4] Make "directory" work with extension control path
Previously extensions installed on a custom path that is available via
extension_control_path GUC that set the "directory" field on .control
file was not being able to CREATE. This was happening because on
get_extension_script_directory was hard coded to search for the script
files only on the share system dir.
This commit fix this issue by using the control->control_dir as a share
dir to return the path of the extension script files.
---
src/backend/commands/extension.c | 99 ++++++++++++++-----
.../t/001_extension_control_path.pl | 93 ++++++++++++-----
2 files changed, 144 insertions(+), 48 deletions(-)
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 180f4af9be3..c2b9874b3e2 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
@@ -376,6 +379,14 @@ get_extension_control_directories(void)
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
+
+ /*
+ * Append "extension" suffix in case is a custom extension control
+ * path.
+ */
+ if (strcmp(piece, "$system") != 0)
+ mangled = psprintf("%s/extension", mangled);
+
pfree(piece);
/* Canonicalize the path based on the OS and add to the list */
@@ -401,28 +412,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 +438,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 +450,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 +545,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 +3866,51 @@ 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;
+
+ /*
+ * If the paths variable is empty, don't do a path search.
+ */
+ if (paths == NIL)
+ return NULL;
+
+ foreach(cell, paths)
+ {
+ char *path = (char *) lfirst(cell);
+ char *full;
+
+ Assert(path != NULL);
+
+ canonicalize_path(path);
+
+ /* only absolute paths */
+ if (!is_absolute_path(path))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("component in parameter \"extension_control_path\" is not an absolute path")));
+
+ full = psprintf("%s/%s", path, basename);
+
+ elog(DEBUG3, "%s: trying \"%s\"", __func__, full);
+
+ if (pg_file_exists(full))
+ return full;
+
+ 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..1ef79d7574f 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
@@ -5,6 +5,7 @@ use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
+use File::Path qw( make_path );
my $node = PostgreSQL::Test::Cluster->new('node');
@@ -12,25 +13,14 @@ $node->init;
# Create a temporary directory for the extension control file
my $ext_dir = PostgreSQL::Test::Utils::tempdir();
+make_path("$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";
+make_path("$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";
+ if (defined $directory)
+ {
+ print $cf "directory = $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();
--
2.39.5 (Apple Git-154)
On Apr 25, 2025, at 17:18, Matheus Alcantara <matheusssilv97@gmail.com> wrote:
Ok, I was testing using extension_control_path = '$system:/my/custom/path'
(starting with the macro) and it was working as expected, testing with
the macro at the end does not work.
Great example of why it’s useful to do as much testing as possible! That’s an entirely reasonable place to start testing :-)
The problem was on find_extension_control_filename() that was appending
the /extension at the end of the entire extension_control_path GUC value
instead of just the custom paths.
Oh yeah, lol, that wouldn’t work.
To append the /extension at each path on extension_control_path would
require some changes on find_in_path() that
find_extension_control_filename() calls, which I think that it would
make the function more complicated. I've them created a similar
find_in_paths() function that works in the same way but it receives a
List of paths instead of the string of paths separated by ":". We can
get this List of paths using get_extension_control_directories() that
also handle the macro substitution like find_in_path().Attached v4 with these fixes. I hope that now you should be able to omit
the /extension from the GUC value.
Yes! It now works with this configuration:
```ini
extension_control_path = '/Users/david/Downloads/share/postgresql:$system'
dynamic_library_path = '/Users/david/Downloads/lib/postgresql:$libdir’
```
Which is nicely more consistent. Kind of want that first one to be called “share_path” now, though, since it’s not just extensions. Although I guess it’s only extension control file searching that uses it (for now).
If I understand this bit correctly:
```c
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
/*
* Append "extension" suffix in case is a custom extension control
* path.
*/
if (strcmp(piece, "$system") != 0)
mangled = psprintf("%s/extension", mangled);
```
The value of `piece` is a single path from the search path, right? If so, I think it’s either `$system` or something else; I don’t it would ever be that `$system` is a substring of a single path. Is that right?
Other than that, I think this patch is good to go. Thanks!
Best,
David
`
On Mon, Apr 28, 2025 at 5:49 PM David E. Wheeler <david@justatheory.com> wrote:
To append the /extension at each path on extension_control_path would
require some changes on find_in_path() that
find_extension_control_filename() calls, which I think that it would
make the function more complicated. I've them created a similar
find_in_paths() function that works in the same way but it receives a
List of paths instead of the string of paths separated by ":". We can
get this List of paths using get_extension_control_directories() that
also handle the macro substitution like find_in_path().Attached v4 with these fixes. I hope that now you should be able to omit
the /extension from the GUC value.Yes! It now works with this configuration:
```ini
extension_control_path = '/Users/david/Downloads/share/postgresql:$system'
dynamic_library_path = '/Users/david/Downloads/lib/postgresql:$libdir’
```Which is nicely more consistent. Kind of want that first one to be called “share_path” now, though, since it’s not just extensions. Although I guess it’s only extension control file searching that uses it (for now).
Thanks for testing!
If I understand this bit correctly:
```c
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);/*
* Append "extension" suffix in case is a custom extension control
* path.
*/
if (strcmp(piece, "$system") != 0)
mangled = psprintf("%s/extension", mangled);
```The value of `piece` is a single path from the search path, right? If so, I think it’s either `$system` or something else; I don’t it would ever be that `$system` is a substring of a single path. Is that right?
Yes, it is a single path from the search path, in your case it will be
"/Users/david/Downloads/share/postgresql" and "$system". We split these
paths based on the system path separator and get the next "piece" here:
char *piece = first_path_var_separator(ecp);
The first_path_var_separator() changes the "ecp" parameter on every call,
it returns the next path on "ecp" and changes it to have the remaining
paths to iterate over it.
Other than that, I think this patch is good to go. Thanks!
Thanks for reviewing!
--
Matheus Alcantara
On Apr 29, 2025, at 09:49, Matheus Alcantara <matheusssilv97@gmail.com> wrote:
Yes, it is a single path from the search path, in your case it will be
"/Users/david/Downloads/share/postgresql" and "$system". We split these
paths based on the system path separator and get the next "piece" here:char *piece = first_path_var_separator(ecp);
The first_path_var_separator() changes the "ecp" parameter on every call,
it returns the next path on "ecp" and changes it to have the remaining
paths to iterate over it.
Right. My point is a minor one, but I thin you can use an if/ else there:
```c
if (strcmp(piece, "$system") == 0) {
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
} else {
/*
* Append "extension" suffix in case is a custom extension
* control path.
*/
mangled = psprintf("%s/extension", mangled);
}
```
Best,
David
On Tue, Apr 29, 2025 at 11:08 AM David E. Wheeler <david@justatheory.com> wrote:
Right. My point is a minor one, but I thin you can use an if/ else there:
```c
if (strcmp(piece, "$system") == 0) {
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
} else {
/*
* Append "extension" suffix in case is a custom extension
* control path.
*/
mangled = psprintf("%s/extension", mangled);
}
```
The substitute_path_macro() already handles the if/else on "piece" but I
think that this if/else version looks nicer. Fixed.
I've also included some documentation changes for this v5 version to
remove the "extension" from the examples and also mention the scenario
when using the "directory" on the .control file.
--
Matheus Alcantara
Attachments:
v5-0001-Make-directory-work-with-extension-control-path.patchapplication/octet-stream; name=v5-0001-Make-directory-work-with-extension-control-path.patchDownload
From 2c2bedf39e6d62dea80fe9d9366ccb6c8e0b45da Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <mths.dev@pm.me>
Date: Wed, 23 Apr 2025 16:11:24 -0300
Subject: [PATCH v5] Make "directory" work with extension control path
Previously extensions installed on a custom path that is available via
extension_control_path GUC that set the "directory" field on .control
file was not being able to CREATE. This was happening because on
get_extension_script_directory was hard coded to search for the script
files only on the share system dir.
This commit fix this issue by using the control->control_dir as a share
dir to return the path of the extension script files.
---
doc/src/sgml/config.sgml | 22 ++--
src/backend/commands/extension.c | 102 +++++++++++++-----
.../t/001_extension_control_path.pl | 93 +++++++++++-----
3 files changed, 161 insertions(+), 56 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 007f1fbe006..6c941f82c92 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -11029,16 +11029,26 @@ 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 all specified path elements are expected to have a
+ <literal>extension</literal> subdirectory which will have the .control
+ and .sql files, this path suffix is automatically appended at the end
+ of each path. (The value for <literal>$system</literal> already
+ includes the <literal>extension</literal> subdirectory.)
+ </para>
+
+ <para>
+ Also note that extension .control file may configure the .sql files to
+ be placed in another directory using the <literal>directory</literal>
+ field (See <xref linkend="extend-extensions-files"/> for details.). If
+ the configured directory is a relative path, it will search based on the
+ path that the .control file was found, for example,
+ <literal>/home/my_project/share/<bla></literal>.
</para>
<para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 180f4af9be3..577b9f2ff0d 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 in case 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,51 @@ 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;
+
+ /*
+ * If the paths variable is empty, don't do a path search.
+ */
+ if (paths == NIL)
+ return NULL;
+
+ foreach(cell, paths)
+ {
+ char *path = (char *) lfirst(cell);
+ char *full;
+
+ Assert(path != NULL);
+
+ canonicalize_path(path);
+
+ /* only absolute paths */
+ if (!is_absolute_path(path))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("component in parameter \"extension_control_path\" is not an absolute path")));
+
+ full = psprintf("%s/%s", path, basename);
+
+ elog(DEBUG3, "%s: trying \"%s\"", __func__, full);
+
+ if (pg_file_exists(full))
+ return full;
+
+ 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..1ef79d7574f 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
@@ -5,6 +5,7 @@ use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
+use File::Path qw( make_path );
my $node = PostgreSQL::Test::Cluster->new('node');
@@ -12,25 +13,14 @@ $node->init;
# Create a temporary directory for the extension control file
my $ext_dir = PostgreSQL::Test::Utils::tempdir();
+make_path("$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";
+make_path("$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";
+ if (defined $directory)
+ {
+ print $cf "directory = $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();
--
2.39.5 (Apple Git-154)
On Apr 29, 2025, at 11:06, Matheus Alcantara <matheusssilv97@gmail.com> wrote:
The substitute_path_macro() already handles the if/else on "piece" but I
think that this if/else version looks nicer. Fixed.I've also included some documentation changes for this v5 version to
remove the "extension" from the examples and also mention the scenario
when using the "directory" on the .control file.
Nice, thanks. I’ve made a PR in my GitHub clone for anyone who likes to look it over that way.
https://github.com/theory/postgres/pull/11/files
Best,
David
On 29.04.25 17:06, Matheus Alcantara wrote:
On Tue, Apr 29, 2025 at 11:08 AM David E. Wheeler <david@justatheory.com> wrote:
Right. My point is a minor one, but I thin you can use an if/ else there:
```c
if (strcmp(piece, "$system") == 0) {
/* Substitute the path macro if needed */
mangled = substitute_path_macro(piece, "$system", system_dir);
} else {
/*
* Append "extension" suffix in case is a custom extension
* control path.
*/
mangled = psprintf("%s/extension", mangled);
}
```The substitute_path_macro() already handles the if/else on "piece" but I
think that this if/else version looks nicer. Fixed.I've also included some documentation changes for this v5 version to
remove the "extension" from the examples and also mention the scenario
when using the "directory" on the .control file.
Thanks, I have committed this. I did a bit of code reformatting and
adjusted the documentation a bit. It's good to get this in before beta1
so that we don't have to change the valid values of
extension_control_path past beta1.
On Fri, May 2, 2025 at 11:51 AM Peter Eisentraut <peter@eisentraut.org> wrote:
Thanks, I have committed this. I did a bit of code reformatting and
adjusted the documentation a bit. It's good to get this in before beta1
so that we don't have to change the valid values of
extension_control_path past beta1.
Thanks Peter!
--
Matheus Alcantara
Re: Matheus Alcantara
Thanks, I have committed this. I did a bit of code reformatting and
adjusted the documentation a bit. It's good to get this in before beta1
so that we don't have to change the valid values of
extension_control_path past beta1.Thanks Peter!
And thanks everyone!
Christoph