*** a/contrib/pg_upgrade_support/pg_upgrade_support.c --- b/contrib/pg_upgrade_support/pg_upgrade_support.c *************** *** 151,156 **** create_empty_extension(PG_FUNCTION_ARGS) --- 151,157 ---- Datum extConfig; Datum extCondition; List *requiredExtensions; + List *features = NIL; /* FIXME, get features from catalogs */ if (PG_ARGISNULL(4)) extConfig = PointerGetDatum(NULL); *************** *** 190,196 **** create_empty_extension(PG_FUNCTION_ARGS) text_to_cstring(extVersion), extConfig, extCondition, ! requiredExtensions); PG_RETURN_VOID(); } --- 191,198 ---- text_to_cstring(extVersion), extConfig, extCondition, ! requiredExtensions, ! features); PG_RETURN_VOID(); } *** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 149,154 **** --- 149,159 ---- + pg_extension_feature + features provided by installed extensions + + + pg_foreign_data_wrapper foreign-data wrapper definitions *************** *** 3058,3063 **** --- 3063,3113 ---- + + <structname>pg_extension_feature</structname> + + + pg_extension_feature + + + + The catalog pg_extension_feature stores + information about the features provided by installed extensions. + See for details about extensions. + + + + <structname>pg_extension_feature</> Columns + + + + + Name + Type + References + Description + + + + + + extoid + oid + pg_extension.oid + Oid of the extension that provides this feature + + + + extfeature + name + + Name of the feature + + + + +
+
<structname>pg_foreign_data_wrapper</structname> *************** *** 6828,6838 **** requires name[] ! Names of prerequisite extensions, or NULL if none comment text Comment string from the extension's control file --- 6878,6894 ---- requires name[] ! Names of prerequisite features, or NULL if none + provides + name[] + Names of provided features + + + comment text Comment string from the extension's control file *** a/doc/src/sgml/extend.sgml --- b/doc/src/sgml/extend.sgml *************** *** 463,471 **** requires (string) ! A list of names of extensions that this extension depends on, ! for example requires = 'foo, bar'. Those ! extensions must be installed before this one can be installed. --- 463,492 ---- requires (string) ! A list of features that this extension depends on, for ! example requires = 'foo, bar'. Those features ! must be provided by an already installed extension before this one ! can be installed. ! ! ! ! ! ! provides (string) ! ! ! A list of names of features that this extension provides, for ! example provides = 'foo, extname_bugfix_12345'. ! Those features can help providing finer dependencies: when updating ! an existing extension you can add new features in this list so that ! it's possible to depend on those new features. It also makes it ! possible to deprecate features that an extension would no longer ! provide. ! ! ! The extension's name itself is always considered a member of ! the provides list, so that you can entirely omit ! this parameter. *** a/src/backend/catalog/Makefile --- b/src/backend/catalog/Makefile *************** *** 36,42 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ ! pg_ts_parser.h pg_ts_template.h pg_extension.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h \ pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \ --- 36,42 ---- pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ ! pg_ts_parser.h pg_ts_template.h pg_extension.h pg_extension_feature.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h \ pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \ *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 35,40 **** --- 35,41 ---- #include "catalog/pg_default_acl.h" #include "catalog/pg_depend.h" #include "catalog/pg_extension.h" + #include "catalog/pg_extension_feature.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" *************** *** 158,164 **** static const Oid object_classes[MAX_OCLASS] = { ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ DefaultAclRelationId, /* OCLASS_DEFACL */ ! ExtensionRelationId /* OCLASS_EXTENSION */ }; --- 159,166 ---- ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ DefaultAclRelationId, /* OCLASS_DEFACL */ ! ExtensionRelationId, /* OCLASS_EXTENSION */ ! ExtensionFeatureRelationId /* OCLASS_EXTENSION_FEATURE */ }; *************** *** 1205,1210 **** doDeletion(const ObjectAddress *object) --- 1207,1216 ---- RemoveExtensionById(object->objectId); break; + case OCLASS_EXTENSION_FEATURE: + RemoveExtensionFeatureById(object->objectId); + break; + default: elog(ERROR, "unrecognized object class: %u", object->classId); *************** *** 2248,2253 **** getObjectClass(const ObjectAddress *object) --- 2254,2262 ---- case ExtensionRelationId: return OCLASS_EXTENSION; + + case ExtensionFeatureRelationId: + return OCLASS_EXTENSION_FEATURE; } /* shouldn't get here */ *************** *** 2882,2887 **** getObjectDescription(const ObjectAddress *object) --- 2891,2908 ---- break; } + case OCLASS_EXTENSION_FEATURE: + { + char *feature; + + feature = get_extension_feature_name(object->objectId); + if (!feature) + elog(ERROR, "cache lookup failed for extension feature %u", + object->objectId); + appendStringInfo(&buffer, _("extension feature %s"), feature); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, *** a/src/backend/catalog/pg_depend.c --- b/src/backend/catalog/pg_depend.c *************** *** 281,286 **** deleteDependencyRecordsForClass(Oid classId, Oid objectId, --- 281,333 ---- } /* + * deleteDependencyRefRecordsForClass -- delete all records with given dependee + * classId/objectId, depender classId, and deptype. + * Returns the number of records deleted. + */ + long + deleteDependencyRefRecordsForClass(Oid refclassId, Oid refobjectId, + Oid classId, char deptype) + { + long count = 0; + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + + depRel = heap_open(DependRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refclassId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refobjectId)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + SnapshotNow, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->classid == classId && depform->deptype == deptype) + { + simple_heap_delete(depRel, &tup->t_self); + count++; + } + } + + systable_endscan(scan); + + heap_close(depRel, RowExclusiveLock); + + return count; + } + + /* * Adjust dependency record(s) to point to a different object of the same type * * classId/objectId specify the referencing object. *** a/src/backend/catalog/system_views.sql --- b/src/backend/catalog/system_views.sql *************** *** 186,192 **** CREATE VIEW pg_available_extensions AS CREATE VIEW pg_available_extension_versions AS SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed, ! E.superuser, E.relocatable, E.schema, E.requires, E.comment FROM pg_available_extension_versions() AS E LEFT JOIN pg_extension AS X ON E.name = X.extname AND E.version = X.extversion; --- 186,192 ---- CREATE VIEW pg_available_extension_versions AS SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed, ! E.superuser, E.relocatable, E.schema, E.requires, E.provides, E.comment FROM pg_available_extension_versions() AS E LEFT JOIN pg_extension AS X ON E.name = X.extname AND E.version = X.extversion; *** a/src/backend/commands/extension.c --- b/src/backend/commands/extension.c *************** *** 36,41 **** --- 36,42 ---- #include "catalog/pg_collation.h" #include "catalog/pg_depend.h" #include "catalog/pg_extension.h" + #include "catalog/pg_extension_feature.h" #include "catalog/pg_namespace.h" #include "catalog/pg_type.h" #include "commands/alter.h" *************** *** 73,78 **** typedef struct ExtensionControlFile --- 74,80 ---- bool superuser; /* must be superuser to install? */ int encoding; /* encoding of the script file, or -1 */ List *requires; /* names of prerequisite extensions */ + List *provides; /* names of provided features */ } ExtensionControlFile; /* *************** *** 89,94 **** typedef struct ExtensionVersionInfo --- 91,117 ---- struct ExtensionVersionInfo *previous; /* current best predecessor */ } ExtensionVersionInfo; + /* + * Data Structure to handle upgrading of extension features dependencies, and + * allow manage features that didn't change, added ones and removed ones. + */ + struct feature + { + Oid oid; /* feature oid */ + char *name; /* feature name */ + int count; /* feature usage count */ + }; + + /* bsearch function to compare string and struct feature by name */ + static int + cmpfeatname(const void *a, const void *b) + { + char *p = (char *) a; + struct feature *f = (struct feature *) b; + + return strcmp(p, f->name); + } + /* Local functions */ static List *find_update_path(List *evi_list, ExtensionVersionInfo *evi_start, *************** *** 101,107 **** static void ApplyExtensionUpdates(Oid extensionOid, ExtensionControlFile *pcontrol, const char *initialVersion, List *updateVersions); ! /* * get_extension_oid - given an extension name, look up the OID --- 124,133 ---- ExtensionControlFile *pcontrol, const char *initialVersion, List *updateVersions); ! static void insert_extension_features(const char *extName, ObjectAddress myself, ! List *features); ! static void insert_extension_feature(Relation rel, ObjectAddress myself, ! const char *feature); /* * get_extension_oid - given an extension name, look up the OID *************** *** 228,233 **** get_extension_schema(Oid ext_oid) --- 254,333 ---- } /* + * Given a feature name, returns its pg_extension_feature oid. + */ + static void + get_extension_feature_oids(const char *feature, bool missing_ok, + Oid *extoid, Oid *featoid) + { + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionFeatureRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_feature_extfeature, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(feature)); + + scandesc = systable_beginscan(rel, ExtensionFeatureNameIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + { + *extoid = ((Form_pg_extension_feature)tuple)->extoid; + *featoid = HeapTupleGetOid(tuple); + } + else + { + *extoid = InvalidOid; + *featoid = InvalidOid; + } + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + if (!OidIsValid(*featoid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("feature \"%s\" is not currently provided", + feature), + errhint("Please install an extension that provides it first"))); + } + + /* + * Look up the prerequisite extensions, and build lists of their OIDs and + * the OIDs of their target schemas. + */ + static void + get_required_extension_features(List *requires, + List **requiredFeatures, + List **requiredSchemas) + { + ListCell *lc; + + *requiredFeatures = NIL; + *requiredSchemas = NIL; + + foreach(lc, requires) + { + char *curreq = (char *) lfirst(lc); + Oid reqext, featoid, reqschema; + + get_extension_feature_oids(curreq, false, &reqext, &featoid); + reqschema = get_extension_schema(reqext); + *requiredFeatures = lappend_oid(*requiredFeatures, featoid); + *requiredSchemas = lappend_oid(*requiredSchemas, reqschema); + } + } + + /* * Utility functions to check validity of extension and version names */ static void *************** *** 557,562 **** parse_extension_control_file(ExtensionControlFile *control, --- 657,677 ---- item->name))); } } + else if (strcmp(item->name, "provides") == 0) + { + /* Need a modifiable copy of string */ + char *rawnames = pstrdup(item->value); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawnames, ',', &control->provides)) + { + /* syntax error in name list */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" must be a list of extension names", + item->name))); + } + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 1186,1192 **** CreateExtension(CreateExtensionStmt *stmt) ExtensionControlFile *pcontrol; ExtensionControlFile *control; List *updateVersions; ! List *requiredExtensions; List *requiredSchemas; Oid extensionOid; ListCell *lc; --- 1301,1307 ---- ExtensionControlFile *pcontrol; ExtensionControlFile *control; List *updateVersions; ! List *requiredFeatures; List *requiredSchemas; Oid extensionOid; ListCell *lc; *************** *** 1414,1441 **** CreateExtension(CreateExtensionStmt *stmt) * Look up the prerequisite extensions, and build lists of their OIDs and * the OIDs of their target schemas. */ ! requiredExtensions = NIL; ! requiredSchemas = NIL; ! foreach(lc, control->requires) ! { ! char *curreq = (char *) lfirst(lc); ! 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); ! } /* * Insert new tuple into pg_extension, and create dependency entries. --- 1529,1537 ---- * Look up the prerequisite extensions, and build lists of their OIDs and * the OIDs of their target schemas. */ ! get_required_extension_features(control->requires, ! &requiredFeatures, ! &requiredSchemas); /* * Insert new tuple into pg_extension, and create dependency entries. *************** *** 1445,1451 **** CreateExtension(CreateExtensionStmt *stmt) versionName, PointerGetDatum(NULL), PointerGetDatum(NULL), ! requiredExtensions); /* * Apply any control-file comment on extension --- 1541,1548 ---- versionName, PointerGetDatum(NULL), PointerGetDatum(NULL), ! requiredFeatures, ! control->provides); /* * Apply any control-file comment on extension *************** *** 1486,1492 **** Oid InsertExtensionTuple(const char *extName, Oid extOwner, Oid schemaOid, bool relocatable, const char *extVersion, Datum extConfig, Datum extCondition, ! List *requiredExtensions) { Oid extensionOid; Relation rel; --- 1583,1589 ---- InsertExtensionTuple(const char *extName, Oid extOwner, Oid schemaOid, bool relocatable, const char *extVersion, Datum extConfig, Datum extCondition, ! List *requiredFeatures, List *features) { Oid extensionOid; Relation rel; *************** *** 1545,1561 **** InsertExtensionTuple(const char *extName, Oid extOwner, recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL); ! foreach(lc, requiredExtensions) { ! Oid reqext = lfirst_oid(lc); ! ObjectAddress otherext; ! otherext.classId = ExtensionRelationId; ! otherext.objectId = reqext; ! otherext.objectSubId = 0; ! recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL); } /* Post creation hook for new extension */ InvokeObjectAccessHook(OAT_POST_CREATE, ExtensionRelationId, extensionOid, 0, NULL); --- 1642,1663 ---- recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL); ! foreach(lc, requiredFeatures) { ! Oid reqfeat = lfirst_oid(lc); ! ObjectAddress feature; ! feature.classId = ExtensionFeatureRelationId; ! feature.objectId = reqfeat; ! feature.objectSubId = 0; ! recordDependencyOn(&myself, &feature, DEPENDENCY_NORMAL); } + /* + * Insert extension's features into pg_extension_feature catalog + */ + insert_extension_features(extName, myself, features); + /* Post creation hook for new extension */ InvokeObjectAccessHook(OAT_POST_CREATE, ExtensionRelationId, extensionOid, 0, NULL); *************** *** 1615,1620 **** RemoveExtensionById(Oid extId) --- 1717,2006 ---- } /* + * Get an extension's feature name given its objectId + */ + char * + get_extension_feature_name(Oid featoid) + { + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + char *name; + + rel = heap_open(ExtensionFeatureRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(featoid)); + + scandesc = systable_beginscan(rel, ExtensionFeatureOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + { + Form_pg_extension_feature f = (Form_pg_extension_feature) GETSTRUCT(tuple); + name = pstrdup(NameStr(f->extfeature)); + } + else + name = NULL; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return name; + } + + /* + * Insert provided features into the pg_extension_feature catalog + */ + static void + insert_extension_features(const char *extName, ObjectAddress extObject, + List *features) + { + Relation rel; + ListCell *lc; + bool provides_itself = false; + + rel = heap_open(ExtensionFeatureRelationId, RowExclusiveLock); + + foreach(lc, features) + { + char *feature = (char *) lfirst(lc); + insert_extension_feature(rel, extObject, feature); + + provides_itself = provides_itself || (strcmp(feature, extName) == 0); + } + + if (!provides_itself) + insert_extension_feature(rel, extObject, extName); + + heap_close(rel, RowExclusiveLock); + } + + static void + insert_extension_feature(Relation rel, + ObjectAddress extObject, const char *feature) + { + Oid featureOid; + Datum values[Natts_pg_extension_feature]; + bool nulls[Natts_pg_extension_feature]; + HeapTuple tuple; + ObjectAddress myself; + Oid ext, featoid; + + /* + * Build a nice error message when the feature is already installed.. + */ + get_extension_feature_oids(feature, true, &ext, &featoid); + if (featoid != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension feature \"%s\" already exists [%u]", + feature, featoid))); + + /* + * Build and insert the pg_extension_feature tuple + */ + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[Anum_pg_extension_feature_extoid - 1] = + ObjectIdGetDatum(extObject.objectId); + values[Anum_pg_extension_feature_extfeature - 1] = + DirectFunctionCall1(namein, CStringGetDatum(feature)); + + tuple = heap_form_tuple(rel->rd_att, values, nulls); + + featureOid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + + heap_freetuple(tuple); + + /* handle internal dependencies between the extension tuple and the + * extension's feature tuple + */ + myself.classId = ExtensionFeatureRelationId; + myself.objectId = featureOid; + myself.objectSubId = 0; + + recordDependencyOn(&myself, &extObject, DEPENDENCY_INTERNAL); + } + + /* static struct feature * */ + static int + list_extension_features(Oid extoid, struct feature **features) + { + int nbfeats = 0, size = 10; + Relation rel, irel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + *features = (struct feature *) palloc(size * sizeof(struct feature)); + + rel = heap_open(ExtensionFeatureRelationId, AccessShareLock); + irel = index_open(ExtensionFeatureExtensionNameIndexId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_feature_extoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extoid)); + + scandesc = systable_beginscan_ordered(rel, irel, SnapshotNow, 1, entry); + + while (HeapTupleIsValid(tuple = systable_getnext_ordered(scandesc, ForwardScanDirection))) + { + Form_pg_extension_feature f = (Form_pg_extension_feature) GETSTRUCT(tuple); + + if (nbfeats == size) + { + size += 10; + *features = repalloc(*features, size); + } + (*features)[nbfeats].oid = HeapTupleGetOid(tuple); + (*features)[nbfeats].name = pstrdup(NameStr(f->extfeature)); + (*features)[nbfeats].count = 0; + + nbfeats++; + } + systable_endscan_ordered(scandesc); + + index_close(irel, AccessShareLock); + heap_close(rel, AccessShareLock); + + return nbfeats; + } + + /* + * Care about an extension's provided features changes: + * - do nothing when the feature was already provided + * - add new dependencies when a new feature is provided + * - delete dependencies that are not provided anymore + */ + static void + update_extension_feature_list(ExtensionControlFile *control, + ObjectAddress ext) + { + Relation rel; + struct feature *features; + int nbfeats = list_extension_features(ext.objectId, &features); + int i; + ListCell *lc; + + /* + * Remove all this extension features dependencies, and add them again + * while processing the new "provides" list. That allows to use the + * pg_depend performDeletion() API to implement removing a feature from the + * provide list: we have to skip the extension providing the feature itself + * when following dependencies in DROP_RESTRICT mode. + */ + i = deleteDependencyRefRecordsForClass(ext.classId, ext.objectId, + ExtensionFeatureRelationId, + DEPENDENCY_INTERNAL); + + /* Have that change visible now, for the performDeletion() call */ + CommandCounterIncrement(); + + rel = heap_open(ExtensionFeatureRelationId, RowExclusiveLock); + + foreach(lc, control->provides) + { + char *feat = (char *) lfirst(lc); + struct feature *found = NULL; + + found = (struct feature *) + bsearch(feat, features, nbfeats, sizeof(struct feature), &cmpfeatname); + + if (found) + { + if (found->count > 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options \"%s\"", + feat))); + + found->count++; + } + else + insert_extension_feature(rel, ext, feat); + } + + /* upgrade is done, remove features not provided anymore, and avoid + * removing the extension's name (will not appear in control->provides) + */ + for(i=0; i < nbfeats; i++) + { + ObjectAddress feature; + + feature.classId = ExtensionFeatureRelationId; + feature.objectId = features[i].oid; + feature.objectSubId = 0; + + if (strcmp(features[i].name, control->name) == 0) + /* + * The extension's name itself is not in the provide list but still + * provided, we have to care about it separately. + */ + recordDependencyOn(&feature, &ext, DEPENDENCY_INTERNAL); + + else if (features[i].count == 0) + /* + * Drop the extension's feature that is no longer provided, raising + * an error instead if some other extensions are still depending on + * it (control->requires installs pg_depend entries for this case). + */ + performDeletion(&feature, DROP_RESTRICT, PERFORM_DELETION_INTERNAL); + + else if (features[i].count > 0) + /* + * Re-install the dependency entry, we removed it only to allow + * using DROP_RESTRICT. + */ + recordDependencyOn(&feature, &ext, DEPENDENCY_INTERNAL); + } + heap_close(rel, RowExclusiveLock); + } + + /* + * Extension feature's deletion. + * + * All we need do here is remove the pg_extension_feature tuple itself. + */ + void + RemoveExtensionFeatureById(Oid extFeatId) + { + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionFeatureRelationId, RowExclusiveLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extFeatId)); + scandesc = systable_beginscan(rel, ExtensionFeatureOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + simple_heap_delete(rel, &tuple->t_self); + + systable_endscan(scandesc); + + heap_close(rel, RowExclusiveLock); + } + + /* * This function lists the available extensions (one row per primary control * file in the control directory). We parse each control file and report the * interesting fields. *************** *** 1836,1843 **** get_available_versions_for_extension(ExtensionControlFile *pcontrol, { ExtensionControlFile *control; char *vername; ! Datum values[7]; ! bool nulls[7]; /* must be a .sql file ... */ if (!is_extension_script_filename(de->d_name)) --- 2222,2229 ---- { ExtensionControlFile *control; char *vername; ! Datum values[8]; ! bool nulls[8]; /* must be a .sql file ... */ if (!is_extension_script_filename(de->d_name)) *************** *** 1905,1915 **** get_available_versions_for_extension(ExtensionControlFile *pcontrol, 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); } --- 2291,2328 ---- NAMEDATALEN, false, 'c'); values[5] = PointerGetDatum(a); } + /* provides */ + nulls[6] = false; + { + Datum *datums; + int ndatums; + ArrayType *a; + ListCell *lc; + + ndatums = 1 + list_length(control->provides); + datums = (Datum *) palloc(ndatums * sizeof(Datum)); + ndatums = 0; + datums[ndatums++] = + DirectFunctionCall1(namein, CStringGetDatum(pcontrol->name)); + foreach(lc, control->provides) + { + char *curreq = (char *) lfirst(lc); + + /* don't add the extension's name more than once in there */ + if (strcmp(curreq,pcontrol->name) != 0) + datums[ndatums++] = + DirectFunctionCall1(namein, CStringGetDatum(curreq)); + } + a = construct_array(datums, ndatums, + NAMEOID, + NAMEDATALEN, false, 'c'); + values[6] = PointerGetDatum(a); + } /* comment */ if (control->comment == NULL) ! nulls[7] = true; else ! values[7] = CStringGetTextDatum(control->comment); tuplestore_putvalues(tupstore, tupdesc, values, nulls); } *************** *** 2505,2511 **** ApplyExtensionUpdates(Oid extensionOid, ExtensionControlFile *control; char *schemaName; Oid schemaOid; ! List *requiredExtensions; List *requiredSchemas; Relation extRel; ScanKeyData key[1]; --- 2918,2924 ---- ExtensionControlFile *control; char *schemaName; Oid schemaOid; ! List *requiredFeatures; List *requiredSchemas; Relation extRel; ScanKeyData key[1]; *************** *** 2516,2522 **** ApplyExtensionUpdates(Oid extensionOid, bool nulls[Natts_pg_extension]; bool repl[Natts_pg_extension]; ObjectAddress myself; - ListCell *lc; /* * Fetch parameters for specific version (pcontrol is not changed) --- 2929,2934 ---- *************** *** 2573,2630 **** 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. */ - requiredExtensions = NIL; - requiredSchemas = NIL; - foreach(lc, control->requires) - { - char *curreq = (char *) lfirst(lc); - 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); - } - - /* - * Remove and recreate dependencies on prerequisite extensions - */ - deleteDependencyRecordsForClass(ExtensionRelationId, extensionOid, - ExtensionRelationId, - DEPENDENCY_NORMAL); - myself.classId = ExtensionRelationId; myself.objectId = extensionOid; myself.objectSubId = 0; ! foreach(lc, requiredExtensions) ! { ! Oid reqext = lfirst_oid(lc); ! ObjectAddress otherext; ! ! otherext.classId = ExtensionRelationId; ! otherext.objectId = reqext; ! otherext.objectSubId = 0; ! ! recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL); ! } /* * Finally, execute the update script file */ execute_extension_script(extensionOid, control, oldVersionName, versionName, requiredSchemas, --- 2985,3005 ---- heap_close(extRel, RowExclusiveLock); /* ! * Update extension features list and dependencies */ myself.classId = ExtensionRelationId; myself.objectId = extensionOid; myself.objectSubId = 0; ! update_extension_feature_list(control, myself); /* * Finally, execute the update script file */ + get_required_extension_features(control->requires, + &requiredFeatures, + &requiredSchemas); + execute_extension_script(extensionOid, control, oldVersionName, versionName, requiredSchemas, *** a/src/include/catalog/dependency.h --- b/src/include/catalog/dependency.h *************** *** 146,151 **** typedef enum ObjectClass --- 146,152 ---- OCLASS_USER_MAPPING, /* pg_user_mapping */ OCLASS_DEFACL, /* pg_default_acl */ OCLASS_EXTENSION, /* pg_extension */ + OCLASS_EXTENSION_FEATURE, /* pg_extension_feature */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; *************** *** 211,216 **** extern long deleteDependencyRecordsFor(Oid classId, Oid objectId, --- 212,220 ---- extern long deleteDependencyRecordsForClass(Oid classId, Oid objectId, Oid refclassId, char deptype); + extern long deleteDependencyRefRecordsForClass(Oid refclassId, Oid refobjectId, + Oid classId, char deptype); + extern long changeDependencyFor(Oid classId, Oid objectId, Oid refClassId, Oid oldRefObjectId, Oid newRefObjectId); *** a/src/include/catalog/indexing.h --- b/src/include/catalog/indexing.h *************** *** 303,308 **** DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(o --- 303,317 ---- DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(extname name_ops)); #define ExtensionNameIndexId 3081 + DECLARE_UNIQUE_INDEX(pg_extension_feature_oid_index, 3180, on pg_extension_feature using btree(oid oid_ops)); + #define ExtensionFeatureOidIndexId 3180 + + DECLARE_UNIQUE_INDEX(pg_extension_feature_name_index, 3181, on pg_extension_feature using btree(extfeature name_ops)); + #define ExtensionFeatureNameIndexId 3181 + + DECLARE_INDEX(pg_extension_feature_extoid_name_index, 3182, on pg_extension_feature using btree(extoid oid_ops, extfeature name_ops)); + #define ExtensionFeatureExtensionNameIndexId 3182 + DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops)); #define RangeTypidIndexId 3542 *** /dev/null --- b/src/include/catalog/pg_extension_feature.h *************** *** 0 **** --- 1,57 ---- + /*------------------------------------------------------------------------- + * + * pg_extension_feature.h + * definition of the system "extension feature" relation + * (pg_extension_features), that tracks what features an extension provides + * + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_extension_feature.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_EXTENSION_FEATURE_H + #define PG_EXTENSION_FEATURE_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_extension_feature definition. cpp turns this into + * typedef struct FormData_pg_extension_feature + * ---------------- + */ + #define ExtensionFeatureRelationId 3179 + + CATALOG(pg_extension_feature,3179) + { + Oid extoid; /* extension Oid */ + NameData extfeature; /* extension feature */ + } FormData_pg_extension_feature; + + /* ---------------- + * Form_pg_extension_feature corresponds to a pointer to a tuple with the + * format of pg_extension_feature relation. + * ---------------- + */ + typedef FormData_pg_extension_feature *Form_pg_extension_feature; + /* ---------------- + * compiler constants for pg_extension_feature + * ---------------- + */ + + #define Natts_pg_extension_feature 2 + #define Anum_pg_extension_feature_extoid 1 + #define Anum_pg_extension_feature_extfeature 2 + + /* ---------------- + * pg_extension_feature has no initial contents + * ---------------- + */ + + #endif /* PG_EXTENSION_FEATURE_H */ *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 4398,4404 **** DESCR("less-equal-greater"); /* Extensions */ DATA(insert OID = 3082 ( pg_available_extensions PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{19,25,25}" "{o,o,o}" "{name,default_version,comment}" _null_ pg_available_extensions _null_ _null_ _null_ )); DESCR("list available extensions"); ! DATA(insert OID = 3083 ( pg_available_extension_versions PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{19,25,16,16,19,1003,25}" "{o,o,o,o,o,o,o}" "{name,version,superuser,relocatable,schema,requires,comment}" _null_ pg_available_extension_versions _null_ _null_ _null_ )); DESCR("list available extension versions"); DATA(insert OID = 3084 ( pg_extension_update_paths PGNSP PGUID 12 10 100 0 0 f f f f t t s 1 0 2249 "19" "{19,25,25,25}" "{i,o,o,o}" "{name,source,target,path}" _null_ pg_extension_update_paths _null_ _null_ _null_ )); DESCR("list an extension's version update paths"); --- 4398,4404 ---- /* Extensions */ DATA(insert OID = 3082 ( pg_available_extensions PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{19,25,25}" "{o,o,o}" "{name,default_version,comment}" _null_ pg_available_extensions _null_ _null_ _null_ )); DESCR("list available extensions"); ! DATA(insert OID = 3083 ( pg_available_extension_versions PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{19,25,16,16,19,1003,1003,25}" "{o,o,o,o,o,o,o,o}" "{name,version,superuser,relocatable,schema,requires,provides,comment}" _null_ pg_available_extension_versions _null_ _null_ _null_ )); DESCR("list available extension versions"); DATA(insert OID = 3084 ( pg_extension_update_paths PGNSP PGUID 12 10 100 0 0 f f f f t t s 1 0 2249 "19" "{19,25,25,25}" "{i,o,o,o}" "{name,source,target,path}" _null_ pg_extension_update_paths _null_ _null_ _null_ )); DESCR("list an extension's version update paths"); *************** *** 4652,4655 **** DESCR("SP-GiST support for suffix tree over text"); #define PROARGMODE_TABLE 't' #endif /* PG_PROC_H */ - --- 4652,4654 ---- *** a/src/include/commands/extension.h --- b/src/include/commands/extension.h *************** *** 30,40 **** extern Oid CurrentExtensionObject; extern void CreateExtension(CreateExtensionStmt *stmt); extern void RemoveExtensionById(Oid extId); extern Oid InsertExtensionTuple(const char *extName, Oid extOwner, Oid schemaOid, bool relocatable, const char *extVersion, Datum extConfig, Datum extCondition, ! List *requiredExtensions); extern void ExecAlterExtensionStmt(AlterExtensionStmt *stmt); --- 30,41 ---- extern void CreateExtension(CreateExtensionStmt *stmt); extern void RemoveExtensionById(Oid extId); + extern void RemoveExtensionFeatureById(Oid extFeatId); extern Oid InsertExtensionTuple(const char *extName, Oid extOwner, Oid schemaOid, bool relocatable, const char *extVersion, Datum extConfig, Datum extCondition, ! List *requiredExtensions, List *features); extern void ExecAlterExtensionStmt(AlterExtensionStmt *stmt); *************** *** 45,48 **** extern char *get_extension_name(Oid ext_oid); --- 46,51 ---- extern void AlterExtensionNamespace(List *names, const char *newschema); + extern char * get_extension_feature_name(Oid featoid); + #endif /* EXTENSION_H */ *** a/src/test/regress/expected/rules.out --- b/src/test/regress/expected/rules.out *************** *** 1279,1285 **** SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem viewname | definition ---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); ! pg_available_extension_versions | SELECT e.name, e.version, (x.extname IS NOT NULL) AS installed, e.superuser, e.relocatable, e.schema, e.requires, e.comment FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment) LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion)))); pg_available_extensions | SELECT e.name, e.default_version, x.extversion AS installed_version, e.comment FROM (pg_available_extensions() e(name, default_version, comment) LEFT JOIN pg_extension x ON ((e.name = x.extname))); pg_cursors | SELECT c.name, c.statement, c.is_holdable, c.is_binary, c.is_scrollable, c.creation_time FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time); pg_group | SELECT pg_authid.rolname AS groname, pg_authid.oid AS grosysid, ARRAY(SELECT pg_auth_members.member FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); --- 1279,1285 ---- viewname | definition ---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); ! pg_available_extension_versions | SELECT e.name, e.version, (x.extname IS NOT NULL) AS installed, e.superuser, e.relocatable, e.schema, e.requires, e.provides, e.comment FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, provides, comment) LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion)))); pg_available_extensions | SELECT e.name, e.default_version, x.extversion AS installed_version, e.comment FROM (pg_available_extensions() e(name, default_version, comment) LEFT JOIN pg_extension x ON ((e.name = x.extname))); pg_cursors | SELECT c.name, c.statement, c.is_holdable, c.is_binary, c.is_scrollable, c.creation_time FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time); pg_group | SELECT pg_authid.rolname AS groname, pg_authid.oid AS grosysid, ARRAY(SELECT pg_auth_members.member FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); *** a/src/test/regress/expected/sanity_check.out --- b/src/test/regress/expected/sanity_check.out *************** *** 103,108 **** SELECT relname, relhasindex --- 103,109 ---- pg_description | t pg_enum | t pg_extension | t + pg_extension_feature | t pg_foreign_data_wrapper | t pg_foreign_server | t pg_foreign_table | t *************** *** 164,170 **** SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (153 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 165,171 ---- timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (154 rows) -- -- another sanity check: every system catalog that has OIDs should have