*** 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; /* 9.3 should 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_wrapperforeign-data wrapper definitions
***************
*** 3058,3063 ****
--- 3063,3113 ----
+
+ pg_extension_feature
+
+
+ pg_extension_feature
+
+
+
+ The catalog pg_extension_feature stores
+ information about the features provided by installed extensions.
+ See for details about extensions.
+
+
+
+ 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
+
+
+
+
+
+ pg_foreign_data_wrapper
***************
*** 6828,6838 ****
requiresname[]
! Names of prerequisite extensions,
or NULL if nonecommenttextComment string from the extension's control file
--- 6878,6894 ----
requiresname[]
! Names of prerequisite features,
or NULL if none
+ provides
+ name[]
+ Names of provided features
+
+
+ commenttextComment 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"
***************
*** 72,78 **** typedef struct ExtensionControlFile
bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
bool superuser; /* must be superuser to install? */
int encoding; /* encoding of the script file, or -1 */
! List *requires; /* names of prerequisite extensions */
} ExtensionControlFile;
/*
--- 73,80 ----
bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
bool superuser; /* must be superuser to install? */
int encoding; /* encoding of the script file, or -1 */
! List *requires; /* names of prerequisite features */
! 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
+ lookup_extension_feature(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, ExtensionFeatureIndexId, 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;
+
+ lookup_extension_feature(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
***************
*** 553,559 **** parse_extension_control_file(ExtensionControlFile *control,
/* syntax error in name list */
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("parameter \"%s\" must be a list of extension names",
item->name)));
}
}
--- 653,674 ----
/* syntax error in name list */
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("parameter \"%s\" must be a list of extension features",
! 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 featuress",
item->name)));
}
}
***************
*** 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,2008 ----
}
/*
+ * 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..
+ */
+ lookup_extension_feature(feature, true, &ext, &featoid);
+ if (featoid != InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" provides feature \"%s\", but existing extension \"%s\" already provides this feature",
+ get_extension_name(extObject.objectId),
+ feature,
+ get_extension_name(ext))));
+
+ /*
+ * 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(ExtensionFeatureIndexId, 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))
--- 2224,2231 ----
{
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);
}
--- 2293,2330 ----
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];
--- 2920,2926 ----
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)
--- 2931,2936 ----
***************
*** 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,
--- 2987,3007 ----
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/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 7604,7609 **** ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
--- 7604,7610 ----
case OCLASS_USER_MAPPING:
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
+ case OCLASS_EXTENSION_FEATURE:
/*
* We don't expect any of these sorts of objects to depend on
*** 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,314 ----
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_index, 3181, on pg_extension_feature using btree(extoid oid_ops, extfeature name_ops));
+ #define ExtensionFeatureIndexId 3181
+
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