Coverage Report

Created: 2025-09-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}