MERGE ... DO NOTHING: require SELECT privileges
[pgsql.git] / src / backend / parser / parse_merge.c
blob73f7a48b3c69beb10eea1682db4e4962990fb965
1 /*-------------------------------------------------------------------------
3 * parse_merge.c
4 * handle merge-statement in parser
6 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
10 * IDENTIFICATION
11 * src/backend/parser/parse_merge.c
13 *-------------------------------------------------------------------------
16 #include "postgres.h"
18 #include "access/sysattr.h"
19 #include "miscadmin.h"
20 #include "nodes/makefuncs.h"
21 #include "parser/analyze.h"
22 #include "parser/parse_collate.h"
23 #include "parser/parsetree.h"
24 #include "parser/parser.h"
25 #include "parser/parse_clause.h"
26 #include "parser/parse_cte.h"
27 #include "parser/parse_expr.h"
28 #include "parser/parse_merge.h"
29 #include "parser/parse_relation.h"
30 #include "parser/parse_target.h"
31 #include "utils/rel.h"
32 #include "utils/relcache.h"
34 static void setNamespaceForMergeWhen(ParseState *pstate,
35 MergeWhenClause *mergeWhenClause,
36 Index targetRTI,
37 Index sourceRTI);
38 static void setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
39 bool rel_visible,
40 bool cols_visible);
43 * Make appropriate changes to the namespace visibility while transforming
44 * individual action's quals and targetlist expressions. In particular, for
45 * INSERT actions we must only see the source relation (since INSERT action is
46 * invoked for NOT MATCHED tuples and hence there is no target tuple to deal
47 * with). On the other hand, UPDATE and DELETE actions can see both source and
48 * target relations.
50 * Also, since the internal join node can hide the source and target
51 * relations, we must explicitly make the respective relation as visible so
52 * that columns can be referenced unqualified from these relations.
54 static void
55 setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause,
56 Index targetRTI, Index sourceRTI)
58 RangeTblEntry *targetRelRTE,
59 *sourceRelRTE;
61 targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable);
62 sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable);
64 if (mergeWhenClause->matched)
66 Assert(mergeWhenClause->commandType == CMD_UPDATE ||
67 mergeWhenClause->commandType == CMD_DELETE ||
68 mergeWhenClause->commandType == CMD_NOTHING);
70 /* MATCHED actions can see both target and source relations. */
71 setNamespaceVisibilityForRTE(pstate->p_namespace,
72 targetRelRTE, true, true);
73 setNamespaceVisibilityForRTE(pstate->p_namespace,
74 sourceRelRTE, true, true);
76 else
79 * NOT MATCHED actions can't see target relation, but they can see
80 * source relation.
82 Assert(mergeWhenClause->commandType == CMD_INSERT ||
83 mergeWhenClause->commandType == CMD_NOTHING);
84 setNamespaceVisibilityForRTE(pstate->p_namespace,
85 targetRelRTE, false, false);
86 setNamespaceVisibilityForRTE(pstate->p_namespace,
87 sourceRelRTE, true, true);
92 * transformMergeStmt -
93 * transforms a MERGE statement
95 Query *
96 transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
98 Query *qry = makeNode(Query);
99 ListCell *l;
100 AclMode targetPerms = ACL_NO_RIGHTS;
101 bool is_terminal[2];
102 Index sourceRTI;
103 List *mergeActionList;
104 Node *joinExpr;
105 ParseNamespaceItem *nsitem;
107 /* There can't be any outer WITH to worry about */
108 Assert(pstate->p_ctenamespace == NIL);
110 qry->commandType = CMD_MERGE;
111 qry->hasRecursive = false;
113 /* process the WITH clause independently of all else */
114 if (stmt->withClause)
116 if (stmt->withClause->recursive)
117 ereport(ERROR,
118 (errcode(ERRCODE_SYNTAX_ERROR),
119 errmsg("WITH RECURSIVE is not supported for MERGE statement")));
121 qry->cteList = transformWithClause(pstate, stmt->withClause);
122 qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
126 * Check WHEN clauses for permissions and sanity
128 is_terminal[0] = false;
129 is_terminal[1] = false;
130 foreach(l, stmt->mergeWhenClauses)
132 MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l);
133 int when_type = (mergeWhenClause->matched ? 0 : 1);
136 * Collect permissions to check, according to action types. We require
137 * SELECT privileges for DO NOTHING because it'd be irregular to have
138 * a target relation with zero privileges checked, in case DO NOTHING
139 * is the only action. There's no damage from that: any meaningful
140 * MERGE command requires at least some access to the table anyway.
142 switch (mergeWhenClause->commandType)
144 case CMD_INSERT:
145 targetPerms |= ACL_INSERT;
146 break;
147 case CMD_UPDATE:
148 targetPerms |= ACL_UPDATE;
149 break;
150 case CMD_DELETE:
151 targetPerms |= ACL_DELETE;
152 break;
153 case CMD_NOTHING:
154 targetPerms |= ACL_SELECT;
155 break;
156 default:
157 elog(ERROR, "unknown action in MERGE WHEN clause");
161 * Check for unreachable WHEN clauses
163 if (is_terminal[when_type])
164 ereport(ERROR,
165 (errcode(ERRCODE_SYNTAX_ERROR),
166 errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
167 if (mergeWhenClause->condition == NULL)
168 is_terminal[when_type] = true;
172 * Set up the MERGE target table. The target table is added to the
173 * namespace below and to joinlist in transform_MERGE_to_join, so don't do
174 * it here.
176 qry->resultRelation = setTargetTable(pstate, stmt->relation,
177 stmt->relation->inh,
178 false, targetPerms);
181 * MERGE is unsupported in various cases
183 if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
184 pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
185 ereport(ERROR,
186 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
187 errmsg("cannot execute MERGE on relation \"%s\"",
188 RelationGetRelationName(pstate->p_target_relation)),
189 errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind)));
190 if (pstate->p_target_relation->rd_rules != NULL &&
191 pstate->p_target_relation->rd_rules->numLocks > 0)
192 ereport(ERROR,
193 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
194 errmsg("cannot execute MERGE on relation \"%s\"",
195 RelationGetRelationName(pstate->p_target_relation)),
196 errdetail("MERGE is not supported for relations with rules.")));
198 /* Now transform the source relation to produce the source RTE. */
199 transformFromClause(pstate,
200 list_make1(stmt->sourceRelation));
201 sourceRTI = list_length(pstate->p_rtable);
202 nsitem = GetNSItemByRangeTablePosn(pstate, sourceRTI, 0);
205 * Check that the target table doesn't conflict with the source table.
206 * This would typically be a checkNameSpaceConflicts call, but we want a
207 * more specific error message.
209 if (strcmp(pstate->p_target_nsitem->p_names->aliasname,
210 nsitem->p_names->aliasname) == 0)
211 ereport(ERROR,
212 errcode(ERRCODE_DUPLICATE_ALIAS),
213 errmsg("name \"%s\" specified more than once",
214 pstate->p_target_nsitem->p_names->aliasname),
215 errdetail("The name is used both as MERGE target table and data source."));
218 * There's no need for a targetlist here; it'll be set up by
219 * preprocess_targetlist later.
221 qry->targetList = NIL;
222 qry->rtable = pstate->p_rtable;
223 qry->rteperminfos = pstate->p_rteperminfos;
226 * Transform the join condition. This includes references to the target
227 * side, so add that to the namespace.
229 addNSItemToQuery(pstate, pstate->p_target_nsitem, false, true, true);
230 joinExpr = transformExpr(pstate, stmt->joinCondition,
231 EXPR_KIND_JOIN_ON);
234 * Create the temporary query's jointree using the joinlist we built using
235 * just the source relation; the target relation is not included. The
236 * quals we use are the join conditions to the merge target. The join
237 * will be constructed fully by transform_MERGE_to_join.
239 qry->jointree = makeFromExpr(pstate->p_joinlist, joinExpr);
242 * We now have a good query shape, so now look at the WHEN conditions and
243 * action targetlists.
245 * Overall, the MERGE Query's targetlist is NIL.
247 * Each individual action has its own targetlist that needs separate
248 * transformation. These transforms don't do anything to the overall
249 * targetlist, since that is only used for resjunk columns.
251 * We can reference any column in Target or Source, which is OK because
252 * both of those already have RTEs. There is nothing like the EXCLUDED
253 * pseudo-relation for INSERT ON CONFLICT.
255 mergeActionList = NIL;
256 foreach(l, stmt->mergeWhenClauses)
258 MergeWhenClause *mergeWhenClause = lfirst_node(MergeWhenClause, l);
259 MergeAction *action;
261 action = makeNode(MergeAction);
262 action->commandType = mergeWhenClause->commandType;
263 action->matched = mergeWhenClause->matched;
265 /* Use an outer join if any INSERT actions exist in the command. */
266 if (action->commandType == CMD_INSERT)
267 qry->mergeUseOuterJoin = true;
270 * Set namespace for the specific action. This must be done before
271 * analyzing the WHEN quals and the action targetlist.
273 setNamespaceForMergeWhen(pstate, mergeWhenClause,
274 qry->resultRelation,
275 sourceRTI);
278 * Transform the WHEN condition.
280 * Note that these quals are NOT added to the join quals; instead they
281 * are evaluated separately during execution to decide which of the
282 * WHEN MATCHED or WHEN NOT MATCHED actions to execute.
284 action->qual = transformWhereClause(pstate, mergeWhenClause->condition,
285 EXPR_KIND_MERGE_WHEN, "WHEN");
288 * Transform target lists for each INSERT and UPDATE action stmt
290 switch (action->commandType)
292 case CMD_INSERT:
294 List *exprList = NIL;
295 ListCell *lc;
296 RTEPermissionInfo *perminfo;
297 ListCell *icols;
298 ListCell *attnos;
299 List *icolumns;
300 List *attrnos;
302 pstate->p_is_insert = true;
304 icolumns = checkInsertTargets(pstate,
305 mergeWhenClause->targetList,
306 &attrnos);
307 Assert(list_length(icolumns) == list_length(attrnos));
309 action->override = mergeWhenClause->override;
312 * Handle INSERT much like in transformInsertStmt
314 if (mergeWhenClause->values == NIL)
317 * We have INSERT ... DEFAULT VALUES. We can handle
318 * this case by emitting an empty targetlist --- all
319 * columns will be defaulted when the planner expands
320 * the targetlist.
322 exprList = NIL;
324 else
327 * Process INSERT ... VALUES with a single VALUES
328 * sublist. We treat this case separately for
329 * efficiency. The sublist is just computed directly
330 * as the Query's targetlist, with no VALUES RTE. So
331 * it works just like a SELECT without any FROM.
335 * Do basic expression transformation (same as a ROW()
336 * expr, but allow SetToDefault at top level)
338 exprList = transformExpressionList(pstate,
339 mergeWhenClause->values,
340 EXPR_KIND_VALUES_SINGLE,
341 true);
343 /* Prepare row for assignment to target table */
344 exprList = transformInsertRow(pstate, exprList,
345 mergeWhenClause->targetList,
346 icolumns, attrnos,
347 false);
351 * Generate action's target list using the computed list
352 * of expressions. Also, mark all the target columns as
353 * needing insert permissions.
355 perminfo = pstate->p_target_nsitem->p_perminfo;
356 forthree(lc, exprList, icols, icolumns, attnos, attrnos)
358 Expr *expr = (Expr *) lfirst(lc);
359 ResTarget *col = lfirst_node(ResTarget, icols);
360 AttrNumber attr_num = (AttrNumber) lfirst_int(attnos);
361 TargetEntry *tle;
363 tle = makeTargetEntry(expr,
364 attr_num,
365 col->name,
366 false);
367 action->targetList = lappend(action->targetList, tle);
369 perminfo->insertedCols =
370 bms_add_member(perminfo->insertedCols,
371 attr_num - FirstLowInvalidHeapAttributeNumber);
374 break;
375 case CMD_UPDATE:
377 pstate->p_is_insert = false;
378 action->targetList =
379 transformUpdateTargetList(pstate,
380 mergeWhenClause->targetList);
382 break;
383 case CMD_DELETE:
384 break;
386 case CMD_NOTHING:
387 action->targetList = NIL;
388 break;
389 default:
390 elog(ERROR, "unknown action in MERGE WHEN clause");
393 mergeActionList = lappend(mergeActionList, action);
396 qry->mergeActionList = mergeActionList;
398 /* RETURNING could potentially be added in the future, but not in SQL std */
399 qry->returningList = NULL;
401 qry->hasTargetSRFs = false;
402 qry->hasSubLinks = pstate->p_hasSubLinks;
404 assign_query_collations(pstate, qry);
406 return qry;
409 static void
410 setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
411 bool rel_visible,
412 bool cols_visible)
414 ListCell *lc;
416 foreach(lc, namespace)
418 ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
420 if (nsitem->p_rte == rte)
422 nsitem->p_rel_visible = rel_visible;
423 nsitem->p_cols_visible = cols_visible;
424 break;