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>
|
||||
</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>
|
||||
|
||||
|
@ -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)
|
||||
if (versionName == NULL)
|
||||
{
|
||||
if (pcontrol->default_version)
|
||||
versionName = pcontrol->default_version;
|
||||
else
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("version to install must be specified")));
|
||||
versionName = NULL; /* keep compiler quiet */
|
||||
}
|
||||
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,9 +1345,49 @@ 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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch control parameters for installation target version
|
||||
@ -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,16 +2115,73 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
|
||||
if (control->requires == NIL)
|
||||
nulls[5] = true;
|
||||
else
|
||||
values[5] = convert_requires_to_datum(control->requires);
|
||||
/* comment */
|
||||
if (control->comment == NULL)
|
||||
nulls[6] = true;
|
||||
else
|
||||
values[6] = CStringGetTextDatum(control->comment);
|
||||
|
||||
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||
|
||||
/*
|
||||
* 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(control->requires);
|
||||
ndatums = list_length(requires);
|
||||
datums = (Datum *) palloc(ndatums * sizeof(Datum));
|
||||
ndatums = 0;
|
||||
foreach(lc, control->requires)
|
||||
foreach(lc, requires)
|
||||
{
|
||||
char *curreq = (char *) lfirst(lc);
|
||||
|
||||
@ -1993,18 +2191,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
|
||||
a = construct_array(datums, ndatums,
|
||||
NAMEOID,
|
||||
NAMEDATALEN, false, 'c');
|
||||
values[5] = PointerGetDatum(a);
|
||||
}
|
||||
/* comment */
|
||||
if (control->comment == NULL)
|
||||
nulls[6] = true;
|
||||
else
|
||||
values[6] = CStringGetTextDatum(control->comment);
|
||||
|
||||
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||
}
|
||||
|
||||
FreeDir(dir);
|
||||
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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user