From 5aa827d36138b46c4392ab4b8efe26550cd7e217 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Mon, 5 Oct 2026 11:45:58 -0400 Subject: [PATCH v1 2/5] Mark all source rels nullable for MERGE NOT MATCHED BY SOURCE When the source of MERGE is a join, its Vars reference the rels under the join, not the join RTE, so they were not marked as nullable by the MERGE join. RETURNING could then treat a NOT NULL source column as never NULL. --- src/backend/optimizer/prep/prepjointree.c | 11 +++++++---- src/test/regress/expected/merge.out | 13 +++++++++++++ src/test/regress/sql/merge.sql | 8 ++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index dfe320beccd..f47ca4f5615 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -348,27 +348,30 @@ transform_MERGE_to_join(Query *parse) */ if (jointype == JOIN_LEFT || jointype == JOIN_FULL) { + /* If the source is a join, its Vars mostly reference the base rels */ + Relids sourcerelids = get_relids_in_jointree(source, true, true); + parse->mergeJoinCondition = add_nulling_relids(parse->mergeJoinCondition, - bms_make_singleton(sourcerti), + sourcerelids, bms_make_singleton(joinrti)); foreach_node(MergeAction, action, parse->mergeActionList) { action->qual = add_nulling_relids(action->qual, - bms_make_singleton(sourcerti), + sourcerelids, bms_make_singleton(joinrti)); action->targetList = (List *) add_nulling_relids((Node *) action->targetList, - bms_make_singleton(sourcerti), + sourcerelids, bms_make_singleton(joinrti)); } parse->returningList = (List *) add_nulling_relids((Node *) parse->returningList, - bms_make_singleton(sourcerti), + sourcerelids, bms_make_singleton(joinrti)); } diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index 9cb1d87066a..8df955f5180 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -2810,6 +2810,19 @@ CREATE VIEW bug18634v AS SELECT * FROM bug18634t WHERE EXISTS (SELECT 1 FROM bug18634t); CREATE TABLE bug18634s (a int, b int, c text); INSERT INTO bug18634s VALUES (1, 2, 'src1'); +-- same with a join as the source, whose Vars must be marked as well +BEGIN; +MERGE INTO bug18634v t USING (bug18634s s JOIN bug18634s s2 ON s.a = s2.a) ON s.a = t.a + WHEN MATCHED THEN UPDATE SET b = s.b + WHEN NOT MATCHED BY SOURCE THEN DELETE + RETURNING merge_action(), s.c, s.tableoid IS NULL AS no_source, t.*; + merge_action | c | no_source | a | b | c +--------------+------+-----------+---+----+------ + UPDATE | src1 | f | 1 | 2 | tgt1 + DELETE | | t | 2 | 20 | tgt2 +(2 rows) + +ROLLBACK; MERGE INTO bug18634v t USING bug18634s s ON s.a = t.a WHEN MATCHED THEN UPDATE SET b = s.b WHEN NOT MATCHED BY SOURCE THEN DELETE diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql index 2660b19f238..9e136f6cb80 100644 --- a/src/test/regress/sql/merge.sql +++ b/src/test/regress/sql/merge.sql @@ -1803,6 +1803,14 @@ CREATE VIEW bug18634v AS CREATE TABLE bug18634s (a int, b int, c text); INSERT INTO bug18634s VALUES (1, 2, 'src1'); +-- same with a join as the source, whose Vars must be marked as well +BEGIN; +MERGE INTO bug18634v t USING (bug18634s s JOIN bug18634s s2 ON s.a = s2.a) ON s.a = t.a + WHEN MATCHED THEN UPDATE SET b = s.b + WHEN NOT MATCHED BY SOURCE THEN DELETE + RETURNING merge_action(), s.c, s.tableoid IS NULL AS no_source, t.*; +ROLLBACK; + MERGE INTO bug18634v t USING bug18634s s ON s.a = t.a WHEN MATCHED THEN UPDATE SET b = s.b WHEN NOT MATCHED BY SOURCE THEN DELETE -- 2.37.1 (Apple Git-137.1)