1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-02 11:44:50 +03:00
postgres/src/backend/parser/parse_merge.c
Dean Rasheed f026c16a2c Fix MERGE's test for unreachable WHEN clauses.
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
2023-01-10 14:17:47 +00:00

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;
}
}
}