</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>
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
<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>
<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.
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>
<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>
#
# 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.
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)
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
+#include "nodes/pg_list.h"
#include "nodes/queryjumble.h"
#include "storage/fd.h"
#include "tcop/utility.h"
#include "utils/varlena.h"
+/* GUC */
+char *Extension_control_path;
+
/* Globally visible state variables */
bool creating_extension = false;
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
ObjectAddress extension,
ObjectAddress object);
static char *read_whole_file(const char *filename, int *length);
+static ExtensionControlFile *new_ExtensionControlFile(const char *extname);
/*
return (extension != NULL) && (strcmp(extension, ".sql") == 0);
}
-static char *
-get_extension_control_directory(void)
+/*
+ * Return a list of directories declared on extension_control_path GUC.
+ */
+static List *
+get_extension_control_directories(void)
{
char sharepath[MAXPGPATH];
- char *result;
+ char *system_dir;
+ char *ecp;
+ List *paths = NIL;
get_share_path(my_exec_path, sharepath);
- result = (char *) palloc(MAXPGPATH);
- snprintf(result, MAXPGPATH, "%s/extension", sharepath);
- return result;
+ system_dir = psprintf("%s/extension", sharepath);
+
+ if (strlen(Extension_control_path) == 0)
+ {
+ paths = lappend(paths, system_dir);
+ }
+ else
+ {
+ /* Duplicate the string so we can modify it */
+ ecp = pstrdup(Extension_control_path);
+
+ for (;;)
+ {
+ int len;
+ char *mangled;
+ char *piece = first_path_var_separator(ecp);
+
+ /* Get the length of the next path on ecp */
+ if (piece == NULL)
+ len = strlen(ecp);
+ else
+ len = piece - ecp;
+
+ /* Copy the next path found on ecp */
+ piece = palloc(len + 1);
+ strlcpy(piece, ecp, len + 1);
+
+ /* Substitute the path macro if needed */
+ mangled = substitute_path_macro(piece, "$system", system_dir);
+ pfree(piece);
+
+ /* Canonicalize the path based on the OS and add to the list */
+ canonicalize_path(mangled);
+ paths = lappend(paths, mangled);
+
+ /* Break if ecp is empty or move to the next path on ecp */
+ if (ecp[len] == '\0')
+ break;
+ else
+ ecp += len + 1;
+ }
+ }
+
+ return paths;
}
+/*
+ * 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, ecp, "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;
}
* 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);
* fields of *control. We parse primary file if version == NULL,
* else the optional auxiliary file for that version.
*
+ * The control file will be search on Extension_control_path paths if
+ * control->control_dir is NULL, otherwise it will use the value of control_dir
+ * to read and parse the .control file, so it assume that the control_dir is a
+ * valid path for the control file being parsed.
+ *
* Control files are supposed to be very short, half a dozen lines,
* so we don't worry about memory allocation risks here. Also we don't
* worry about what encoding it's in; all values are expected to be ASCII.
if (version)
filename = get_extension_aux_control_filename(control, version);
else
- filename = get_extension_control_filename(control->name);
+ {
+ /*
+ * If control_dir is already set, use it, else do a path search.
+ */
+ if (control->control_dir)
+ {
+ filename = psprintf("%s/%s.control", control->control_dir, control->name);
+ }
+ else
+ 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",
static ExtensionControlFile *
read_extension_control_file(const char *extname)
{
- ExtensionControlFile *control;
-
- /*
- * Set up default values. Pointer fields are initially null.
- */
- control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
- control->name = pstrdup(extname);
- control->relocatable = false;
- control->superuser = true;
- control->trusted = false;
- control->encoding = -1;
+ ExtensionControlFile *control = new_ExtensionControlFile(extname);
/*
* Parse the primary control file.
pg_available_extensions(PG_FUNCTION_ARGS)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
+ List *locations;
DIR *dir;
struct dirent *de;
/* Build tuplestore to hold the result rows */
InitMaterializedSRF(fcinfo, 0);
- location = get_extension_control_directory();
- dir = AllocateDir(location);
+ locations = get_extension_control_directories();
- /*
- * If the control directory doesn't exist, we want to silently return an
- * empty set. Any other error will be reported by ReadDir.
- */
- if (dir == NULL && errno == ENOENT)
+ foreach_ptr(char, location, locations)
{
- /* do nothing */
- }
- else
- {
- while ((de = ReadDir(dir, location)) != NULL)
+ dir = AllocateDir(location);
+
+ /*
+ * If the control directory doesn't exist, we want to silently return
+ * an empty set. Any other error will be reported by ReadDir.
+ */
+ if (dir == NULL && errno == ENOENT)
{
- ExtensionControlFile *control;
- char *extname;
- Datum values[3];
- bool nulls[3];
+ /* do nothing */
+ }
+ else
+ {
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ ExtensionControlFile *control;
+ char *extname;
+ Datum values[3];
+ bool nulls[3];
- if (!is_extension_control_filename(de->d_name))
- continue;
+ if (!is_extension_control_filename(de->d_name))
+ continue;
- /* extract extension name from 'name.control' filename */
- extname = pstrdup(de->d_name);
- *strrchr(extname, '.') = '\0';
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
- /* ignore it if it's an auxiliary control file */
- if (strstr(extname, "--"))
- continue;
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
- control = read_extension_control_file(extname);
+ control = new_ExtensionControlFile(extname);
+ control->control_dir = pstrdup(location);
+ parse_extension_control_file(control, NULL);
- memset(values, 0, sizeof(values));
- memset(nulls, 0, sizeof(nulls));
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
- /* name */
- values[0] = DirectFunctionCall1(namein,
- CStringGetDatum(control->name));
- /* default_version */
- if (control->default_version == NULL)
- nulls[1] = true;
- else
- values[1] = CStringGetTextDatum(control->default_version);
- /* comment */
- if (control->comment == NULL)
- nulls[2] = true;
- else
- values[2] = CStringGetTextDatum(control->comment);
+ /* name */
+ values[0] = DirectFunctionCall1(namein,
+ CStringGetDatum(control->name));
+ /* default_version */
+ if (control->default_version == NULL)
+ nulls[1] = true;
+ else
+ values[1] = CStringGetTextDatum(control->default_version);
+ /* comment */
+ if (control->comment == NULL)
+ nulls[2] = true;
+ else
+ values[2] = CStringGetTextDatum(control->comment);
- tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
- values, nulls);
- }
+ tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+ values, nulls);
+ }
- FreeDir(dir);
+ FreeDir(dir);
+ }
}
return (Datum) 0;
pg_available_extension_versions(PG_FUNCTION_ARGS)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
+ List *locations;
DIR *dir;
struct dirent *de;
/* Build tuplestore to hold the result rows */
InitMaterializedSRF(fcinfo, 0);
- location = get_extension_control_directory();
- dir = AllocateDir(location);
+ locations = get_extension_control_directories();
- /*
- * If the control directory doesn't exist, we want to silently return an
- * empty set. Any other error will be reported by ReadDir.
- */
- if (dir == NULL && errno == ENOENT)
- {
- /* do nothing */
- }
- else
+ foreach_ptr(char, location, locations)
{
- while ((de = ReadDir(dir, location)) != NULL)
+ dir = AllocateDir(location);
+
+ /*
+ * If the control directory doesn't exist, we want to silently return
+ * an empty set. Any other error will be reported by ReadDir.
+ */
+ if (dir == NULL && errno == ENOENT)
+ {
+ /* do nothing */
+ }
+ else
{
- ExtensionControlFile *control;
- char *extname;
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ ExtensionControlFile *control;
+ char *extname;
- if (!is_extension_control_filename(de->d_name))
- continue;
+ if (!is_extension_control_filename(de->d_name))
+ continue;
- /* extract extension name from 'name.control' filename */
- extname = pstrdup(de->d_name);
- *strrchr(extname, '.') = '\0';
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
- /* ignore it if it's an auxiliary control file */
- if (strstr(extname, "--"))
- continue;
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
- /* read the control file */
- control = read_extension_control_file(extname);
+ /* read the control file */
+ control = new_ExtensionControlFile(extname);
+ control->control_dir = pstrdup(location);
+ parse_extension_control_file(control, NULL);
- /* scan extension's script directory for install scripts */
- get_available_versions_for_extension(control, rsinfo->setResult,
- rsinfo->setDesc);
- }
+ /* scan extension's script directory for install scripts */
+ get_available_versions_for_extension(control, rsinfo->setResult,
+ rsinfo->setDesc);
+ }
- FreeDir(dir);
+ FreeDir(dir);
+ }
}
return (Datum) 0;
extension_file_exists(const char *extensionName)
{
bool result = false;
- char *location;
+ List *locations;
DIR *dir;
struct dirent *de;
- location = get_extension_control_directory();
- dir = AllocateDir(location);
+ locations = get_extension_control_directories();
- /*
- * If the control directory doesn't exist, we want to silently return
- * false. Any other error will be reported by ReadDir.
- */
- if (dir == NULL && errno == ENOENT)
- {
- /* do nothing */
- }
- else
+ foreach_ptr(char, location, locations)
{
- while ((de = ReadDir(dir, location)) != NULL)
+ dir = AllocateDir(location);
+
+ /*
+ * If the control directory doesn't exist, we want to silently return
+ * false. Any other error will be reported by ReadDir.
+ */
+ if (dir == NULL && errno == ENOENT)
{
- char *extname;
+ /* do nothing */
+ }
+ else
+ {
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ char *extname;
- if (!is_extension_control_filename(de->d_name))
- continue;
+ if (!is_extension_control_filename(de->d_name))
+ continue;
- /* extract extension name from 'name.control' filename */
- extname = pstrdup(de->d_name);
- *strrchr(extname, '.') = '\0';
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
- /* ignore it if it's an auxiliary control file */
- if (strstr(extname, "--"))
- continue;
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
- /* done if it matches request */
- if (strcmp(extname, extensionName) == 0)
- {
- result = true;
- break;
+ /* done if it matches request */
+ if (strcmp(extname, extensionName) == 0)
+ {
+ result = true;
+ break;
+ }
}
- }
- FreeDir(dir);
+ FreeDir(dir);
+ }
+ if (result)
+ break;
}
return result;
*length = bytes_to_read;
return buf;
}
+
+static ExtensionControlFile *
+new_ExtensionControlFile(const char *extname)
+{
+ /*
+ * Set up default values. Pointer fields are initially null.
+ */
+ ExtensionControlFile *control = palloc0_object(ExtensionControlFile);
+
+ control->name = pstrdup(extname);
+ control->relocatable = false;
+ control->superuser = true;
+ control->trusted = false;
+ control->encoding = -1;
+
+ return control;
+}
const Pg_magic_struct *module_magic_data);
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);
/* Magic structure that module needs to match to be accepted */
static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
/*
* 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.
*
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);
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;
* Substitute for any macros appearing in the given string.
* Result is always freshly palloc'd.
*/
-static char *
-substitute_libpath_macro(const char *name)
+char *
+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;
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);
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);
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;
#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"
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."),
# - Other Defaults -
#dynamic_library_path = '$libdir'
+#extension_control_path = '$system'
#gin_fuzzy_search_limit = 0
#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
*/
extern PGDLLIMPORT char *Dynamic_library_path;
+extern char *substitute_path_macro(const char *str, const char *macro, const char *value);
+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);
test_ext_req_schema3--1.0.sql
REGRESS = test_extensions test_extdepend
+TAP_TESTS = 1
# force C locale for output stability
NO_LOCALE = 1
],
'regress_args': ['--no-locale'],
},
+ 'tap': {
+ 'tests': [
+ 't/001_extension_control_path.pl',
+ ],
+ },
}
--- /dev/null
+# Copyright (c) 2024-2025, PostgreSQL Global Development Group
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+
+$node->init;
+
+# Create a temporary directory for the extension control file
+my $ext_dir = PostgreSQL::Test::Utils::tempdir();
+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;
+
+# Use the correct separator and escape \ when running on Windows.
+my $sep = $windows_os ? ";" : ":";
+$node->append_conf(
+ 'postgresql.conf', qq{
+extension_control_path = '\$system$sep@{[ $windows_os ? ($ext_dir =~ s/\\/\\\\/gr) : $ext_dir ]}'
+});
+
+# Start node
+$node->start;
+
+my $ecp = $node->safe_psql('postgres', 'show extension_control_path;');
+
+is($ecp, "\$system$sep$ext_dir",
+ "custom extension control directory path configured");
+
+$node->safe_psql('postgres', "CREATE EXTENSION $ext_name");
+
+my $ret = $node->safe_psql('postgres',
+ "select * from pg_available_extensions where name = '$ext_name'");
+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',
+ "select * from pg_available_extension_versions where name = '$ext_name'");
+is( $ret2,
+ "test_custom_ext_paths|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',
+ "select count(*) > 0 as ok from pg_available_extensions where name = 'amcheck'"
+);
+is($ret3, "t",
+ "\$system extension is installed correctly on pg_available_extensions");
+
+
+my $ret4 = $node->safe_psql('postgres',
+ "set extension_control_path = ''; select count(*) > 0 as ok from pg_available_extensions where name = 'amcheck'"
+);
+is($ret4, "t",
+ "\$system extension is installed correctly on pg_available_extensions with empty extension_control_path"
+);
+
+done_testing();