mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
Allow CREATE EXTENSION to follow extension update paths.
Previously, to update an extension you had to produce both a version-update script and a new base installation script. It's become more and more obvious that that's tedious, duplicative, and error-prone. This patch attempts to improve matters by allowing the new base installation script to be omitted. CREATE EXTENSION will install a requested version if it can find a base script and a chain of update scripts that will get there. As in the existing update logic, shorter chains are preferred if there's more than one possibility, with an arbitrary tie-break rule for chains of equal length. Also adjust the pg_available_extension_versions view to show such versions as installable. While at it, refactor the code so that CASCADE processing works for extensions requested during ApplyExtensionUpdates(). Without this, addition of a new requirement in an updated extension would require creating a new base script, even if there was no other reason to do that. (It would be easy at this point to add a CASCADE option to ALTER EXTENSION UPDATE, to allow the same thing to happen during a manually-commanded version update, but I have not done that here.) Tom Lane, reviewed by Andres Freund Discussion: <20160905005919.jz2m2yh3und2dsuy@alap3.anarazel.de>
This commit is contained in:
parent
28e5e5648c
commit
40b449ae84
@ -885,6 +885,47 @@ SELECT * FROM pg_extension_update_paths('<replaceable>extension_name</>');
|
|||||||
</para>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
|
|
||||||
|
<sect2>
|
||||||
|
<title>Installing Extensions using Update Scripts</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
An extension that has been around for awhile will probably exist in
|
||||||
|
several versions, for which the author will need to write update scripts.
|
||||||
|
For example, if you have released a <literal>foo</> extension in
|
||||||
|
versions <literal>1.0</>, <literal>1.1</>, and <literal>1.2</>, there
|
||||||
|
should be update scripts <filename>foo--1.0--1.1.sql</>
|
||||||
|
and <filename>foo--1.1--1.2.sql</>.
|
||||||
|
Before <productname>PostgreSQL</> 10, it was necessary to also create
|
||||||
|
new script files <filename>foo--1.1.sql</> and <filename>foo--1.2.sql</>
|
||||||
|
that directly build the newer extension versions, or else the newer
|
||||||
|
versions could not be installed directly, only by
|
||||||
|
installing <literal>1.0</> and then updating. That was tedious and
|
||||||
|
duplicative, but now it's unnecessary, because <command>CREATE
|
||||||
|
EXTENSION</> can follow update chains automatically.
|
||||||
|
For example, if only the script
|
||||||
|
files <filename>foo--1.0.sql</>, <filename>foo--1.0--1.1.sql</>,
|
||||||
|
and <filename>foo--1.1--1.2.sql</> are available then a request to
|
||||||
|
install version <literal>1.2</> is honored by running those three
|
||||||
|
scripts in sequence. The processing is the same as if you'd first
|
||||||
|
installed <literal>1.0</> and then updated to <literal>1.2</>.
|
||||||
|
(As with <command>ALTER EXTENSION UPDATE</>, if multiple pathways are
|
||||||
|
available then the shortest is preferred.) Arranging an extension's
|
||||||
|
script files in this style can reduce the amount of maintenance effort
|
||||||
|
needed to produce small updates.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If you use secondary (version-specific) control files with an extension
|
||||||
|
maintained in this style, keep in mind that each version needs a control
|
||||||
|
file even if it has no stand-alone installation script, as that control
|
||||||
|
file will determine how the implicit update to that version is performed.
|
||||||
|
For example, if <filename>foo--1.0.control</> specifies <literal>requires
|
||||||
|
= 'bar'</> but <literal>foo</>'s other control files do not, the
|
||||||
|
extension's dependency on <literal>bar</> will be dropped when updating
|
||||||
|
from <literal>1.0</> to another version.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
<sect2 id="extend-extensions-example">
|
<sect2 id="extend-extensions-example">
|
||||||
<title>Extension Example</title>
|
<title>Extension Example</title>
|
||||||
|
|
||||||
|
@ -100,14 +100,25 @@ typedef struct ExtensionVersionInfo
|
|||||||
static List *find_update_path(List *evi_list,
|
static List *find_update_path(List *evi_list,
|
||||||
ExtensionVersionInfo *evi_start,
|
ExtensionVersionInfo *evi_start,
|
||||||
ExtensionVersionInfo *evi_target,
|
ExtensionVersionInfo *evi_target,
|
||||||
|
bool reject_indirect,
|
||||||
bool reinitialize);
|
bool reinitialize);
|
||||||
|
static Oid get_required_extension(char *reqExtensionName,
|
||||||
|
char *extensionName,
|
||||||
|
char *origSchemaName,
|
||||||
|
bool cascade,
|
||||||
|
List *parents,
|
||||||
|
bool is_create);
|
||||||
static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
|
static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
|
||||||
Tuplestorestate *tupstore,
|
Tuplestorestate *tupstore,
|
||||||
TupleDesc tupdesc);
|
TupleDesc tupdesc);
|
||||||
|
static Datum convert_requires_to_datum(List *requires);
|
||||||
static void ApplyExtensionUpdates(Oid extensionOid,
|
static void ApplyExtensionUpdates(Oid extensionOid,
|
||||||
ExtensionControlFile *pcontrol,
|
ExtensionControlFile *pcontrol,
|
||||||
const char *initialVersion,
|
const char *initialVersion,
|
||||||
List *updateVersions);
|
List *updateVersions,
|
||||||
|
char *origSchemaName,
|
||||||
|
bool cascade,
|
||||||
|
bool is_create);
|
||||||
static char *read_whole_file(const char *filename, int *length);
|
static char *read_whole_file(const char *filename, int *length);
|
||||||
|
|
||||||
|
|
||||||
@ -1071,7 +1082,7 @@ identify_update_path(ExtensionControlFile *control,
|
|||||||
evi_target = get_ext_ver_info(newVersion, &evi_list);
|
evi_target = get_ext_ver_info(newVersion, &evi_list);
|
||||||
|
|
||||||
/* Find shortest path */
|
/* Find shortest path */
|
||||||
result = find_update_path(evi_list, evi_start, evi_target, false);
|
result = find_update_path(evi_list, evi_start, evi_target, false, false);
|
||||||
|
|
||||||
if (result == NIL)
|
if (result == NIL)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -1086,9 +1097,13 @@ identify_update_path(ExtensionControlFile *control,
|
|||||||
* Apply Dijkstra's algorithm to find the shortest path from evi_start to
|
* Apply Dijkstra's algorithm to find the shortest path from evi_start to
|
||||||
* evi_target.
|
* evi_target.
|
||||||
*
|
*
|
||||||
|
* If reject_indirect is true, ignore paths that go through installable
|
||||||
|
* versions. This saves work when the caller will consider starting from
|
||||||
|
* all installable versions anyway.
|
||||||
|
*
|
||||||
* If reinitialize is false, assume the ExtensionVersionInfo list has not
|
* If reinitialize is false, assume the ExtensionVersionInfo list has not
|
||||||
* been used for this before, and the initialization done by get_ext_ver_info
|
* been used for this before, and the initialization done by get_ext_ver_info
|
||||||
* is still good.
|
* is still good. Otherwise, reinitialize all transient fields used here.
|
||||||
*
|
*
|
||||||
* Result is a List of names of versions to transition through (the initial
|
* Result is a List of names of versions to transition through (the initial
|
||||||
* version is *not* included). Returns NIL if no such path.
|
* version is *not* included). Returns NIL if no such path.
|
||||||
@ -1097,6 +1112,7 @@ static List *
|
|||||||
find_update_path(List *evi_list,
|
find_update_path(List *evi_list,
|
||||||
ExtensionVersionInfo *evi_start,
|
ExtensionVersionInfo *evi_start,
|
||||||
ExtensionVersionInfo *evi_target,
|
ExtensionVersionInfo *evi_target,
|
||||||
|
bool reject_indirect,
|
||||||
bool reinitialize)
|
bool reinitialize)
|
||||||
{
|
{
|
||||||
List *result;
|
List *result;
|
||||||
@ -1105,6 +1121,8 @@ find_update_path(List *evi_list,
|
|||||||
|
|
||||||
/* Caller error if start == target */
|
/* Caller error if start == target */
|
||||||
Assert(evi_start != evi_target);
|
Assert(evi_start != evi_target);
|
||||||
|
/* Caller error if reject_indirect and target is installable */
|
||||||
|
Assert(!(reject_indirect && evi_target->installable));
|
||||||
|
|
||||||
if (reinitialize)
|
if (reinitialize)
|
||||||
{
|
{
|
||||||
@ -1131,6 +1149,9 @@ find_update_path(List *evi_list,
|
|||||||
ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
|
ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
|
||||||
int newdist;
|
int newdist;
|
||||||
|
|
||||||
|
/* if reject_indirect, treat installable versions as unreachable */
|
||||||
|
if (reject_indirect && evi2->installable)
|
||||||
|
continue;
|
||||||
newdist = evi->distance + 1;
|
newdist = evi->distance + 1;
|
||||||
if (newdist < evi2->distance)
|
if (newdist < evi2->distance)
|
||||||
{
|
{
|
||||||
@ -1166,6 +1187,67 @@ find_update_path(List *evi_list,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a target version that is not directly installable, find the
|
||||||
|
* best installation sequence starting from a directly-installable version.
|
||||||
|
*
|
||||||
|
* evi_list: previously-collected version update graph
|
||||||
|
* evi_target: member of that list that we want to reach
|
||||||
|
*
|
||||||
|
* Returns the best starting-point version, or NULL if there is none.
|
||||||
|
* On success, *best_path is set to the path from the start point.
|
||||||
|
*
|
||||||
|
* If there's more than one possible start point, prefer shorter update paths,
|
||||||
|
* and break any ties arbitrarily on the basis of strcmp'ing the starting
|
||||||
|
* versions' names.
|
||||||
|
*/
|
||||||
|
static ExtensionVersionInfo *
|
||||||
|
find_install_path(List *evi_list, ExtensionVersionInfo *evi_target,
|
||||||
|
List **best_path)
|
||||||
|
{
|
||||||
|
ExtensionVersionInfo *evi_start = NULL;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
*best_path = NIL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't expect to be called for an installable target, but if we are,
|
||||||
|
* the answer is easy: just start from there, with an empty update path.
|
||||||
|
*/
|
||||||
|
if (evi_target->installable)
|
||||||
|
return evi_target;
|
||||||
|
|
||||||
|
/* Consider all installable versions as start points */
|
||||||
|
foreach(lc, evi_list)
|
||||||
|
{
|
||||||
|
ExtensionVersionInfo *evi1 = (ExtensionVersionInfo *) lfirst(lc);
|
||||||
|
List *path;
|
||||||
|
|
||||||
|
if (!evi1->installable)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find shortest path from evi1 to evi_target; but no need to consider
|
||||||
|
* paths going through other installable versions.
|
||||||
|
*/
|
||||||
|
path = find_update_path(evi_list, evi1, evi_target, true, true);
|
||||||
|
if (path == NIL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Remember best path */
|
||||||
|
if (evi_start == NULL ||
|
||||||
|
list_length(path) < list_length(*best_path) ||
|
||||||
|
(list_length(path) == list_length(*best_path) &&
|
||||||
|
strcmp(evi_start->name, evi1->name) < 0))
|
||||||
|
{
|
||||||
|
evi_start = evi1;
|
||||||
|
*best_path = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return evi_start;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CREATE EXTENSION worker
|
* CREATE EXTENSION worker
|
||||||
*
|
*
|
||||||
@ -1175,17 +1257,16 @@ find_update_path(List *evi_list,
|
|||||||
* installed, allowing us to error out if we recurse to one of those.
|
* installed, allowing us to error out if we recurse to one of those.
|
||||||
*/
|
*/
|
||||||
static ObjectAddress
|
static ObjectAddress
|
||||||
CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *parents)
|
CreateExtensionInternal(char *extensionName,
|
||||||
|
char *schemaName,
|
||||||
|
char *versionName,
|
||||||
|
char *oldVersionName,
|
||||||
|
bool cascade,
|
||||||
|
List *parents,
|
||||||
|
bool is_create)
|
||||||
{
|
{
|
||||||
DefElem *d_schema = NULL;
|
char *origSchemaName = schemaName;
|
||||||
DefElem *d_new_version = NULL;
|
|
||||||
DefElem *d_old_version = NULL;
|
|
||||||
DefElem *d_cascade = NULL;
|
|
||||||
char *schemaName = NULL;
|
|
||||||
Oid schemaOid = InvalidOid;
|
Oid schemaOid = InvalidOid;
|
||||||
char *versionName;
|
|
||||||
char *oldVersionName;
|
|
||||||
bool cascade = false;
|
|
||||||
Oid extowner = GetUserId();
|
Oid extowner = GetUserId();
|
||||||
ExtensionControlFile *pcontrol;
|
ExtensionControlFile *pcontrol;
|
||||||
ExtensionControlFile *control;
|
ExtensionControlFile *control;
|
||||||
@ -1193,87 +1274,43 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
|
|||||||
List *requiredExtensions;
|
List *requiredExtensions;
|
||||||
List *requiredSchemas;
|
List *requiredSchemas;
|
||||||
Oid extensionOid;
|
Oid extensionOid;
|
||||||
ListCell *lc;
|
|
||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read the primary control file. Note we assume that it does not contain
|
* Read the primary control file. Note we assume that it does not contain
|
||||||
* any non-ASCII data, so there is no need to worry about encoding at this
|
* any non-ASCII data, so there is no need to worry about encoding at this
|
||||||
* point.
|
* point.
|
||||||
*/
|
*/
|
||||||
pcontrol = read_extension_control_file(stmt->extname);
|
pcontrol = read_extension_control_file(extensionName);
|
||||||
|
|
||||||
/*
|
|
||||||
* Read the statement option list
|
|
||||||
*/
|
|
||||||
foreach(lc, stmt->options)
|
|
||||||
{
|
|
||||||
DefElem *defel = (DefElem *) lfirst(lc);
|
|
||||||
|
|
||||||
if (strcmp(defel->defname, "schema") == 0)
|
|
||||||
{
|
|
||||||
if (d_schema)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
||||||
errmsg("conflicting or redundant options"),
|
|
||||||
parser_errposition(pstate, defel->location)));
|
|
||||||
d_schema = defel;
|
|
||||||
}
|
|
||||||
else if (strcmp(defel->defname, "new_version") == 0)
|
|
||||||
{
|
|
||||||
if (d_new_version)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
||||||
errmsg("conflicting or redundant options"),
|
|
||||||
parser_errposition(pstate, defel->location)));
|
|
||||||
d_new_version = defel;
|
|
||||||
}
|
|
||||||
else if (strcmp(defel->defname, "old_version") == 0)
|
|
||||||
{
|
|
||||||
if (d_old_version)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
||||||
errmsg("conflicting or redundant options"),
|
|
||||||
parser_errposition(pstate, defel->location)));
|
|
||||||
d_old_version = defel;
|
|
||||||
}
|
|
||||||
else if (strcmp(defel->defname, "cascade") == 0)
|
|
||||||
{
|
|
||||||
if (d_cascade)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
||||||
errmsg("conflicting or redundant options"),
|
|
||||||
parser_errposition(pstate, defel->location)));
|
|
||||||
d_cascade = defel;
|
|
||||||
cascade = defGetBoolean(d_cascade);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
elog(ERROR, "unrecognized option: %s", defel->defname);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine the version to install
|
* Determine the version to install
|
||||||
*/
|
*/
|
||||||
if (d_new_version && d_new_version->arg)
|
if (versionName == NULL)
|
||||||
versionName = strVal(d_new_version->arg);
|
|
||||||
else if (pcontrol->default_version)
|
|
||||||
versionName = pcontrol->default_version;
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
ereport(ERROR,
|
if (pcontrol->default_version)
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
versionName = pcontrol->default_version;
|
||||||
errmsg("version to install must be specified")));
|
else
|
||||||
versionName = NULL; /* keep compiler quiet */
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("version to install must be specified")));
|
||||||
}
|
}
|
||||||
check_valid_version_name(versionName);
|
check_valid_version_name(versionName);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine the (unpackaged) version to update from, if any, and then
|
* Figure out which script(s) we need to run to install the desired
|
||||||
* figure out what sequence of update scripts we need to apply.
|
* version of the extension. If we do not have a script that directly
|
||||||
|
* does what is needed, we try to find a sequence of update scripts that
|
||||||
|
* will get us there.
|
||||||
*/
|
*/
|
||||||
if (d_old_version && d_old_version->arg)
|
if (oldVersionName)
|
||||||
{
|
{
|
||||||
oldVersionName = strVal(d_old_version->arg);
|
/*
|
||||||
|
* "FROM old_version" was specified, indicating that we're trying to
|
||||||
|
* update from some unpackaged version of the extension. Locate a
|
||||||
|
* series of update scripts that will do it.
|
||||||
|
*/
|
||||||
check_valid_version_name(oldVersionName);
|
check_valid_version_name(oldVersionName);
|
||||||
|
|
||||||
if (strcmp(oldVersionName, versionName) == 0)
|
if (strcmp(oldVersionName, versionName) == 0)
|
||||||
@ -1308,8 +1345,48 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* No FROM, so we're installing from scratch. If there is an install
|
||||||
|
* script for the desired version, we only need to run that one.
|
||||||
|
*/
|
||||||
|
char *filename;
|
||||||
|
struct stat fst;
|
||||||
|
|
||||||
oldVersionName = NULL;
|
oldVersionName = NULL;
|
||||||
updateVersions = NIL;
|
|
||||||
|
filename = get_extension_script_filename(pcontrol, NULL, versionName);
|
||||||
|
if (stat(filename, &fst) == 0)
|
||||||
|
{
|
||||||
|
/* Easy, no extra scripts */
|
||||||
|
updateVersions = NIL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Look for best way to install this version */
|
||||||
|
List *evi_list;
|
||||||
|
ExtensionVersionInfo *evi_start;
|
||||||
|
ExtensionVersionInfo *evi_target;
|
||||||
|
|
||||||
|
/* Extract the version update graph from the script directory */
|
||||||
|
evi_list = get_ext_ver_list(pcontrol);
|
||||||
|
|
||||||
|
/* Identify the target version */
|
||||||
|
evi_target = get_ext_ver_info(versionName, &evi_list);
|
||||||
|
|
||||||
|
/* Identify best path to reach target */
|
||||||
|
evi_start = find_install_path(evi_list, evi_target,
|
||||||
|
&updateVersions);
|
||||||
|
|
||||||
|
/* Fail if no path ... */
|
||||||
|
if (evi_start == NULL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("extension \"%s\" has no installation script nor update path for version \"%s\"",
|
||||||
|
pcontrol->name, versionName)));
|
||||||
|
|
||||||
|
/* Otherwise, install best starting point and then upgrade */
|
||||||
|
versionName = evi_start->name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1320,13 +1397,8 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
|
|||||||
/*
|
/*
|
||||||
* Determine the target schema to install the extension into
|
* Determine the target schema to install the extension into
|
||||||
*/
|
*/
|
||||||
if (d_schema && d_schema->arg)
|
if (schemaName)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* User given schema, CREATE EXTENSION ... WITH SCHEMA ...
|
|
||||||
*/
|
|
||||||
schemaName = strVal(d_schema->arg);
|
|
||||||
|
|
||||||
/* If the user is giving us the schema name, it must exist already. */
|
/* If the user is giving us the schema name, it must exist already. */
|
||||||
schemaOid = get_namespace_oid(schemaName, false);
|
schemaOid = get_namespace_oid(schemaName, false);
|
||||||
}
|
}
|
||||||
@ -1374,7 +1446,7 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
|
|||||||
else if (!OidIsValid(schemaOid))
|
else if (!OidIsValid(schemaOid))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Neither user nor author of the extension specified schema, use the
|
* Neither user nor author of the extension specified schema; use the
|
||||||
* current default creation namespace, which is the first explicit
|
* current default creation namespace, which is the first explicit
|
||||||
* entry in the search_path.
|
* entry in the search_path.
|
||||||
*/
|
*/
|
||||||
@ -1415,66 +1487,12 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
|
|||||||
Oid reqext;
|
Oid reqext;
|
||||||
Oid reqschema;
|
Oid reqschema;
|
||||||
|
|
||||||
reqext = get_extension_oid(curreq, true);
|
reqext = get_required_extension(curreq,
|
||||||
if (!OidIsValid(reqext))
|
extensionName,
|
||||||
{
|
origSchemaName,
|
||||||
if (cascade)
|
cascade,
|
||||||
{
|
parents,
|
||||||
/* Must install it. */
|
is_create);
|
||||||
CreateExtensionStmt *ces;
|
|
||||||
ListCell *lc2;
|
|
||||||
ObjectAddress addr;
|
|
||||||
List *cascade_parents;
|
|
||||||
|
|
||||||
/* Check extension name validity before trying to cascade. */
|
|
||||||
check_valid_extension_name(curreq);
|
|
||||||
|
|
||||||
/* Check for cyclic dependency between extensions. */
|
|
||||||
foreach(lc2, parents)
|
|
||||||
{
|
|
||||||
char *pname = (char *) lfirst(lc2);
|
|
||||||
|
|
||||||
if (strcmp(pname, curreq) == 0)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INVALID_RECURSION),
|
|
||||||
errmsg("cyclic dependency detected between extensions \"%s\" and \"%s\"",
|
|
||||||
curreq, stmt->extname)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ereport(NOTICE,
|
|
||||||
(errmsg("installing required extension \"%s\"",
|
|
||||||
curreq)));
|
|
||||||
|
|
||||||
/* Build a CREATE EXTENSION statement to pass down. */
|
|
||||||
ces = makeNode(CreateExtensionStmt);
|
|
||||||
ces->extname = curreq;
|
|
||||||
ces->if_not_exists = false;
|
|
||||||
|
|
||||||
/* Propagate the CASCADE option. */
|
|
||||||
ces->options = list_make1(d_cascade);
|
|
||||||
|
|
||||||
/* Propagate the SCHEMA option if given. */
|
|
||||||
if (d_schema && d_schema->arg)
|
|
||||||
ces->options = lappend(ces->options, d_schema);
|
|
||||||
|
|
||||||
/* Add current extension to list of parents to pass down. */
|
|
||||||
cascade_parents =
|
|
||||||
lappend(list_copy(parents), stmt->extname);
|
|
||||||
|
|
||||||
/* Create the required extension. */
|
|
||||||
addr = CreateExtensionInternal(pstate, ces, cascade_parents);
|
|
||||||
|
|
||||||
/* Get its newly-assigned OID. */
|
|
||||||
reqext = addr.objectId;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
||||||
errmsg("required extension \"%s\" is not installed",
|
|
||||||
curreq),
|
|
||||||
errhint("Use CREATE EXTENSION ... CASCADE to install required extensions too.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
reqschema = get_extension_schema(reqext);
|
reqschema = get_extension_schema(reqext);
|
||||||
requiredExtensions = lappend_oid(requiredExtensions, reqext);
|
requiredExtensions = lappend_oid(requiredExtensions, reqext);
|
||||||
requiredSchemas = lappend_oid(requiredSchemas, reqschema);
|
requiredSchemas = lappend_oid(requiredSchemas, reqschema);
|
||||||
@ -1510,17 +1528,100 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
|
|||||||
* though a series of ALTER EXTENSION UPDATE commands were given
|
* though a series of ALTER EXTENSION UPDATE commands were given
|
||||||
*/
|
*/
|
||||||
ApplyExtensionUpdates(extensionOid, pcontrol,
|
ApplyExtensionUpdates(extensionOid, pcontrol,
|
||||||
versionName, updateVersions);
|
versionName, updateVersions,
|
||||||
|
origSchemaName, cascade, is_create);
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the OID of an extension listed in "requires", possibly creating it.
|
||||||
|
*/
|
||||||
|
static Oid
|
||||||
|
get_required_extension(char *reqExtensionName,
|
||||||
|
char *extensionName,
|
||||||
|
char *origSchemaName,
|
||||||
|
bool cascade,
|
||||||
|
List *parents,
|
||||||
|
bool is_create)
|
||||||
|
{
|
||||||
|
Oid reqExtensionOid;
|
||||||
|
|
||||||
|
reqExtensionOid = get_extension_oid(reqExtensionName, true);
|
||||||
|
if (!OidIsValid(reqExtensionOid))
|
||||||
|
{
|
||||||
|
if (cascade)
|
||||||
|
{
|
||||||
|
/* Must install it. */
|
||||||
|
ObjectAddress addr;
|
||||||
|
List *cascade_parents;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
/* Check extension name validity before trying to cascade. */
|
||||||
|
check_valid_extension_name(reqExtensionName);
|
||||||
|
|
||||||
|
/* Check for cyclic dependency between extensions. */
|
||||||
|
foreach(lc, parents)
|
||||||
|
{
|
||||||
|
char *pname = (char *) lfirst(lc);
|
||||||
|
|
||||||
|
if (strcmp(pname, reqExtensionName) == 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_RECURSION),
|
||||||
|
errmsg("cyclic dependency detected between extensions \"%s\" and \"%s\"",
|
||||||
|
reqExtensionName, extensionName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ereport(NOTICE,
|
||||||
|
(errmsg("installing required extension \"%s\"",
|
||||||
|
reqExtensionName)));
|
||||||
|
|
||||||
|
/* Add current extension to list of parents to pass down. */
|
||||||
|
cascade_parents = lappend(list_copy(parents), extensionName);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the required extension. We propagate the SCHEMA option
|
||||||
|
* if any, and CASCADE, but no other options.
|
||||||
|
*/
|
||||||
|
addr = CreateExtensionInternal(reqExtensionName,
|
||||||
|
origSchemaName,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
cascade,
|
||||||
|
cascade_parents,
|
||||||
|
is_create);
|
||||||
|
|
||||||
|
/* Get its newly-assigned OID. */
|
||||||
|
reqExtensionOid = addr.objectId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||||
|
errmsg("required extension \"%s\" is not installed",
|
||||||
|
reqExtensionName),
|
||||||
|
is_create ?
|
||||||
|
errhint("Use CREATE EXTENSION ... CASCADE to install required extensions too.") : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return reqExtensionOid;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CREATE EXTENSION
|
* CREATE EXTENSION
|
||||||
*/
|
*/
|
||||||
ObjectAddress
|
ObjectAddress
|
||||||
CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
|
CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
|
||||||
{
|
{
|
||||||
|
DefElem *d_schema = NULL;
|
||||||
|
DefElem *d_new_version = NULL;
|
||||||
|
DefElem *d_old_version = NULL;
|
||||||
|
DefElem *d_cascade = NULL;
|
||||||
|
char *schemaName = NULL;
|
||||||
|
char *versionName = NULL;
|
||||||
|
char *oldVersionName = NULL;
|
||||||
|
bool cascade = false;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
/* Check extension name validity before any filesystem access */
|
/* Check extension name validity before any filesystem access */
|
||||||
check_valid_extension_name(stmt->extname);
|
check_valid_extension_name(stmt->extname);
|
||||||
|
|
||||||
@ -1556,8 +1657,63 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
|
|||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
errmsg("nested CREATE EXTENSION is not supported")));
|
errmsg("nested CREATE EXTENSION is not supported")));
|
||||||
|
|
||||||
/* Finally create the extension. */
|
/* Deconstruct the statement option list */
|
||||||
return CreateExtensionInternal(pstate, stmt, NIL);
|
foreach(lc, stmt->options)
|
||||||
|
{
|
||||||
|
DefElem *defel = (DefElem *) lfirst(lc);
|
||||||
|
|
||||||
|
if (strcmp(defel->defname, "schema") == 0)
|
||||||
|
{
|
||||||
|
if (d_schema)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options"),
|
||||||
|
parser_errposition(pstate, defel->location)));
|
||||||
|
d_schema = defel;
|
||||||
|
schemaName = defGetString(d_schema);
|
||||||
|
}
|
||||||
|
else if (strcmp(defel->defname, "new_version") == 0)
|
||||||
|
{
|
||||||
|
if (d_new_version)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options"),
|
||||||
|
parser_errposition(pstate, defel->location)));
|
||||||
|
d_new_version = defel;
|
||||||
|
versionName = defGetString(d_new_version);
|
||||||
|
}
|
||||||
|
else if (strcmp(defel->defname, "old_version") == 0)
|
||||||
|
{
|
||||||
|
if (d_old_version)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options"),
|
||||||
|
parser_errposition(pstate, defel->location)));
|
||||||
|
d_old_version = defel;
|
||||||
|
oldVersionName = defGetString(d_old_version);
|
||||||
|
}
|
||||||
|
else if (strcmp(defel->defname, "cascade") == 0)
|
||||||
|
{
|
||||||
|
if (d_cascade)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options"),
|
||||||
|
parser_errposition(pstate, defel->location)));
|
||||||
|
d_cascade = defel;
|
||||||
|
cascade = defGetBoolean(d_cascade);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
elog(ERROR, "unrecognized option: %s", defel->defname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call CreateExtensionInternal to do the real work. */
|
||||||
|
return CreateExtensionInternal(stmt->extname,
|
||||||
|
schemaName,
|
||||||
|
versionName,
|
||||||
|
oldVersionName,
|
||||||
|
cascade,
|
||||||
|
NIL,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1914,43 +2070,28 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
|
|||||||
Tuplestorestate *tupstore,
|
Tuplestorestate *tupstore,
|
||||||
TupleDesc tupdesc)
|
TupleDesc tupdesc)
|
||||||
{
|
{
|
||||||
int extnamelen = strlen(pcontrol->name);
|
List *evi_list;
|
||||||
char *location;
|
ListCell *lc;
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
|
|
||||||
location = get_extension_script_directory(pcontrol);
|
/* Extract the version update graph from the script directory */
|
||||||
dir = AllocateDir(location);
|
evi_list = get_ext_ver_list(pcontrol);
|
||||||
/* Note this will fail if script directory doesn't exist */
|
|
||||||
while ((de = ReadDir(dir, location)) != NULL)
|
/* For each installable version ... */
|
||||||
|
foreach(lc, evi_list)
|
||||||
{
|
{
|
||||||
|
ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
|
||||||
ExtensionControlFile *control;
|
ExtensionControlFile *control;
|
||||||
char *vername;
|
|
||||||
Datum values[7];
|
Datum values[7];
|
||||||
bool nulls[7];
|
bool nulls[7];
|
||||||
|
ListCell *lc2;
|
||||||
|
|
||||||
/* must be a .sql file ... */
|
if (!evi->installable)
|
||||||
if (!is_extension_script_filename(de->d_name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* ... matching extension name followed by separator */
|
|
||||||
if (strncmp(de->d_name, pcontrol->name, extnamelen) != 0 ||
|
|
||||||
de->d_name[extnamelen] != '-' ||
|
|
||||||
de->d_name[extnamelen + 1] != '-')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* extract version name from 'extname--something.sql' filename */
|
|
||||||
vername = pstrdup(de->d_name + extnamelen + 2);
|
|
||||||
*strrchr(vername, '.') = '\0';
|
|
||||||
|
|
||||||
/* ignore it if it's an update script */
|
|
||||||
if (strstr(vername, "--"))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fetch parameters for specific version (pcontrol is not changed)
|
* Fetch parameters for specific version (pcontrol is not changed)
|
||||||
*/
|
*/
|
||||||
control = read_extension_aux_control_file(pcontrol, vername);
|
control = read_extension_aux_control_file(pcontrol, evi->name);
|
||||||
|
|
||||||
memset(values, 0, sizeof(values));
|
memset(values, 0, sizeof(values));
|
||||||
memset(nulls, 0, sizeof(nulls));
|
memset(nulls, 0, sizeof(nulls));
|
||||||
@ -1959,7 +2100,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
|
|||||||
values[0] = DirectFunctionCall1(namein,
|
values[0] = DirectFunctionCall1(namein,
|
||||||
CStringGetDatum(control->name));
|
CStringGetDatum(control->name));
|
||||||
/* version */
|
/* version */
|
||||||
values[1] = CStringGetTextDatum(vername);
|
values[1] = CStringGetTextDatum(evi->name);
|
||||||
/* superuser */
|
/* superuser */
|
||||||
values[2] = BoolGetDatum(control->superuser);
|
values[2] = BoolGetDatum(control->superuser);
|
||||||
/* relocatable */
|
/* relocatable */
|
||||||
@ -1974,27 +2115,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
|
|||||||
if (control->requires == NIL)
|
if (control->requires == NIL)
|
||||||
nulls[5] = true;
|
nulls[5] = true;
|
||||||
else
|
else
|
||||||
{
|
values[5] = convert_requires_to_datum(control->requires);
|
||||||
Datum *datums;
|
|
||||||
int ndatums;
|
|
||||||
ArrayType *a;
|
|
||||||
ListCell *lc;
|
|
||||||
|
|
||||||
ndatums = list_length(control->requires);
|
|
||||||
datums = (Datum *) palloc(ndatums * sizeof(Datum));
|
|
||||||
ndatums = 0;
|
|
||||||
foreach(lc, control->requires)
|
|
||||||
{
|
|
||||||
char *curreq = (char *) lfirst(lc);
|
|
||||||
|
|
||||||
datums[ndatums++] =
|
|
||||||
DirectFunctionCall1(namein, CStringGetDatum(curreq));
|
|
||||||
}
|
|
||||||
a = construct_array(datums, ndatums,
|
|
||||||
NAMEOID,
|
|
||||||
NAMEDATALEN, false, 'c');
|
|
||||||
values[5] = PointerGetDatum(a);
|
|
||||||
}
|
|
||||||
/* comment */
|
/* comment */
|
||||||
if (control->comment == NULL)
|
if (control->comment == NULL)
|
||||||
nulls[6] = true;
|
nulls[6] = true;
|
||||||
@ -2002,9 +2123,75 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
|
|||||||
values[6] = CStringGetTextDatum(control->comment);
|
values[6] = CStringGetTextDatum(control->comment);
|
||||||
|
|
||||||
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||||
}
|
|
||||||
|
|
||||||
FreeDir(dir);
|
/*
|
||||||
|
* Find all non-directly-installable versions that would be installed
|
||||||
|
* starting from this version, and report them, inheriting the
|
||||||
|
* parameters that aren't changed in updates from this version.
|
||||||
|
*/
|
||||||
|
foreach(lc2, evi_list)
|
||||||
|
{
|
||||||
|
ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc2);
|
||||||
|
List *best_path;
|
||||||
|
|
||||||
|
if (evi2->installable)
|
||||||
|
continue;
|
||||||
|
if (find_install_path(evi_list, evi2, &best_path) == evi)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Fetch parameters for this version (pcontrol is not changed)
|
||||||
|
*/
|
||||||
|
control = read_extension_aux_control_file(pcontrol, evi2->name);
|
||||||
|
|
||||||
|
/* name stays the same */
|
||||||
|
/* version */
|
||||||
|
values[1] = CStringGetTextDatum(evi2->name);
|
||||||
|
/* superuser */
|
||||||
|
values[2] = BoolGetDatum(control->superuser);
|
||||||
|
/* relocatable */
|
||||||
|
values[3] = BoolGetDatum(control->relocatable);
|
||||||
|
/* schema stays the same */
|
||||||
|
/* requires */
|
||||||
|
if (control->requires == NIL)
|
||||||
|
nulls[5] = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
values[5] = convert_requires_to_datum(control->requires);
|
||||||
|
nulls[5] = false;
|
||||||
|
}
|
||||||
|
/* comment stays the same */
|
||||||
|
|
||||||
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a list of extension names to a name[] Datum
|
||||||
|
*/
|
||||||
|
static Datum
|
||||||
|
convert_requires_to_datum(List *requires)
|
||||||
|
{
|
||||||
|
Datum *datums;
|
||||||
|
int ndatums;
|
||||||
|
ArrayType *a;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
ndatums = list_length(requires);
|
||||||
|
datums = (Datum *) palloc(ndatums * sizeof(Datum));
|
||||||
|
ndatums = 0;
|
||||||
|
foreach(lc, requires)
|
||||||
|
{
|
||||||
|
char *curreq = (char *) lfirst(lc);
|
||||||
|
|
||||||
|
datums[ndatums++] =
|
||||||
|
DirectFunctionCall1(namein, CStringGetDatum(curreq));
|
||||||
|
}
|
||||||
|
a = construct_array(datums, ndatums,
|
||||||
|
NAMEOID,
|
||||||
|
NAMEDATALEN, false, 'c');
|
||||||
|
return PointerGetDatum(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2076,7 +2263,7 @@ pg_extension_update_paths(PG_FUNCTION_ARGS)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Find shortest path from evi1 to evi2 */
|
/* Find shortest path from evi1 to evi2 */
|
||||||
path = find_update_path(evi_list, evi1, evi2, true);
|
path = find_update_path(evi_list, evi1, evi2, false, true);
|
||||||
|
|
||||||
/* Emit result row */
|
/* Emit result row */
|
||||||
memset(values, 0, sizeof(values));
|
memset(values, 0, sizeof(values));
|
||||||
@ -2808,7 +2995,8 @@ ExecAlterExtensionStmt(ParseState *pstate, AlterExtensionStmt *stmt)
|
|||||||
* time
|
* time
|
||||||
*/
|
*/
|
||||||
ApplyExtensionUpdates(extensionOid, control,
|
ApplyExtensionUpdates(extensionOid, control,
|
||||||
oldVersionName, updateVersions);
|
oldVersionName, updateVersions,
|
||||||
|
NULL, false, false);
|
||||||
|
|
||||||
ObjectAddressSet(address, ExtensionRelationId, extensionOid);
|
ObjectAddressSet(address, ExtensionRelationId, extensionOid);
|
||||||
|
|
||||||
@ -2827,7 +3015,10 @@ static void
|
|||||||
ApplyExtensionUpdates(Oid extensionOid,
|
ApplyExtensionUpdates(Oid extensionOid,
|
||||||
ExtensionControlFile *pcontrol,
|
ExtensionControlFile *pcontrol,
|
||||||
const char *initialVersion,
|
const char *initialVersion,
|
||||||
List *updateVersions)
|
List *updateVersions,
|
||||||
|
char *origSchemaName,
|
||||||
|
bool cascade,
|
||||||
|
bool is_create)
|
||||||
{
|
{
|
||||||
const char *oldVersionName = initialVersion;
|
const char *oldVersionName = initialVersion;
|
||||||
ListCell *lcv;
|
ListCell *lcv;
|
||||||
@ -2906,8 +3097,9 @@ ApplyExtensionUpdates(Oid extensionOid,
|
|||||||
heap_close(extRel, RowExclusiveLock);
|
heap_close(extRel, RowExclusiveLock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Look up the prerequisite extensions for this version, and build
|
* Look up the prerequisite extensions for this version, install them
|
||||||
* lists of their OIDs and the OIDs of their target schemas.
|
* if necessary, and build lists of their OIDs and the OIDs of their
|
||||||
|
* target schemas.
|
||||||
*/
|
*/
|
||||||
requiredExtensions = NIL;
|
requiredExtensions = NIL;
|
||||||
requiredSchemas = NIL;
|
requiredSchemas = NIL;
|
||||||
@ -2917,16 +3109,12 @@ ApplyExtensionUpdates(Oid extensionOid,
|
|||||||
Oid reqext;
|
Oid reqext;
|
||||||
Oid reqschema;
|
Oid reqschema;
|
||||||
|
|
||||||
/*
|
reqext = get_required_extension(curreq,
|
||||||
* We intentionally don't use get_extension_oid's default error
|
control->name,
|
||||||
* message here, because it would be confusing in this context.
|
origSchemaName,
|
||||||
*/
|
cascade,
|
||||||
reqext = get_extension_oid(curreq, true);
|
NIL,
|
||||||
if (!OidIsValid(reqext))
|
is_create);
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
||||||
errmsg("required extension \"%s\" is not installed",
|
|
||||||
curreq)));
|
|
||||||
reqschema = get_extension_schema(reqext);
|
reqschema = get_extension_schema(reqext);
|
||||||
requiredExtensions = lappend_oid(requiredExtensions, reqext);
|
requiredExtensions = lappend_oid(requiredExtensions, reqext);
|
||||||
requiredSchemas = lappend_oid(requiredSchemas, reqschema);
|
requiredSchemas = lappend_oid(requiredSchemas, reqschema);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user