diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3f41a17b1fe7b..b09190d8aa4a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10539,6 +10539,74 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
+
+ extension_control_path (string)
+
+ extension_control_path configuration parameter
+
+
+
+
+ A path to search for extensions, specifically extension control files
+ (name.control). The
+ remaining extension script and secondary control files are then loaded
+ from the same directory where the primary control file was found.
+ See for details.
+
+
+
+ The value for extension_control_path 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 $system, the
+ compiled-in PostgreSQL extension
+ directory is substituted for $system; this
+ is where the extensions provided by the standard
+ PostgreSQL distribution are installed.
+ (Use pg_config --sharedir to find out the name of
+ this directory.) For example:
+
+extension_control_path = '/usr/local/share/postgresql/extension:/home/my_project/share/extension:$system'
+
+ or, in a Windows environment:
+
+extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\extension;$system'
+
+ Note that the path elements should typically end in
+ extension if the normal installation layouts are
+ followed. (The value for $system already includes
+ the extension suffix.)
+
+
+
+ The default value for this parameter is
+ '$system'. If the value is set to an empty
+ string, the default '$system' is also assumed.
+
+
+
+ This parameter can be changed at run time by superusers and users
+ with the appropriate SET 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 postgresql.conf configuration
+ file.
+
+
+
+ Note that if you set this parameter to be able to load extensions from
+ nonstandard locations, you will most likely also need to set to a correspondent location, for
+ example,
+
+extension_control_path = '/usr/local/share/postgresql/extension:$system'
+dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
+
+
+
+
+
gin_fuzzy_search_limit (integer)
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index ba492ca27c089..64f8e133caeb3 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -649,6 +649,11 @@ RETURNS anycompatible AS ...
control file can specify a different directory for the script file(s).
+
+ Additional locations for extension control files can be configured using
+ the parameter .
+
+
The file format for an extension control file is the same as for the
postgresql.conf file, namely a list of
@@ -669,9 +674,9 @@ RETURNS anycompatible AS ...
The directory containing the extension's SQL script
file(s). Unless an absolute path is given, the name is relative to
- the installation's SHAREDIR directory. The
- default behavior is equivalent to specifying
- directory = 'extension'.
+ the installation's SHAREDIR directory. By default,
+ the script files are looked for in the same directory where the
+ control file was found.
@@ -719,8 +724,8 @@ RETURNS anycompatible AS ...
The value of this parameter will be substituted for each occurrence
of MODULE_PATHNAME in the script file(s). If it is not
- set, no substitution is made. Typically, this is set to
- $libdir/shared_library_name and
+ set, no substitution is made. Typically, this is set to just
+ shared_library_name and
then MODULE_PATHNAME is used in CREATE
FUNCTION commands for C-language functions, so that the script
files do not need to hard-wire the name of the shared library.
@@ -1804,6 +1809,10 @@ include $(PGXS)
setting PG_CONFIG to point to its
pg_config program, either within the makefile
or on the make command line.
+ You can also select a separate installation directory for your extension
+ by setting the make variable prefix
+ on the make command line. (But this will then require
+ additional setup to get the server to find the extension there.)
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index ca2b80d669c5e..713abd9c49447 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -90,8 +90,10 @@ CREATE EXTENSION [ IF NOT EXISTS ] extension_name
The name of the extension to be
installed. PostgreSQL will create the
- extension using details from the file
- SHAREDIR/extension/extension_name.control.
+ extension using details from the file extension_name.control,
+ found via the server's extension control path (set by .)
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index eac3d0012113d..7c1bffbea0702 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 ba540e3de5be0..b11eab417b001 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 87b233cb8871e..46a46715ec7f3 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 c9d8cd796a8a2..9681dcf7ed677 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"
@@ -4261,6 +4262,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 079efa1baa799..c641f7cf8d9b6 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -779,6 +779,7 @@ autovacuum_worker_slots = 16 # autovacuum worker slots to allocate
# - 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 0b63640512019..24419bfb5c902 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 e609ea875a759..5811307a82c9e 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);