ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
					 ResultRelInfo *relinfo,
					 ItemPointer tupleid, TupleTableSlot *slot)
{
	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
	HeapTuple	slottuple = ExecMaterializeSlot(slot);
	HeapTuple	newtuple = slottuple;
	TriggerData LocTriggerData;
	HeapTuple	trigtuple;
	HeapTuple	oldtuple;
	TupleTableSlot *newSlot;
	int			i;
	Bitmapset  *modifiedCols;
	Bitmapset  *keyCols;
	LockTupleMode lockmode;

	/*
	 * Compute lock mode to use.  If columns that are part of the key have not
	 * been modified, then we can use a weaker lock, allowing for better
	 * concurrency.
	 */
	modifiedCols = GetModifiedColumns(relinfo, estate);
	keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
										 INDEX_ATTR_BITMAP_KEY);
	if (bms_overlap(keyCols, modifiedCols))
		lockmode = LockTupleExclusive;
	else
		lockmode = LockTupleNoKeyExclusive;

	/* get a copy of the on-disk tuple we are planning to update */
	trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
								   lockmode, &newSlot);
	if (trigtuple == NULL)
		return NULL;			/* cancel the update action */

	/*
	 * In READ COMMITTED isolation level it's possible that target tuple was
	 * changed due to concurrent update.  In that case we have a raw subplan
	 * output tuple in newSlot, and need to run it through the junk filter to
	 * produce an insertable tuple.
	 *
	 * Caution: more than likely, the passed-in slot is the same as the
	 * junkfilter's output slot, so we are clobbering the original value of
	 * slottuple by doing the filtering.  This is OK since neither we nor our
	 * caller have any more interest in the prior contents of that slot.
	 */
	if (newSlot != NULL)
	{
		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
		slottuple = ExecMaterializeSlot(slot);
		newtuple = slottuple;
	}


	LocTriggerData.type = T_TriggerData;
	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_BEFORE;
	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
	for (i = 0; i < trigdesc->numtriggers; i++)
	{
		Trigger    *trigger = &trigdesc->triggers[i];

		if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_UPDATE))
			continue;
		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
							modifiedCols, trigtuple, newtuple))
			continue;

		LocTriggerData.tg_trigtuple = trigtuple;
		LocTriggerData.tg_newtuple = oldtuple = newtuple;
		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
		LocTriggerData.tg_trigger = trigger;
		newtuple = ExecCallTriggerFunc(&LocTriggerData,
									   i,
									   relinfo->ri_TrigFunctions,
									   relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(estate));
		if (oldtuple != newtuple && oldtuple != slottuple)
			heap_freetuple(oldtuple);
		if (newtuple == NULL)
		{
			heap_freetuple(trigtuple);
			return NULL;		/* "do nothing" */
		}
	}
	heap_freetuple(trigtuple);

	if (newtuple != slottuple)
	{
		/*
		 * Return the modified tuple using the es_trig_tuple_slot.	We assume
		 * the tuple was allocated in per-tuple memory context, and therefore
		 * will go away by itself. The tuple table slot should not try to
		 * clear it.
		 */
		TupleTableSlot *newslot = estate->es_trig_tuple_slot;
		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);

		if (newslot->tts_tupleDescriptor != tupdesc)
			ExecSetSlotDescriptor(newslot, tupdesc);
		ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
		slot = newslot;
	}
	return slot;
}