* In the current implementation, has_subclass returns whether a
* particular class *might* have a subclass. It will not return the
* correct result if a class had a subclass which was later dropped.
- * This is because relhassubclass in pg_class is not updated when a
- * subclass is dropped, primarily because of concurrency concerns.
+ * This is because relhassubclass in pg_class is not updated immediately
+ * when a subclass is dropped, primarily because of concurrency concerns.
*
* Currently has_subclass is only used as an efficiency hack to skip
- * unnecessary inheritance searches, so this is OK.
+ * unnecessary inheritance searches, so this is OK. Note that ANALYZE
+ * on a childless table will clean up the obsolete relhassubclass flag.
*
* Although this doesn't actually touch pg_inherits, it seems reasonable
* to keep it here since it's normally used with the other routines here.
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "commands/dbcommands.h"
+#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
#include "miscadmin.h"
/*
* Check that there's at least one descendant, else fail. This could
* happen despite analyze_rel's relhassubclass check, if table once had a
- * child but no longer does.
+ * child but no longer does. In that case, we can clear the
+ * relhassubclass field so as not to make the same mistake again later.
+ * (This is safe because we hold ShareUpdateExclusiveLock.)
*/
if (list_length(tableOIDs) < 2)
{
- /*
- * XXX It would be desirable to clear relhassubclass here, but we
- * don't have adequate lock to do that safely.
- */
+ /* CCI because we already updated the pg_class row in this command */
+ CommandCounterIncrement();
+ SetRelationHasSubclass(RelationGetRelid(onerel), false);
return 0;
}
static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
int16 seqNumber, Relation inhRelation);
static int findAttrByName(const char *attributeName, List *schema);
-static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass);
static void AlterIndexNamespaces(Relation classRel, Relation rel,
Oid oldNspOid, Oid newNspOid);
static void AlterSeqNamespaces(Relation classRel, Relation rel,
* add children to the same parent simultaneously, and that parent has
* no pre-existing children, then both will attempt to update the
* parent's relhassubclass field, leading to a "tuple concurrently
- * updated" error.
+ * updated" error. Also, this interlocks against a concurrent ANALYZE
+ * on the parent table, which might otherwise be attempting to clear
+ * the parent's relhassubclass field, if its previous children were
+ * recently dropped.
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
/*
* Mark the parent as having subclasses.
*/
- setRelhassubclassInRelation(parentOid, true);
+ SetRelationHasSubclass(parentOid, true);
}
/*
return 0;
}
+
/*
- * Update a relation's pg_class.relhassubclass entry to the given value
+ * SetRelationHasSubclass
+ * Set the value of the relation's relhassubclass field in pg_class.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ * ShareUpdateExclusiveLock is sufficient.
+ *
+ * NOTE: an important side-effect of this operation is that an SI invalidation
+ * message is sent out to all backends --- including me --- causing plans
+ * referencing the relation to be rebuilt with the new list of children.
+ * This must happen even if we find that no change is needed in the pg_class
+ * row.
*/
-static void
-setRelhassubclassInRelation(Oid relationId, bool relhassubclass)
+void
+SetRelationHasSubclass(Oid relationId, bool relhassubclass)
{
Relation relationRelation;
HeapTuple tuple;
/*
* Fetch a modifiable copy of the tuple, modify it, update pg_class.
- *
- * If the tuple already has the right relhassubclass setting, we don't
- * need to update it, but we still need to issue an SI inval message.
*/
relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));