Hello.

I had a case of unexpected error caused by ALTER TABLE NO
INHERIT.

=# CREATE TABLE p (a int);
=# CREATE TABLE c1 () INHERITS (p);

session A=# BEGIN;
session A=# ALTER TABLE c1 NO INHERIT p;

session B=# EXPLAIN ANALYZE SELECT * FROM p;
(blocked)

session A=# COMMIT;

session B: ERROR:  could not find inherited attribute "a" of relation "c1"

This happens at least back to 9.1 to master and doesn't seem to
be a designed behavior.

The cause is that NO INHERIT doesn't take an exlusive lock on the
parent. This allows expand_inherited_rtentry to add the child
relation into appendrel after removal from the inheritance but
still exists.

I see two ways to fix this.

The first patch adds a recheck of inheritance relationship if the
corresponding attribute is missing in the child in
make_inh_translation_list(). The recheck is a bit complex but it
is not performed unless the sequence above is happen. It checks
duplication of relid (or cycles in inheritance) following
find_all_inheritors (but doing a bit different) but I'm not sure
it is really useful.


The second patch lets ALTER TABLE NO INHERIT to acquire locks on
the parent first.

Since the latter has a larger impact on the current behavior and
we already treat "DROP TABLE child" case in the similar way, I
suppose that the first approach would be preferable.


Any comments or thoughts?

regards,

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index e5fb52c..1670d11 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -42,6 +42,8 @@ typedef struct SeenRelsEntry
 	ListCell   *numparents_cell;	/* corresponding list cell */
 } SeenRelsEntry;
 
+static bool is_descendent_of_internal(Oid parentId, Oid childId,
+									  HTAB *seen_rels);
 /*
  * find_inheritance_children
  *
@@ -376,3 +378,71 @@ typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
 
 	return result;
 }
+
+/*
+ * Check if the child is really a descendent of the parent
+ */
+bool
+is_descendent_of(Oid parentId, Oid childId)
+{
+	HTAB	   *seen_rels;
+	HASHCTL		ctl;
+	bool		ischild = false;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(Oid);
+	ctl.hcxt = CurrentMemoryContext;
+
+	seen_rels = hash_create("is_descendent_of temporary table",
+							32, /* start small and extend */
+							&ctl,
+							HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+
+	ischild = is_descendent_of_internal(parentId, childId, seen_rels);
+
+	hash_destroy(seen_rels);
+
+	return ischild;
+}
+
+static bool
+is_descendent_of_internal(Oid parentId, Oid childId, HTAB *seen_rels)
+{
+	Relation	inhrel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	bool		ischild = false;
+	HeapTuple	inheritsTuple;
+
+	inhrel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key[0], Anum_pg_inherits_inhparent,
+				BTEqualStrategyNumber, F_OIDEQ,	ObjectIdGetDatum(parentId));
+	scan = systable_beginscan(inhrel, InheritsParentIndexId, true,
+							  NULL, 1, key);
+
+	while ((inheritsTuple = systable_getnext(scan)) != NULL)
+	{
+		bool found;
+		Oid inhrelid = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhrelid;
+
+		hash_search(seen_rels, &inhrelid, HASH_ENTER, &found);
+
+		/*
+		 * Recursively check into children. Although there can't theoretically
+		 * be any cycles in the inheritance graph, check the cycles following
+		 * find_all_inheritors.
+		 */
+		if (inhrelid == childId ||
+			(!found && is_descendent_of_internal(inhrelid, childId, seen_rels)))
+		{
+			ischild = true;
+			break;
+		}
+	}
+
+	systable_endscan(scan);
+	heap_close(inhrel, AccessShareLock);
+
+	return ischild;
+}
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index cf46b74..f1c582a 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -99,10 +99,9 @@ static List *generate_append_tlist(List *colTypes, List *colCollations,
 static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist);
 static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
 						 Index rti);
