mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Disallow collecting transition tuples from child foreign tables.
Commit9e6104c66disallowed transition tables on foreign tables, but failed to account for cases where a foreign table is a child table of a partitioned/inherited table on which transition tables exist, leading to incorrect transition tuples collected from such foreign tables for queries on the parent table triggering transition capture. This occurred not only for inherited UPDATE/DELETE but for partitioned INSERT later supported by commit3d956d956, which should have handled it at least for the INSERT case, but didn't. To fix, modify ExecAR*Triggers to throw an error if the given relation is a foreign table requesting transition capture. Also, this commit fixes make_modifytable so that in case of an inherited UPDATE/DELETE triggering transition capture, FDWs choose normal operations to modify child foreign tables, not DirectModify; which is needed because they would otherwise skip the calls to ExecAR*Triggers at execution, causing unexpected behavior. Author: Etsuro Fujita <etsuro.fujita@gmail.com> Reviewed-by: Amit Langote <amitlangote09@gmail.com> Discussion: https://postgr.es/m/CAPmGK14QJYikKzBDCe3jMbpGENnQ7popFmbEgm-XTNuk55oyHg%40mail.gmail.com Backpatch-through: 13
This commit is contained in:
		@@ -2606,6 +2606,15 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 | 
			
		||||
{
 | 
			
		||||
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 | 
			
		||||
 | 
			
		||||
	if (relinfo->ri_FdwRoutine && transition_capture &&
 | 
			
		||||
		transition_capture->tcs_insert_new_table)
 | 
			
		||||
	{
 | 
			
		||||
		Assert(relinfo->ri_RootResultRelInfo);
 | 
			
		||||
		ereport(ERROR,
 | 
			
		||||
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 | 
			
		||||
				 errmsg("cannot collect transition tuples from child foreign tables")));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ((trigdesc && trigdesc->trig_insert_after_row) ||
 | 
			
		||||
		(transition_capture && transition_capture->tcs_insert_new_table))
 | 
			
		||||
		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 | 
			
		||||
@@ -2864,6 +2873,15 @@ ExecARDeleteTriggers(EState *estate,
 | 
			
		||||
{
 | 
			
		||||
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 | 
			
		||||
 | 
			
		||||
	if (relinfo->ri_FdwRoutine && transition_capture &&
 | 
			
		||||
		transition_capture->tcs_delete_old_table)
 | 
			
		||||
	{
 | 
			
		||||
		Assert(relinfo->ri_RootResultRelInfo);
 | 
			
		||||
		ereport(ERROR,
 | 
			
		||||
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 | 
			
		||||
				 errmsg("cannot collect transition tuples from child foreign tables")));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ((trigdesc && trigdesc->trig_delete_after_row) ||
 | 
			
		||||
		(transition_capture && transition_capture->tcs_delete_old_table))
 | 
			
		||||
	{
 | 
			
		||||
@@ -3206,6 +3224,16 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 | 
			
		||||
{
 | 
			
		||||
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 | 
			
		||||
 | 
			
		||||
	if (relinfo->ri_FdwRoutine && transition_capture &&
 | 
			
		||||
		(transition_capture->tcs_update_old_table ||
 | 
			
		||||
		 transition_capture->tcs_update_new_table))
 | 
			
		||||
	{
 | 
			
		||||
		Assert(relinfo->ri_RootResultRelInfo);
 | 
			
		||||
		ereport(ERROR,
 | 
			
		||||
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 | 
			
		||||
				 errmsg("cannot collect transition tuples from child foreign tables")));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ((trigdesc && trigdesc->trig_update_after_row) ||
 | 
			
		||||
		(transition_capture &&
 | 
			
		||||
		 (transition_capture->tcs_update_old_table ||
 | 
			
		||||
 
 | 
			
		||||
@@ -6989,6 +6989,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 | 
			
		||||
				 List *mergeActionLists, int epqParam)
 | 
			
		||||
{
 | 
			
		||||
	ModifyTable *node = makeNode(ModifyTable);
 | 
			
		||||
	bool		transition_tables = false;
 | 
			
		||||
	bool		transition_tables_valid = false;
 | 
			
		||||
	List	   *fdw_private_list;
 | 
			
		||||
	Bitmapset  *direct_modify_plans;
 | 
			
		||||
	ListCell   *lc;
 | 
			
		||||
@@ -7134,7 +7136,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 | 
			
		||||
		 * callback functions needed for that and (2) there are no local
 | 
			
		||||
		 * structures that need to be run for each modified row: row-level
 | 
			
		||||
		 * triggers on the foreign table, stored generated columns, WITH CHECK
 | 
			
		||||
		 * OPTIONs from parent views.
 | 
			
		||||
		 * OPTIONs from parent views, transition tables on the named relation.
 | 
			
		||||
		 */
 | 
			
		||||
		direct_modify = false;
 | 
			
		||||
		if (fdwroutine != NULL &&
 | 
			
		||||
@@ -7145,7 +7147,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 | 
			
		||||
			withCheckOptionLists == NIL &&
 | 
			
		||||
			!has_row_triggers(root, rti, operation) &&
 | 
			
		||||
			!has_stored_generated_columns(root, rti))
 | 
			
		||||
			direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
 | 
			
		||||
		{
 | 
			
		||||
			/* transition_tables is the same for all result relations */
 | 
			
		||||
			if (!transition_tables_valid)
 | 
			
		||||
			{
 | 
			
		||||
				transition_tables = has_transition_tables(root,
 | 
			
		||||
														  nominalRelation,
 | 
			
		||||
														  operation);
 | 
			
		||||
				transition_tables_valid = true;
 | 
			
		||||
			}
 | 
			
		||||
			if (!transition_tables)
 | 
			
		||||
				direct_modify = fdwroutine->PlanDirectModify(root, node,
 | 
			
		||||
															 rti, i);
 | 
			
		||||
		}
 | 
			
		||||
		if (direct_modify)
 | 
			
		||||
			direct_modify_plans = bms_add_member(direct_modify_plans, i);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2217,6 +2217,60 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * has_transition_tables
 | 
			
		||||
 *
 | 
			
		||||
 * Detect whether the specified relation has any transition tables for event.
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
has_transition_tables(PlannerInfo *root, Index rti, CmdType event)
 | 
			
		||||
{
 | 
			
		||||
	RangeTblEntry *rte = planner_rt_fetch(rti, root);
 | 
			
		||||
	Relation	relation;
 | 
			
		||||
	TriggerDesc *trigDesc;
 | 
			
		||||
	bool		result = false;
 | 
			
		||||
 | 
			
		||||
	Assert(rte->rtekind == RTE_RELATION);
 | 
			
		||||
 | 
			
		||||
	/* Currently foreign tables cannot have transition tables */
 | 
			
		||||
	if (rte->relkind == RELKIND_FOREIGN_TABLE)
 | 
			
		||||
		return result;
 | 
			
		||||
 | 
			
		||||
	/* Assume we already have adequate lock */
 | 
			
		||||
	relation = table_open(rte->relid, NoLock);
 | 
			
		||||
 | 
			
		||||
	trigDesc = relation->trigdesc;
 | 
			
		||||
	switch (event)
 | 
			
		||||
	{
 | 
			
		||||
		case CMD_INSERT:
 | 
			
		||||
			if (trigDesc &&
 | 
			
		||||
				trigDesc->trig_insert_new_table)
 | 
			
		||||
				result = true;
 | 
			
		||||
			break;
 | 
			
		||||
		case CMD_UPDATE:
 | 
			
		||||
			if (trigDesc &&
 | 
			
		||||
				(trigDesc->trig_update_old_table ||
 | 
			
		||||
				 trigDesc->trig_update_new_table))
 | 
			
		||||
				result = true;
 | 
			
		||||
			break;
 | 
			
		||||
		case CMD_DELETE:
 | 
			
		||||
			if (trigDesc &&
 | 
			
		||||
				trigDesc->trig_delete_old_table)
 | 
			
		||||
				result = true;
 | 
			
		||||
			break;
 | 
			
		||||
			/* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
 | 
			
		||||
		case CMD_MERGE:
 | 
			
		||||
			result = false;
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			elog(ERROR, "unrecognized CmdType: %d", (int) event);
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	table_close(relation, NoLock);
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * has_stored_generated_columns
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user