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:
Tom Lane 2016-09-11 14:15:07 -04:00
parent 28e5e5648c
commit 40b449ae84
2 changed files with 440 additions and 211 deletions

View File

@ -885,6 +885,47 @@ SELECT * FROM pg_extension_update_paths('<replaceable>extension_name</>');
</para>
</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">
<title>Extension Example</title>

View File

@ -100,14 +100,25 @@ typedef struct ExtensionVersionInfo
static List *find_update_path(List *evi_list,
ExtensionVersionInfo *evi_start,
ExtensionVersionInfo *evi_target,
bool reject_indirect,
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,
Tuplestorestate *tupstore,
TupleDesc tupdesc);
static Datum convert_requires_to_datum(List *requires);
static void ApplyExtensionUpdates(Oid extensionOid,
ExtensionControlFile *pcontrol,
const char *initialVersion,
List *updateVersions);
List *updateVersions,
char *origSchemaName,
bool cascade,
bool is_create);
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);
/* 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)
ereport(ERROR,
@ -1086,9 +1097,13 @@ identify_update_path(ExtensionControlFile *control,
* Apply Dijkstra's algorithm to find the shortest path from evi_start to
* 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
* 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
* version is *not* included). Returns NIL if no such path.
@ -1097,6 +1112,7 @@ static List *
find_update_path(List *evi_list,
ExtensionVersionInfo *evi_start,
ExtensionVersionInfo *evi_target,
bool reject_indirect,
bool reinitialize)
{
List *result;
@ -1105,6 +1121,8 @@ find_update_path(List *evi_list,
/* Caller error if start == target */
Assert(evi_start != evi_target);
/* Caller error if reject_indirect and target is installable */
Assert(!(reject_indirect && evi_target->installable));
if (reinitialize)
{
@ -1131,6 +1149,9 @@ find_update_path(List *evi_list,
ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
int newdist;
/* if reject_indirect, treat installable versions as unreachable */
if (reject_indirect && evi2->installable)
continue;
newdist = evi->distance + 1;
if (newdist < evi2->distance)
{
@ -1166,6 +1187,67 @@ find_update_path(List *evi_list,
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
*
@ -1175,17 +1257,16 @@ find_update_path(List *evi_list,
* installed, allowing us to error out if we recurse to one of those.
*/
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;
DefElem *d_new_version = NULL;
DefElem *d_old_version = NULL;
DefElem *d_cascade = NULL;
char *schemaName = NULL;
char *origSchemaName = schemaName;
Oid schemaOid = InvalidOid;
char *versionName;
char *oldVersionName;
bool cascade = false;
Oid extowner = GetUserId();
ExtensionControlFile *pcontrol;
ExtensionControlFile *control;
@ -1193,87 +1274,43 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
List *requiredExtensions;
List *requiredSchemas;
Oid extensionOid;
ListCell *lc;
ObjectAddress address;
ListCell *lc;
/*
* 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
* point.
*/
pcontrol = read_extension_control_file(stmt->extname);
/*
* 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);
}
pcontrol = read_extension_control_file(extensionName);
/*
* Determine the version to install
*/
if (d_new_version && d_new_version->arg)
versionName = strVal(d_new_version->arg);
else if (pcontrol->default_version)
versionName = pcontrol->default_version;
else
if (versionName == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("version to install must be specified")));
versionName = NULL; /* keep compiler quiet */
if (pcontrol->default_version)
versionName = pcontrol->default_version;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("version to install must be specified")));
}
check_valid_version_name(versionName);
/*
* Determine the (unpackaged) version to update from, if any, and then
* figure out what sequence of update scripts we need to apply.
* Figure out which script(s) we need to run to install the desired
* 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);
if (strcmp(oldVersionName, versionName) == 0)
@ -1308,8 +1345,48 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
}
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;
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
*/
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. */
schemaOid = get_namespace_oid(schemaName, false);
}
@ -1374,7 +1446,7 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
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
* entry in the search_path.
*/
@ -1415,66 +1487,12 @@ CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *par
Oid reqext;
Oid reqschema;
reqext = get_extension_oid(curreq, true);
if (!OidIsValid(reqext))
{
if (cascade)
{
/* Must install it. */
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.")));
}
reqext = get_required_extension(curreq,
extensionName,
origSchemaName,
cascade,
parents,
is_create);
reqschema = get_extension_schema(reqext);
requiredExtensions = lappend_oid(requiredExtensions, reqext);
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
*/
ApplyExtensionUpdates(extensionOid, pcontrol,
versionName, updateVersions);
versionName, updateVersions,
origSchemaName, cascade, is_create);
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
*/
ObjectAddress
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_valid_extension_name(stmt->extname);
@ -1556,8 +1657,63 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("nested CREATE EXTENSION is not supported")));
/* Finally create the extension. */
return CreateExtensionInternal(pstate, stmt, NIL);
/* Deconstruct 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;
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,
TupleDesc tupdesc)
{
int extnamelen = strlen(pcontrol->name);
char *location;
DIR *dir;
struct dirent *de;
List *evi_list;
ListCell *lc;
location = get_extension_script_directory(pcontrol);
dir = AllocateDir(location);
/* Note this will fail if script directory doesn't exist */
while ((de = ReadDir(dir, location)) != NULL)
/* Extract the version update graph from the script directory */
evi_list = get_ext_ver_list(pcontrol);
/* For each installable version ... */
foreach(lc, evi_list)
{
ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
ExtensionControlFile *control;
char *vername;
Datum values[7];
bool nulls[7];
ListCell *lc2;
/* 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, 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, "--"))
if (!evi->installable)
continue;
/*
* 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(nulls, 0, sizeof(nulls));
@ -1959,7 +2100,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
values[0] = DirectFunctionCall1(namein,
CStringGetDatum(control->name));
/* version */
values[1] = CStringGetTextDatum(vername);
values[1] = CStringGetTextDatum(evi->name);
/* superuser */
values[2] = BoolGetDatum(control->superuser);
/* relocatable */
@ -1974,27 +2115,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
if (control->requires == NIL)
nulls[5] = true;
else
{
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);
}
values[5] = convert_requires_to_datum(control->requires);
/* comment */
if (control->comment == NULL)
nulls[6] = true;
@ -2002,9 +2123,75 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
values[6] = CStringGetTextDatum(control->comment);
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;
/* 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 */
memset(values, 0, sizeof(values));
@ -2808,7 +2995,8 @@ ExecAlterExtensionStmt(ParseState *pstate, AlterExtensionStmt *stmt)
* time
*/
ApplyExtensionUpdates(extensionOid, control,
oldVersionName, updateVersions);
oldVersionName, updateVersions,
NULL, false, false);
ObjectAddressSet(address, ExtensionRelationId, extensionOid);
@ -2827,7 +3015,10 @@ static void
ApplyExtensionUpdates(Oid extensionOid,
ExtensionControlFile *pcontrol,
const char *initialVersion,
List *updateVersions)
List *updateVersions,
char *origSchemaName,
bool cascade,
bool is_create)
{
const char *oldVersionName = initialVersion;
ListCell *lcv;
@ -2906,8 +3097,9 @@ ApplyExtensionUpdates(Oid extensionOid,
heap_close(extRel, RowExclusiveLock);
/*
* Look up the prerequisite extensions for this version, and build
* lists of their OIDs and the OIDs of their target schemas.
* Look up the prerequisite extensions for this version, install them
* if necessary, and build lists of their OIDs and the OIDs of their
* target schemas.
*/
requiredExtensions = NIL;
requiredSchemas = NIL;
@ -2917,16 +3109,12 @@ ApplyExtensionUpdates(Oid extensionOid,
Oid reqext;
Oid reqschema;
/*
* We intentionally don't use get_extension_oid's default error
* message here, because it would be confusing in this context.
*/
reqext = get_extension_oid(curreq, true);
if (!OidIsValid(reqext))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("required extension \"%s\" is not installed",
curreq)));
reqext = get_required_extension(curreq,
control->name,
origSchemaName,
cascade,
NIL,
is_create);
reqschema = get_extension_schema(reqext);
requiredExtensions = lappend_oid(requiredExtensions, reqext);
requiredSchemas = lappend_oid(requiredSchemas, reqschema);