-static void make_inh_translation_list(Relation oldrelation,
+static List *make_inh_translation_list(Relation oldrelation,
 						  Relation newrelation,
-						  Index newvarno,
-						  List **translated_vars);
+						  Index newvarno);
 static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
 					List *translated_vars);
 static Node *adjust_appendrel_attrs_mutator(Node *node,
@@ -1502,14 +1501,28 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		 */
 		if (childrte->relkind != RELKIND_PARTITIONED_TABLE)
 		{
+			List *translated_vars =
+				make_inh_translation_list(oldrelation, newrelation,
+										  childRTindex);
+
+			if (!translated_vars)
+			{
+				/*
+				 * This childrel is no longer a child of the parent.
+				 * Close the child relation releasing locks.
+				 */
+				if (childOID != parentOID)
+					heap_close(newrelation, lockmode);
+				continue;
+			}
+
 			need_append = true;
 			appinfo = makeNode(AppendRelInfo);
 			appinfo->parent_relid = rti;
 			appinfo->child_relid = childRTindex;
 			appinfo->parent_reltype = oldrelation->rd_rel->reltype;
 			appinfo->child_reltype = newrelation->rd_rel->reltype;
-			make_inh_translation_list(oldrelation, newrelation, childRTindex,
-									  &appinfo->translated_vars);
+			appinfo->translated_vars = translated_vars;
 			appinfo->parent_reloid = parentOID;
 			appinfos = lappend(appinfos, appinfo);
 
@@ -1614,14 +1627,14 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 /*
  * make_inh_translation_list
  *	  Build the list of translations from parent Vars to child Vars for
- *	  an inheritance child.
+ *	  an inheritance child. Returns NULL if the two relations are no longer in
+ *	  a inheritance relationship.
  *
  * For paranoia's sake, we match type/collation as well as attribute name.
  */
-static void
+static List *
 make_inh_translation_list(Relation oldrelation, Relation newrelation,
-						  Index newvarno,
-						  List **translated_vars)
+						  Index newvarno)
 {
 	List	   *vars = NIL;
 	TupleDesc	old_tupdesc = RelationGetDescr(oldrelation);
@@ -1691,8 +1704,18 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
 					break;
 			}
 			if (new_attno >= newnatts)
+			{
+				/*
+				 * It's possible that newrelation is no longer a child of the
+				 * newrelation. We just ingore it for the case.
+				 */
+				if (!is_descendent_of(oldrelation->rd_id, newrelation->rd_id))
+					return NULL;
+
+				/* If still a child, something's wrong. */
 				elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"",
 					 attname, RelationGetRelationName(newrelation));
+			}
 		}
 
 		/* Found it, check type and collation match */
@@ -1711,7 +1734,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
 									 0));
 	}
 
-	*translated_vars = vars;
+	return vars;
 }
 
 /*
diff --git a/src/include/catalog/pg_inherits_fn.h b/src/include/catalog/pg_inherits_fn.h
index abfa476..3affe40 100644
--- a/src/include/catalog/pg_inherits_fn.h
+++ b/src/include/catalog/pg_inherits_fn.h
@@ -22,5 +22,6 @@ extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
 					List **parents);
 extern bool has_subclass(Oid relationId);
 extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
+extern bool is_descendent_of(Oid parentId, Oid childId);
 
 #endif							/* PG_INHERITS_FN_H */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7d9c769..f69631f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3397,6 +3397,30 @@ AlterTableGetLockLevel(List *cmds)
 }
 
 /*
+ * Some AT commands require to take a lock on the parent prior to the target.
+ */
+void
+AlterTableLockInhParent(List *cmds, LOCKMODE lockmode)
+{
+	ListCell   *lcmd;
+
+	foreach(lcmd, cmds)
+	{
+		AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
+
+		switch (cmd->subtype)
+		{		
+		case AT_DropInherit:
+			RangeVarGetRelid((RangeVar *)cmd->def, lockmode, false);
+			break;
+
+		default:
+			break;
+		}
+	}
+}
+
+/*
  * ATController provides top level control over the phases.
  *
  * parsetree is passed in to allow it to be passed to event triggers
@@ -11446,12 +11470,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of a partition")));
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
+	/* Requreid lock is already held */
+	parent_rel = heap_openrv(parent, NoLock);
 
 	/*
 	 * We don't bother to check ownership of the parent table --- ownership of
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ddacac8..ce3406b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1102,6 +1102,9 @@ ProcessUtilitySlow(ParseState *pstate,
 					 * permissions.
 					 */
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
+
+					/* Lock the parent first if required */
+					AlterTableLockInhParent(atstmt->cmds, lockmode);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
 					if (OidIsValid(relid))
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..c643326 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -27,6 +27,7 @@ extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 
 extern void RemoveRelations(DropStmt *drop);
 
+void AlterTableLockInhParent(List *cmds, LOCKMODE lockmode);
 extern Oid	AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);
 
 extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt);
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to