mirror of
https://github.com/postgres/postgres.git
synced 2025-05-02 11:44:50 +03:00
The former code would only detect an unreachable WHEN clause if it had an AND condition. Fix, so that unreachable unconditional WHEN clauses are also detected. Back-patch to v15, where MERGE was added. Discussion: https://postgr.es/m/CAEZATCVQ=7E2z4cSBB49jjeGGsB6WeoYQY32NDeSvcHiLUZ=ow@mail.gmail.com
423 lines
12 KiB
C
423 lines
12 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* parse_merge.c
|
|
* handle merge-statement in parser
|
|
*
|
|
* Portions Copyright (c) 1996-2023, 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 "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "parser/analyze.h"
|
|
#include "parser/parse_collate.h"
|
|
#include "parser/parsetree.h"
|
|
#include "parser/parser.h"
|
|
#include "parser/parse_clause.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 "utils/rel.h"
|
|
#include "utils/relcache.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 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.
|
|
*
|
|
* 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->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
|
|
{
|
|
/*
|
|
* NOT MATCHED 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[2];
|
|
Index sourceRTI;
|
|
List *mergeActionList;
|
|
Node *joinExpr;
|
|
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[0] = false;
|
|
is_terminal[1] = false;
|
|
foreach(l, stmt->mergeWhenClauses)
|
|
{
|
|
MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l);
|
|
int when_type = (mergeWhenClause->matched ? 0 : 1);
|
|
|
|
/*
|
|
* Collect action types so we can check target permissions
|
|
*/
|
|
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:
|
|
break;
|
|
default:
|
|
elog(ERROR, "unknown action in MERGE WHEN clause");
|
|
}
|
|
|
|
/*
|
|
* Check for unreachable WHEN clauses
|
|
*/
|
|
if (is_terminal[when_type])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
|
|
if (mergeWhenClause->condition == NULL)
|
|
is_terminal[when_type] = 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.
|
|
*/
|
|
qry->resultRelation = setTargetTable(pstate, stmt->relation,
|
|
stmt->relation->inh,
|
|
false, targetPerms);
|
|
|
|
/*
|
|
* MERGE is unsupported in various cases
|
|
*/
|
|
if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
|
|
pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
|
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)));
|
|
if (pstate->p_target_relation->rd_rules != NULL &&
|
|
pstate->p_target_relation->rd_rules->numLocks > 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cannot execute MERGE on relation \"%s\"",
|
|
RelationGetRelationName(pstate->p_target_relation)),
|
|
errdetail("MERGE is not supported for relations with rules.")));
|
|
|
|
/* 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);
|
|
joinExpr = 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
|
|
* quals we use are the join conditions to the merge target. The join
|
|
* will be constructed fully by transform_MERGE_to_join.
|
|
*/
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, joinExpr);
|
|
|
|
/*
|
|
* 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->matched = mergeWhenClause->matched;
|
|
|
|
/* Use an outer join if any INSERT actions exist in the command. */
|
|
if (action->commandType == CMD_INSERT)
|
|
qry->mergeUseOuterJoin = true;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* RETURNING could potentially be added in the future, but not in SQL std */
|
|
qry->returningList = NULL;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|