/src/postgres/src/backend/parser/parse_merge.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * parse_merge.c |
4 | | * handle merge-statement in parser |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, 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 |
52 | | setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause, |
53 | | Index targetRTI, Index sourceRTI) |
54 | 0 | { |
55 | 0 | RangeTblEntry *targetRelRTE, |
56 | 0 | *sourceRelRTE; |
57 | |
|
58 | 0 | targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable); |
59 | 0 | sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable); |
60 | |
|
61 | 0 | if (mergeWhenClause->matchKind == MERGE_WHEN_MATCHED) |
62 | 0 | { |
63 | 0 | Assert(mergeWhenClause->commandType == CMD_UPDATE || |
64 | 0 | mergeWhenClause->commandType == CMD_DELETE || |
65 | 0 | mergeWhenClause->commandType == CMD_NOTHING); |
66 | | |
67 | | /* MATCHED actions can see both target and source relations. */ |
68 | 0 | setNamespaceVisibilityForRTE(pstate->p_namespace, |
69 | 0 | targetRelRTE, true, true); |
70 | 0 | setNamespaceVisibilityForRTE(pstate->p_namespace, |
71 | 0 | sourceRelRTE, true, true); |
72 | 0 | } |
73 | 0 | else if (mergeWhenClause->matchKind == MERGE_WHEN_NOT_MATCHED_BY_SOURCE) |
74 | 0 | { |
75 | | /* |
76 | | * NOT MATCHED BY SOURCE actions can see the target relation, but they |
77 | | * can't see the source relation. |
78 | | */ |
79 | 0 | Assert(mergeWhenClause->commandType == CMD_UPDATE || |
80 | 0 | mergeWhenClause->commandType == CMD_DELETE || |
81 | 0 | mergeWhenClause->commandType == CMD_NOTHING); |
82 | 0 | setNamespaceVisibilityForRTE(pstate->p_namespace, |
83 | 0 | targetRelRTE, true, true); |
84 | 0 | setNamespaceVisibilityForRTE(pstate->p_namespace, |
85 | 0 | sourceRelRTE, false, false); |
86 | 0 | } |
87 | 0 | else /* MERGE_WHEN_NOT_MATCHED_BY_TARGET */ |
88 | 0 | { |
89 | | /* |
90 | | * NOT MATCHED [BY TARGET] actions can't see target relation, but they |
91 | | * can see source relation. |
92 | | */ |
93 | 0 | Assert(mergeWhenClause->commandType == CMD_INSERT || |
94 | 0 | mergeWhenClause->commandType == CMD_NOTHING); |
95 | 0 | setNamespaceVisibilityForRTE(pstate->p_namespace, |
96 | 0 | targetRelRTE, false, false); |
97 | 0 | setNamespaceVisibilityForRTE(pstate->p_namespace, |
98 | 0 | sourceRelRTE, true, true); |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | | /* |
103 | | * transformMergeStmt - |
104 | | * transforms a MERGE statement |
105 | | */ |
106 | | Query * |
107 | | transformMergeStmt(ParseState *pstate, MergeStmt *stmt) |
108 | 0 | { |
109 | 0 | Query *qry = makeNode(Query); |
110 | 0 | ListCell *l; |
111 | 0 | AclMode targetPerms = ACL_NO_RIGHTS; |
112 | 0 | bool is_terminal[NUM_MERGE_MATCH_KINDS]; |
113 | 0 | Index sourceRTI; |
114 | 0 | List *mergeActionList; |
115 | 0 | ParseNamespaceItem *nsitem; |
116 | | |
117 | | /* There can't be any outer WITH to worry about */ |
118 | 0 | Assert(pstate->p_ctenamespace == NIL); |
119 | |
|
120 | 0 | qry->commandType = CMD_MERGE; |
121 | 0 | qry->hasRecursive = false; |
122 | | |
123 | | /* process the WITH clause independently of all else */ |
124 | 0 | if (stmt->withClause) |
125 | 0 | { |
126 | 0 | if (stmt->withClause->recursive) |
127 | 0 | ereport(ERROR, |
128 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
129 | 0 | errmsg("WITH RECURSIVE is not supported for MERGE statement"))); |
130 | | |
131 | 0 | qry->cteList = transformWithClause(pstate, stmt->withClause); |
132 | 0 | qry->hasModifyingCTE = pstate->p_hasModifyingCTE; |
133 | 0 | } |
134 | | |
135 | | /* |
136 | | * Check WHEN clauses for permissions and sanity |
137 | | */ |
138 | 0 | is_terminal[MERGE_WHEN_MATCHED] = false; |
139 | 0 | is_terminal[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = false; |
140 | 0 | is_terminal[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = false; |
141 | 0 | foreach(l, stmt->mergeWhenClauses) |
142 | 0 | { |
143 | 0 | 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 | 0 | switch (mergeWhenClause->commandType) |
153 | 0 | { |
154 | 0 | case CMD_INSERT: |
155 | 0 | targetPerms |= ACL_INSERT; |
156 | 0 | break; |
157 | 0 | case CMD_UPDATE: |
158 | 0 | targetPerms |= ACL_UPDATE; |
159 | 0 | break; |
160 | 0 | case CMD_DELETE: |
161 | 0 | targetPerms |= ACL_DELETE; |
162 | 0 | break; |
163 | 0 | case CMD_NOTHING: |
164 | 0 | targetPerms |= ACL_SELECT; |
165 | 0 | break; |
166 | 0 | default: |
167 | 0 | elog(ERROR, "unknown action in MERGE WHEN clause"); |
168 | 0 | } |
169 | | |
170 | | /* |
171 | | * Check for unreachable WHEN clauses |
172 | | */ |
173 | 0 | if (is_terminal[mergeWhenClause->matchKind]) |
174 | 0 | ereport(ERROR, |
175 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
176 | 0 | errmsg("unreachable WHEN clause specified after unconditional WHEN clause"))); |
177 | 0 | if (mergeWhenClause->condition == NULL) |
178 | 0 | is_terminal[mergeWhenClause->matchKind] = true; |
179 | 0 | } |
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 | | */ |
192 | 0 | qry->resultRelation = setTargetTable(pstate, stmt->relation, |
193 | 0 | stmt->relation->inh, |
194 | 0 | false, targetPerms); |
195 | 0 | qry->mergeTargetRelation = qry->resultRelation; |
196 | | |
197 | | /* The target relation must be a table or a view */ |
198 | 0 | if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION && |
199 | 0 | pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && |
200 | 0 | pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW) |
201 | 0 | ereport(ERROR, |
202 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
203 | 0 | errmsg("cannot execute MERGE on relation \"%s\"", |
204 | 0 | RelationGetRelationName(pstate->p_target_relation)), |
205 | 0 | errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind))); |
206 | | |
207 | | /* Now transform the source relation to produce the source RTE. */ |
208 | 0 | transformFromClause(pstate, |
209 | 0 | list_make1(stmt->sourceRelation)); |
210 | 0 | sourceRTI = list_length(pstate->p_rtable); |
211 | 0 | 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 | 0 | if (strcmp(pstate->p_target_nsitem->p_names->aliasname, |
219 | 0 | nsitem->p_names->aliasname) == 0) |
220 | 0 | ereport(ERROR, |
221 | 0 | errcode(ERRCODE_DUPLICATE_ALIAS), |
222 | 0 | errmsg("name \"%s\" specified more than once", |
223 | 0 | pstate->p_target_nsitem->p_names->aliasname), |
224 | 0 | 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 | | */ |
230 | 0 | qry->targetList = NIL; |
231 | 0 | qry->rtable = pstate->p_rtable; |
232 | 0 | 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 | | */ |
238 | 0 | addNSItemToQuery(pstate, pstate->p_target_nsitem, false, true, true); |
239 | 0 | qry->mergeJoinCondition = transformExpr(pstate, stmt->joinCondition, |
240 | 0 | 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 | 0 | qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
248 | | |
249 | | /* Transform the RETURNING list, if any */ |
250 | 0 | transformReturningClause(pstate, qry, stmt->returningClause, |
251 | 0 | 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 | | */ |
267 | 0 | mergeActionList = NIL; |
268 | 0 | foreach(l, stmt->mergeWhenClauses) |
269 | 0 | { |
270 | 0 | MergeWhenClause *mergeWhenClause = lfirst_node(MergeWhenClause, l); |
271 | 0 | MergeAction *action; |
272 | |
|
273 | 0 | action = makeNode(MergeAction); |
274 | 0 | action->commandType = mergeWhenClause->commandType; |
275 | 0 | 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 | | */ |
281 | 0 | setNamespaceForMergeWhen(pstate, mergeWhenClause, |
282 | 0 | qry->resultRelation, |
283 | 0 | 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 | 0 | action->qual = transformWhereClause(pstate, mergeWhenClause->condition, |
293 | 0 | EXPR_KIND_MERGE_WHEN, "WHEN"); |
294 | | |
295 | | /* |
296 | | * Transform target lists for each INSERT and UPDATE action stmt |
297 | | */ |
298 | 0 | switch (action->commandType) |
299 | 0 | { |
300 | 0 | case CMD_INSERT: |
301 | 0 | { |
302 | 0 | List *exprList = NIL; |
303 | 0 | ListCell *lc; |
304 | 0 | RTEPermissionInfo *perminfo; |
305 | 0 | ListCell *icols; |
306 | 0 | ListCell *attnos; |
307 | 0 | List *icolumns; |
308 | 0 | List *attrnos; |
309 | |
|
310 | 0 | pstate->p_is_insert = true; |
311 | |
|
312 | 0 | icolumns = checkInsertTargets(pstate, |
313 | 0 | mergeWhenClause->targetList, |
314 | 0 | &attrnos); |
315 | 0 | Assert(list_length(icolumns) == list_length(attrnos)); |
316 | |
|
317 | 0 | action->override = mergeWhenClause->override; |
318 | | |
319 | | /* |
320 | | * Handle INSERT much like in transformInsertStmt |
321 | | */ |
322 | 0 | if (mergeWhenClause->values == NIL) |
323 | 0 | { |
324 | | /* |
325 | | * We have INSERT ... DEFAULT VALUES. We can handle |
326 | | * this case by emitting an empty targetlist --- all |
327 | | * columns will be defaulted when the planner expands |
328 | | * the targetlist. |
329 | | */ |
330 | 0 | exprList = NIL; |
331 | 0 | } |
332 | 0 | else |
333 | 0 | { |
334 | | /* |
335 | | * Process INSERT ... VALUES with a single VALUES |
336 | | * sublist. We treat this case separately for |
337 | | * efficiency. The sublist is just computed directly |
338 | | * as the Query's targetlist, with no VALUES RTE. So |
339 | | * it works just like a SELECT without any FROM. |
340 | | */ |
341 | | |
342 | | /* |
343 | | * Do basic expression transformation (same as a ROW() |
344 | | * expr, but allow SetToDefault at top level) |
345 | | */ |
346 | 0 | exprList = transformExpressionList(pstate, |
347 | 0 | mergeWhenClause->values, |
348 | 0 | EXPR_KIND_VALUES_SINGLE, |
349 | 0 | true); |
350 | | |
351 | | /* Prepare row for assignment to target table */ |
352 | 0 | exprList = transformInsertRow(pstate, exprList, |
353 | 0 | mergeWhenClause->targetList, |
354 | 0 | icolumns, attrnos, |
355 | 0 | false); |
356 | 0 | } |
357 | | |
358 | | /* |
359 | | * Generate action's target list using the computed list |
360 | | * of expressions. Also, mark all the target columns as |
361 | | * needing insert permissions. |
362 | | */ |
363 | 0 | perminfo = pstate->p_target_nsitem->p_perminfo; |
364 | 0 | forthree(lc, exprList, icols, icolumns, attnos, attrnos) |
365 | 0 | { |
366 | 0 | Expr *expr = (Expr *) lfirst(lc); |
367 | 0 | ResTarget *col = lfirst_node(ResTarget, icols); |
368 | 0 | AttrNumber attr_num = (AttrNumber) lfirst_int(attnos); |
369 | 0 | TargetEntry *tle; |
370 | |
|
371 | 0 | tle = makeTargetEntry(expr, |
372 | 0 | attr_num, |
373 | 0 | col->name, |
374 | 0 | false); |
375 | 0 | action->targetList = lappend(action->targetList, tle); |
376 | |
|
377 | 0 | perminfo->insertedCols = |
378 | 0 | bms_add_member(perminfo->insertedCols, |
379 | 0 | attr_num - FirstLowInvalidHeapAttributeNumber); |
380 | 0 | } |
381 | 0 | } |
382 | 0 | break; |
383 | 0 | case CMD_UPDATE: |
384 | 0 | { |
385 | 0 | pstate->p_is_insert = false; |
386 | 0 | action->targetList = |
387 | 0 | transformUpdateTargetList(pstate, |
388 | 0 | mergeWhenClause->targetList); |
389 | 0 | } |
390 | 0 | break; |
391 | 0 | case CMD_DELETE: |
392 | 0 | break; |
393 | | |
394 | 0 | case CMD_NOTHING: |
395 | 0 | action->targetList = NIL; |
396 | 0 | break; |
397 | 0 | default: |
398 | 0 | elog(ERROR, "unknown action in MERGE WHEN clause"); |
399 | 0 | } |
400 | | |
401 | 0 | mergeActionList = lappend(mergeActionList, action); |
402 | 0 | } |
403 | | |
404 | 0 | qry->mergeActionList = mergeActionList; |
405 | |
|
406 | 0 | qry->hasTargetSRFs = false; |
407 | 0 | qry->hasSubLinks = pstate->p_hasSubLinks; |
408 | |
|
409 | 0 | assign_query_collations(pstate, qry); |
410 | |
|
411 | 0 | return qry; |
412 | 0 | } |
413 | | |
414 | | static void |
415 | | setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte, |
416 | | bool rel_visible, |
417 | | bool cols_visible) |
418 | 0 | { |
419 | 0 | ListCell *lc; |
420 | |
|
421 | 0 | foreach(lc, namespace) |
422 | 0 | { |
423 | 0 | ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc); |
424 | |
|
425 | 0 | if (nsitem->p_rte == rte) |
426 | 0 | { |
427 | 0 | nsitem->p_rel_visible = rel_visible; |
428 | 0 | nsitem->p_cols_visible = cols_visible; |
429 | 0 | break; |
430 | 0 | } |
431 | 0 | } |
432 | 0 | } |