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 <pe...@eisentraut.org>
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/e7c7bffb-8857-48d4-a71f-88b359fad...@justatheory.com
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