mirror of
https://github.com/postgres/postgres.git
synced 2025-06-29 10:41:53 +03:00
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. Some use cases of this are: (1) testing extensions during package builds, (2) installing extensions outside security-restricted containers like Python.app (on macOS), (3) adding extensions to PostgreSQL running in a Kubernetes environment using operators such as CloudNativePG without having to rebuild the base image for each new extension. There is also a tweak in Makefile.global so that it is possible to install extensions using PGXS into an different directory than the default, using 'make install prefix=/else/where'. This previously only worked when specifying the subdirectories, like 'make install datadir=/else/where/share pkglibdir=/else/where/lib', for purely implementation reasons. (Of course, without the path feature, installing elsewhere was rarely useful.) Author: Peter Eisentraut <peter@eisentraut.org> Co-authored-by: Matheus Alcantara <matheusssilv97@gmail.com> Reviewed-by: David E. Wheeler <david@justatheory.com> Reviewed-by: Gabriele Bartolini <gabriele.bartolini@enterprisedb.com> Reviewed-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com> Reviewed-by: Niccolò Fei <niccolo.fei@enterprisedb.com> Discussion: https://www.postgresql.org/message-id/flat/E7C7BFFB-8857-48D4-A71F-88B359FADCFD@justatheory.com
This commit is contained in:
@ -10954,6 +10954,74 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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">
|
<varlistentry id="guc-gin-fuzzy-search-limit" xreflabel="gin_fuzzy_search_limit">
|
||||||
<term><varname>gin_fuzzy_search_limit</varname> (<type>integer</type>)
|
<term><varname>gin_fuzzy_search_limit</varname> (<type>integer</type>)
|
||||||
<indexterm>
|
<indexterm>
|
||||||
|
@ -649,6 +649,11 @@ RETURNS anycompatible AS ...
|
|||||||
control file can specify a different directory for the script file(s).
|
control file can specify a different directory for the script file(s).
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Additional locations for extension control files can be configured using
|
||||||
|
the parameter <xref linkend="guc-extension-control-path"/>.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The file format for an extension control file is the same as for the
|
The file format for an extension control file is the same as for the
|
||||||
<filename>postgresql.conf</filename> file, namely a list of
|
<filename>postgresql.conf</filename> file, namely a list of
|
||||||
@ -669,9 +674,9 @@ RETURNS anycompatible AS ...
|
|||||||
<para>
|
<para>
|
||||||
The directory containing the extension's <acronym>SQL</acronym> script
|
The directory containing the extension's <acronym>SQL</acronym> script
|
||||||
file(s). Unless an absolute path is given, the name is relative to
|
file(s). Unless an absolute path is given, the name is relative to
|
||||||
the installation's <literal>SHAREDIR</literal> directory. The
|
the installation's <literal>SHAREDIR</literal> directory. By default,
|
||||||
default behavior is equivalent to specifying
|
the script files are looked for in the same directory where the
|
||||||
<literal>directory = 'extension'</literal>.
|
control file was found.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -719,8 +724,8 @@ RETURNS anycompatible AS ...
|
|||||||
<para>
|
<para>
|
||||||
The value of this parameter will be substituted for each occurrence
|
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
|
of <literal>MODULE_PATHNAME</literal> in the script file(s). If it is not
|
||||||
set, no substitution is made. Typically, this is set to
|
set, no substitution is made. Typically, this is set to just
|
||||||
<literal>$libdir/<replaceable>shared_library_name</replaceable></literal> and
|
<literal><replaceable>shared_library_name</replaceable></literal> and
|
||||||
then <literal>MODULE_PATHNAME</literal> is used in <command>CREATE
|
then <literal>MODULE_PATHNAME</literal> is used in <command>CREATE
|
||||||
FUNCTION</command> commands for C-language functions, so that the script
|
FUNCTION</command> commands for C-language functions, so that the script
|
||||||
files do not need to hard-wire the name of the shared library.
|
files do not need to hard-wire the name of the shared library.
|
||||||
@ -1804,6 +1809,10 @@ include $(PGXS)
|
|||||||
setting <varname>PG_CONFIG</varname> to point to its
|
setting <varname>PG_CONFIG</varname> to point to its
|
||||||
<command>pg_config</command> program, either within the makefile
|
<command>pg_config</command> program, either within the makefile
|
||||||
or on the <literal>make</literal> command line.
|
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>
|
<para>
|
||||||
|
@ -90,8 +90,10 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
|
|||||||
<para>
|
<para>
|
||||||
The name of the extension to be
|
The name of the extension to be
|
||||||
installed. <productname>PostgreSQL</productname> will create the
|
installed. <productname>PostgreSQL</productname> will create the
|
||||||
extension using details from the file
|
extension using details from the file <filename><replaceable
|
||||||
<literal>SHAREDIR/extension/</literal><replaceable class="parameter">extension_name</replaceable><literal>.control</literal>.
|
class="parameter">extension_name</replaceable>.control</filename>,
|
||||||
|
found via the server's extension control path (set by <xref
|
||||||
|
linkend="guc-extension-control-path"/>.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -87,9 +87,19 @@ endif # not PGXS
|
|||||||
#
|
#
|
||||||
# In a PGXS build, we cannot use the values inserted into Makefile.global
|
# In a PGXS build, we cannot use the values inserted into Makefile.global
|
||||||
# by configure, since the installation tree may have been relocated.
|
# 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;
|
# Note that prefix, exec_prefix, and datarootdir aren't defined in a PGXS build;
|
||||||
# makefiles may only use the derived variables such as bindir.
|
# makefiles may only use the derived variables such as bindir.
|
||||||
@ -147,11 +157,6 @@ localedir := @localedir@
|
|||||||
|
|
||||||
else # PGXS case
|
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)
|
bindir := $(shell $(PG_CONFIG) --bindir)
|
||||||
datadir := $(shell $(PG_CONFIG) --sharedir)
|
datadir := $(shell $(PG_CONFIG) --sharedir)
|
||||||
sysconfdir := $(shell $(PG_CONFIG) --sysconfdir)
|
sysconfdir := $(shell $(PG_CONFIG) --sysconfdir)
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
#include "mb/pg_wchar.h"
|
#include "mb/pg_wchar.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "nodes/pg_list.h"
|
||||||
#include "nodes/queryjumble.h"
|
#include "nodes/queryjumble.h"
|
||||||
#include "storage/fd.h"
|
#include "storage/fd.h"
|
||||||
#include "tcop/utility.h"
|
#include "tcop/utility.h"
|
||||||
@ -69,6 +70,9 @@
|
|||||||
#include "utils/varlena.h"
|
#include "utils/varlena.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* GUC */
|
||||||
|
char *Extension_control_path;
|
||||||
|
|
||||||
/* Globally visible state variables */
|
/* Globally visible state variables */
|
||||||
bool creating_extension = false;
|
bool creating_extension = false;
|
||||||
Oid CurrentExtensionObject = InvalidOid;
|
Oid CurrentExtensionObject = InvalidOid;
|
||||||
@ -79,6 +83,7 @@ Oid CurrentExtensionObject = InvalidOid;
|
|||||||
typedef struct ExtensionControlFile
|
typedef struct ExtensionControlFile
|
||||||
{
|
{
|
||||||
char *name; /* name of the extension */
|
char *name; /* name of the extension */
|
||||||
|
char *control_dir; /* directory where control file was found */
|
||||||
char *directory; /* directory for script files */
|
char *directory; /* directory for script files */
|
||||||
char *default_version; /* default install target version, if any */
|
char *default_version; /* default install target version, if any */
|
||||||
char *module_pathname; /* string to substitute for
|
char *module_pathname; /* string to substitute for
|
||||||
@ -146,6 +151,7 @@ static void ExecAlterExtensionContentsRecurse(AlterExtensionContentsStmt *stmt,
|
|||||||
ObjectAddress extension,
|
ObjectAddress extension,
|
||||||
ObjectAddress object);
|
ObjectAddress object);
|
||||||
static char *read_whole_file(const char *filename, int *length);
|
static char *read_whole_file(const char *filename, int *length);
|
||||||
|
static ExtensionControlFile *new_ExtensionControlFile(const char *extname);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -328,29 +334,104 @@ is_extension_script_filename(const char *filename)
|
|||||||
return (extension != NULL) && (strcmp(extension, ".sql") == 0);
|
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 sharepath[MAXPGPATH];
|
||||||
char *result;
|
char *system_dir;
|
||||||
|
char *ecp;
|
||||||
|
List *paths = NIL;
|
||||||
|
|
||||||
get_share_path(my_exec_path, sharepath);
|
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 *
|
static char *
|
||||||
get_extension_control_filename(const char *extname)
|
find_extension_control_filename(ExtensionControlFile *control)
|
||||||
{
|
{
|
||||||
char sharepath[MAXPGPATH];
|
char sharepath[MAXPGPATH];
|
||||||
|
char *system_dir;
|
||||||
|
char *basename;
|
||||||
|
char *ecp;
|
||||||
char *result;
|
char *result;
|
||||||
|
|
||||||
|
Assert(control->name);
|
||||||
|
|
||||||
get_share_path(my_exec_path, sharepath);
|
get_share_path(my_exec_path, sharepath);
|
||||||
result = (char *) palloc(MAXPGPATH);
|
system_dir = psprintf("%s/extension", sharepath);
|
||||||
snprintf(result, MAXPGPATH, "%s/extension/%s.control",
|
|
||||||
sharepath, extname);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
@ -366,7 +447,7 @@ get_extension_script_directory(ExtensionControlFile *control)
|
|||||||
* installation's share directory.
|
* installation's share directory.
|
||||||
*/
|
*/
|
||||||
if (!control->directory)
|
if (!control->directory)
|
||||||
return get_extension_control_directory();
|
return pstrdup(control->control_dir);
|
||||||
|
|
||||||
if (is_absolute_path(control->directory))
|
if (is_absolute_path(control->directory))
|
||||||
return pstrdup(control->directory);
|
return pstrdup(control->directory);
|
||||||
@ -424,6 +505,11 @@ get_extension_script_filename(ExtensionControlFile *control,
|
|||||||
* fields of *control. We parse primary file if version == NULL,
|
* fields of *control. We parse primary file if version == NULL,
|
||||||
* else the optional auxiliary file for that version.
|
* 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,
|
* 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
|
* 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.
|
* worry about what encoding it's in; all values are expected to be ASCII.
|
||||||
@ -444,27 +530,35 @@ parse_extension_control_file(ExtensionControlFile *control,
|
|||||||
if (version)
|
if (version)
|
||||||
filename = get_extension_aux_control_filename(control, version);
|
filename = get_extension_aux_control_filename(control, version);
|
||||||
else
|
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 ((file = AllocateFile(filename, "r")) == NULL)
|
||||||
{
|
{
|
||||||
if (errno == ENOENT)
|
/* no complaint for missing auxiliary file */
|
||||||
|
if (errno == ENOENT && version)
|
||||||
{
|
{
|
||||||
/* no complaint for missing auxiliary file */
|
pfree(filename);
|
||||||
if (version)
|
return;
|
||||||
{
|
|
||||||
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.")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode_for_file_access(),
|
(errcode_for_file_access(),
|
||||||
errmsg("could not open extension control file \"%s\": %m",
|
errmsg("could not open extension control file \"%s\": %m",
|
||||||
@ -603,17 +697,7 @@ parse_extension_control_file(ExtensionControlFile *control,
|
|||||||
static ExtensionControlFile *
|
static ExtensionControlFile *
|
||||||
read_extension_control_file(const char *extname)
|
read_extension_control_file(const char *extname)
|
||||||
{
|
{
|
||||||
ExtensionControlFile *control;
|
ExtensionControlFile *control = new_ExtensionControlFile(extname);
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse the primary control file.
|
* Parse the primary control file.
|
||||||
@ -2121,68 +2205,74 @@ Datum
|
|||||||
pg_available_extensions(PG_FUNCTION_ARGS)
|
pg_available_extensions(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||||
char *location;
|
List *locations;
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
|
|
||||||
/* Build tuplestore to hold the result rows */
|
/* Build tuplestore to hold the result rows */
|
||||||
InitMaterializedSRF(fcinfo, 0);
|
InitMaterializedSRF(fcinfo, 0);
|
||||||
|
|
||||||
location = get_extension_control_directory();
|
locations = get_extension_control_directories();
|
||||||
dir = AllocateDir(location);
|
|
||||||
|
|
||||||
/*
|
foreach_ptr(char, location, locations)
|
||||||
* 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 */
|
dir = AllocateDir(location);
|
||||||
}
|
|
||||||
else
|
/*
|
||||||
{
|
* If the control directory doesn't exist, we want to silently return
|
||||||
while ((de = ReadDir(dir, location)) != NULL)
|
* an empty set. Any other error will be reported by ReadDir.
|
||||||
|
*/
|
||||||
|
if (dir == NULL && errno == ENOENT)
|
||||||
{
|
{
|
||||||
ExtensionControlFile *control;
|
/* do nothing */
|
||||||
char *extname;
|
|
||||||
Datum values[3];
|
|
||||||
bool nulls[3];
|
|
||||||
|
|
||||||
if (!is_extension_control_filename(de->d_name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
|
|
||||||
control = read_extension_control_file(extname);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
|
|
||||||
values, nulls);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while ((de = ReadDir(dir, location)) != NULL)
|
||||||
|
{
|
||||||
|
ExtensionControlFile *control;
|
||||||
|
char *extname;
|
||||||
|
Datum values[3];
|
||||||
|
bool nulls[3];
|
||||||
|
|
||||||
FreeDir(dir);
|
if (!is_extension_control_filename(de->d_name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeDir(dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Datum) 0;
|
return (Datum) 0;
|
||||||
@ -2201,51 +2291,57 @@ Datum
|
|||||||
pg_available_extension_versions(PG_FUNCTION_ARGS)
|
pg_available_extension_versions(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||||
char *location;
|
List *locations;
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
|
|
||||||
/* Build tuplestore to hold the result rows */
|
/* Build tuplestore to hold the result rows */
|
||||||
InitMaterializedSRF(fcinfo, 0);
|
InitMaterializedSRF(fcinfo, 0);
|
||||||
|
|
||||||
location = get_extension_control_directory();
|
locations = get_extension_control_directories();
|
||||||
dir = AllocateDir(location);
|
|
||||||
|
|
||||||
/*
|
foreach_ptr(char, location, locations)
|
||||||
* 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 */
|
dir = AllocateDir(location);
|
||||||
}
|
|
||||||
else
|
/*
|
||||||
{
|
* If the control directory doesn't exist, we want to silently return
|
||||||
while ((de = ReadDir(dir, location)) != NULL)
|
* an empty set. Any other error will be reported by ReadDir.
|
||||||
|
*/
|
||||||
|
if (dir == NULL && errno == ENOENT)
|
||||||
{
|
{
|
||||||
ExtensionControlFile *control;
|
/* do nothing */
|
||||||
char *extname;
|
|
||||||
|
|
||||||
if (!is_extension_control_filename(de->d_name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
|
|
||||||
/* read the control file */
|
|
||||||
control = read_extension_control_file(extname);
|
|
||||||
|
|
||||||
/* scan extension's script directory for install scripts */
|
|
||||||
get_available_versions_for_extension(control, rsinfo->setResult,
|
|
||||||
rsinfo->setDesc);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while ((de = ReadDir(dir, location)) != NULL)
|
||||||
|
{
|
||||||
|
ExtensionControlFile *control;
|
||||||
|
char *extname;
|
||||||
|
|
||||||
FreeDir(dir);
|
if (!is_extension_control_filename(de->d_name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeDir(dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Datum) 0;
|
return (Datum) 0;
|
||||||
@ -2373,47 +2469,53 @@ bool
|
|||||||
extension_file_exists(const char *extensionName)
|
extension_file_exists(const char *extensionName)
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
char *location;
|
List *locations;
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
|
|
||||||
location = get_extension_control_directory();
|
locations = get_extension_control_directories();
|
||||||
dir = AllocateDir(location);
|
|
||||||
|
|
||||||
/*
|
foreach_ptr(char, location, locations)
|
||||||
* 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 */
|
dir = AllocateDir(location);
|
||||||
}
|
|
||||||
else
|
/*
|
||||||
{
|
* If the control directory doesn't exist, we want to silently return
|
||||||
while ((de = ReadDir(dir, location)) != NULL)
|
* false. Any other error will be reported by ReadDir.
|
||||||
|
*/
|
||||||
|
if (dir == NULL && errno == ENOENT)
|
||||||
{
|
{
|
||||||
char *extname;
|
/* do nothing */
|
||||||
|
|
||||||
if (!is_extension_control_filename(de->d_name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
|
|
||||||
/* done if it matches request */
|
|
||||||
if (strcmp(extname, extensionName) == 0)
|
|
||||||
{
|
|
||||||
result = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while ((de = ReadDir(dir, location)) != NULL)
|
||||||
|
{
|
||||||
|
char *extname;
|
||||||
|
|
||||||
FreeDir(dir);
|
if (!is_extension_control_filename(de->d_name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
/* done if it matches request */
|
||||||
|
if (strcmp(extname, extensionName) == 0)
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeDir(dir);
|
||||||
|
}
|
||||||
|
if (result)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -3691,3 +3793,20 @@ read_whole_file(const char *filename, int *length)
|
|||||||
*length = bytes_to_read;
|
*length = bytes_to_read;
|
||||||
return buf;
|
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;
|
||||||
|
}
|
||||||
|
@ -71,8 +71,6 @@ pg_noreturn static void incompatible_module_error(const char *libname,
|
|||||||
const Pg_magic_struct *module_magic_data);
|
const Pg_magic_struct *module_magic_data);
|
||||||
static char *expand_dynamic_library_name(const char *name);
|
static char *expand_dynamic_library_name(const char *name);
|
||||||
static void check_restricted_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 */
|
/* Magic structure that module needs to match to be accepted */
|
||||||
static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
|
static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
|
||||||
@ -398,7 +396,7 @@ incompatible_module_error(const char *libname,
|
|||||||
/*
|
/*
|
||||||
* If name contains a slash, check if the file exists, if so return
|
* 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
|
* 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
|
* expanded file name. If the previous failed, append DLSUFFIX and
|
||||||
* try again. If all fails, just return the original name.
|
* try again. If all fails, just return the original name.
|
||||||
*
|
*
|
||||||
@ -413,17 +411,25 @@ expand_dynamic_library_name(const char *name)
|
|||||||
|
|
||||||
Assert(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);
|
have_slash = (first_dir_separator(name) != NULL);
|
||||||
|
|
||||||
if (!have_slash)
|
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)
|
if (full)
|
||||||
return full;
|
return full;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
full = substitute_libpath_macro(name);
|
full = substitute_path_macro(name, "$libdir", pkglib_path);
|
||||||
if (pg_file_exists(full))
|
if (pg_file_exists(full))
|
||||||
return full;
|
return full;
|
||||||
pfree(full);
|
pfree(full);
|
||||||
@ -433,14 +439,14 @@ expand_dynamic_library_name(const char *name)
|
|||||||
|
|
||||||
if (!have_slash)
|
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);
|
pfree(new);
|
||||||
if (full)
|
if (full)
|
||||||
return full;
|
return full;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
full = substitute_libpath_macro(new);
|
full = substitute_path_macro(new, "$libdir", pkglib_path);
|
||||||
pfree(new);
|
pfree(new);
|
||||||
if (pg_file_exists(full))
|
if (pg_file_exists(full))
|
||||||
return full;
|
return full;
|
||||||
@ -474,48 +480,61 @@ check_restricted_library_name(const char *name)
|
|||||||
* Substitute for any macros appearing in the given string.
|
* Substitute for any macros appearing in the given string.
|
||||||
* Result is always freshly palloc'd.
|
* Result is always freshly palloc'd.
|
||||||
*/
|
*/
|
||||||
static char *
|
char *
|
||||||
substitute_libpath_macro(const char *name)
|
substitute_path_macro(const char *str, const char *macro, const char *value)
|
||||||
{
|
{
|
||||||
const char *sep_ptr;
|
const char *sep_ptr;
|
||||||
|
|
||||||
Assert(name != NULL);
|
Assert(str != NULL);
|
||||||
|
Assert(macro[0] == '$');
|
||||||
|
|
||||||
/* Currently, we only recognize $libdir at the start of the string */
|
/* Currently, we only recognize $macro at the start of the string */
|
||||||
if (name[0] != '$')
|
if (str[0] != '$')
|
||||||
return pstrdup(name);
|
return pstrdup(str);
|
||||||
|
|
||||||
if ((sep_ptr = first_dir_separator(name)) == NULL)
|
if ((sep_ptr = first_dir_separator(str)) == NULL)
|
||||||
sep_ptr = name + strlen(name);
|
sep_ptr = str + strlen(str);
|
||||||
|
|
||||||
if (strlen("$libdir") != sep_ptr - name ||
|
if (strlen(macro) != sep_ptr - str ||
|
||||||
strncmp(name, "$libdir", strlen("$libdir")) != 0)
|
strncmp(str, macro, strlen(macro)) != 0)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_NAME),
|
(errcode(ERRCODE_INVALID_NAME),
|
||||||
errmsg("invalid macro name in dynamic library path: %s",
|
errmsg("invalid macro name in path: %s",
|
||||||
name)));
|
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
|
* 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,
|
* is returned in freshly palloc'd memory. If the file is not found,
|
||||||
* return NULL.
|
* 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 *
|
char *
|
||||||
find_in_dynamic_libpath(const char *basename)
|
find_in_path(const char *basename, const char *path, const char *path_param,
|
||||||
|
const char *macro, const char *macro_val)
|
||||||
{
|
{
|
||||||
const char *p;
|
const char *p;
|
||||||
size_t baselen;
|
size_t baselen;
|
||||||
|
|
||||||
Assert(basename != NULL);
|
Assert(basename != NULL);
|
||||||
Assert(first_dir_separator(basename) == NULL);
|
Assert(first_dir_separator(basename) == NULL);
|
||||||
Assert(Dynamic_library_path != NULL);
|
Assert(path != NULL);
|
||||||
|
Assert(path_param != NULL);
|
||||||
|
|
||||||
p = Dynamic_library_path;
|
p = path;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the path variable is empty, don't do a path search.
|
||||||
|
*/
|
||||||
if (strlen(p) == 0)
|
if (strlen(p) == 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@ -532,7 +551,7 @@ find_in_dynamic_libpath(const char *basename)
|
|||||||
if (piece == p)
|
if (piece == p)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_NAME),
|
(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)
|
if (piece == NULL)
|
||||||
len = strlen(p);
|
len = strlen(p);
|
||||||
@ -542,7 +561,7 @@ find_in_dynamic_libpath(const char *basename)
|
|||||||
piece = palloc(len + 1);
|
piece = palloc(len + 1);
|
||||||
strlcpy(piece, p, len + 1);
|
strlcpy(piece, p, len + 1);
|
||||||
|
|
||||||
mangled = substitute_libpath_macro(piece);
|
mangled = substitute_path_macro(piece, macro, macro_val);
|
||||||
pfree(piece);
|
pfree(piece);
|
||||||
|
|
||||||
canonicalize_path(mangled);
|
canonicalize_path(mangled);
|
||||||
@ -551,13 +570,13 @@ find_in_dynamic_libpath(const char *basename)
|
|||||||
if (!is_absolute_path(mangled))
|
if (!is_absolute_path(mangled))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_NAME),
|
(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);
|
full = palloc(strlen(mangled) + 1 + baselen + 1);
|
||||||
sprintf(full, "%s/%s", mangled, basename);
|
sprintf(full, "%s/%s", mangled, basename);
|
||||||
pfree(mangled);
|
pfree(mangled);
|
||||||
|
|
||||||
elog(DEBUG3, "find_in_dynamic_libpath: trying \"%s\"", full);
|
elog(DEBUG3, "%s: trying \"%s\"", __func__, full);
|
||||||
|
|
||||||
if (pg_file_exists(full))
|
if (pg_file_exists(full))
|
||||||
return full;
|
return full;
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
#include "catalog/storage.h"
|
#include "catalog/storage.h"
|
||||||
#include "commands/async.h"
|
#include "commands/async.h"
|
||||||
|
#include "commands/extension.h"
|
||||||
#include "commands/event_trigger.h"
|
#include "commands/event_trigger.h"
|
||||||
#include "commands/tablespace.h"
|
#include "commands/tablespace.h"
|
||||||
#include "commands/trigger.h"
|
#include "commands/trigger.h"
|
||||||
@ -4364,6 +4365,18 @@ struct config_string ConfigureNamesString[] =
|
|||||||
NULL, NULL, NULL
|
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,
|
{"krb_server_keyfile", PGC_SIGHUP, CONN_AUTH_AUTH,
|
||||||
gettext_noop("Sets the location of the Kerberos server key file."),
|
gettext_noop("Sets the location of the Kerberos server key file."),
|
||||||
|
@ -804,6 +804,7 @@ autovacuum_worker_slots = 16 # autovacuum worker slots to allocate
|
|||||||
# - Other Defaults -
|
# - Other Defaults -
|
||||||
|
|
||||||
#dynamic_library_path = '$libdir'
|
#dynamic_library_path = '$libdir'
|
||||||
|
#extension_control_path = '$system'
|
||||||
#gin_fuzzy_search_limit = 0
|
#gin_fuzzy_search_limit = 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
#include "catalog/objectaddress.h"
|
#include "catalog/objectaddress.h"
|
||||||
#include "parser/parse_node.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
|
* creating_extension is only true while running a CREATE EXTENSION or ALTER
|
||||||
|
@ -740,6 +740,9 @@ extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
|
|||||||
*/
|
*/
|
||||||
extern PGDLLIMPORT char *Dynamic_library_path;
|
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,
|
extern void *load_external_function(const char *filename, const char *funcname,
|
||||||
bool signalNotFound, void **filehandle);
|
bool signalNotFound, void **filehandle);
|
||||||
extern void *lookup_external_function(void *filehandle, const char *funcname);
|
extern void *lookup_external_function(void *filehandle, const char *funcname);
|
||||||
|
@ -28,6 +28,7 @@ DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
|
|||||||
test_ext_req_schema3--1.0.sql
|
test_ext_req_schema3--1.0.sql
|
||||||
|
|
||||||
REGRESS = test_extensions test_extdepend
|
REGRESS = test_extensions test_extdepend
|
||||||
|
TAP_TESTS = 1
|
||||||
|
|
||||||
# force C locale for output stability
|
# force C locale for output stability
|
||||||
NO_LOCALE = 1
|
NO_LOCALE = 1
|
||||||
|
@ -57,4 +57,9 @@ tests += {
|
|||||||
],
|
],
|
||||||
'regress_args': ['--no-locale'],
|
'regress_args': ['--no-locale'],
|
||||||
},
|
},
|
||||||
|
'tap': {
|
||||||
|
'tests': [
|
||||||
|
't/001_extension_control_path.pl',
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
# 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();
|
Reference in New Issue
Block a user