The easiest way to do that is to have ExecDelete() return the TM_Result status to ExecCrossPartitionUpdate(). I think ExecCrossPartitionUpdate() should then return the TM_Result status to ExecUpdateAct(), so that it can return the correct status, rather than just assuming TM_Updated on failure, though possibly that's not strictly necessary.
The simplest fix is for ExecMergeMatched() to pass canSetTag to ExecUpdateAct(), so that it updates the command tag, if it does a cross-partition update. That makes that code path in ExecMergeMatched() more consistent with ExecUpdate().
So I think we need something like the attached.
I think this is the right way to go. +1.
BTW, while testing this patch, I encountered some confusion regarding cross-partition update. As we know, cross-partition update works by first deleting the old tuple from the current partition. So if we have BEFORE ROW DELETE triggers that suppress the delete, the update would be suppressed. For in-partition update, there is no such problem. For instance (based on Alexander's query):
CREATE TABLE t (a int) PARTITION BY RANGE (a); CREATE TABLE tp1 PARTITION OF t FOR VALUES FROM (0) TO (10); CREATE TABLE tp2 PARTITION OF t FOR VALUES FROM (10) TO (20);
INSERT INTO t VALUES (0), (5); -- into tp1 INSERT INTO t VALUES (10), (15); -- into tp2
CREATE FUNCTION tf() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NULL; END; $$;
CREATE TRIGGER tr BEFORE DELETE ON t FOR EACH ROW EXECUTE PROCEDURE tf();
-- update suppressed by the BEFORE ROW DELETE trigger # UPDATE t SET a = 15 WHERE a = 0; UPDATE 0
-- update performed successfully # UPDATE t SET a = 5 WHERE a = 0; UPDATE 1