public inbox for [email protected]
help / color / mirror / Atom feedFrom: Peter Eisentraut <[email protected]>
To: PostgreSQL-development <[email protected]>
Cc: Gabriele Bartolini <[email protected]>
Cc: Craig Ringer <[email protected]>
Cc: David E. Wheeler <[email protected]>
Cc: Robert Haas <[email protected]>
Cc: Alvaro Herrera <[email protected]>
Subject: Re: RFC: Additional Directory for Extensions
Date: Thu, 5 Dec 2024 12:07:31 +0100
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <CA+TgmoaMk==vOUmWwJ5ZA-EueNqa9RQaWMEiHCF1ZKW_YCcj8g@mail.gmail.com>
<[email protected]>
<CA+TgmoYcAmhnSUHKdJsS1wOR-9+y6dbLhfJ3Ad+iZEDmA1mPXg@mail.gmail.com>
<[email protected]>
<[email protected]>
<CA+VUV5oWkMVvtNB0BG2CM8SrmOkeW6fEtXGehbniHqR9MhMzVw@mail.gmail.com>
<CAGRY4nxP6A5Dz23g+aGD-agdVwUj_qrG6szd-mWc0E5OFMBg4w@mail.gmail.com>
<[email protected]>
<CAGRY4ny=Oy-F0qCRFQa=XtEqyUbMygvbqqKahAvbae7v4LTOrQ@mail.gmail.com>
<[email protected]>
<CAGRY4nx8Fmea4udk--PKdvfgpNdqC+rcueAmGwuw2PD9dv+zqQ@mail.gmail.com>
<[email protected]>
<[email protected]>
On 11.11.24 08:16, Peter Eisentraut wrote:
> I implemented a patch along the lines Craig had suggested. It's a new
> GUC variable that is a path for extension control files. It's called
> extension_control_path, and it works exactly the same way as
> dynamic_library_path. Except that the magic token is called $system
> instead of $libdir. In fact, most of the patch is refactoring the
> routines in dfmgr.c to not hardcode dynamic_library_path but allow
> searching for any file in any path. Once a control file is found, the
> other extension support files (script files and auxiliary control files)
> are looked for in the same directory.
> There are some TODOs in the patch. Some of those are for documentation
> that needs to be completed. Others are for functions like
> pg_available_extensions() that need to be rewritten to be aware of the
> path. I think this would be pretty straightforward.
I've made a bit of progress on this patch, filled in some documentation
and resolved some TODO markers. Also:
> Some open problems or discussion points:
>
> - You can install extensions into alternative directories using PGXS like
>
> make install datadir=/else/where/share pkglibdir=/else/where/lib
>
> This works. I was hoping it would work to use
>
> make install prefix=/else/where
>
> but that doesn't because of some details in Makefile.global. I think we
> can tweak that a little bit to make that work too.
This works now.
> - The biggest problem is that many extensions set in their control file
>
> module_pathname = '$libdir/foo'
>
> This disables the use of dynamic_library_path, so this whole idea of
> installing an extension elsewhere won't work that way. The obvious
> solution is that extensions change this to just 'foo'. But this will
> require a lot updating work for many extensions, or a lot of patching by
> packagers.
I have solved this by just stripping off "$libdir/" from the front of
the filename. This works for now. We can think about other ways to
tweak this, perhaps, but I don't see any drawback to this in practice.
This patch is now complete enough for testing, I think. As I mentioned
earlier, I haven't updated pg_available_extensions() etc. to support the
path, but that shouldn't prevent some testing.
From fdf1bd8abbb9aaff3747c57d653ef19f8be31fe8 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Thu, 5 Dec 2024 11:49:05 +0100
Subject: [PATCH v1] 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.
Discussion: https://www.postgresql.org/message-id/flat/[email protected]
TODO: Some utility functions such as pg_available_extensions() are not
adjusted to be aware of the path yet.
---
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 | 87 ++++++++++++++-----
src/backend/utils/fmgr/dfmgr.c | 76 ++++++++++------
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 | 2 +
10 files changed, 230 insertions(+), 63 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e0c8325a39c..94ef9fb44d7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10476,6 +10476,74 @@ <title>Other Defaults</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-extension-control-path" xreflabel="extension_control_path">
+ <term><varname>extension_control_path</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>extension_control_path</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ A path to search for extensions, specifically extension control files
+ (<filename><replaceable>name</replaceable>.control</filename>). The
+ remaining extension script and secondary control files are then loaded
+ from the same directory where the primary control file was found.
+ See <xref linkend="extend-extensions-files"/> for details.
+ </para>
+
+ <para>
+ The value for <varname>extension_control_path</varname> must be a
+ list of absolute directory paths separated by colons (or semi-colons
+ on Windows). If a list element starts
+ with the special string <literal>$system</literal>, the
+ compiled-in <productname>PostgreSQL</productname> extension
+ directory is substituted for <literal>$system</literal>; this
+ is where the extensions provided by the standard
+ <productname>PostgreSQL</productname> distribution are installed.
+ (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'
+</programlisting>
+ or, in a Windows environment:
+<programlisting>
+extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\extension;$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.)
+ </para>
+
+ <para>
+ The default value for this parameter is
+ <literal>'$system'</literal>. If the value is set to an empty
+ string, the default <literal>'$system'</literal> is also assumed.
+ </para>
+
+ <para>
+ This parameter can be changed at run time by superusers and users
+ with the appropriate <literal>SET</literal> privilege, but a
+ setting done that way will only persist until the end of the
+ client connection, so this method should be reserved for
+ development purposes. The recommended way to set this parameter
+ is in the <filename>postgresql.conf</filename> configuration
+ file.
+ </para>
+
+ <para>
+ Note that if you set this parameter to be able to load extensions from
+ nonstandard locations, you will most likely also need to set <xref
+ linkend="guc-dynamic-library-path"/> to a correspondent location, for
+ example,
+<programlisting>
+extension_control_path = '/usr/local/share/postgresql/extension:$system'
+dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
+</programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-gin-fuzzy-search-limit" xreflabel="gin_fuzzy_search_limit">
<term><varname>gin_fuzzy_search_limit</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 218940ee5ce..0a20d06abfb 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -649,6 +649,11 @@ <title>Extension Files</title>
control file can specify a different directory for the script file(s).
</para>
+ <para>
+ Additional locations for extension control files can be configured using
+ the parameter <xref linkend="guc-extension-control-path"/>.
+ </para>
+
<para>
The file format for an extension control file is the same as for the
<filename>postgresql.conf</filename> file, namely a list of
@@ -669,9 +674,9 @@ <title>Extension Files</title>
<para>
The directory containing the extension's <acronym>SQL</acronym> script
file(s). Unless an absolute path is given, the name is relative to
- the installation's <literal>SHAREDIR</literal> directory. The
- default behavior is equivalent to specifying
- <literal>directory = 'extension'</literal>.
+ the installation's <literal>SHAREDIR</literal> directory. By default,
+ the script files are looked for in the same directory where the
+ control file was found.
</para>
</listitem>
</varlistentry>
@@ -719,8 +724,8 @@ <title>Extension Files</title>
<para>
The value of this parameter will be substituted for each occurrence
of <literal>MODULE_PATHNAME</literal> in the script file(s). If it is not
- set, no substitution is made. Typically, this is set to
- <literal>$libdir/<replaceable>shared_library_name</replaceable></literal> and
+ set, no substitution is made. Typically, this is set to just
+ <literal><replaceable>shared_library_name</replaceable></literal> and
then <literal>MODULE_PATHNAME</literal> is used in <command>CREATE
FUNCTION</command> commands for C-language functions, so that the script
files do not need to hard-wire the name of the shared library.
@@ -1808,6 +1813,10 @@ <title>Extension Building Infrastructure</title>
setting <varname>PG_CONFIG</varname> to point to its
<command>pg_config</command> program, either within the makefile
or on the <literal>make</literal> command line.
+ You can also select a separate installation directory for your extension
+ by setting the <literal>make</literal> variable <varname>prefix</varname>
+ on the <literal>make</literal> command line. (But this will then require
+ additional setup to get the server to find the extension there.)
</para>
<para>
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index ca2b80d669c..713abd9c494 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -90,8 +90,10 @@ <title>Parameters</title>
<para>
The name of the extension to be
installed. <productname>PostgreSQL</productname> will create the
- extension using details from the file
- <literal>SHAREDIR/extension/</literal><replaceable class="parameter">extension_name</replaceable><literal>.control</literal>.
+ extension using details from the file <filename><replaceable
+ class="parameter">extension_name</replaceable>.control</filename>,
+ found via the server's extension control path (set by <xref
+ linkend="guc-extension-control-path"/>.)
</para>
</listitem>
</varlistentry>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index eac3d001211..7c1bffbea07 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -87,9 +87,19 @@ endif # not PGXS
#
# In a PGXS build, we cannot use the values inserted into Makefile.global
# by configure, since the installation tree may have been relocated.
-# Instead get the path values from pg_config.
+# Instead get the path values from pg_config. But users can specify
+# prefix explicitly, if they want to select their own installation
+# location.
-ifndef PGXS
+ifdef PGXS
+# Extension makefiles should set PG_CONFIG, but older ones might not
+ifndef PG_CONFIG
+PG_CONFIG = pg_config
+endif
+endif
+
+# This means: if ((not PGXS) or prefix)
+ifneq (,$(if $(PGXS),,1)$(prefix))
# Note that prefix, exec_prefix, and datarootdir aren't defined in a PGXS build;
# makefiles may only use the derived variables such as bindir.
@@ -147,11 +157,6 @@ localedir := @localedir@
else # PGXS case
-# Extension makefiles should set PG_CONFIG, but older ones might not
-ifndef PG_CONFIG
-PG_CONFIG = pg_config
-endif
-
bindir := $(shell $(PG_CONFIG) --bindir)
datadir := $(shell $(PG_CONFIG) --sharedir)
sysconfdir := $(shell $(PG_CONFIG) --sysconfdir)
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index af6bd8ff426..9115a48d25f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -69,6 +69,9 @@
#include "utils/varlena.h"
+/* GUC */
+char *Extension_control_path;
+
/* Globally visible state variables */
bool creating_extension = false;
Oid CurrentExtensionObject = InvalidOid;
@@ -79,6 +82,7 @@ Oid CurrentExtensionObject = InvalidOid;
typedef struct ExtensionControlFile
{
char *name; /* name of the extension */
+ char *control_dir; /* directory where control file was found */
char *directory; /* directory for script files */
char *default_version; /* default install target version, if any */
char *module_pathname; /* string to substitute for
@@ -328,6 +332,12 @@ is_extension_script_filename(const char *filename)
return (extension != NULL) && (strcmp(extension, ".sql") == 0);
}
+/*
+ * TODO
+ *
+ * This is now only for finding/listing available extensions. Rewrite to use
+ * path. See further TODOs below.
+ */
static char *
get_extension_control_directory(void)
{
@@ -341,16 +351,45 @@ get_extension_control_directory(void)
return result;
}
+/*
+ * Find control file for extension with name in control->name, looking in the
+ * path. Return the full file name, or NULL if not found. If found, the
+ * directory is recorded in control->control_dir.
+ */
static char *
-get_extension_control_filename(const char *extname)
+find_extension_control_filename(ExtensionControlFile *control)
{
char sharepath[MAXPGPATH];
+ char *system_dir;
+ char *basename;
+ char *ecp;
char *result;
+ Assert(control->name);
+
get_share_path(my_exec_path, sharepath);
- result = (char *) palloc(MAXPGPATH);
- snprintf(result, MAXPGPATH, "%s/extension/%s.control",
- sharepath, extname);
+ 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, Extension_control_path, "extension_control_path", "$system", system_dir);
+
+ if (result)
+ {
+ const char *p;
+
+ p = strrchr(result, '/');
+ Assert(p);
+ control->control_dir = pnstrdup(result, p - result);
+ }
return result;
}
@@ -366,7 +405,7 @@ get_extension_script_directory(ExtensionControlFile *control)
* installation's share directory.
*/
if (!control->directory)
- return get_extension_control_directory();
+ return pstrdup(control->control_dir);
if (is_absolute_path(control->directory))
return pstrdup(control->directory);
@@ -444,27 +483,25 @@ parse_extension_control_file(ExtensionControlFile *control,
if (version)
filename = get_extension_aux_control_filename(control, version);
else
- filename = get_extension_control_filename(control->name);
+ filename = find_extension_control_filename(control);
+
+ if (!filename)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("extension \"%s\" is not available", control->name),
+ errhint("The extension must first be installed on the system where PostgreSQL is running.")));
+ }
if ((file = AllocateFile(filename, "r")) == NULL)
{
- if (errno == ENOENT)
+ /* no complaint for missing auxiliary file */
+ if (errno == ENOENT && version)
{
- /* no complaint for missing auxiliary file */
- if (version)
- {
- pfree(filename);
- return;
- }
-
- /* missing control file indicates extension is not installed */
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("extension \"%s\" is not available", control->name),
- errdetail("Could not open extension control file \"%s\": %m.",
- filename),
- errhint("The extension must first be installed on the system where PostgreSQL is running.")));
+ pfree(filename);
+ return;
}
+
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open extension control file \"%s\": %m",
@@ -2114,6 +2151,8 @@ RemoveExtensionById(Oid extId)
* The system view pg_available_extensions provides a user interface to this
* SRF, adding information about whether the extensions are installed in the
* current DB.
+ *
+ * TODO: make aware of path
*/
Datum
pg_available_extensions(PG_FUNCTION_ARGS)
@@ -2194,6 +2233,8 @@ pg_available_extensions(PG_FUNCTION_ARGS)
* The system view pg_available_extension_versions provides a user interface
* to this SRF, adding information about which versions are installed in the
* current DB.
+ *
+ * TODO: make aware of path
*/
Datum
pg_available_extension_versions(PG_FUNCTION_ARGS)
@@ -2366,6 +2407,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
* directory. That's not a bulletproof check, since the file might be
* invalid, but this is only used for hints so it doesn't have to be 100%
* right.
+ *
+ * TODO: make aware of path
*/
bool
extension_file_exists(const char *extensionName)
@@ -2445,6 +2488,8 @@ convert_requires_to_datum(List *requires)
/*
* This function reports the version update paths that exist for the
* specified extension.
+ *
+ * TODO: make aware of path
*/
Datum
pg_extension_update_paths(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 8e81ecc7491..2372d0944c0 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -71,8 +71,7 @@ static void incompatible_module_error(const char *libname,
const Pg_magic_struct *module_magic_data) pg_attribute_noreturn();
static char *expand_dynamic_library_name(const char *name);
static void check_restricted_library_name(const char *name);
-static char *substitute_libpath_macro(const char *name);
-static char *find_in_dynamic_libpath(const char *basename);
+static char *substitute_path_macro(const char *str, const char *macro, const char *value);
/* Magic structure that module needs to match to be accepted */
static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
@@ -398,7 +397,7 @@ incompatible_module_error(const char *libname,
/*
* If name contains a slash, check if the file exists, if so return
* the name. Else (no slash) try to expand using search path (see
- * find_in_dynamic_libpath below); if that works, return the fully
+ * find_in_path below); if that works, return the fully
* expanded file name. If the previous failed, append DLSUFFIX and
* try again. If all fails, just return the original name.
*
@@ -413,17 +412,25 @@ expand_dynamic_library_name(const char *name)
Assert(name);
+ /*
+ * If the value starts with "$libdir/", strip that. This is because many
+ * extensions have hardcoded '$libdir/foo' as their library name, which
+ * prevents using the path.
+ */
+ if (strncmp(name, "$libdir/", 8) == 0)
+ name += 8;
+
have_slash = (first_dir_separator(name) != NULL);
if (!have_slash)
{
- full = find_in_dynamic_libpath(name);
+ full = find_in_path(name, Dynamic_library_path, "dynamic_library_path", "$libdir", pkglib_path);
if (full)
return full;
}
else
{
- full = substitute_libpath_macro(name);
+ full = substitute_path_macro(name, "$libdir", pkglib_path);
if (pg_file_exists(full))
return full;
pfree(full);
@@ -433,14 +440,14 @@ expand_dynamic_library_name(const char *name)
if (!have_slash)
{
- full = find_in_dynamic_libpath(new);
+ full = find_in_path(new, Dynamic_library_path, "dynamic_library_path", "$libdir", pkglib_path);
pfree(new);
if (full)
return full;
}
else
{
- full = substitute_libpath_macro(new);
+ full = substitute_path_macro(new, "$libdir", pkglib_path);
pfree(new);
if (pg_file_exists(full))
return full;
@@ -475,47 +482,60 @@ check_restricted_library_name(const char *name)
* Result is always freshly palloc'd.
*/
static char *
-substitute_libpath_macro(const char *name)
+substitute_path_macro(const char *str, const char *macro, const char *value)
{
const char *sep_ptr;
- Assert(name != NULL);
+ Assert(str != NULL);
+ Assert(macro[0] == '$');
- /* Currently, we only recognize $libdir at the start of the string */
- if (name[0] != '$')
- return pstrdup(name);
+ /* Currently, we only recognize $macro at the start of the string */
+ if (str[0] != '$')
+ return pstrdup(str);
- if ((sep_ptr = first_dir_separator(name)) == NULL)
- sep_ptr = name + strlen(name);
+ if ((sep_ptr = first_dir_separator(str)) == NULL)
+ sep_ptr = str + strlen(str);
- if (strlen("$libdir") != sep_ptr - name ||
- strncmp(name, "$libdir", strlen("$libdir")) != 0)
+ if (strlen(macro) != sep_ptr - str ||
+ strncmp(str, macro, strlen(macro)) != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
- errmsg("invalid macro name in dynamic library path: %s",
- name)));
+ errmsg("invalid macro name in path: %s",
+ str)));
- return psprintf("%s%s", pkglib_path, sep_ptr);
+ return psprintf("%s%s", value, sep_ptr);
}
/*
* Search for a file called 'basename' in the colon-separated search
- * path Dynamic_library_path. If the file is found, the full file name
+ * path given. If the file is found, the full file name
* is returned in freshly palloc'd memory. If the file is not found,
* return NULL.
+ *
+ * path_param is the name of the parameter that path came from, for error
+ * messages.
+ *
+ * macro and macro_val allow substituting a macro; see
+ * substitute_path_macro().
*/
-static char *
-find_in_dynamic_libpath(const char *basename)
+char *
+find_in_path(const char *basename, const char *path, const char *path_param,
+ const char *macro, const char *macro_val)
{
const char *p;
size_t baselen;
Assert(basename != NULL);
Assert(first_dir_separator(basename) == NULL);
- Assert(Dynamic_library_path != NULL);
+ Assert(path != NULL);
+ Assert(path_param != NULL);
+
+ p = path;
- p = Dynamic_library_path;
+ /*
+ * If the path variable is empty, don't do a path search.
+ */
if (strlen(p) == 0)
return NULL;
@@ -532,7 +552,7 @@ find_in_dynamic_libpath(const char *basename)
if (piece == p)
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
- errmsg("zero-length component in parameter \"dynamic_library_path\"")));
+ errmsg("zero-length component in parameter \"%s\"", path_param)));
if (piece == NULL)
len = strlen(p);
@@ -542,7 +562,7 @@ find_in_dynamic_libpath(const char *basename)
piece = palloc(len + 1);
strlcpy(piece, p, len + 1);
- mangled = substitute_libpath_macro(piece);
+ mangled = substitute_path_macro(piece, macro, macro_val);
pfree(piece);
canonicalize_path(mangled);
@@ -551,13 +571,13 @@ find_in_dynamic_libpath(const char *basename)
if (!is_absolute_path(mangled))
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
- errmsg("component in parameter \"dynamic_library_path\" is not an absolute path")));
+ errmsg("component in parameter \"%s\" is not an absolute path", path_param)));
full = palloc(strlen(mangled) + 1 + baselen + 1);
sprintf(full, "%s/%s", mangled, basename);
pfree(mangled);
- elog(DEBUG3, "find_in_dynamic_libpath: trying \"%s\"", full);
+ elog(DEBUG3, "%s: trying \"%s\"", __func__, full);
if (pg_file_exists(full))
return full;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8cf1afbad20..5a280f62b77 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -39,6 +39,7 @@
#include "catalog/namespace.h"
#include "catalog/storage.h"
#include "commands/async.h"
+#include "commands/extension.h"
#include "commands/event_trigger.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
@@ -4252,6 +4253,18 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"extension_control_path", PGC_SUSET, CLIENT_CONN_OTHER,
+ gettext_noop("Sets the path for extension control files."),
+ gettext_noop("The remaining extension script and secondary control files are then loaded "
+ "from the same directory where the primary control file was found."),
+ GUC_SUPERUSER_ONLY
+ },
+ &Extension_control_path,
+ "$system",
+ NULL, NULL, NULL
+ },
+
{
{"krb_server_keyfile", PGC_SIGHUP, CONN_AUTH_AUTH,
gettext_noop("Sets the location of the Kerberos server key file."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a2ac7575ca7..83e72b33fb7 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -774,6 +774,7 @@
# - Other Defaults -
#dynamic_library_path = '$libdir'
+#extension_control_path = '$system'
#gin_fuzzy_search_limit = 0
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index c6f3f867eb7..fe8a97570ea 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -17,6 +17,8 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+/* GUC */
+extern PGDLLIMPORT char *Extension_control_path;
/*
* creating_extension is only true while running a CREATE EXTENSION or ALTER
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1e3795de4a8..2930b61cee5 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -740,6 +740,8 @@ extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
*/
extern PGDLLIMPORT char *Dynamic_library_path;
+extern char *find_in_path(const char *basename, const char *path, const char *path_param,
+ const char *macro, const char *macro_val);
extern void *load_external_function(const char *filename, const char *funcname,
bool signalNotFound, void **filehandle);
extern void *lookup_external_function(void *filehandle, const char *funcname);
base-commit: 71cb352904c1833fe067d6f191269710fe2ca06f
--
2.47.1
Attachments:
[text/plain] v1-0001-extension_control_path.patch (23.4K, ../[email protected]/2-v1-0001-extension_control_path.patch)
download | inline diff:
From fdf1bd8abbb9aaff3747c57d653ef19f8be31fe8 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Thu, 5 Dec 2024 11:49:05 +0100
Subject: [PATCH v1] 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.
Discussion: https://www.postgresql.org/message-id/flat/[email protected]
TODO: Some utility functions such as pg_available_extensions() are not
adjusted to be aware of the path yet.
---
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 | 87 ++++++++++++++-----
src/backend/utils/fmgr/dfmgr.c | 76 ++++++++++------
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 | 2 +
10 files changed, 230 insertions(+), 63 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e0c8325a39c..94ef9fb44d7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10476,6 +10476,74 @@ <title>Other Defaults</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-extension-control-path" xreflabel="extension_control_path">
+ <term><varname>extension_control_path</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>extension_control_path</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ A path to search for extensions, specifically extension control files
+ (<filename><replaceable>name</replaceable>.control</filename>). The
+ remaining extension script and secondary control files are then loaded
+ from the same directory where the primary control file was found.
+ See <xref linkend="extend-extensions-files"/> for details.
+ </para>
+
+ <para>
+ The value for <varname>extension_control_path</varname> must be a
+ list of absolute directory paths separated by colons (or semi-colons
+ on Windows). If a list element starts
+ with the special string <literal>$system</literal>, the
+ compiled-in <productname>PostgreSQL</productname> extension
+ directory is substituted for <literal>$system</literal>; this
+ is where the extensions provided by the standard
+ <productname>PostgreSQL</productname> distribution are installed.
+ (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'
+</programlisting>
+ or, in a Windows environment:
+<programlisting>
+extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\extension;$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.)
+ </para>
+
+ <para>
+ The default value for this parameter is
+ <literal>'$system'</literal>. If the value is set to an empty
+ string, the default <literal>'$system'</literal> is also assumed.
+ </para>
+
+ <para>
+ This parameter can be changed at run time by superusers and users
+ with the appropriate <literal>SET</literal> privilege, but a
+ setting done that way will only persist until the end of the
+ client connection, so this method should be reserved for
+ development purposes. The recommended way to set this parameter
+ is in the <filename>postgresql.conf</filename> configuration
+ file.
+ </para>
+
+ <para>
+ Note that if you set this parameter to be able to load extensions from
+ nonstandard locations, you will most likely also need to set <xref
+ linkend="guc-dynamic-library-path"/> to a correspondent location, for
+ example,
+<programlisting>
+extension_control_path = '/usr/local/share/postgresql/extension:$system'
+dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
+</programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-gin-fuzzy-search-limit" xreflabel="gin_fuzzy_search_limit">
<term><varname>gin_fuzzy_search_limit</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 218940ee5ce..0a20d06abfb 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -649,6 +649,11 @@ <title>Extension Files</title>
control file can specify a different directory for the script file(s).
</para>
+ <para>
+ Additional locations for extension control files can be configured using
+ the parameter <xref linkend="guc-extension-control-path"/>.
+ </para>
+
<para>
The file format for an extension control file is the same as for the
<filename>postgresql.conf</filename> file, namely a list of
@@ -669,9 +674,9 @@ <title>Extension Files</title>
<para>
The directory containing the extension's <acronym>SQL</acronym> script
file(s). Unless an absolute path is given, the name is relative to
- the installation's <literal>SHAREDIR</literal> directory. The
- default behavior is equivalent to specifying
- <literal>directory = 'extension'</literal>.
+ the installation's <literal>SHAREDIR</literal> directory. By default,
+ the script files are looked for in the same directory where the
+ control file was found.
</para>
</listitem>
</varlistentry>
@@ -719,8 +724,8 @@ <title>Extension Files</title>
<para>
The value of this parameter will be substituted for each occurrence
of <literal>MODULE_PATHNAME</literal> in the script file(s). If it is not
- set, no substitution is made. Typically, this is set to
- <literal>$libdir/<replaceable>shared_library_name</replaceable></literal> and
+ set, no substitution is made. Typically, this is set to just
+ <literal><replaceable>shared_library_name</replaceable></literal> and
then <literal>MODULE_PATHNAME</literal> is used in <command>CREATE
FUNCTION</command> commands for C-language functions, so that the script
files do not need to hard-wire the name of the shared library.
@@ -1808,6 +1813,10 @@ <title>Extension Building Infrastructure</title>
setting <varname>PG_CONFIG</varname> to point to its
<command>pg_config</command> program, either within the makefile
or on the <literal>make</literal> command line.
+ You can also select a separate installation directory for your extension
+ by setting the <literal>make</literal> variable <varname>prefix</varname>
+ on the <literal>make</literal> command line. (But this will then require
+ additional setup to get the server to find the extension there.)
</para>
<para>
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index ca2b80d669c..713abd9c494 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -90,8 +90,10 @@ <title>Parameters</title>
<para>
The name of the extension to be
installed. <productname>PostgreSQL</productname> will create the
- extension using details from the file
- <literal>SHAREDIR/extension/</literal><replaceable class="parameter">extension_name</replaceable><literal>.control</literal>.
+ extension using details from the file <filename><replaceable
+ class="parameter">extension_name</replaceable>.control</filename>,
+ found via the server's extension control path (set by <xref
+ linkend="guc-extension-control-path"/>.)
</para>
</listitem>
</varlistentry>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index eac3d001211..7c1bffbea07 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -87,9 +87,19 @@ endif # not PGXS
#
# In a PGXS build, we cannot use the values inserted into Makefile.global
# by configure, since the installation tree may have been relocated.
-# Instead get the path values from pg_config.
+# Instead get the path values from pg_config. But users can specify
+# prefix explicitly, if they want to select their own installation
+# location.
-ifndef PGXS
+ifdef PGXS
+# Extension makefiles should set PG_CONFIG, but older ones might not
+ifndef PG_CONFIG
+PG_CONFIG = pg_config
+endif
+endif
+
+# This means: if ((not PGXS) or prefix)
+ifneq (,$(if $(PGXS),,1)$(prefix))
# Note that prefix, exec_prefix, and datarootdir aren't defined in a PGXS build;
# makefiles may only use the derived variables such as bindir.
@@ -147,11 +157,6 @@ localedir := @localedir@
else # PGXS case
-# Extension makefiles should set PG_CONFIG, but older ones might not
-ifndef PG_CONFIG
-PG_CONFIG = pg_config
-endif
-
bindir := $(shell $(PG_CONFIG) --bindir)
datadir := $(shell $(PG_CONFIG) --sharedir)
sysconfdir := $(shell $(PG_CONFIG) --sysconfdir)
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index af6bd8ff426..9115a48d25f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -69,6 +69,9 @@
#include "utils/varlena.h"
+/* GUC */
+char *Extension_control_path;
+
/* Globally visible state variables */
bool creating_extension = false;
Oid CurrentExtensionObject = InvalidOid;
@@ -79,6 +82,7 @@ Oid CurrentExtensionObject = InvalidOid;
typedef struct ExtensionControlFile
{
char *name; /* name of the extension */
+ char *control_dir; /* directory where control file was found */
char *directory; /* directory for script files */
char *default_version; /* default install target version, if any */
char *module_pathname; /* string to substitute for
@@ -328,6 +332,12 @@ is_extension_script_filename(const char *filename)
return (extension != NULL) && (strcmp(extension, ".sql") == 0);
}
+/*
+ * TODO
+ *
+ * This is now only for finding/listing available extensions. Rewrite to use
+ * path. See further TODOs below.
+ */
static char *
get_extension_control_directory(void)
{
@@ -341,16 +351,45 @@ get_extension_control_directory(void)
return result;
}
+/*
+ * Find control file for extension with name in control->name, looking in the
+ * path. Return the full file name, or NULL if not found. If found, the
+ * directory is recorded in control->control_dir.
+ */
static char *
-get_extension_control_filename(const char *extname)
+find_extension_control_filename(ExtensionControlFile *control)
{
char sharepath[MAXPGPATH];
+ char *system_dir;
+ char *basename;
+ char *ecp;
char *result;
+ Assert(control->name);
+
get_share_path(my_exec_path, sharepath);
- result = (char *) palloc(MAXPGPATH);
- snprintf(result, MAXPGPATH, "%s/extension/%s.control",
- sharepath, extname);
+ 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, Extension_control_path, "extension_control_path", "$system", system_dir);
+
+ if (result)
+ {
+ const char *p;
+
+ p = strrchr(result, '/');
+ Assert(p);
+ control->control_dir = pnstrdup(result, p - result);
+ }
return result;
}
@@ -366,7 +405,7 @@ get_extension_script_directory(ExtensionControlFile *control)
* installation's share directory.
*/
if (!control->directory)
- return get_extension_control_directory();
+ return pstrdup(control->control_dir);
if (is_absolute_path(control->directory))
return pstrdup(control->directory);
@@ -444,27 +483,25 @@ parse_extension_control_file(ExtensionControlFile *control,
if (version)
filename = get_extension_aux_control_filename(control, version);
else
- filename = get_extension_control_filename(control->name);
+ filename = find_extension_control_filename(control);
+
+ if (!filename)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("extension \"%s\" is not available", control->name),
+ errhint("The extension must first be installed on the system where PostgreSQL is running.")));
+ }
if ((file = AllocateFile(filename, "r")) == NULL)
{
- if (errno == ENOENT)
+ /* no complaint for missing auxiliary file */
+ if (errno == ENOENT && version)
{
- /* no complaint for missing auxiliary file */
- if (version)
- {
- pfree(filename);
- return;
- }
-
- /* missing control file indicates extension is not installed */
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("extension \"%s\" is not available", control->name),
- errdetail("Could not open extension control file \"%s\": %m.",
- filename),
- errhint("The extension must first be installed on the system where PostgreSQL is running.")));
+ pfree(filename);
+ return;
}
+
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open extension control file \"%s\": %m",
@@ -2114,6 +2151,8 @@ RemoveExtensionById(Oid extId)
* The system view pg_available_extensions provides a user interface to this
* SRF, adding information about whether the extensions are installed in the
* current DB.
+ *
+ * TODO: make aware of path
*/
Datum
pg_available_extensions(PG_FUNCTION_ARGS)
@@ -2194,6 +2233,8 @@ pg_available_extensions(PG_FUNCTION_ARGS)
* The system view pg_available_extension_versions provides a user interface
* to this SRF, adding information about which versions are installed in the
* current DB.
+ *
+ * TODO: make aware of path
*/
Datum
pg_available_extension_versions(PG_FUNCTION_ARGS)
@@ -2366,6 +2407,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
* directory. That's not a bulletproof check, since the file might be
* invalid, but this is only used for hints so it doesn't have to be 100%
* right.
+ *
+ * TODO: make aware of path
*/
bool
extension_file_exists(const char *extensionName)
@@ -2445,6 +2488,8 @@ convert_requires_to_datum(List *requires)
/*
* This function reports the version update paths that exist for the
* specified extension.
+ *
+ * TODO: make aware of path
*/
Datum
pg_extension_update_paths(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 8e81ecc7491..2372d0944c0 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -71,8 +71,7 @@ static void incompatible_module_error(const char *libname,
const Pg_magic_struct *module_magic_data) pg_attribute_noreturn();
static char *expand_dynamic_library_name(const char *name);
static void check_restricted_library_name(const char *name);
-static char *substitute_libpath_macro(const char *name);
-static char *find_in_dynamic_libpath(const char *basename);
+static char *substitute_path_macro(const char *str, const char *macro, const char *value);
/* Magic structure that module needs to match to be accepted */
static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
@@ -398,7 +397,7 @@ incompatible_module_error(const char *libname,
/*
* If name contains a slash, check if the file exists, if so return
* the name. Else (no slash) try to expand using search path (see
- * find_in_dynamic_libpath below); if that works, return the fully
+ * find_in_path below); if that works, return the fully
* expanded file name. If the previous failed, append DLSUFFIX and
* try again. If all fails, just return the original name.
*
@@ -413,17 +412,25 @@ expand_dynamic_library_name(const char *name)
Assert(name);
+ /*
+ * If the value starts with "$libdir/", strip that. This is because many
+ * extensions have hardcoded '$libdir/foo' as their library name, which
+ * prevents using the path.
+ */
+ if (strncmp(name, "$libdir/", 8) == 0)
+ name += 8;
+
have_slash = (first_dir_separator(name) != NULL);
if (!have_slash)
{
- full = find_in_dynamic_libpath(name);
+ full = find_in_path(name, Dynamic_library_path, "dynamic_library_path", "$libdir", pkglib_path);
if (full)
return full;
}
else
{
- full = substitute_libpath_macro(name);
+ full = substitute_path_macro(name, "$libdir", pkglib_path);
if (pg_file_exists(full))
return full;
pfree(full);
@@ -433,14 +440,14 @@ expand_dynamic_library_name(const char *name)
if (!have_slash)
{
- full = find_in_dynamic_libpath(new);
+ full = find_in_path(new, Dynamic_library_path, "dynamic_library_path", "$libdir", pkglib_path);
pfree(new);
if (full)
return full;
}
else
{
- full = substitute_libpath_macro(new);
+ full = substitute_path_macro(new, "$libdir", pkglib_path);
pfree(new);
if (pg_file_exists(full))
return full;
@@ -475,47 +482,60 @@ check_restricted_library_name(const char *name)
* Result is always freshly palloc'd.
*/
static char *
-substitute_libpath_macro(const char *name)
+substitute_path_macro(const char *str, const char *macro, const char *value)
{
const char *sep_ptr;
- Assert(name != NULL);
+ Assert(str != NULL);
+ Assert(macro[0] == '$');
- /* Currently, we only recognize $libdir at the start of the string */
- if (name[0] != '$')
- return pstrdup(name);
+ /* Currently, we only recognize $macro at the start of the string */
+ if (str[0] != '$')
+ return pstrdup(str);
- if ((sep_ptr = first_dir_separator(name)) == NULL)
- sep_ptr = name + strlen(name);
+ if ((sep_ptr = first_dir_separator(str)) == NULL)
+ sep_ptr = str + strlen(str);
- if (strlen("$libdir") != sep_ptr - name ||
- strncmp(name, "$libdir", strlen("$libdir")) != 0)
+ if (strlen(macro) != sep_ptr - str ||
+ strncmp(str, macro, strlen(macro)) != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
- errmsg("invalid macro name in dynamic library path: %s",
- name)));
+ errmsg("invalid macro name in path: %s",
+ str)));
- return psprintf("%s%s", pkglib_path, sep_ptr);
+ return psprintf("%s%s", value, sep_ptr);
}
/*
* Search for a file called 'basename' in the colon-separated search
- * path Dynamic_library_path. If the file is found, the full file name
+ * path given. If the file is found, the full file name
* is returned in freshly palloc'd memory. If the file is not found,
* return NULL.
+ *
+ * path_param is the name of the parameter that path came from, for error
+ * messages.
+ *
+ * macro and macro_val allow substituting a macro; see
+ * substitute_path_macro().
*/
-static char *
-find_in_dynamic_libpath(const char *basename)
+char *
+find_in_path(const char *basename, const char *path, const char *path_param,
+ const char *macro, const char *macro_val)
{
const char *p;
size_t baselen;
Assert(basename != NULL);
Assert(first_dir_separator(basename) == NULL);
- Assert(Dynamic_library_path != NULL);
+ Assert(path != NULL);
+ Assert(path_param != NULL);
+
+ p = path;
- p = Dynamic_library_path;
+ /*
+ * If the path variable is empty, don't do a path search.
+ */
if (strlen(p) == 0)
return NULL;
@@ -532,7 +552,7 @@ find_in_dynamic_libpath(const char *basename)
if (piece == p)
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
- errmsg("zero-length component in parameter \"dynamic_library_path\"")));
+ errmsg("zero-length component in parameter \"%s\"", path_param)));
if (piece == NULL)
len = strlen(p);
@@ -542,7 +562,7 @@ find_in_dynamic_libpath(const char *basename)
piece = palloc(len + 1);
strlcpy(piece, p, len + 1);
- mangled = substitute_libpath_macro(piece);
+ mangled = substitute_path_macro(piece, macro, macro_val);
pfree(piece);
canonicalize_path(mangled);
@@ -551,13 +571,13 @@ find_in_dynamic_libpath(const char *basename)
if (!is_absolute_path(mangled))
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
- errmsg("component in parameter \"dynamic_library_path\" is not an absolute path")));
+ errmsg("component in parameter \"%s\" is not an absolute path", path_param)));
full = palloc(strlen(mangled) + 1 + baselen + 1);
sprintf(full, "%s/%s", mangled, basename);
pfree(mangled);
- elog(DEBUG3, "find_in_dynamic_libpath: trying \"%s\"", full);
+ elog(DEBUG3, "%s: trying \"%s\"", __func__, full);
if (pg_file_exists(full))
return full;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8cf1afbad20..5a280f62b77 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -39,6 +39,7 @@
#include "catalog/namespace.h"
#include "catalog/storage.h"
#include "commands/async.h"
+#include "commands/extension.h"
#include "commands/event_trigger.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
@@ -4252,6 +4253,18 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"extension_control_path", PGC_SUSET, CLIENT_CONN_OTHER,
+ gettext_noop("Sets the path for extension control files."),
+ gettext_noop("The remaining extension script and secondary control files are then loaded "
+ "from the same directory where the primary control file was found."),
+ GUC_SUPERUSER_ONLY
+ },
+ &Extension_control_path,
+ "$system",
+ NULL, NULL, NULL
+ },
+
{
{"krb_server_keyfile", PGC_SIGHUP, CONN_AUTH_AUTH,
gettext_noop("Sets the location of the Kerberos server key file."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a2ac7575ca7..83e72b33fb7 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -774,6 +774,7 @@
# - Other Defaults -
#dynamic_library_path = '$libdir'
+#extension_control_path = '$system'
#gin_fuzzy_search_limit = 0
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index c6f3f867eb7..fe8a97570ea 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -17,6 +17,8 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+/* GUC */
+extern PGDLLIMPORT char *Extension_control_path;
/*
* creating_extension is only true while running a CREATE EXTENSION or ALTER
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1e3795de4a8..2930b61cee5 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -740,6 +740,8 @@ extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
*/
extern PGDLLIMPORT char *Dynamic_library_path;
+extern char *find_in_path(const char *basename, const char *path, const char *path_param,
+ const char *macro, const char *macro_val);
extern void *load_external_function(const char *filename, const char *funcname,
bool signalNotFound, void **filehandle);
extern void *lookup_external_function(void *filehandle, const char *funcname);
base-commit: 71cb352904c1833fe067d6f191269710fe2ca06f
--
2.47.1
view thread (44+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: RFC: Additional Directory for Extensions
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox