diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0aec11f443212..b55b037e0f9e7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10418,6 +10418,43 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
+
+ extension_destdir (string)
+
+ extension_destdir configuration parameter
+
+
+
+
+ Specifies a directory prefix into which extensions should be
+ installed. Only superusers and users with the appropriate
+ SET privilege can change this setting. When set,
+ the postmaster will search this directory for an extension before
+ searching the default paths.
+
+
+
+ For example, this configuration:
+
+extension_destdir = '/mnt/extensions'
+
+ will allow PostgreSQL to first look for
+ extension control files, SQL files, and loadable modules installed in
+ /mnt/extensions and fall back on the
+ default directories if they're not found there.
+
+
+
+ Note that the files should be installed in their full paths under the
+ extension_destdir prefix. When using
+ PGXS to install an extension, pass
+ the destination directory via the DESTDIR variable
+ to install the files in the proper location. For more information see
+ .
+
+
+
+
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 218940ee5ce19..6653955d53879 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -669,7 +669,8 @@ 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
+ the SHAREDIR under the
+ prefix and to the installation's SHAREDIR directory. The
default behavior is equivalent to specifying
directory = 'extension'.
@@ -1710,6 +1711,15 @@ include $(PGXS)
+
+ DESTDIR
+
+
+ install all files under this directory prefix
+
+
+
+
NO_INSTALLCHECK
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index fab59ad5f6624..a084829b2c99e 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -337,6 +337,16 @@ get_extension_control_filename(const char *extname)
get_share_path(my_exec_path, sharepath);
result = (char *) palloc(MAXPGPATH);
+ /*
+ * If extension_destdir is set, try to find the file there first
+ */
+ if (*extension_destdir != '\0')
+ {
+ snprintf(result, MAXPGPATH, "%s%s/extension/%s.control",
+ extension_destdir, sharepath, extname);
+ if (pg_file_exists(result))
+ return result;
+ }
snprintf(result, MAXPGPATH, "%s/extension/%s.control",
sharepath, extname);
@@ -376,6 +386,16 @@ get_extension_aux_control_filename(ExtensionControlFile *control,
scriptdir = get_extension_script_directory(control);
result = (char *) palloc(MAXPGPATH);
+ /*
+ * If extension_destdir is set, try to find the file there first
+ */
+ if (*extension_destdir != '\0')
+ {
+ snprintf(result, MAXPGPATH, "%s%s/%s--%s.control",
+ extension_destdir, scriptdir, control->name, version);
+ if (pg_file_exists(result))
+ return result;
+ }
snprintf(result, MAXPGPATH, "%s/%s--%s.control",
scriptdir, control->name, version);
@@ -394,6 +414,23 @@ get_extension_script_filename(ExtensionControlFile *control,
scriptdir = get_extension_script_directory(control);
result = (char *) palloc(MAXPGPATH);
+ /*
+ * If extension_destdir is set, try to find the file there first
+ */
+ if (*extension_destdir != '\0')
+ {
+ if (from_version)
+ snprintf(result, MAXPGPATH, "%s%s/%s--%s--%s.sql",
+ extension_destdir, scriptdir, control->name, from_version, version);
+ else
+ snprintf(result, MAXPGPATH, "%s%s/%s--%s.sql",
+ extension_destdir, scriptdir, control->name, version);
+ if (pg_file_exists(result))
+ {
+ pfree(scriptdir);
+ return result;
+ }
+ }
if (from_version)
snprintf(result, MAXPGPATH, "%s/%s--%s--%s.sql",
scriptdir, control->name, from_version, version);
@@ -1153,6 +1190,59 @@ get_ext_ver_list(ExtensionControlFile *control)
DIR *dir;
struct dirent *de;
+ /*
+ * If extension_destdir is set, try to find the files there first
+ */
+ if (*extension_destdir != '\0')
+ {
+ char location[MAXPGPATH];
+
+ snprintf(location, MAXPGPATH, "%s%s", extension_destdir,
+ get_extension_script_directory(control));
+ dir = AllocateDir(location);
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ char *vername;
+ char *vername2;
+ ExtensionVersionInfo *evi;
+ ExtensionVersionInfo *evi2;
+
+ /* must be a .sql file ... */
+ if (!is_extension_script_filename(de->d_name))
+ continue;
+
+ /* ... matching extension name followed by separator */
+ if (strncmp(de->d_name, control->name, extnamelen) != 0 ||
+ de->d_name[extnamelen] != '-' ||
+ de->d_name[extnamelen + 1] != '-')
+ continue;
+
+ /* extract version name(s) from 'extname--something.sql' filename */
+ vername = pstrdup(de->d_name + extnamelen + 2);
+ *strrchr(vername, '.') = '\0';
+ vername2 = strstr(vername, "--");
+ if (!vername2)
+ {
+ /* It's an install, not update, script; record its version name */
+ evi = get_ext_ver_info(vername, &evi_list);
+ evi->installable = true;
+ continue;
+ }
+ *vername2 = '\0'; /* terminate first version */
+ vername2 += 2; /* and point to second */
+
+ /* if there's a third --, it's bogus, ignore it */
+ if (strstr(vername2, "--"))
+ continue;
+
+ /* Create ExtensionVersionInfos and link them together */
+ evi = get_ext_ver_info(vername, &evi_list);
+ evi2 = get_ext_ver_info(vername2, &evi_list);
+ evi->reachable = lappend(evi->reachable, evi2);
+ }
+ FreeDir(dir);
+ }
+
location = get_extension_script_directory(control);
dir = AllocateDir(location);
while ((de = ReadDir(dir, location)) != NULL)
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index c7aa789b51b41..5c0cccfdae806 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -35,6 +35,7 @@
#include "miscadmin.h"
#include "storage/fd.h"
#include "storage/shmem.h"
+#include "utils/guc.h"
#include "utils/hsearch.h"
@@ -419,7 +420,7 @@ expand_dynamic_library_name(const char *name)
{
bool have_slash;
char *new;
- char *full;
+ char *full, *full2;
Assert(name);
@@ -434,6 +435,19 @@ expand_dynamic_library_name(const char *name)
else
{
full = substitute_libpath_macro(name);
+ /*
+ * If extension_destdir is set, try to find the file there first
+ */
+ if (*extension_destdir != '\0')
+ {
+ full2 = psprintf("%s%s", extension_destdir, full);
+ if (pg_file_exists(full2))
+ {
+ pfree(full);
+ return full2;
+ }
+ pfree(full2);
+ }
if (pg_file_exists(full))
return full;
pfree(full);
@@ -452,6 +466,19 @@ expand_dynamic_library_name(const char *name)
{
full = substitute_libpath_macro(new);
pfree(new);
+ /*
+ * If extension_destdir is set, try to find the file there first
+ */
+ if (*extension_destdir != '\0')
+ {
+ full2 = psprintf("%s%s", extension_destdir, full);
+ if (pg_file_exists(full2))
+ {
+ pfree(full);
+ return full2;
+ }
+ pfree(full2);
+ }
if (pg_file_exists(full))
return full;
pfree(full);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 686309db58b98..d79741e140db7 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -539,6 +539,7 @@ char *ConfigFileName;
char *HbaFileName;
char *IdentFileName;
char *external_pid_file;
+char *extension_destdir;
char *application_name;
@@ -4554,6 +4555,17 @@ struct config_string ConfigureNamesString[] =
check_canonical_path, NULL, NULL
},
+ {
+ {"extension_destdir", PGC_SUSET, FILE_LOCATIONS,
+ gettext_noop("Path to prepend for extension loading."),
+ gettext_noop("This directory is prepended to paths when loading extensions (control and SQL files), and to the '$libdir' directive when loading modules that back functions. The location is made configurable to allow build-time testing of extensions that do not have been installed to their proper location yet."),
+ GUC_SUPERUSER_ONLY
+ },
+ &extension_destdir,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the name of the SSL library."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 667e0dc40a24f..6a5164813521c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -771,6 +771,8 @@
# - Other Defaults -
#dynamic_library_path = '$libdir'
+#extension_destdir = '' # prepend path when loading extensions
+ # and shared objects (added by Debian)
#gin_fuzzy_search_limit = 0
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 840b0fe57ff47..cfbd4b90eb3d4 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -285,6 +285,7 @@ extern PGDLLIMPORT char *ConfigFileName;
extern PGDLLIMPORT char *HbaFileName;
extern PGDLLIMPORT char *IdentFileName;
extern PGDLLIMPORT char *external_pid_file;
+extern PGDLLIMPORT char *extension_destdir;
extern PGDLLIMPORT char *application_name;