/*-------------------------------------------------------------------------
*
* parse_merge . c
* handle merge - statement in parser
*
* Portions Copyright ( c ) 1996 - 2025 , PostgreSQL Global Development Group
* Portions Copyright ( c ) 1994 , Regents of the University of California
*
*
* IDENTIFICATION
* src / backend / parser / parse_merge . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include "access/sysattr.h"
#include "nodes/makefuncs.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
#include "parser/parse_collate.h"
#include "parser/parse_cte.h"
#include "parser/parse_expr.h"
#include "parser/parse_merge.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parsetree.h"
#include "utils/rel.h"
static void setNamespaceForMergeWhen(ParseState *pstate,
MergeWhenClause *mergeWhenClause,
Index targetRTI,
Index sourceRTI);
static void setNamespaceVisibilityForRTE(List *namespace , RangeTblEntry *rte,
bool rel_visible,
bool cols_visible);
/*
* Make appropriate changes to the namespace visibility while transforming
* individual action ' s quals and targetlist expressions . In particular , for
* INSERT actions we must only see the source relation ( since INSERT action is
* invoked for NOT MATCHED [ BY TARGET ] tuples and hence there is no target
* tuple to deal with ) . On the other hand , UPDATE and DELETE actions can see
* both source and target relations , unless invoked for NOT MATCHED BY SOURCE .
*
* Also , since the internal join node can hide the source and target
* relations , we must explicitly make the respective relation as visible so
* that columns can be referenced unqualified from these relations .
*/
static void
setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause,
Index targetRTI, Index sourceRTI)
{
RangeTblEntry *targetRelRTE,
*sourceRelRTE;
targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable);
sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable);
if (mergeWhenClause->matchKind == MERGE_WHEN_MATCHED)
{
Assert(mergeWhenClause->commandType == CMD_UPDATE ||
mergeWhenClause->commandType == CMD_DELETE ||
mergeWhenClause->commandType == CMD_NOTHING);
/* MATCHED actions can see both target and source relations. */
setNamespaceVisibilityForRTE(pstate->p_namespace,
targetRelRTE, true , true );
setNamespaceVisibilityForRTE(pstate->p_namespace,
sourceRelRTE, true , true );
}
else if (mergeWhenClause->matchKind == MERGE_WHEN_NOT_MATCHED_BY_SOURCE)
{
/*
* NOT MATCHED BY SOURCE actions can see the target relation , but they
* can ' t see the source relation .
*/
Assert(mergeWhenClause->commandType == CMD_UPDATE ||
mergeWhenClause->commandType == CMD_DELETE ||
mergeWhenClause->commandType == CMD_NOTHING);
setNamespaceVisibilityForRTE(pstate->p_namespace,
targetRelRTE, true , true );
setNamespaceVisibilityForRTE(pstate->p_namespace,
sourceRelRTE, false , false );
}
else /* MERGE_WHEN_NOT_MATCHED_BY_TARGET */
{
/*
* NOT MATCHED [ BY TARGET ] actions can ' t see target relation , but they
* can see source relation .
*/
Assert(mergeWhenClause->commandType == CMD_INSERT ||
mergeWhenClause->commandType == CMD_NOTHING);
setNamespaceVisibilityForRTE(pstate->p_namespace,
targetRelRTE, false , false );
setNamespaceVisibilityForRTE(pstate->p_namespace,
sourceRelRTE, true , true );
}
}
/*
* transformMergeStmt -
* transforms a MERGE statement
*/
Query *
transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
{
Query *qry = makeNode(Query);
ListCell *l;
AclMode targetPerms = ACL_NO_RIGHTS;
bool is_terminal[NUM_MERGE_MATCH_KINDS];
Index sourceRTI;
List *mergeActionList;
ParseNamespaceItem *nsitem;
/* There can't be any outer WITH to worry about */
Assert(pstate->p_ctenamespace == NIL);
qry->commandType = CMD_MERGE;
qry->hasRecursive = false ;
/* process the WITH clause independently of all else */
if (stmt->withClause)
{
if (stmt->withClause->recursive)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WITH RECURSIVE is not supported for MERGE statement" )));
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/*
* Check WHEN clauses for permissions and sanity
*/
is_terminal[MERGE_WHEN_MATCHED] = false ;
is_terminal[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = false ;
is_terminal[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = false ;
foreach(l, stmt->mergeWhenClauses)
{
MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l);
/*
* Collect permissions to check , according to action types . We require
* SELECT privileges for DO NOTHING because it ' d be irregular to have
* a target relation with zero privileges checked , in case DO NOTHING
* is the only action . There ' s no damage from that : any meaningful
* MERGE command requires at least some access to the table anyway .
*/
switch (mergeWhenClause->commandType)
{
case CMD_INSERT:
targetPerms |= ACL_INSERT;
break ;
case CMD_UPDATE:
targetPerms |= ACL_UPDATE;
break ;
case CMD_DELETE:
targetPerms |= ACL_DELETE;
break ;
case CMD_NOTHING:
targetPerms |= ACL_SELECT;
break ;
default :
elog(ERROR, "unknown action in MERGE WHEN clause" );
}
/*
* Check for unreachable WHEN clauses
*/
if (is_terminal[mergeWhenClause->matchKind])
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unreachable WHEN clause specified after unconditional WHEN clause" )));
if (mergeWhenClause->condition == NULL)
is_terminal[mergeWhenClause->matchKind] = true ;
}
/*
* Set up the MERGE target table . The target table is added to the
* namespace below and to joinlist in transform_MERGE_to_join , so don ' t do
* it here .
*
* Initially mergeTargetRelation is the same as resultRelation , so data is
* read from the table being updated . However , that might be changed by
* the rewriter , if the target is a trigger - updatable view , to allow
* target data to be read from the expanded view query while updating the
* original view relation .
*/
qry->resultRelation = setTargetTable(pstate, stmt->relation,
stmt->relation->inh,
false , targetPerms);
qry->mergeTargetRelation = qry->resultRelation;
/* The target relation must be a table or a view */
if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot execute MERGE on relation \" %s\"" ,
RelationGetRelationName(pstate->p_target_relation)),
errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind)));
/* Now transform the source relation to produce the source RTE. */
transformFromClause(pstate,
list_make1(stmt->sourceRelation));
sourceRTI = list_length(pstate->p_rtable);
nsitem = GetNSItemByRangeTablePosn(pstate, sourceRTI, 0 );
/*
* Check that the target table doesn ' t conflict with the source table .
* This would typically be a checkNameSpaceConflicts call , but we want a
* more specific error message .
*/
if (strcmp(pstate->p_target_nsitem->p_names->aliasname,
nsitem->p_names->aliasname) == 0 )
ereport(ERROR,
errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("name \" %s\" specified more than once" ,
pstate->p_target_nsitem->p_names->aliasname),
errdetail("The name is used both as MERGE target table and data source." ));
/*
* There ' s no need for a targetlist here ; it ' ll be set up by
* preprocess_targetlist later .
*/
qry->targetList = NIL;
qry->rtable = pstate->p_rtable;
qry->rteperminfos = pstate->p_rteperminfos;
/*
* Transform the join condition . This includes references to the target
* side , so add that to the namespace .
*/
addNSItemToQuery(pstate, pstate->p_target_nsitem, false , true , true );
qry->mergeJoinCondition = transformExpr(pstate, stmt->joinCondition,
EXPR_KIND_JOIN_ON);
/*
* Create the temporary query ' s jointree using the joinlist we built using
* just the source relation ; the target relation is not included . The join
* will be constructed fully by transform_MERGE_to_join .
*/
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
/* Transform the RETURNING list, if any */
transformReturningClause(pstate, qry, stmt->returningClause,
EXPR_KIND_MERGE_RETURNING);
/*
* We now have a good query shape , so now look at the WHEN conditions and
* action targetlists .
*
* Overall , the MERGE Query ' s targetlist is NIL .
*
* Each individual action has its own targetlist that needs separate
* transformation . These transforms don ' t do anything to the overall
* targetlist , since that is only used for resjunk columns .
*
* We can reference any column in Target or Source , which is OK because
* both of those already have RTEs . There is nothing like the EXCLUDED
* pseudo - relation for INSERT ON CONFLICT .
*/
mergeActionList = NIL;
foreach(l, stmt->mergeWhenClauses)
{
MergeWhenClause *mergeWhenClause = lfirst_node(MergeWhenClause, l);
MergeAction *action;
action = makeNode(MergeAction);
action->commandType = mergeWhenClause->commandType;
action->matchKind = mergeWhenClause->matchKind;
/*
* Set namespace for the specific action . This must be done before
* analyzing the WHEN quals and the action targetlist .
*/
setNamespaceForMergeWhen(pstate, mergeWhenClause,
qry->resultRelation,
sourceRTI);
/*
* Transform the WHEN condition .
*
* Note that these quals are NOT added to the join quals ; instead they
* are evaluated separately during execution to decide which of the
* WHEN MATCHED or WHEN NOT MATCHED actions to execute .
*/
action->qual = transformWhereClause(pstate, mergeWhenClause->condition,
EXPR_KIND_MERGE_WHEN, "WHEN" );
/*
* Transform target lists for each INSERT and UPDATE action stmt
*/
switch (action->commandType)
{
case CMD_INSERT:
{
List *exprList = NIL;
ListCell *lc;
RTEPermissionInfo *perminfo;
ListCell *icols;
ListCell *attnos;
List *icolumns;
List *attrnos;
pstate->p_is_insert = true ;
icolumns = checkInsertTargets(pstate,
mergeWhenClause->targetList,
&attrnos);
Assert(list_length(icolumns) == list_length(attrnos));
action->override = mergeWhenClause->override;
/*
* Handle INSERT much like in transformInsertStmt
*/
if (mergeWhenClause->values == NIL)
{
/*
* We have INSERT . . . DEFAULT VALUES . We can handle
* this case by emitting an empty targetlist - - - all
* columns will be defaulted when the planner expands
* the targetlist .
*/
exprList = NIL;
}
else
{
/*
* Process INSERT . . . VALUES with a single VALUES
* sublist . We treat this case separately for
* efficiency . The sublist is just computed directly
* as the Query ' s targetlist , with no VALUES RTE . So
* it works just like a SELECT without any FROM .
*/
/*
* Do basic expression transformation ( same as a ROW ( )
* expr , but allow SetToDefault at top level )
*/
exprList = transformExpressionList(pstate,
mergeWhenClause->values,
EXPR_KIND_VALUES_SINGLE,
true );
/* Prepare row for assignment to target table */
exprList = transformInsertRow(pstate, exprList,
mergeWhenClause->targetList,
icolumns, attrnos,
false );
}
/*
* Generate action ' s target list using the computed list
* of expressions . Also , mark all the target columns as
* needing insert permissions .
*/
perminfo = pstate->p_target_nsitem->p_perminfo;
forthree(lc, exprList, icols, icolumns, attnos, attrnos)
{
Expr *expr = (Expr *) lfirst(lc);
ResTarget *col = lfirst_node(ResTarget, icols);
AttrNumber attr_num = (AttrNumber) lfirst_int(attnos);
TargetEntry *tle;
tle = makeTargetEntry(expr,
attr_num,
col->name,
false );
action->targetList = lappend(action->targetList, tle);
perminfo->insertedCols =
bms_add_member(perminfo->insertedCols,
attr_num - FirstLowInvalidHeapAttributeNumber);
}
}
break ;
case CMD_UPDATE:
{
pstate->p_is_insert = false ;
action->targetList =
transformUpdateTargetList(pstate,
mergeWhenClause->targetList);
}
break ;
case CMD_DELETE:
break ;
case CMD_NOTHING:
action->targetList = NIL;
break ;
default :
elog(ERROR, "unknown action in MERGE WHEN clause" );
}
mergeActionList = lappend(mergeActionList, action);
}
qry->mergeActionList = mergeActionList;
qry->hasTargetSRFs = false ;
qry->hasSubLinks = pstate->p_hasSubLinks;
assign_query_collations(pstate, qry);
return qry;
}
static void
setNamespaceVisibilityForRTE(List *namespace , RangeTblEntry *rte,
bool rel_visible,
bool cols_visible)
{
ListCell *lc;
foreach(lc, namespace )
{
ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
if (nsitem->p_rte == rte)
{
nsitem->p_rel_visible = rel_visible;
nsitem->p_cols_visible = cols_visible;
break ;
}
}
}
Messung V0.5 in Prozent C=94 H=100 G=96
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland