LCOV - code coverage report
Current view: top level - src/backend/parser - parse_merge.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 96.7 % 120 116
Test Date: 2026-07-03 19:57:34 Functions: 100.0 % 3 3
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 77.4 % 84 65

             Branch data     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                 :        2072 : setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause,
      53                 :             :                          Index targetRTI, Index sourceRTI)
      54                 :             : {
      55                 :             :     RangeTblEntry *targetRelRTE,
      56                 :             :                *sourceRelRTE;
      57                 :             : 
      58                 :        2072 :     targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable);
      59                 :        2072 :     sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable);
      60                 :             : 
      61         [ +  + ]:        2072 :     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                 :        1273 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      69                 :             :                                      targetRelRTE, true, true);
      70                 :        1273 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      71                 :             :                                      sourceRelRTE, true, true);
      72                 :             :     }
      73         [ +  + ]:         799 :     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                 :          98 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      83                 :             :                                      targetRelRTE, true, true);
      84                 :          98 :         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                 :         701 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      96                 :             :                                      targetRelRTE, false, false);
      97                 :         701 :         setNamespaceVisibilityForRTE(pstate->p_namespace,
      98                 :             :                                      sourceRelRTE, true, true);
      99                 :             :     }
     100                 :        2072 : }
     101                 :             : 
     102                 :             : /*
     103                 :             :  * transformMergeStmt -
     104                 :             :  *    transforms a MERGE statement
     105                 :             :  */
     106                 :             : Query *
     107                 :        1383 : transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
     108                 :             : {
     109                 :        1383 :     Query      *qry = makeNode(Query);
     110                 :             :     ListCell   *l;
     111                 :        1383 :     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                 :        1383 :     qry->commandType = CMD_MERGE;
     121                 :        1383 :     qry->hasRecursive = false;
     122                 :             : 
     123                 :             :     /* process the WITH clause independently of all else */
     124         [ +  + ]:        1383 :     if (stmt->withClause)
     125                 :             :     {
     126         [ +  + ]:          38 :         if (stmt->withClause->recursive)
     127         [ +  - ]:           4 :             ereport(ERROR,
     128                 :             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     129                 :             :                      errmsg("WITH RECURSIVE is not supported for MERGE statement")));
     130                 :             : 
     131                 :          34 :         qry->cteList = transformWithClause(pstate, stmt->withClause);
     132                 :          34 :         qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
     133                 :             :     }
     134                 :             : 
     135                 :             :     /*
     136                 :             :      * Check WHEN clauses for permissions and sanity
     137                 :             :      */
     138                 :        1379 :     is_terminal[MERGE_WHEN_MATCHED] = false;
     139                 :        1379 :     is_terminal[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = false;
     140                 :        1379 :     is_terminal[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = false;
     141   [ +  -  +  +  :        3475 :     foreach(l, stmt->mergeWhenClauses)
                   +  + ]
     142                 :             :     {
     143                 :        2100 :         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   [ +  +  +  +  :        2100 :         switch (mergeWhenClause->commandType)
                      - ]
     153                 :             :         {
     154                 :         696 :             case CMD_INSERT:
     155                 :         696 :                 targetPerms |= ACL_INSERT;
     156                 :         696 :                 break;
     157                 :         986 :             case CMD_UPDATE:
     158                 :         986 :                 targetPerms |= ACL_UPDATE;
     159                 :         986 :                 break;
     160                 :         355 :             case CMD_DELETE:
     161                 :         355 :                 targetPerms |= ACL_DELETE;
     162                 :         355 :                 break;
     163                 :          63 :             case CMD_NOTHING:
     164                 :          63 :                 targetPerms |= ACL_SELECT;
     165                 :          63 :                 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         [ +  + ]:        2100 :         if (is_terminal[mergeWhenClause->matchKind])
     174         [ +  - ]:           4 :             ereport(ERROR,
     175                 :             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     176                 :             :                      errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
     177         [ +  + ]:        2096 :         if (mergeWhenClause->condition == NULL)
     178                 :        1567 :             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                 :        2750 :     qry->resultRelation = setTargetTable(pstate, stmt->relation,
     193                 :        1375 :                                          stmt->relation->inh,
     194                 :             :                                          false, targetPerms);
     195                 :        1375 :     qry->mergeTargetRelation = qry->resultRelation;
     196                 :             : 
     197                 :             :     /* The target relation must be a table or a view */
     198         [ +  + ]:        1375 :     if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
     199         [ +  + ]:         561 :         pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
     200         [ +  + ]:         468 :         pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW)
     201         [ +  - ]:           4 :         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                 :        1371 :     transformFromClause(pstate,
     209                 :        1371 :                         list_make1(stmt->sourceRelation));
     210                 :        1367 :     sourceRTI = list_length(pstate->p_rtable);
     211                 :        1367 :     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                 :        1367 :     if (strcmp(pstate->p_target_nsitem->p_names->aliasname,
     219         [ +  + ]:        1367 :                nsitem->p_names->aliasname) == 0)
     220         [ +  - ]:           4 :         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                 :        1363 :     qry->targetList = NIL;
     231                 :        1363 :     qry->rtable = pstate->p_rtable;
     232                 :        1363 :     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                 :        1363 :     addNSItemToQuery(pstate, pstate->p_target_nsitem, false, true, true);
     239                 :        1363 :     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                 :        1363 :     qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
     248                 :             : 
     249                 :             :     /* Transform the RETURNING list, if any */
     250                 :        1363 :     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                 :        1363 :     mergeActionList = NIL;
     268   [ +  -  +  +  :        3411 :     foreach(l, stmt->mergeWhenClauses)
                   +  + ]
     269                 :             :     {
     270                 :        2072 :         MergeWhenClause *mergeWhenClause = lfirst_node(MergeWhenClause, l);
     271                 :             :         MergeAction *action;
     272                 :             : 
     273                 :        2072 :         action = makeNode(MergeAction);
     274                 :        2072 :         action->commandType = mergeWhenClause->commandType;
     275                 :        2072 :         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                 :        2072 :         setNamespaceForMergeWhen(pstate, mergeWhenClause,
     282                 :        2072 :                                  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                 :        2072 :         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   [ +  +  +  +  :        2060 :         switch (action->commandType)
                      - ]
     299                 :             :         {
     300                 :         680 :             case CMD_INSERT:
     301                 :             :                 {
     302                 :         680 :                     List       *exprList = NIL;
     303                 :             :                     ListCell   *lc;
     304                 :             :                     RTEPermissionInfo *perminfo;
     305                 :             :                     ListCell   *icols;
     306                 :             :                     ListCell   *attnos;
     307                 :             :                     List       *icolumns;
     308                 :             :                     List       *attrnos;
     309                 :             : 
     310                 :         680 :                     icolumns = checkInsertTargets(pstate,
     311                 :             :                                                   mergeWhenClause->targetList,
     312                 :             :                                                   &attrnos);
     313                 :             :                     Assert(list_length(icolumns) == list_length(attrnos));
     314                 :             : 
     315                 :         680 :                     action->override = mergeWhenClause->override;
     316                 :             : 
     317                 :             :                     /*
     318                 :             :                      * Handle INSERT much like in transformInsertStmt
     319                 :             :                      */
     320         [ +  + ]:         680 :                     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                 :          16 :                         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                 :         664 :                         exprList = transformExpressionList(pstate,
     345                 :             :                                                            mergeWhenClause->values,
     346                 :             :                                                            EXPR_KIND_VALUES_SINGLE,
     347                 :             :                                                            true);
     348                 :             : 
     349                 :             :                         /* Prepare row for assignment to target table */
     350                 :         656 :                         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                 :         672 :                     perminfo = pstate->p_target_nsitem->p_perminfo;
     362   [ +  +  +  +  :        2152 :                     forthree(lc, exprList, icols, icolumns, attnos, attrnos)
          +  -  +  +  +  
          -  +  +  +  +  
          +  -  +  -  +  
                      + ]
     363                 :             :                     {
     364                 :        1480 :                         Expr       *expr = (Expr *) lfirst(lc);
     365                 :        1480 :                         ResTarget  *col = lfirst_node(ResTarget, icols);
     366                 :        1480 :                         AttrNumber  attr_num = (AttrNumber) lfirst_int(attnos);
     367                 :             :                         TargetEntry *tle;
     368                 :             : 
     369                 :        1480 :                         tle = makeTargetEntry(expr,
     370                 :             :                                               attr_num,
     371                 :             :                                               col->name,
     372                 :             :                                               false);
     373                 :        1480 :                         action->targetList = lappend(action->targetList, tle);
     374                 :             : 
     375                 :        1480 :                         perminfo->insertedCols =
     376                 :        1480 :                             bms_add_member(perminfo->insertedCols,
     377                 :             :                                            attr_num - FirstLowInvalidHeapAttributeNumber);
     378                 :             :                     }
     379                 :             :                 }
     380                 :         672 :                 break;
     381                 :         978 :             case CMD_UPDATE:
     382                 :         974 :                 action->targetList =
     383                 :         978 :                     transformUpdateTargetList(pstate,
     384                 :             :                                               mergeWhenClause->targetList, NULL);
     385                 :         974 :                 break;
     386                 :         343 :             case CMD_DELETE:
     387                 :         343 :                 break;
     388                 :             : 
     389                 :          59 :             case CMD_NOTHING:
     390                 :          59 :                 action->targetList = NIL;
     391                 :          59 :                 break;
     392                 :           0 :             default:
     393         [ #  # ]:           0 :                 elog(ERROR, "unknown action in MERGE WHEN clause");
     394                 :             :         }
     395                 :             : 
     396                 :        2048 :         mergeActionList = lappend(mergeActionList, action);
     397                 :             :     }
     398                 :             : 
     399                 :        1339 :     qry->mergeActionList = mergeActionList;
     400                 :             : 
     401                 :        1339 :     qry->hasTargetSRFs = false;
     402                 :        1339 :     qry->hasSubLinks = pstate->p_hasSubLinks;
     403                 :             : 
     404                 :        1339 :     assign_query_collations(pstate, qry);
     405                 :             : 
     406                 :        1339 :     return qry;
     407                 :             : }
     408                 :             : 
     409                 :             : static void
     410                 :        4144 : setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
     411                 :             :                              bool rel_visible,
     412                 :             :                              bool cols_visible)
     413                 :             : {
     414                 :             :     ListCell   *lc;
     415                 :             : 
     416   [ +  -  +  -  :        6472 :     foreach(lc, namespace)
                   +  - ]
     417                 :             :     {
     418                 :        6472 :         ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
     419                 :             : 
     420         [ +  + ]:        6472 :         if (nsitem->p_rte == rte)
     421                 :             :         {
     422                 :        4144 :             nsitem->p_rel_visible = rel_visible;
     423                 :        4144 :             nsitem->p_cols_visible = cols_visible;
     424                 :        4144 :             break;
     425                 :             :         }
     426                 :             :     }
     427                 :        4144 : }
        

Generated by: LCOV version 2.0-1