Hi,
Sorry for the delays, I'm back on PostgreSQL related work again.
Hitoshi Harada <[email protected]> writes:
>>> I just tried DROP EXTENSION now, and found it broken :(
Please find v2 of the patch. I did change the dependency management in
between the simple cases and the more challenging ones and forgot that I
had to retest it all in between, which is what happen on a tight
schedule and when working at night, I guess.
So the best option I've found here had me add a new function in
pg_depend.c, it's working as intended now.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
diff --git a/contrib/pg_upgrade_support/pg_upgrade_support.c b/contrib/pg_upgrade_support/pg_upgrade_support.c
index 472f152..b5633d4 100644
--- a/contrib/pg_upgrade_support/pg_upgrade_support.c
+++ b/contrib/pg_upgrade_support/pg_upgrade_support.c
@@ -151,6 +151,7 @@ create_empty_extension(PG_FUNCTION_ARGS)
Datum extConfig;
Datum extCondition;
List *requiredExtensions;
+ List *features = NIL; /* FIXME, get features from catalogs */
if (PG_ARGISNULL(4))
extConfig = PointerGetDatum(NULL);
@@ -190,7 +191,8 @@ create_empty_extension(PG_FUNCTION_ARGS)
text_to_cstring(extVersion),
extConfig,
extCondition,
- requiredExtensions);
+ requiredExtensions,
+ features);
PG_RETURN_VOID();
}
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ea98cb7..8090758 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -149,6 +149,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-extension"><structname>pg_extension_feature</structname></link></entry>
+ <entry>features provided by installed extensions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-foreign-data-wrapper"><structname>pg_foreign_data_wrapper</structname></link></entry>
<entry>foreign-data wrapper definitions</entry>
</row>
@@ -3058,6 +3063,51 @@
</para>
</sect1>
+ <sect1 id="catalog-pg-extension-feature">
+ <title><structname>pg_extension_feature</structname></title>
+
+ <indexterm zone="catalog-pg-extension-feature">
+ <primary>pg_extension</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_extension_feature</structname> stores
+ information about the features provided by installed extensions.
+ See <xref linkend="extend-extensions"> for details about extensions.
+ </para>
+
+ <table>
+ <title><structname>pg_extension_features</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>extoid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-extension"><structname>pg_extension</structname></link>.oid</literal></entry>
+ <entry>Oid of the extension that provides this feature</entry>
+ </row>
+
+ <row>
+ <entry><structfield>extfeature</structfield></entry>
+ <entry><type>name</type></entry>
+ <entry></entry>
+ <entry>Name of the feature</entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
<sect1 id="catalog-pg-foreign-data-wrapper">
<title><structname>pg_foreign_data_wrapper</structname></title>
@@ -6815,11 +6865,17 @@
<row>
<entry><structfield>requires</structfield></entry>
<entry><type>name[]</type></entry>
- <entry>Names of prerequisite extensions,
+ <entry>Names of prerequisite features,
or <literal>NULL</literal> if none</entry>
</row>
<row>
+ <entry><structfield>provides</structfield></entry>
+ <entry><type>name[]</type></entry>
+ <entry>Names of provided features</entry>
+ </row>
+
+ <row>
<entry><structfield>comment</structfield></entry>
<entry><type>text</type></entry>
<entry>Comment string from the extension's control file</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 8d5b9d0..af5fc4c 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -463,9 +463,30 @@
<term><varname>requires</varname> (<type>string</type>)</term>
<listitem>
<para>
- A list of names of extensions that this extension depends on,
- for example <literal>requires = 'foo, bar'</literal>. Those
- extensions must be installed before this one can be installed.
+ A list of features that this extension depends on, for
+ example <literal>requires = 'foo, bar'</literal>. Those features
+ must be provided by an already installed extension before this one
+ can be installed.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>provides</varname> (<type>string</type>)</term>
+ <listitem>
+ <para>
+ A list of names of features that this extension provides, for
+ example <literal>provides = 'foo, extname_bugfix_12345'</literal>.
+ 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.
+ </para>
+ <para>
+ The extension's name itself is always considered a member of
+ the <literal>provides</literal> list, so that you can entirely omit
+ this parameter.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 5a4419d..fec3e0d 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -36,7 +36,7 @@ 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_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 \
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index db86262..87e3e80 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -34,6 +34,7 @@
#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"
@@ -157,7 +158,8 @@ static const Oid object_classes[MAX_OCLASS] = {
ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */
UserMappingRelationId, /* OCLASS_USER_MAPPING */
DefaultAclRelationId, /* OCLASS_DEFACL */
- ExtensionRelationId /* OCLASS_EXTENSION */
+ ExtensionRelationId, /* OCLASS_EXTENSION */
+ ExtensionFeatureRelationId /* OCLASS_EXTENSION_FEATURE */
};
@@ -1176,6 +1178,10 @@ doDeletion(const ObjectAddress *object)
RemoveExtensionById(object->objectId);
break;
+ case OCLASS_EXTENSION_FEATURE:
+ RemoveExtensionFeatureById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
@@ -2188,6 +2194,9 @@ getObjectClass(const ObjectAddress *object)
case ExtensionRelationId:
return OCLASS_EXTENSION;
+
+ case ExtensionFeatureRelationId:
+ return OCLASS_EXTENSION_FEATURE;
}
/* shouldn't get here */
@@ -2822,6 +2831,18 @@ getObjectDescription(const ObjectAddress *object)
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,
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 843f03d..3085866 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -281,6 +281,53 @@ deleteDependencyRecordsForClass(Oid classId, Oid objectId,
}
/*
+ * 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.
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 30b0bd0..0f0c718 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -161,7 +161,7 @@ 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
+ 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;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 6ecbbc7..eb90b05 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -36,6 +36,7 @@
#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,6 +74,7 @@ typedef struct ExtensionControlFile
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,6 +91,27 @@ typedef struct ExtensionVersionInfo
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,7 +124,10 @@ static void ApplyExtensionUpdates(Oid extensionOid,
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,6 +254,80 @@ get_extension_schema(Oid ext_oid)
}
/*
+ * 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, 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("extension's feature \"%s\" is not installed",
+ feature)));
+}
+
+/*
+ * 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;
+
+ /* use get_extension_providing error message for missing requirements */
+ 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,6 +657,21 @@ parse_extension_control_file(ExtensionControlFile *control,
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,7 +1301,7 @@ CreateExtension(CreateExtensionStmt *stmt)
ExtensionControlFile *pcontrol;
ExtensionControlFile *control;
List *updateVersions;
- List *requiredExtensions;
+ List *requiredFeatures;
List *requiredSchemas;
Oid extensionOid;
ListCell *lc;
@@ -1414,28 +1529,9 @@ 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);
- }
+ get_required_extension_features(control->requires,
+ &requiredFeatures,
+ &requiredSchemas);
/*
* Insert new tuple into pg_extension, and create dependency entries.
@@ -1445,7 +1541,8 @@ CreateExtension(CreateExtensionStmt *stmt)
versionName,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
- requiredExtensions);
+ requiredFeatures,
+ control->provides);
/*
* Apply any control-file comment on extension
@@ -1486,7 +1583,7 @@ Oid
InsertExtensionTuple(const char *extName, Oid extOwner,
Oid schemaOid, bool relocatable, const char *extVersion,
Datum extConfig, Datum extCondition,
- List *requiredExtensions)
+ List *requiredFeatures, List *features)
{
Oid extensionOid;
Relation rel;
@@ -1545,17 +1642,22 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
- foreach(lc, requiredExtensions)
+ foreach(lc, requiredFeatures)
{
- Oid reqext = lfirst_oid(lc);
- ObjectAddress otherext;
+ Oid reqfeat = lfirst_oid(lc);
+ ObjectAddress feature;
- otherext.classId = ExtensionRelationId;
- otherext.objectId = reqext;
- otherext.objectSubId = 0;
+ feature.classId = ExtensionFeatureRelationId;
+ feature.objectId = reqfeat;
+ feature.objectSubId = 0;
- recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+ 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);
@@ -1615,6 +1717,291 @@ RemoveExtensionById(Oid extId)
}
/*
+ * 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 = (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(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);
+ elog(NOTICE, "update_extension_feature_list deleted %d features", i);
+
+ /* 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,8 +2223,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
{
ExtensionControlFile *control;
char *vername;
- Datum values[7];
- bool nulls[7];
+ Datum values[8];
+ bool nulls[8];
/* must be a .sql file ... */
if (!is_extension_script_filename(de->d_name))
@@ -1905,11 +2292,52 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
NAMEDATALEN, false, 'c');
values[5] = PointerGetDatum(a);
}
+ /* provides */
+ nulls[6] = false;
+ if (control->provides == NIL)
+ {
+ Datum *datums;
+ ArrayType *a;
+
+ datums = (Datum *) palloc(1 * sizeof(Datum));
+ datums[0] =
+ DirectFunctionCall1(namein, CStringGetDatum(pcontrol->name));
+ a = construct_array(datums, 1,
+ NAMEOID,
+ NAMEDATALEN, false, 'c');
+ values[6] = PointerGetDatum(a);
+ }
+ else
+ {
+ 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[6] = true;
+ nulls[7] = true;
else
- values[6] = CStringGetTextDatum(control->comment);
+ values[7] = CStringGetTextDatum(control->comment);
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
@@ -2505,7 +2933,7 @@ ApplyExtensionUpdates(Oid extensionOid,
ExtensionControlFile *control;
char *schemaName;
Oid schemaOid;
- List *requiredExtensions;
+ List *requiredFeatures;
List *requiredSchemas;
Relation extRel;
ScanKeyData key[1];
@@ -2516,7 +2944,6 @@ 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)
@@ -2573,58 +3000,21 @@ 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.
+ * Update extension features list and dependencies
*/
- 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);
- }
+ 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,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 28e68c5..72c357e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -146,6 +146,7 @@ typedef enum ObjectClass
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,6 +212,9 @@ extern long deleteDependencyRecordsFor(Oid classId, Oid objectId,
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);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 450ec25..4ebad12 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -303,6 +303,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(o
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
diff --git a/src/include/catalog/pg_extension_feature.h b/src/include/catalog/pg_extension_feature.h
new file mode 100644
index 0000000..c602ee5
--- /dev/null
+++ b/src/include/catalog/pg_extension_feature.h
@@ -0,0 +1,57 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_extension_feature.h
+ * definition of the system "extension feature" relation
+ * (pg_extension_features), that traks 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 */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d926a88..25e9805 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4382,7 +4382,7 @@ DESCR("less-equal-greater");
/* Extensions */
DATA(insert OID = 3082 ( pg_available_extensions PGNSP PGUID 12 10 100 0 0 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 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_ ));
+DATA(insert OID = 3083 ( pg_available_extension_versions PGNSP PGUID 12 10 100 0 0 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 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");
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 7fc8a92..18973cc 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -30,11 +30,12 @@ extern Oid CurrentExtensionObject;
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 *requiredExtensions, List *features);
extern void ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
@@ -45,4 +46,6 @@ extern char *get_extension_name(Oid ext_oid);
extern void AlterExtensionNamespace(List *names, const char *newschema);
+extern char * get_extension_feature_name(Oid featoid);
+
#endif /* EXTENSION_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f67b8dc..9476fc8 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1279,7 +1279,7 @@ 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_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);
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 7f560d2..c0c1fff 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -103,6 +103,7 @@ SELECT relname, relhasindex
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,7 +165,7 @@ SELECT relname, relhasindex
timetz_tbl | f
tinterval_tbl | f
varchar_tbl | f
-(153 rows)
+(154 rows)
--
-- another sanity check: every system catalog that has OIDs should have
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers