LCOV - code coverage report
Current view: top level - src/backend/parser - parse_merge.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 96.7 % 120 116
Test Date: 2026-03-02 14:15:04 Functions: 100.0 % 3 3
Legend: Lines:     hit not hit

            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
      52         1600 : setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause,
      53              :                          Index targetRTI, Index sourceRTI)
      54              : {
      55              :     RangeTblEntry *targetRelRTE,
      56              :                *sourceRelRTE;
      57              : 
      58         1600 :     targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable);
      59         1600 :     sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable);
      60              : 
      61         1600 :     if (mergeWhenClause->matchKind == MERGE_WHEN_MATCHED)
      62              :     {
      63              :         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          985 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      69              :                                      targetRelRTE, true, true);
      70          985 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      71              :                                      sourceRelRTE, true, true);
      72              :     }
      73          615 :     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              :         Assert(mergeWhenClause->commandType == CMD_UPDATE ||
      80              :                mergeWhenClause->commandType == CMD_DELETE ||
      81              :                mergeWhenClause->commandType == CMD_NOTHING);
      82           75 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      83              :                                      targetRelRTE, true, true);
      84           75 :         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              :          */
      93              :         Assert(mergeWhenClause->commandType == CMD_INSERT ||
      94              :                mergeWhenClause->commandType == CMD_NOTHING);
      95          540 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      96              :                                      targetRelRTE, false, false);
      97          540 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      98              :                                      sourceRelRTE, true, true);
      99              :     }
     100         1600 : }
     101              : 
     102              : /*
     103              :  * transformMergeStmt -
     104              :  *    transforms a MERGE statement
     105              :  */
     106              : Query *
     107         1059 : transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
     108              : {
     109         1059 :     Query      *qry = makeNode(Query);
     110              :     ListCell   *l;
     111         1059 :     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              :     Assert(pstate->p_ctenamespace == NIL);
     119              : 
     120         1059 :     qry->commandType = CMD_MERGE;
     121         1059 :     qry->hasRecursive = false;
     122              : 
     123              :     /* process the WITH clause independently of all else */
     124         1059 :     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              :      */
     138         1056 :     is_terminal[MERGE_WHEN_MATCHED] = false;
     139         1056 :     is_terminal[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = false;
     140         1056 :     is_terminal[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = false;
     141         2674 :     foreach(l, stmt->mergeWhenClauses)
     142              :     {
     143         1621 :         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         1621 :         switch (mergeWhenClause->commandType)
     153              :         {
     154          536 :             case CMD_INSERT:
     155          536 :                 targetPerms |= ACL_INSERT;
     156          536 :                 break;
     157          772 :             case CMD_UPDATE:
     158          772 :                 targetPerms |= ACL_UPDATE;
     159          772 :                 break;
     160          265 :             case CMD_DELETE:
     161          265 :                 targetPerms |= ACL_DELETE;
     162          265 :                 break;
     163           48 :             case CMD_NOTHING:
     164           48 :                 targetPerms |= ACL_SELECT;
     165           48 :                 break;
     166            0 :             default:
     167            0 :                 elog(ERROR, "unknown action in MERGE WHEN clause");
     168              :         }
     169              : 
     170              :         /*
     171              :          * Check for unreachable WHEN clauses
     172              :          */
     173         1621 :         if (is_terminal[mergeWhenClause->matchKind])
     174            3 :             ereport(ERROR,
     175              :                     (errcode(ERRCODE_SYNTAX_ERROR),
     176              :                      errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
     177         1618 :         if (mergeWhenClause->condition == NULL)
     178         1205 :             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              :      */
     192         2106 :     qry->resultRelation = setTargetTable(pstate, stmt->relation,
     193         1053 :                                          stmt->relation->inh,
     194              :                                          false, targetPerms);
     195         1053 :     qry->mergeTargetRelation = qry->resultRelation;
     196              : 
     197              :     /* The target relation must be a table or a view */
     198         1053 :     if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
     199          427 :         pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
     200          351 :         pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW)
     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         1050 :     transformFromClause(pstate,
     209         1050 :                         list_make1(stmt->sourceRelation));
     210         1047 :     sourceRTI = list_length(pstate->p_rtable);
     211         1047 :     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         1047 :     if (strcmp(pstate->p_target_nsitem->p_names->aliasname,
     219         1047 :                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              :      */
     230         1044 :     qry->targetList = NIL;
     231         1044 :     qry->rtable = pstate->p_rtable;
     232         1044 :     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         1044 :     addNSItemToQuery(pstate, pstate->p_target_nsitem, false, true, true);
     239         1044 :     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         1044 :     qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
     248              : 
     249              :     /* Transform the RETURNING list, if any */
     250         1044 :     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              :      */
     267         1044 :     mergeActionList = NIL;
     268         2626 :     foreach(l, stmt->mergeWhenClauses)
     269              :     {
     270         1600 :         MergeWhenClause *mergeWhenClause = lfirst_node(MergeWhenClause, l);
     271              :         MergeAction *action;
     272              : 
     273         1600 :         action = makeNode(MergeAction);
     274         1600 :         action->commandType = mergeWhenClause->commandType;
     275         1600 :         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         1600 :         setNamespaceForMergeWhen(pstate, mergeWhenClause,
     282         1600 :                                  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         1600 :         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         1591 :         switch (action->commandType)
     299              :         {
     300          524 :             case CMD_INSERT:
     301              :                 {
     302          524 :                     List       *exprList = NIL;
     303              :                     ListCell   *lc;
     304              :                     RTEPermissionInfo *perminfo;
     305              :                     ListCell   *icols;
     306              :                     ListCell   *attnos;
     307              :                     List       *icolumns;
     308              :                     List       *attrnos;
     309              : 
     310          524 :                     icolumns = checkInsertTargets(pstate,
     311              :                                                   mergeWhenClause->targetList,
     312              :                                                   &attrnos);
     313              :                     Assert(list_length(icolumns) == list_length(attrnos));
     314              : 
     315          524 :                     action->override = mergeWhenClause->override;
     316              : 
     317              :                     /*
     318              :                      * Handle INSERT much like in transformInsertStmt
     319              :                      */
     320          524 :                     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          512 :                         exprList = transformExpressionList(pstate,
     345              :                                                            mergeWhenClause->values,
     346              :                                                            EXPR_KIND_VALUES_SINGLE,
     347              :                                                            true);
     348              : 
     349              :                         /* Prepare row for assignment to target table */
     350          506 :                         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              :                      */
     361          518 :                     perminfo = pstate->p_target_nsitem->p_perminfo;
     362         1657 :                     forthree(lc, exprList, icols, icolumns, attnos, attrnos)
     363              :                     {
     364         1139 :                         Expr       *expr = (Expr *) lfirst(lc);
     365         1139 :                         ResTarget  *col = lfirst_node(ResTarget, icols);
     366         1139 :                         AttrNumber  attr_num = (AttrNumber) lfirst_int(attnos);
     367              :                         TargetEntry *tle;
     368              : 
     369         1139 :                         tle = makeTargetEntry(expr,
     370              :                                               attr_num,
     371              :                                               col->name,
     372              :                                               false);
     373         1139 :                         action->targetList = lappend(action->targetList, tle);
     374              : 
     375         1139 :                         perminfo->insertedCols =
     376         1139 :                             bms_add_member(perminfo->insertedCols,
     377              :                                            attr_num - FirstLowInvalidHeapAttributeNumber);
     378              :                     }
     379              :                 }
     380          518 :                 break;
     381          766 :             case CMD_UPDATE:
     382          763 :                 action->targetList =
     383          766 :                     transformUpdateTargetList(pstate,
     384              :                                               mergeWhenClause->targetList);
     385          763 :                 break;
     386          256 :             case CMD_DELETE:
     387          256 :                 break;
     388              : 
     389           45 :             case CMD_NOTHING:
     390           45 :                 action->targetList = NIL;
     391           45 :                 break;
     392            0 :             default:
     393            0 :                 elog(ERROR, "unknown action in MERGE WHEN clause");
     394              :         }
     395              : 
     396         1582 :         mergeActionList = lappend(mergeActionList, action);
     397              :     }
     398              : 
     399         1026 :     qry->mergeActionList = mergeActionList;
     400              : 
     401         1026 :     qry->hasTargetSRFs = false;
     402         1026 :     qry->hasSubLinks = pstate->p_hasSubLinks;
     403              : 
     404         1026 :     assign_query_collations(pstate, qry);
     405              : 
     406         1026 :     return qry;
     407              : }
     408              : 
     409              : static void
     410         3200 : setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
     411              :                              bool rel_visible,
     412              :                              bool cols_visible)
     413              : {
     414              :     ListCell   *lc;
     415              : 
     416         4992 :     foreach(lc, namespace)
     417              :     {
     418         4992 :         ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
     419              : 
     420         4992 :         if (nsitem->p_rte == rte)
     421              :         {
     422         3200 :             nsitem->p_rel_visible = rel_visible;
     423         3200 :             nsitem->p_cols_visible = cols_visible;
     424         3200 :             break;
     425              :         }
     426              :     }
     427         3200 : }
        

Generated by: LCOV version 2.0-1