Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * parse_merge.c
4 : : * handle merge-statement in parser
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/parser/parse_merge.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : :
16 : : #include "postgres.h"
17 : :
18 : : #include "access/sysattr.h"
19 : : #include "nodes/makefuncs.h"
20 : : #include "parser/analyze.h"
21 : : #include "parser/parse_clause.h"
22 : : #include "parser/parse_collate.h"
23 : : #include "parser/parse_cte.h"
24 : : #include "parser/parse_expr.h"
25 : : #include "parser/parse_merge.h"
26 : : #include "parser/parse_relation.h"
27 : : #include "parser/parse_target.h"
28 : : #include "parser/parsetree.h"
29 : : #include "utils/rel.h"
30 : :
31 : : static void setNamespaceForMergeWhen(ParseState *pstate,
32 : : MergeWhenClause *mergeWhenClause,
33 : : Index targetRTI,
34 : : Index sourceRTI);
35 : : static void setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
36 : : bool rel_visible,
37 : : bool cols_visible);
38 : :
39 : : /*
40 : : * Make appropriate changes to the namespace visibility while transforming
41 : : * individual action's quals and targetlist expressions. In particular, for
42 : : * INSERT actions we must only see the source relation (since INSERT action is
43 : : * invoked for NOT MATCHED [BY TARGET] tuples and hence there is no target
44 : : * tuple to deal with). On the other hand, UPDATE and DELETE actions can see
45 : : * both source and target relations, unless invoked for NOT MATCHED BY SOURCE.
46 : : *
47 : : * Also, since the internal join node can hide the source and target
48 : : * relations, we must explicitly make the respective relation as visible so
49 : : * that columns can be referenced unqualified from these relations.
50 : : */
51 : : static void
1448 alvherre@alvh.no-ip. 52 :CBC 1605 : setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause,
53 : : Index targetRTI, Index sourceRTI)
54 : : {
55 : : RangeTblEntry *targetRelRTE,
56 : : *sourceRelRTE;
57 : :
58 : 1605 : targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable);
59 : 1605 : sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable);
60 : :
715 dean.a.rasheed@gmail 61 [ + + ]: 1605 : if (mergeWhenClause->matchKind == MERGE_WHEN_MATCHED)
62 : : {
1448 alvherre@alvh.no-ip. 63 [ + + + + : 987 : Assert(mergeWhenClause->commandType == CMD_UPDATE ||
- + ]
64 : : mergeWhenClause->commandType == CMD_DELETE ||
65 : : mergeWhenClause->commandType == CMD_NOTHING);
66 : :
67 : : /* MATCHED actions can see both target and source relations. */
68 : 987 : setNamespaceVisibilityForRTE(pstate->p_namespace,
69 : : targetRelRTE, true, true);
70 : 987 : setNamespaceVisibilityForRTE(pstate->p_namespace,
71 : : sourceRelRTE, true, true);
72 : : }
715 dean.a.rasheed@gmail 73 [ + + ]: 618 : else if (mergeWhenClause->matchKind == MERGE_WHEN_NOT_MATCHED_BY_SOURCE)
74 : : {
75 : : /*
76 : : * NOT MATCHED BY SOURCE actions can see the target relation, but they
77 : : * can't see the source relation.
78 : : */
79 [ + + - + : 76 : Assert(mergeWhenClause->commandType == CMD_UPDATE ||
- - ]
80 : : mergeWhenClause->commandType == CMD_DELETE ||
81 : : mergeWhenClause->commandType == CMD_NOTHING);
82 : 76 : setNamespaceVisibilityForRTE(pstate->p_namespace,
83 : : targetRelRTE, true, true);
84 : 76 : setNamespaceVisibilityForRTE(pstate->p_namespace,
85 : : sourceRelRTE, false, false);
86 : : }
87 : : else /* MERGE_WHEN_NOT_MATCHED_BY_TARGET */
88 : : {
89 : : /*
90 : : * NOT MATCHED [BY TARGET] actions can't see target relation, but they
91 : : * can see source relation.
92 : : */
1448 alvherre@alvh.no-ip. 93 [ + + - + ]: 542 : Assert(mergeWhenClause->commandType == CMD_INSERT ||
94 : : mergeWhenClause->commandType == CMD_NOTHING);
95 : 542 : setNamespaceVisibilityForRTE(pstate->p_namespace,
96 : : targetRelRTE, false, false);
97 : 542 : setNamespaceVisibilityForRTE(pstate->p_namespace,
98 : : sourceRelRTE, true, true);
99 : : }
100 : 1605 : }
101 : :
102 : : /*
103 : : * transformMergeStmt -
104 : : * transforms a MERGE statement
105 : : */
106 : : Query *
107 : 1061 : transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
108 : : {
109 : 1061 : Query *qry = makeNode(Query);
110 : : ListCell *l;
111 : 1061 : AclMode targetPerms = ACL_NO_RIGHTS;
112 : : bool is_terminal[NUM_MERGE_MATCH_KINDS];
113 : : Index sourceRTI;
114 : : List *mergeActionList;
115 : : ParseNamespaceItem *nsitem;
116 : :
117 : : /* There can't be any outer WITH to worry about */
118 [ - + ]: 1061 : Assert(pstate->p_ctenamespace == NIL);
119 : :
120 : 1061 : qry->commandType = CMD_MERGE;
121 : 1061 : qry->hasRecursive = false;
122 : :
123 : : /* process the WITH clause independently of all else */
124 [ + + ]: 1061 : if (stmt->withClause)
125 : : {
126 [ + + ]: 29 : if (stmt->withClause->recursive)
127 [ + - ]: 3 : ereport(ERROR,
128 : : (errcode(ERRCODE_SYNTAX_ERROR),
129 : : errmsg("WITH RECURSIVE is not supported for MERGE statement")));
130 : :
131 : 26 : qry->cteList = transformWithClause(pstate, stmt->withClause);
132 : 26 : qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
133 : : }
134 : :
135 : : /*
136 : : * Check WHEN clauses for permissions and sanity
137 : : */
715 dean.a.rasheed@gmail 138 : 1058 : is_terminal[MERGE_WHEN_MATCHED] = false;
139 : 1058 : is_terminal[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = false;
140 : 1058 : is_terminal[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = false;
1448 alvherre@alvh.no-ip. 141 [ + - + + : 2681 : foreach(l, stmt->mergeWhenClauses)
+ + ]
142 : : {
143 : 1626 : MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l);
144 : :
145 : : /*
146 : : * Collect permissions to check, according to action types. We require
147 : : * SELECT privileges for DO NOTHING because it'd be irregular to have
148 : : * a target relation with zero privileges checked, in case DO NOTHING
149 : : * is the only action. There's no damage from that: any meaningful
150 : : * MERGE command requires at least some access to the table anyway.
151 : : */
152 [ + + + + : 1626 : switch (mergeWhenClause->commandType)
- ]
153 : : {
154 : 538 : case CMD_INSERT:
155 : 538 : targetPerms |= ACL_INSERT;
156 : 538 : break;
157 : 775 : case CMD_UPDATE:
158 : 775 : targetPerms |= ACL_UPDATE;
159 : 775 : break;
160 : 265 : case CMD_DELETE:
161 : 265 : targetPerms |= ACL_DELETE;
162 : 265 : break;
163 : 48 : case CMD_NOTHING:
753 164 : 48 : targetPerms |= ACL_SELECT;
1448 165 : 48 : break;
1448 alvherre@alvh.no-ip. 166 :UBC 0 : default:
167 [ # # ]: 0 : elog(ERROR, "unknown action in MERGE WHEN clause");
168 : : }
169 : :
170 : : /*
171 : : * Check for unreachable WHEN clauses
172 : : */
715 dean.a.rasheed@gmail 173 [ + + ]:CBC 1626 : if (is_terminal[mergeWhenClause->matchKind])
1448 alvherre@alvh.no-ip. 174 [ + - ]: 3 : ereport(ERROR,
175 : : (errcode(ERRCODE_SYNTAX_ERROR),
176 : : errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
1160 dean.a.rasheed@gmail 177 [ + + ]: 1623 : if (mergeWhenClause->condition == NULL)
715 178 : 1210 : is_terminal[mergeWhenClause->matchKind] = true;
179 : : }
180 : :
181 : : /*
182 : : * Set up the MERGE target table. The target table is added to the
183 : : * namespace below and to joinlist in transform_MERGE_to_join, so don't do
184 : : * it here.
185 : : *
186 : : * Initially mergeTargetRelation is the same as resultRelation, so data is
187 : : * read from the table being updated. However, that might be changed by
188 : : * the rewriter, if the target is a trigger-updatable view, to allow
189 : : * target data to be read from the expanded view query while updating the
190 : : * original view relation.
191 : : */
1448 alvherre@alvh.no-ip. 192 : 2110 : qry->resultRelation = setTargetTable(pstate, stmt->relation,
193 : 1055 : stmt->relation->inh,
194 : : false, targetPerms);
745 dean.a.rasheed@gmail 195 : 1055 : qry->mergeTargetRelation = qry->resultRelation;
196 : :
197 : : /* The target relation must be a table or a view */
1448 alvherre@alvh.no-ip. 198 [ + + ]: 1055 : if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
745 dean.a.rasheed@gmail 199 [ + + ]: 427 : pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
200 [ + + ]: 351 : pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW)
1448 alvherre@alvh.no-ip. 201 [ + - ]: 3 : ereport(ERROR,
202 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
203 : : errmsg("cannot execute MERGE on relation \"%s\"",
204 : : RelationGetRelationName(pstate->p_target_relation)),
205 : : errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind)));
206 : :
207 : : /* Now transform the source relation to produce the source RTE. */
208 : 1052 : transformFromClause(pstate,
209 : 1052 : list_make1(stmt->sourceRelation));
210 : 1049 : sourceRTI = list_length(pstate->p_rtable);
211 : 1049 : nsitem = GetNSItemByRangeTablePosn(pstate, sourceRTI, 0);
212 : :
213 : : /*
214 : : * Check that the target table doesn't conflict with the source table.
215 : : * This would typically be a checkNameSpaceConflicts call, but we want a
216 : : * more specific error message.
217 : : */
218 : 1049 : if (strcmp(pstate->p_target_nsitem->p_names->aliasname,
219 [ + + ]: 1049 : nsitem->p_names->aliasname) == 0)
220 [ + - ]: 3 : ereport(ERROR,
221 : : errcode(ERRCODE_DUPLICATE_ALIAS),
222 : : errmsg("name \"%s\" specified more than once",
223 : : pstate->p_target_nsitem->p_names->aliasname),
224 : : errdetail("The name is used both as MERGE target table and data source."));
225 : :
226 : : /*
227 : : * There's no need for a targetlist here; it'll be set up by
228 : : * preprocess_targetlist later.
229 : : */
1433 230 : 1046 : qry->targetList = NIL;
1448 231 : 1046 : qry->rtable = pstate->p_rtable;
1195 232 : 1046 : qry->rteperminfos = pstate->p_rteperminfos;
233 : :
234 : : /*
235 : : * Transform the join condition. This includes references to the target
236 : : * side, so add that to the namespace.
237 : : */
1448 238 : 1046 : addNSItemToQuery(pstate, pstate->p_target_nsitem, false, true, true);
715 dean.a.rasheed@gmail 239 : 1046 : qry->mergeJoinCondition = transformExpr(pstate, stmt->joinCondition,
240 : : EXPR_KIND_JOIN_ON);
241 : :
242 : : /*
243 : : * Create the temporary query's jointree using the joinlist we built using
244 : : * just the source relation; the target relation is not included. The join
245 : : * will be constructed fully by transform_MERGE_to_join.
246 : : */
247 : 1046 : qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
248 : :
249 : : /* Transform the RETURNING list, if any */
423 250 : 1046 : transformReturningClause(pstate, qry, stmt->returningClause,
251 : : EXPR_KIND_MERGE_RETURNING);
252 : :
253 : : /*
254 : : * We now have a good query shape, so now look at the WHEN conditions and
255 : : * action targetlists.
256 : : *
257 : : * Overall, the MERGE Query's targetlist is NIL.
258 : : *
259 : : * Each individual action has its own targetlist that needs separate
260 : : * transformation. These transforms don't do anything to the overall
261 : : * targetlist, since that is only used for resjunk columns.
262 : : *
263 : : * We can reference any column in Target or Source, which is OK because
264 : : * both of those already have RTEs. There is nothing like the EXCLUDED
265 : : * pseudo-relation for INSERT ON CONFLICT.
266 : : */
1448 alvherre@alvh.no-ip. 267 : 1046 : mergeActionList = NIL;
268 [ + - + + : 2633 : foreach(l, stmt->mergeWhenClauses)
+ + ]
269 : : {
270 : 1605 : MergeWhenClause *mergeWhenClause = lfirst_node(MergeWhenClause, l);
271 : : MergeAction *action;
272 : :
273 : 1605 : action = makeNode(MergeAction);
274 : 1605 : action->commandType = mergeWhenClause->commandType;
715 dean.a.rasheed@gmail 275 : 1605 : action->matchKind = mergeWhenClause->matchKind;
276 : :
277 : : /*
278 : : * Set namespace for the specific action. This must be done before
279 : : * analyzing the WHEN quals and the action targetlist.
280 : : */
1448 alvherre@alvh.no-ip. 281 : 1605 : setNamespaceForMergeWhen(pstate, mergeWhenClause,
282 : 1605 : qry->resultRelation,
283 : : sourceRTI);
284 : :
285 : : /*
286 : : * Transform the WHEN condition.
287 : : *
288 : : * Note that these quals are NOT added to the join quals; instead they
289 : : * are evaluated separately during execution to decide which of the
290 : : * WHEN MATCHED or WHEN NOT MATCHED actions to execute.
291 : : */
292 : 1605 : action->qual = transformWhereClause(pstate, mergeWhenClause->condition,
293 : : EXPR_KIND_MERGE_WHEN, "WHEN");
294 : :
295 : : /*
296 : : * Transform target lists for each INSERT and UPDATE action stmt
297 : : */
298 [ + + + + : 1596 : switch (action->commandType)
- ]
299 : : {
300 : 526 : case CMD_INSERT:
301 : : {
302 : 526 : List *exprList = NIL;
303 : : ListCell *lc;
304 : : RTEPermissionInfo *perminfo;
305 : : ListCell *icols;
306 : : ListCell *attnos;
307 : : List *icolumns;
308 : : List *attrnos;
309 : :
310 : 526 : icolumns = checkInsertTargets(pstate,
311 : : mergeWhenClause->targetList,
312 : : &attrnos);
313 [ - + ]: 526 : Assert(list_length(icolumns) == list_length(attrnos));
314 : :
315 : 526 : action->override = mergeWhenClause->override;
316 : :
317 : : /*
318 : : * Handle INSERT much like in transformInsertStmt
319 : : */
320 [ + + ]: 526 : if (mergeWhenClause->values == NIL)
321 : : {
322 : : /*
323 : : * We have INSERT ... DEFAULT VALUES. We can handle
324 : : * this case by emitting an empty targetlist --- all
325 : : * columns will be defaulted when the planner expands
326 : : * the targetlist.
327 : : */
328 : 12 : exprList = NIL;
329 : : }
330 : : else
331 : : {
332 : : /*
333 : : * Process INSERT ... VALUES with a single VALUES
334 : : * sublist. We treat this case separately for
335 : : * efficiency. The sublist is just computed directly
336 : : * as the Query's targetlist, with no VALUES RTE. So
337 : : * it works just like a SELECT without any FROM.
338 : : */
339 : :
340 : : /*
341 : : * Do basic expression transformation (same as a ROW()
342 : : * expr, but allow SetToDefault at top level)
343 : : */
344 : 514 : exprList = transformExpressionList(pstate,
345 : : mergeWhenClause->values,
346 : : EXPR_KIND_VALUES_SINGLE,
347 : : true);
348 : :
349 : : /* Prepare row for assignment to target table */
350 : 508 : exprList = transformInsertRow(pstate, exprList,
351 : : mergeWhenClause->targetList,
352 : : icolumns, attrnos,
353 : : false);
354 : : }
355 : :
356 : : /*
357 : : * Generate action's target list using the computed list
358 : : * of expressions. Also, mark all the target columns as
359 : : * needing insert permissions.
360 : : */
1195 361 : 520 : perminfo = pstate->p_target_nsitem->p_perminfo;
1448 362 [ + + + + : 1663 : forthree(lc, exprList, icols, icolumns, attnos, attrnos)
+ - + + +
- + + + +
+ - + - +
+ ]
363 : : {
364 : 1143 : Expr *expr = (Expr *) lfirst(lc);
365 : 1143 : ResTarget *col = lfirst_node(ResTarget, icols);
366 : 1143 : AttrNumber attr_num = (AttrNumber) lfirst_int(attnos);
367 : : TargetEntry *tle;
368 : :
369 : 1143 : tle = makeTargetEntry(expr,
370 : : attr_num,
371 : : col->name,
372 : : false);
373 : 1143 : action->targetList = lappend(action->targetList, tle);
374 : :
1195 375 : 1143 : perminfo->insertedCols =
376 : 1143 : bms_add_member(perminfo->insertedCols,
377 : : attr_num - FirstLowInvalidHeapAttributeNumber);
378 : : }
379 : : }
1448 380 : 520 : break;
381 : 769 : case CMD_UPDATE:
31 dean.a.rasheed@gmail 382 :GNC 766 : action->targetList =
383 : 769 : transformUpdateTargetList(pstate,
384 : : mergeWhenClause->targetList);
1448 alvherre@alvh.no-ip. 385 :CBC 766 : break;
386 : 256 : case CMD_DELETE:
387 : 256 : break;
388 : :
389 : 45 : case CMD_NOTHING:
390 : 45 : action->targetList = NIL;
391 : 45 : break;
1448 alvherre@alvh.no-ip. 392 :UBC 0 : default:
393 [ # # ]: 0 : elog(ERROR, "unknown action in MERGE WHEN clause");
394 : : }
395 : :
1448 alvherre@alvh.no-ip. 396 :CBC 1587 : mergeActionList = lappend(mergeActionList, action);
397 : : }
398 : :
399 : 1028 : qry->mergeActionList = mergeActionList;
400 : :
401 : 1028 : qry->hasTargetSRFs = false;
402 : 1028 : qry->hasSubLinks = pstate->p_hasSubLinks;
403 : :
404 : 1028 : assign_query_collations(pstate, qry);
405 : :
406 : 1028 : return qry;
407 : : }
408 : :
409 : : static void
410 : 3210 : setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
411 : : bool rel_visible,
412 : : bool cols_visible)
413 : : {
414 : : ListCell *lc;
415 : :
416 [ + - + - : 5007 : foreach(lc, namespace)
+ - ]
417 : : {
418 : 5007 : ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
419 : :
420 [ + + ]: 5007 : if (nsitem->p_rte == rte)
421 : : {
422 : 3210 : nsitem->p_rel_visible = rel_visible;
423 : 3210 : nsitem->p_cols_visible = cols_visible;
424 : 3210 : break;
425 : : }
426 : : }
427 : 3210 : }
|