Wrong varnullingrels for MERGE into a trigger-updatable view

From: Tender Wang <tndrwang(at)gmail(dot)com>
To: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Cc: Dean Rasheed <dean(dot)a(dot)rasheed(at)gmail(dot)com>
Subject: Wrong varnullingrels for MERGE into a trigger-updatable view
Date: 2026-11-13 13:13:01
Message-ID: CAHewXNkYF4dZ85mFwScSd+bMYPLg=zvfL1GJz47dxpwFPPjuOg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

I found another “wrong varnullingrels” error involving MERGE into a
trigger-updatable view:

CREATE TABLE mt (id int, k int, v text);
CREATE VIEW mv AS SELECT id, k, v FROM mt;

CREATE FUNCTION mv_insert() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
RETURN NEW;
END
$$;

CREATE TRIGGER mv_insert
INSTEAD OF INSERT ON mv
FOR EACH ROW EXECUTE FUNCTION mv_insert();

CREATE TABLE ms (id int, k int, v text);
INSERT INTO ms VALUES (1, 1, 'source');

MERGE INTO mv t
USING ms s RIGHT JOIN ms w ON s.k = w.k
ON w.k = t.k AND t.k = 0
WHEN MATCHED AND s.k < t.id THEN DO NOTHING
WHEN NOT MATCHED AND s.v IS NOT NULL THEN
INSERT (v) VALUES (s.v);

On the current master, this fails during planning with an error like:

ERROR: wrong varnullingrels (b) (expected (b 6)) for Var 7/1

transform_MERGE_to_join() constructs a right join for the
WHEN NOT MATCHED BY TARGET action, making the target relation a
nullable side of the MERGE join. It marks target Vars in
parse->targetList as nullable by that join, but does not do the same
for target Vars in MERGE action expressions or the RETURNING list.

In this example, the “t.id” Var from the MATCHED action qual is later
added to the processed target list for evaluation by ModifyTable. It
does not contain the MERGE join's nullingrel, while the corresponding
Var produced by the child join does. The mismatch is eventually
detected by setrefs.c.

My initial fix mirrored the existing

if (jointype == JOIN_LEFT || jointype == JOIN_FULL)

block for right and full joins, using the target relation instead of
the source relation. However, that duplicated most of the code for
processing action quals, action target lists, and the RETURNING list.

The attached patch instead refactors the existing handling. It first
collects all relation RT indexes on nullable sides of the MERGE join:

- source relids for a left join;
- the target relid for a right join;
- both for a full join.

It then uses that set to mark Vars in the query target list, MERGE
action expressions, and RETURNING list. For left and full joins, it
also marks the copy of the merge join condition evaluated above the
join for NOT MATCHED BY SOURCE processing. The original condition in
the JoinExpr remains unchanged.

Thoughts?

--
Tender Wang

Attachment Content-Type Size
0001-Fix-nullingrels-for-expressions-above-MERGE-joins.patch text/plain 8.1 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Yash Jadhav 2026-11-13 13:14:24 Re: Extensible sync handler registration (register_sync_handler)
Previous Message He Kuang 2026-11-13 12:51:54 [PATCH] Fix hash join costing when the inner side is provably unique