Re: Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple) - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple) |
Date | |
Msg-id | 1213.1473255992@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple) (Andres Freund <andres@anarazel.de>) |
Responses |
Re: Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple)
Re: Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple) |
List | pgsql-hackers |
Andres Freund <andres@anarazel.de> writes: > On 2016-09-05 22:24:09 -0400, Tom Lane wrote: >> Ordinarily I'd be willing to stick this on the queue for the next >> commitfest, but it seems like we ought to try to get it pushed now >> so that Stephen can make use of the feature for his superuser changes. >> Thoughts? > Seems sensible to me. I can have a look at it one of the next few days > if you want. Thanks for offering. Attached is an updated patch that addresses a couple of issues I noticed: 1. When I did the previous patch, I was thinking that any parameters specified in an auxiliary .control file for the base version would apply to any non-base versions based on it; so I had the pg_available_extension_versions view just duplicate those parameters. But actually, most of those parameters are just applied on-the-fly while processing the update script, so it *does* work for them to be different for different versions. The exceptions are schema (which you can't modify during an update) and comment (while we could replace the comment during an update, it doesn't seem like a good idea because we might overwrite a user-written comment if we did). (I think this behavior is undocumented BTW :-(.) So this fixes the view to only inherit those two parameters from the base version. 2. I noticed that CASCADE was not implemented for required extensions processed by ApplyExtensionUpdates, which seems like a bad thing if CREATE processing is going to rely more heavily on that. This only matters if different versions have different requires lists, but that is supposed to be supported, and all the catalog-hacking for it is there. So I made this work. It took a fair amount of refactoring in order to avoid code duplication, but it wasn't really a very big change. At this point it's awfully tempting to make ALTER EXTENSION UPDATE grow a CASCADE option to allow automatic installation of new requirements of the new version, but I didn't do that here. Still no SGML doc updates. regards, tom lane diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index df49a78..59f9e21 100644 *** a/src/backend/commands/extension.c --- b/src/backend/commands/extension.c *************** typedef struct ExtensionVersionInfo *** 100,113 **** static List *find_update_path(List *evi_list, ExtensionVersionInfo *evi_start, ExtensionVersionInfo *evi_target, bool reinitialize); static void get_available_versions_for_extension(ExtensionControlFile *pcontrol, Tuplestorestate *tupstore, TupleDesc tupdesc); static void ApplyExtensionUpdates(Oid extensionOid, ExtensionControlFile *pcontrol, const char *initialVersion, ! List *updateVersions); static char *read_whole_file(const char *filename, int *length); --- 100,124 ---- 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, ! char *origSchemaName, ! bool cascade, ! bool is_create); static char *read_whole_file(const char *filename, int *length); *************** identify_update_path(ExtensionControlFil *** 1071,1077 **** evi_target = get_ext_ver_info(newVersion, &evi_list); /* Find shortest path */ ! result = find_update_path(evi_list, evi_start, evi_target, false); if (result == NIL) ereport(ERROR, --- 1082,1088 ---- evi_target = get_ext_ver_info(newVersion, &evi_list); /* Find shortest path */ ! result = find_update_path(evi_list, evi_start, evi_target, false, false); if (result == NIL) ereport(ERROR, *************** identify_update_path(ExtensionControlFil *** 1086,1091 **** --- 1097,1105 ---- * 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 (presumably, caller will consider starting from such versions). + * * 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. *************** static List * *** 1097,1102 **** --- 1111,1117 ---- find_update_path(List *evi_list, ExtensionVersionInfo *evi_start, ExtensionVersionInfo *evi_target, + bool reject_indirect, bool reinitialize) { List *result; *************** find_update_path(List *evi_list, *** 1105,1110 **** --- 1120,1127 ---- /* 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) { *************** find_update_path(List *evi_list, *** 1131,1136 **** --- 1148,1156 ---- 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) { *************** find_update_path(List *evi_list, *** 1167,1172 **** --- 1187,1249 ---- } /* + * 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; + + /* Target should not be installable */ + Assert(!evi_target->installable); + + *best_path = NIL; + + /* 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 * * When CASCADE is specified, CreateExtensionInternal() recurses if required *************** find_update_path(List *evi_list, *** 1175,1191 **** * installed, allowing us to error out if we recurse to one of those. */ static ObjectAddress ! CreateExtensionInternal(ParseState *pstate, CreateExtensionStmt *stmt, List *parents) { ! DefElem *d_schema = NULL; ! DefElem *d_new_version = NULL; ! DefElem *d_old_version = NULL; ! DefElem *d_cascade = NULL; ! char *schemaName = NULL; Oid schemaOid = InvalidOid; - char *versionName; - char *oldVersionName; - bool cascade = false; Oid extowner = GetUserId(); ExtensionControlFile *pcontrol; ExtensionControlFile *control; --- 1252,1267 ---- * installed, allowing us to error out if we recurse to one of those. */ static ObjectAddress ! CreateExtensionInternal(char *extensionName, ! char *schemaName, ! char *versionName, ! char *oldVersionName, ! bool cascade, ! List *parents, ! bool is_create) { ! char *origSchemaName = schemaName; Oid schemaOid = InvalidOid; Oid extowner = GetUserId(); ExtensionControlFile *pcontrol; ExtensionControlFile *control; *************** CreateExtensionInternal(ParseState *psta *** 1193,1279 **** List *requiredExtensions; List *requiredSchemas; Oid extensionOid; - ListCell *lc; ObjectAddress address; /* * 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); ! } /* * 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 { ! 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. */ ! if (d_old_version && d_old_version->arg) { ! oldVersionName = strVal(d_old_version->arg); check_valid_version_name(oldVersionName); if (strcmp(oldVersionName, versionName) == 0) --- 1269,1311 ---- List *requiredExtensions; List *requiredSchemas; Oid extensionOid; 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(extensionName); /* * Determine the version to install */ ! 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"))); } check_valid_version_name(versionName); /* ! * 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 (oldVersionName) { ! /* ! * "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) *************** CreateExtensionInternal(ParseState *psta *** 1308,1315 **** } else { oldVersionName = NULL; ! updateVersions = NIL; } /* --- 1340,1400 ---- } 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_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); ! ! /* ! * We don't expect it to be installable, but maybe somebody added ! * a suitable script right after our stat() test. ! */ ! if (evi_target->installable) ! { ! /* Easy, no extra scripts */ ! updateVersions = NIL; ! } ! else ! { ! /* Identify best path to reach target */ ! ExtensionVersionInfo *evi_start; ! ! 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 for version \"%s\"", ! pcontrol->name, versionName))); ! ! /* Otherwise, install best starting point and then upgrade */ ! versionName = evi_start->name; ! } ! } } /* *************** CreateExtensionInternal(ParseState *psta *** 1320,1332 **** /* * Determine the target schema to install the extension into */ ! if (d_schema && d_schema->arg) { - /* - * 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); } --- 1405,1412 ---- /* * Determine the target schema to install the extension into */ ! if (schemaName) { /* If the user is giving us the schema name, it must exist already. */ schemaOid = get_namespace_oid(schemaName, false); } *************** CreateExtensionInternal(ParseState *psta *** 1374,1380 **** else if (!OidIsValid(schemaOid)) { /* ! * 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. */ --- 1454,1460 ---- else if (!OidIsValid(schemaOid)) { /* ! * 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. */ *************** CreateExtensionInternal(ParseState *psta *** 1415,1480 **** 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."))); ! } ! reqschema = get_extension_schema(reqext); requiredExtensions = lappend_oid(requiredExtensions, reqext); requiredSchemas = lappend_oid(requiredSchemas, reqschema); --- 1495,1506 ---- Oid reqext; Oid reqschema; ! 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); *************** CreateExtensionInternal(ParseState *psta *** 1510,1526 **** * though a series of ALTER EXTENSION UPDATE commands were given */ ApplyExtensionUpdates(extensionOid, pcontrol, ! versionName, updateVersions); return address; } /* * CREATE EXTENSION */ ObjectAddress CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt) { /* Check extension name validity before any filesystem access */ check_valid_extension_name(stmt->extname); --- 1536,1635 ---- * though a series of ALTER EXTENSION UPDATE commands were given */ ApplyExtensionUpdates(extensionOid, pcontrol, ! 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); *************** CreateExtension(ParseState *pstate, Crea *** 1556,1563 **** (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("nested CREATE EXTENSION is not supported"))); ! /* Finally create the extension. */ ! return CreateExtensionInternal(pstate, stmt, NIL); } /* --- 1665,1727 ---- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("nested CREATE EXTENSION is not supported"))); ! /* 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); } /* *************** get_available_versions_for_extension(Ext *** 1914,1956 **** Tuplestorestate *tupstore, TupleDesc tupdesc) { ! int extnamelen = strlen(pcontrol->name); ! char *location; ! DIR *dir; ! struct dirent *de; ! 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) { ExtensionControlFile *control; - char *vername; Datum values[7]; bool nulls[7]; ! /* 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, "--")) continue; /* * Fetch parameters for specific version (pcontrol is not changed) */ ! control = read_extension_aux_control_file(pcontrol, vername); memset(values, 0, sizeof(values)); memset(nulls, 0, sizeof(nulls)); --- 2078,2105 ---- Tuplestorestate *tupstore, TupleDesc tupdesc) { ! List *evi_list; ! ListCell *lc; ! /* 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; Datum values[7]; bool nulls[7]; + ListCell *lc2; ! if (!evi->installable) continue; /* * Fetch parameters for specific version (pcontrol is not changed) */ ! control = read_extension_aux_control_file(pcontrol, evi->name); memset(values, 0, sizeof(values)); memset(nulls, 0, sizeof(nulls)); *************** get_available_versions_for_extension(Ext *** 1959,1965 **** values[0] = DirectFunctionCall1(namein, CStringGetDatum(control->name)); /* version */ ! values[1] = CStringGetTextDatum(vername); /* superuser */ values[2] = BoolGetDatum(control->superuser); /* relocatable */ --- 2108,2114 ---- values[0] = DirectFunctionCall1(namein, CStringGetDatum(control->name)); /* version */ ! values[1] = CStringGetTextDatum(evi->name); /* superuser */ values[2] = BoolGetDatum(control->superuser); /* relocatable */ *************** get_available_versions_for_extension(Ext *** 1974,2000 **** 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); ! } /* comment */ if (control->comment == NULL) nulls[6] = true; --- 2123,2129 ---- if (control->requires == NIL) nulls[5] = true; else ! values[5] = convert_requires_to_datum(control->requires); /* comment */ if (control->comment == NULL) nulls[6] = true; *************** get_available_versions_for_extension(Ext *** 2002,2010 **** values[6] = CStringGetTextDatum(control->comment); tuplestore_putvalues(tupstore, tupdesc, values, nulls); } ! FreeDir(dir); } /* --- 2131,2205 ---- 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 + * appropriate parameters 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); } /* *************** pg_extension_update_paths(PG_FUNCTION_AR *** 2076,2082 **** continue; /* Find shortest path from evi1 to evi2 */ ! path = find_update_path(evi_list, evi1, evi2, true); /* Emit result row */ memset(values, 0, sizeof(values)); --- 2271,2277 ---- continue; /* Find shortest path from evi1 to evi2 */ ! path = find_update_path(evi_list, evi1, evi2, false, true); /* Emit result row */ memset(values, 0, sizeof(values)); *************** ExecAlterExtensionStmt(ParseState *pstat *** 2808,2814 **** * time */ ApplyExtensionUpdates(extensionOid, control, ! oldVersionName, updateVersions); ObjectAddressSet(address, ExtensionRelationId, extensionOid); --- 3003,3010 ---- * time */ ApplyExtensionUpdates(extensionOid, control, ! oldVersionName, updateVersions, ! NULL, false, false); ObjectAddressSet(address, ExtensionRelationId, extensionOid); *************** static void *** 2827,2833 **** ApplyExtensionUpdates(Oid extensionOid, ExtensionControlFile *pcontrol, const char *initialVersion, ! List *updateVersions) { const char *oldVersionName = initialVersion; ListCell *lcv; --- 3023,3032 ---- ApplyExtensionUpdates(Oid extensionOid, ExtensionControlFile *pcontrol, const char *initialVersion, ! List *updateVersions, ! char *origSchemaName, ! bool cascade, ! bool is_create) { const char *oldVersionName = initialVersion; ListCell *lcv; *************** ApplyExtensionUpdates(Oid extensionOid, *** 2906,2913 **** 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. */ requiredExtensions = NIL; requiredSchemas = NIL; --- 3105,3113 ---- heap_close(extRel, RowExclusiveLock); /* ! * 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; *************** ApplyExtensionUpdates(Oid extensionOid, *** 2917,2932 **** 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))); reqschema = get_extension_schema(reqext); requiredExtensions = lappend_oid(requiredExtensions, reqext); requiredSchemas = lappend_oid(requiredSchemas, reqschema); --- 3117,3128 ---- Oid reqext; Oid reqschema; ! 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);
pgsql-hackers by date: