case T_ModifyTable:
ExplainMemberNodes(((ModifyTable *) plan)->plans,
((ModifyTableState *) planstate)->mt_plans,
- ((ModifyTableState *) planstate)->onConflict,
+ NULL,
ancestors,
es);
break;
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalExcluded(ExcludedExprState *excludedExpr,
- ExprContext *econtext, bool *isNull,
- ExprDoneCond *isDone);
/* ----------------------------------------------------------------
return 0; /* keep compiler quiet */
}
-/* ----------------------------------------------------------------
- * ExecEvalExcluded
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalExcluded(ExcludedExprState *excludedExpr, ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone)
-{
- /*
- * ExcludedExpr is essentially an expression that adapts its single Var
- * argument to refer to the expression context inner slot's tuple, which is
- * reserved for the purpose of referencing EXCLUDED.* tuples within ON
- * CONFLICT DO UPDATE auxiliary queries' EPQ expression context (this makes
- * special use of the EvalPlanQual() mechanism to UPDATE).
- *
- * nodeModifyTable.c assigns its own table slot in the auxiliary queries'
- * EPQ expression state (originating in the parent INSERT node) on the
- * assumption that it may only be used by ExcludedExpr, and on the
- * assumption that the inner slot is not otherwise useful. This occurs in
- * advance of the expression evaluation for UPDATE (which calls here are
- * part of) once per slot proposed for insertion, and works because of
- * restrictions on the structure of ON CONFLICT DO UPDATE auxiliary
- * queries.
- *
- * Just evaluate nested Var.
- */
- return ExecEvalScalarVar(excludedExpr->arg, econtext, isNull, isDone);
-}
-
/*
* ExecEvalExprSwitchContext
*
state = (ExprState *) makeNode(ExprState);
state->evalfunc = ExecEvalCurrentOfExpr;
break;
- case T_ExcludedExpr:
- {
- ExcludedExpr *excludedexpr = (ExcludedExpr *) node;
- ExcludedExprState *cstate = makeNode(ExcludedExprState);
- Var *contained = (Var *) excludedexpr->arg;
-
- /*
- * varno forced to INNER_VAR -- see remarks within
- * ExecLockUpdateTuple().
- *
- * We rely on the assumption that the only place that
- * ExcludedExpr may appear is where EXCLUDED Var references
- * originally appeared after parse analysis. The rewriter
- * replaces these with ExcludedExpr that reference the
- * corresponding Var within the ON CONFLICT DO UPDATE target
- * RTE.
- */
- Assert(IsA(contained, Var));
-
- contained->varno = INNER_VAR;
- cstate->arg = ExecInitExpr((Expr *) contained, parent);
- state = (ExprState *) cstate;
- state->evalfunc = (ExprStateEvalFunc) ExecEvalExcluded;
- }
- break;
case T_TargetEntry:
{
TargetEntry *tle = (TargetEntry *) node;
#include "utils/tqual.h"
-static bool ExecOnConflictUpdate(ResultRelInfo *resultRelInfo,
+static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
ItemPointer conflictTid,
TupleTableSlot *planSlot,
TupleTableSlot *insertSlot,
- ModifyTableState *onConflict,
+ EPQState *epqstate,
EState *estate,
bool canSetTag,
TupleTableSlot **returning);
* ----------------------------------------------------------------
*/
static TupleTableSlot *
-ExecInsert(TupleTableSlot *slot,
+ExecInsert(ModifyTableState *mtstate,
+ TupleTableSlot *slot,
TupleTableSlot *planSlot,
- ModifyTableState *onConflict,
+ EPQState *epqstate,
List *arbiterIndexes,
ConfCmd conf,
EState *estate,
{
TupleTableSlot *returning = NULL;
- if (ExecOnConflictUpdate(resultRelInfo, &conflictTid,
- planSlot, slot, onConflict,
+ if (ExecOnConflictUpdate(mtstate, resultRelInfo, &conflictTid,
+ planSlot, slot, epqstate,
estate, canSetTag, &returning))
return returning;
else
* the caller must retry the INSERT from scratch.
*/
static bool
-ExecOnConflictUpdate(ResultRelInfo *resultRelInfo,
+ExecOnConflictUpdate(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
ItemPointer conflictTid,
TupleTableSlot *planSlot,
TupleTableSlot *insertSlot,
- ModifyTableState *onConflict,
+ EPQState *epqstate,
EState *estate,
bool canSetTag,
TupleTableSlot **returning)
HeapUpdateFailureData hufd;
HTSU_Result test;
Buffer buffer;
- TupleTableSlot *slot;
- ExprContext *econtext;
LockTupleMode lockmode;
+ TupleTableSlot *slot;
/* Determine lock mode to use */
lockmode = ExecUpdateLockMode(estate, resultRelInfo);
return false; /* keep compiler quiet */
case HeapTupleMayBeUpdated:
+ slot = mtstate->mt_fuck;
+ ExecSetSlotDescriptor(slot, RelationGetDescr(relation));
+ ExecStoreTuple(&tuple, slot, buffer, false);
+
/*
* Success -- we're done, as tuple is locked. Verify that the
* tuple is visible to our MVCC snapshot if the current isolation
* matters.
*/
- /* must provide our own instrumentation support */
- if (onConflict->ps.instrument)
- InstrStartNode(onConflict->ps.instrument);
-
- /*
- * Conceptually, the parent ModifyTable is like a relation scan
- * node that uses a dirty snapshot, returning rows which the
- * auxiliary plan must operate on.
- *
- * Note that this code path merely re-uses some parts of the
- * EvalPlanQual() infrastructure; it does not really use
- * EvalPlanQual(), because the UPDATE chain is not actually walked.
- */
- EvalPlanQualBegin(&onConflict->mt_epqstate, onConflict->ps.state);
-
- /*
- * Save EPQ expression context. Auxiliary plan's scan node (which
- * would have been just initialized by EvalPlanQualBegin() on the
- * first time through here per query) cannot fail to provide one.
- */
- econtext = onConflict->mt_epqstate.planstate->ps_ExprContext;
+ {
+ ExprContext *econtext = mtstate->ps.ps_ExprContext;
- /*
- * UPDATE affects the same ResultRelation as INSERT in the context
- * of ON CONFLICT DO UPDATE, so parent's target rti can be used
- */
- Assert(resultRelInfo->ri_RangeTableIndex ==
- onConflict->ps.state->es_result_relation_info->ri_RangeTableIndex);
+ /*
+ * Reset per-tuple memory context to free any expression evaluation
+ * storage allocated in the previous cycle.
+ */
+ ResetExprContext(econtext);
- EvalPlanQualSetTuple(&onConflict->mt_epqstate,
- resultRelInfo->ri_RangeTableIndex, copyTuple);
+ /* Make tuple and any needed join variables available to ExecProject */
+ econtext->ecxt_scantuple = slot;
+ econtext->ecxt_outertuple = insertSlot;
+ econtext->ecxt_innertuple = NULL;
- /*
- * Make available excluded-from-insertion tuple (EXCLUDED.*) for
- * referencing within UPDATE expression (that is, make available a
- * slot with that tuple, possibly already modified by BEFORE INSERT
- * row triggers).
- *
- * This is for the benefit of any ExcludedExpr that may appear
- * within the auxiliary UPDATE's targetlist or WHERE clause (the
- * EXCLUDED.* tuple is referenced as an ExcludedExpr). The nested
- * ExcludedExpr's Var is set during ExcludedExpr initialization to
- * have an INNER_VAR varno on the assumption that the inner slot of
- * the EPQ scanstate's expression context will be found to contain
- * this excluded tuple slot (that is, on the assumption that during
- * expression evaluation, ecxt_innertuple will have been assigned
- * the insertSlot here, after ExcludedExpr initialization but in
- * advance of this expression evaluation).
- *
- * See handling of ExcludedExpr within handleRewrite.c and
- * execQual.c.
- */
- econtext->ecxt_innertuple = insertSlot;
+ ExecProject(resultRelInfo->ri_onConflictSetProj, NULL);
+ slot = mtstate->mt_outertupleslot;
- /*
- * Execute auxiliary UPDATE for slot proposed for insertion, to
- * generate new tuple version to supersede TARGET.* tuple.
- *
- * This may include expression evaluation of ExcludedExpr (which
- * occurs in no other context).
- */
- slot = EvalPlanQualNext(&onConflict->mt_epqstate);
+ if (resultRelInfo->ri_WithCheckOptions != NIL)
+ {
+ TupleTableSlot *opts;
+
+ /* Construct temp slot for locked tuple from target */
+ opts = MakeSingleTupleTableSlot(slot->tts_tupleDescriptor);
+ ExecStoreTuple(copyTuple, opts, InvalidBuffer, false);
+
+ /*
+ * Check existing/TARGET.* tuple against UPDATE-applicable
+ * USING security barrier quals (if any), enforced here as RLS
+ * checks/WCOs.
+ *
+ * The rewriter creates UPDATE RLS checks/WCOs for UPDATE
+ * security quals, and stores them as WCOs of "kind"
+ * WCO_RLS_CONFLICT_CHECK, but that's almost the extent of its
+ * special handling for ON CONFLICT DO UPDATE.
+ *
+ * Because the auxiliary query shares its parent's target RTE
+ * (and because auxiliary planstate shares its parent's
+ * resultRelinfo), the rewriter will also have associated
+ * UPDATE applicable straight RLS checks/WCOs for the benefit
+ * of ExecUpdate(). INSERTs and UPDATEs naturally have
+ * mutually exclusive WCO kinds, so there is no danger of
+ * spurious over-enforcement in the INSERT or UPDATE path.
+ */
+ ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
+ slot, mtstate->ps.state);
+ }
- if (resultRelInfo->ri_WithCheckOptions != NIL)
- {
- TupleTableSlot *opts;
+ if (!TupIsNull(slot))
+ *returning = ExecUpdate(&tuple.t_data->t_ctid, NULL,
+ slot, planSlot,
+ &mtstate->mt_epqstate,
+ mtstate->ps.state, canSetTag);
- /* Construct temp slot for locked tuple from target */
- opts = MakeSingleTupleTableSlot(slot->tts_tupleDescriptor);
- ExecStoreTuple(copyTuple, opts, InvalidBuffer, false);
+ ReleaseBuffer(buffer);
/*
- * Check existing/TARGET.* tuple against UPDATE-applicable
- * USING security barrier quals (if any), enforced here as RLS
- * checks/WCOs.
- *
- * The rewriter creates UPDATE RLS checks/WCOs for UPDATE
- * security quals, and stores them as WCOs of "kind"
- * WCO_RLS_CONFLICT_CHECK, but that's almost the extent of its
- * special handling for ON CONFLICT DO UPDATE.
- *
- * Because the auxiliary query shares its parent's target RTE
- * (and because auxiliary planstate shares its parent's
- * resultRelinfo), the rewriter will also have associated
- * UPDATE applicable straight RLS checks/WCOs for the benefit
- * of ExecUpdate(). INSERTs and UPDATEs naturally have
- * mutually exclusive WCO kinds, so there is no danger of
- * spurious over-enforcement in the INSERT or UPDATE path.
+ * As when executing an UPDATE's ModifyTable node in the
+ * conventional manner, reset the per-output-tuple ExprContext
*/
- ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
- slot, onConflict->ps.state);
+ //ResetExprContext(econtext);
}
-
- if (!TupIsNull(slot))
- *returning = ExecUpdate(&tuple.t_data->t_ctid, NULL,
- slot, planSlot,
- &onConflict->mt_epqstate,
- onConflict->ps.state, canSetTag);
-
- ReleaseBuffer(buffer);
-
- /*
- * As when executing an UPDATE's ModifyTable node in the
- * conventional manner, reset the per-output-tuple ExprContext
- */
- ResetPerTupleExprContext(onConflict->ps.state);
-
- /* must provide our own instrumentation support */
- if (onConflict->ps.instrument)
- InstrStopNode(onConflict->ps.instrument, *returning ? 1 : 0);
-
return true;
case HeapTupleUpdated:
if (IsolationUsesXactSnapshot())
case CMD_INSERT:
ExecBSInsertTriggers(node->ps.state, node->resultRelInfo);
if (node->conf == CONF_INSERT)
- ExecBSUpdateTriggers(node->onConflict->state,
+ ExecBSUpdateTriggers(node->ps.state,
node->resultRelInfo);
break;
case CMD_UPDATE:
{
case CMD_INSERT:
if (node->conf == CONF_INSERT)
- ExecASUpdateTriggers(node->onConflict->state,
+ ExecASUpdateTriggers(node->ps.state,
node->resultRelInfo);
ExecASInsertTriggers(node->ps.state, node->resultRelInfo);
break;
{
EState *estate = node->ps.state;
CmdType operation = node->operation;
- ModifyTableState *onConflict = (ModifyTableState *) node->onConflict;
ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo;
PlanState *subplanstate;
switch (operation)
{
case CMD_INSERT:
- slot = ExecInsert(slot, planSlot, onConflict,
+ slot = ExecInsert(node, slot, planSlot,
+ &node->mt_epqstate,
node->arbiterIndexes, node->conf, estate,
node->canSetTag);
break;
{
ModifyTableState *mtstate;
CmdType operation = node->operation;
- Plan *onConflictPlan = node->onConflictPlan;
int nplans = list_length(node->plans);
ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo;
}
}
+
+ if (mtstate->conf != CONF_NONE)
+ {
+ /* already exists if created by RETURNING processing above */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ }
+
+ /*
+ * Initialize target list and qual for ON CONFLICT .. UPDATE .... [WHERE]
+ *
+ * ATM there will never be more than one child here, but we might want to
+ * expand that at some point. So just do it right from the start.
+ */
+#ifdef NOT_YET
+ resultRelInfo = mtstate->resultRelInfo;
+ i = 0;
+ foreach(l, node->onConflictSetWheres)
+ {
+ Node *qual;
+ List *expr;
+
+ qual = (Node *) lfirst(l);
+
+ Assert(IsA(qual, Expr));
+
+ expr = (List *) ExecInitExpr((Expr *) qual, mtstate->mt_plans[i]);
+
+ resultRelInfo->ri_onConflictSetWhere = expr;
+ resultRelInfo++;
+ i++;
+ }
+#endif
+
+ if (node->onConflictSets)
+ {
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ Assert(mtstate->ps.ps_ExprContext != NULL);
+
+ /* take the first one... */
+ tupDesc = ExecTypeFromTL((List *) linitial(node->onConflictSets), false);
+
+ slot = ExecInitExtraTupleSlot(mtstate->ps.state);
+ ExecSetSlotDescriptor(slot, tupDesc);/* FIXME: arguably wrong? */
+ mtstate->mt_outertupleslot = slot;
+ mtstate->mt_fuck = ExecInitExtraTupleSlot(mtstate->ps.state);
+
+ econtext = mtstate->ps.ps_ExprContext;
+
+ resultRelInfo = mtstate->resultRelInfo;
+ foreach(l, node->onConflictSets)
+ {
+ List *rlist = (List *) lfirst(l);
+ List *rliststate;
+
+ rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
+
+ resultRelInfo->ri_onConflictSetProj =
+ ExecBuildProjectionInfo(rliststate, econtext, slot,
+ resultRelInfo->ri_RelationDesc->rd_att);
+
+ resultRelInfo->ri_onConflictSet = rlist;
+ resultRelInfo++;
+ }
+ }
+
/* select first subplan */
mtstate->mt_whichplan = 0;
subplan = (Plan *) linitial(node->plans);
}
break;
case CMD_UPDATE:
- junk_filter_needed = (mtstate->conf == CONF_NONE);
- break;
case CMD_DELETE:
junk_filter_needed = true;
break;
}
}
- /* Initialize auxiliary ModifyTable node, for ON CONFLICT DO UPDATE */
- if (onConflictPlan)
- {
- Assert(mtstate->conf == CONF_INSERT);
-
- /*
- * ExecModifyTable() is never called for auxiliary update
- * ModifyTableState. Execution of the auxiliary plan is driven by its
- * parent in an ad-hoc fashion.
- */
- mtstate->onConflict = ExecInitNode(onConflictPlan, estate, eflags);
- }
-
/*
* Set up a tuple table slot for use for trigger output tuples. In a plan
* containing multiple ModifyTable nodes, all can share one such slot, so
*/
for (i = 0; i < node->mt_nplans; i++)
ExecEndNode(node->mt_plans[i]);
-
- ExecEndNode(node->onConflict);
}
void
COPY_NODE_FIELD(plans);
COPY_SCALAR_FIELD(conf);
COPY_NODE_FIELD(arbiterIndexes);
- COPY_NODE_FIELD(onConflictPlan);
+ COPY_NODE_FIELD(onConflictSets);
+ COPY_NODE_FIELD(onConflictSetWheres);
+ COPY_SCALAR_FIELD(excludedRel);
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
return newnode;
}
-/*
- * _copyExcludedExpr
- */
-static ExcludedExpr *
-_copyExcludedExpr(const ExcludedExpr *from)
-{
- ExcludedExpr *newnode = makeNode(ExcludedExpr);
-
- COPY_NODE_FIELD(arg);
-
- return newnode;
-}
-
/*
* _copyInferenceElem
*/
COPY_SCALAR_FIELD(conf);
COPY_NODE_FIELD(infer);
- COPY_NODE_FIELD(updatequery);
+ COPY_NODE_FIELD(targetList);
+ COPY_NODE_FIELD(whereClause);
COPY_LOCATION_FIELD(location);
return newnode;
COPY_SCALAR_FIELD(conf);
COPY_NODE_FIELD(arbiterElems);
COPY_NODE_FIELD(arbiterWhere);
- COPY_NODE_FIELD(onConflict);
+ COPY_NODE_FIELD(onConflictSet);
+ COPY_NODE_FIELD(onConflictSetWhere);
COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(groupClause);
COPY_NODE_FIELD(havingQual);
case T_CurrentOfExpr:
retval = _copyCurrentOfExpr(from);
break;
- case T_ExcludedExpr:
- retval = _copyExcludedExpr(from);
- break;
case T_InferenceElem:
retval = _copyInferenceElem(from);
break;
return true;
}
-static bool
-_equalExcludedExpr(const ExcludedExpr *a, const ExcludedExpr *b)
-{
- COMPARE_NODE_FIELD(arg);
-
- return true;
-}
-
static bool
_equalTargetEntry(const TargetEntry *a, const TargetEntry *b)
{
COMPARE_SCALAR_FIELD(conf);
COMPARE_NODE_FIELD(arbiterElems);
COMPARE_NODE_FIELD(arbiterWhere);
- COMPARE_NODE_FIELD(onConflict);
+ COMPARE_NODE_FIELD(onConflictSet);
+ COMPARE_NODE_FIELD(onConflictSetWhere);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(groupClause);
COMPARE_NODE_FIELD(havingQual);
{
COMPARE_SCALAR_FIELD(conf);
COMPARE_NODE_FIELD(infer);
- COMPARE_NODE_FIELD(updatequery);
+ COMPARE_NODE_FIELD(targetList);
+ COMPARE_NODE_FIELD(whereClause);
COMPARE_LOCATION_FIELD(location);
return true;
case T_CurrentOfExpr:
retval = _equalCurrentOfExpr(a, b);
break;
- case T_ExcludedExpr:
- retval = _equalExcludedExpr(a, b);
- break;
case T_InferenceElem:
retval = _equalInferenceElem(a, b);
break;
case T_CurrentOfExpr:
type = BOOLOID;
break;
- case T_ExcludedExpr:
- {
- const ExcludedExpr *n = (const ExcludedExpr *) expr;
-
- type = exprType((Node *) n->arg);
- }
- break;
case T_InferenceElem:
{
const InferenceElem *n = (const InferenceElem *) expr;
return ((const CoerceToDomainValue *) expr)->typeMod;
case T_SetToDefault:
return ((const SetToDefault *) expr)->typeMod;
- case T_ExcludedExpr:
- {
- const ExcludedExpr *n = (const ExcludedExpr *) expr;
-
- return ((const Var *) n->arg)->vartypmod;
- }
case T_PlaceHolderVar:
return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
default:
case T_CurrentOfExpr:
coll = InvalidOid; /* result is always boolean */
break;
- case T_ExcludedExpr:
- coll = exprCollation((Node *) ((const ExcludedExpr *) expr)->arg);
- break;
case T_InferenceElem:
coll = exprCollation((Node *) ((const InferenceElem *) expr)->expr);
break;
case T_CurrentOfExpr:
Assert(!OidIsValid(collation)); /* result is always boolean */
break;
- case T_ExcludedExpr:
- {
- Var *v = (Var *) ((ExcludedExpr *) expr)->arg;
- v->varcollid = collation;
- }
- break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
/* just use argument's location */
loc = exprLocation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
- case T_ExcludedExpr:
- /* just use nested expr's location */
- loc = exprLocation((Node *) ((const ExcludedExpr *) expr)->arg);
- break;
case T_InferenceElem:
/* just use nested expr's location */
loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
break;
case T_PlaceHolderVar:
return walker(((PlaceHolderVar *) node)->phexpr, context);
- case T_ExcludedExpr:
- return walker(((ExcludedExpr *) node)->arg, context);
case T_InferenceElem:
return walker(((InferenceElem *) node)->expr, context);
case T_AppendRelInfo:
return true;
if (walker(query->arbiterWhere, context))
return true;
- if (walker(query->onConflict, context))
+ if (walker(query->onConflictSet, context))
+ return true;
+ if (walker(query->onConflictSetWhere, context))
return true;
if (walker((Node *) query->returningList, context))
return true;
return (Node *) newnode;
}
break;
- case T_ExcludedExpr:
- {
- ExcludedExpr *excludedexpr = (ExcludedExpr *) node;
- ExcludedExpr *newnode;
-
- FLATCOPY(newnode, excludedexpr, ExcludedExpr);
- MUTATE(newnode->arg, newnode->arg, Node *);
- return (Node *) newnode;
- }
- break;
case T_InferenceElem:
{
InferenceElem *inferenceelemdexpr = (InferenceElem *) node;
MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
MUTATE(query->arbiterElems, query->arbiterElems, List *);
MUTATE(query->arbiterWhere, query->arbiterWhere, Node *);
- MUTATE(query->onConflict, query->onConflict, Node *);
+ MUTATE(query->onConflictSet, query->onConflictSet, List *);
+ MUTATE(query->onConflictSetWhere, query->onConflictSetWhere, Node *);
MUTATE(query->returningList, query->returningList, List *);
MUTATE(query->jointree, query->jointree, FromExpr *);
MUTATE(query->setOperations, query->setOperations, Node *);
if (walker(stmt->infer, context))
return true;
- if (walker(stmt->updatequery, context))
+ if (walker(stmt->whereClause, context))
+ return true;
+ if (walker(stmt->targetList, context))
return true;
}
break;
WRITE_NODE_FIELD(plans);
WRITE_ENUM_FIELD(conf, ConfType);
WRITE_NODE_FIELD(arbiterIndexes);
- WRITE_NODE_FIELD(onConflictPlan);
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
WRITE_NODE_FIELD(rowMarks);
+ WRITE_NODE_FIELD(onConflictSets);
+ WRITE_NODE_FIELD(onConflictSetWheres);
+ WRITE_INT_FIELD(excludedRel);
WRITE_INT_FIELD(epqParam);
}
WRITE_ENUM_FIELD(conf, ConfType);
WRITE_NODE_FIELD(arbiterElems);
WRITE_NODE_FIELD(arbiterWhere);
- WRITE_NODE_FIELD(onConflict);
+ WRITE_NODE_FIELD(onConflictSet);
+ WRITE_NODE_FIELD(onConflictSetWhere);
WRITE_NODE_FIELD(returningList);
WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(havingQual);
case T_CurrentOfExpr:
_outCurrentOfExpr(str, obj);
break;
- case T_ExcludedExpr:
- _outExcludedExpr(str, obj);
- break;
case T_InferenceElem:
_outInferenceElem(str, obj);
break;
READ_ENUM_FIELD(conf, ConfCmd);
READ_NODE_FIELD(arbiterElems);
READ_NODE_FIELD(arbiterWhere);
- READ_NODE_FIELD(onConflict);
+ READ_NODE_FIELD(onConflictSet);
+ READ_NODE_FIELD(onConflictSetWhere);
READ_NODE_FIELD(returningList);
READ_NODE_FIELD(groupClause);
READ_NODE_FIELD(havingQual);
READ_DONE();
}
-/*
- * _readExcludedExpr
- */
-static ExcludedExpr *
-_readExcludedExpr(void)
-{
- READ_LOCALS(ExcludedExpr);
-
- READ_NODE_FIELD(arg);
-
- READ_DONE();
-}
-
/*
* _readInferenceElem
*/
return_value = _readSetToDefault();
else if (MATCH("CURRENTOFEXPR", 13))
return_value = _readCurrentOfExpr();
- else if (MATCH("EXCLUDED", 8))
- return_value = _readExcludedExpr();
else if (MATCH("INFERENCEELEM", 13))
return_value = _readInferenceElem();
else if (MATCH("TARGETENTRY", 11))
Index nominalRelation,
List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, Plan *onConflictPlan, ConfCmd conf,
+ List *rowMarks, List *onConflictSets,
+ List *onConflictSetWheres, ConfCmd conf,
int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
node->plans = subplans;
node->conf = conf;
node->arbiterIndexes = NIL;
- node->onConflictPlan = onConflictPlan;
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
+ node->onConflictSets = onConflictSets;
+ node->onConflictSetWheres = onConflictSetWheres;
node->epqParam = epqParam;
-
+ node->excludedRel = nominalRelation + 1;
/*
* For each result relation that is a foreign table, allow the FDW to
* construct private plan data, and accumulate it all into a list.
parse->limitCount = preprocess_expression(root, parse->limitCount,
EXPRKIND_LIMIT);
+ /* XXX */
+ parse->onConflictSet = (List *)
+ preprocess_expression(root, (Node *) parse->onConflictSet,
+ EXPRKIND_TARGET);
+
+ parse->onConflictSetWhere = (Node *)
+ preprocess_expression(root, (Node *) parse->onConflictSetWhere,
+ EXPRKIND_QUAL);
+
+
root->append_rel_list = (List *)
preprocess_expression(root, (Node *) root->append_rel_list,
EXPRKIND_APPINFO);
List *withCheckOptionLists;
List *returningLists;
List *rowMarks;
+ List *onConflictSets;
+ List *onConflictSetWheres;
/*
* Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
else
rowMarks = root->rowMarks;
+ if (parse->onConflictSet)
+ onConflictSets = list_make1(parse->onConflictSet);
+ else
+ onConflictSets = NIL;
+
+ if (parse->onConflictSetWhere)
+ onConflictSetWheres = list_make1(parse->onConflictSetWhere);
+ else
+ onConflictSetWheres = NIL;
+
+
plan = (Plan *) make_modifytable(root,
parse->commandType,
parse->canSetTag,
withCheckOptionLists,
returningLists,
rowMarks,
- NULL,
+ onConflictSets,
+ onConflictSetWheres,
parse->conf,
SS_assign_special_param(root));
-
- if (parse->onConflict)
- {
- Query *conflictQry = (Query *) parse->onConflict;
- ModifyTable *parent = (ModifyTable *) plan;
-
- /*
- * An ON CONFLICT DO UPDATE query is a subquery of its parent
- * INSERT ModifyTable, but isn't formally a subplan -- it's an
- * "auxiliary" plan.
- *
- * During execution, the auxiliary plan state is used to
- * execute the UPDATE query in an ad-hoc manner, driven by the
- * parent. The executor will only ever execute the auxiliary
- * plan through its parent. onConflictPlan is "auxiliary" to
- * its parent in the sense that it's strictly encapsulated
- * from other code (for example, the executor does not
- * separately track it within estate as a plan that needs to
- * have execution finished when it appears within a
- * data-modifying CTE -- only the parent is specifically
- * tracked for that purpose).
- *
- * There is a fundamental nexus between parent and auxiliary
- * plans that makes a fully unified representation seem
- * compelling (a "CMD_UPSERT" ModifyTable plan and Query).
- * That would obviate the need to specially track auxiliary
- * state across all stages of execution just for this case;
- * the optimizer would then not have to generate a
- * fully-formed, independent UPDATE subquery plan (with a
- * scanstate only useful for EvalPlanQual() re-evaluation).
- * However, it's convenient to plan each ModifyTable
- * separately, as doing so maximizes code reuse. The
- * alternative must be to introduce abstractions that (for
- * example) allow a single "CMD_UPSERT" ModifyTable to have
- * two distinct types of targetlist (that will need to be
- * processed differently during parsing and rewriting anyway).
- * The auxiliary UPDATE plan is a good trade-off between a
- * fully-fledged "CMD_UPSERT" representation, and the opposite
- * extreme of tracking two separate ModifyTable nodes, joined
- * by a contrived join type, with (for example) odd properties
- * around tuple visibility not well encapsulated. A contrived
- * join based design would also necessitate teaching
- * ModifyTable nodes to support rescan just for the benefit of
- * ON CONFLICT DO UPDATE.
- */
- parent->onConflictPlan = subquery_planner(glob, conflictQry,
- root, hasRecursion,
- 0, NULL);
- }
}
}
List *resultRelations = NIL;
List *withCheckOptionLists = NIL;
List *returningLists = NIL;
+ List *onConflictSets = NIL;
+ List *onConflictSetWheres = NIL;
List *rowMarks;
ListCell *lc;
if (parse->returningList)
returningLists = lappend(returningLists,
subroot.parse->returningList);
+ if (parse->onConflictSet)
+ onConflictSets = lappend(onConflictSets,
+ subroot.parse->onConflictSet);
+ if (parse->onConflictSetWhere)
+ onConflictSetWheres = lappend(onConflictSetWheres,
+ subroot.parse->onConflictSetWhere);
}
/* Mark result as unordered (probably unnecessary) */
withCheckOptionLists,
returningLists,
rowMarks,
- NULL,
+ onConflictSets,
+ onConflictSetWheres,
parse->conf,
SS_assign_special_param(root));
}
/* Preprocess targetlist */
tlist = preprocess_targetlist(root, tlist);
+
+ if (parse->onConflictSet)
+ {
+ parse->onConflictSet = preprocess_targetlist_cmd(root,
+ parse->onConflictSet,
+ CMD_UPDATE,
+ parse->resultRelation,
+ parse->rtable);
+ }
+
/*
* Expand any rangetable entries that have security barrier quals.
* This may add new security barrier subquery RTEs to the rangetable.
#include "tcop/utility.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
-
+#include "rewrite/rewriteManip.h" /* FIXME: move */
typedef struct
{
static bool extract_query_dependencies_walker(Node *node,
PlannerInfo *context);
+static indexed_tlist *build_tlist_index_other_vars(List *tlist, Index ignore_rel);
/*****************************************************************************
*
{
lfirst_int(l) += rtoffset;
}
+
foreach(l, splan->rowMarks)
{
PlanRowMark *rc = (PlanRowMark *) lfirst(l);
rtoffset);
}
+ splan->excludedRel += rtoffset;
+
+ fix_scan_list(root, splan->onConflictSets, rtoffset);
+
+ /* FIXME: This should be moved away from here */
+ /*
+ * Setup pseudo relation EXCLUDED in the outer slot.
+ */
+ ChangeVarNodes((Node *) splan->onConflictSets,
+ splan->excludedRel, OUTER_VAR, 0);
+
/*
* Append this ModifyTable node's final result relation RT
* index(es) to the global list for the plan, and set its
* global list.
*/
splan->resultRelIndex = list_length(root->glob->resultRelations);
-
- if (!splan->onConflictPlan)
- {
- /*
- * Only actually append result relation for non-auxiliary
- * ModifyTable plans
- */
- root->glob->resultRelations =
- list_concat(root->glob->resultRelations,
- list_copy(splan->resultRelations));
- }
- else
- {
- /*
- * Adjust rtoffset passed to child, to compensate for
- * dummy RTE left by EXCLUDED.* alias in auxiliary plan.
- * Auxiliary plan will target same resultRelation from
- * flattened range table as its parent.
- */
- splan->onConflictPlan =
- set_plan_refs(root, splan->onConflictPlan,
- rtoffset - PRS2_OLD_VARNO);
-
- /*
- * Set up the visible plan targetlist as being the same as
- * the parent. Again, this is for the use of EXPLAIN only.
- */
- splan->onConflictPlan->targetlist = splan->plan.targetlist;
- }
+ root->glob->resultRelations =
+ list_concat(root->glob->resultRelations,
+ list_copy(splan->resultRelations));
}
break;
case T_Append:
locally_added_param);
finalize_primnode((Node *) mtplan->returningLists,
&context);
+ finalize_primnode((Node *) mtplan->onConflictSets,
+ &context);
foreach(l, mtplan->plans)
{
context.paramids =
valid_params,
scan_params));
}
-
- /*
- * No need to directly handle onConflictPlan here, since it
- * cannot have params (due to parse analysis enforced
- * restrictions prohibiting subqueries).
- */
}
break;
pullup_replace_vars((Node *) parse->targetList, &rvcontext);
parse->returningList = (List *)
pullup_replace_vars((Node *) parse->returningList, &rvcontext);
+ parse->onConflictSet = (List *)
+ pullup_replace_vars((Node *) parse->onConflictSet, &rvcontext);
replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
lowest_nulling_outer_join);
Assert(parse->setOperations == NULL);
pullup_replace_vars((Node *) parse->targetList, &rvcontext);
parse->returningList = (List *)
pullup_replace_vars((Node *) parse->returningList, &rvcontext);
+ parse->onConflictSet = (List *)
+ pullup_replace_vars((Node *) parse->onConflictSet, &rvcontext);
replace_vars_in_jointree((Node *) parse->jointree, &rvcontext, NULL);
Assert(parse->setOperations == NULL);
parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext);
Index result_relation, List *range_table);
+List *
+preprocess_targetlist_cmd(PlannerInfo *root,
+ List *tlist,
+ int command_type,
+ int result_relation,
+ List *range_table)
+{
+
+ tlist = expand_targetlist(tlist, command_type,
+ result_relation, range_table);
+
+ return tlist;
+}
+
/*
* preprocess_targetlist
* Driver for preprocessing the parse tree targetlist.
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->conf = CONF_NONE;
- qry->onConflict = NULL;
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
ListCell *icols;
ListCell *attnos;
ListCell *lc;
+ RangeTblEntry *exclRte;
+
/* There can't be any outer WITH to worry about */
Assert(pstate->p_ctenamespace == NIL);
qry->commandType = CMD_INSERT;
pstate->p_is_insert = true;
- pstate->p_is_on_conflict = conf != CONF_NONE;
/* process the WITH clause independently of all else */
if (stmt->withClause)
qry->resultRelation = setTargetTable(pstate, stmt->relation,
false, false, ACL_INSERT);
+
+ if (stmt->confClause)
+ {
+ addRTEtoQuery(pstate, pstate->p_target_rangetblentry, false, true, true);
+
+ }
+
+ if (conf == CONF_INSERT)
+ {
+ pstate->p_target_rangetblentry->requiredPerms |= ACL_UPDATE;
+
+ exclRte = addRangeTableEntryForRelation(pstate,
+ pstate->p_target_relation,
+ makeAlias("excluded", NIL),
+ false, false);
+
+ /*
+ * Add EXCLUDED RTE to namespace. It does not matter that the RTE is
+ * not added to the Query joinlist, since its Vars are merely
+ * placeholders for ExcludedExpr.
+ */
+ addRTEtoQuery(pstate, exclRte, false, true, true);
+
+ /* Append parent/our target to Query rtable (should be last) */
+ /* FRAKME pstate->p_rtable = lappend(pstate->p_rtable, exclRte); */
+ pstate->p_rtable = lappend(pstate->p_rtable, exclRte);
+ }
+
/* Validate stmt->cols list, or build default list if no list given */
icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
Assert(list_length(icolumns) == list_length(attrnos));
attnos = lnext(attnos);
}
+ if (stmt->confClause && list_length(stmt->confClause->targetList) > 0)
+ {
+ ListCell *tlo, *tln;
+ Node *qual;
+
+ qry->onConflictSet =
+ transformTargetList(pstate, stmt->confClause->targetList,
+ EXPR_KIND_UPDATE_SOURCE);
+
+ Assert(list_length(stmt->confClause->targetList) ==
+ list_length(qry->onConflictSet));
+
+ forboth(tln, qry->onConflictSet, tlo, stmt->confClause->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tln);
+ ResTarget *origTarget = (ResTarget *) lfirst(tlo);
+ int attrno;
+
+ if (tle->resjunk)
+ elog(ERROR, "not expecting resjunks here");
+
+ attrno = attnameAttNum(pstate->p_target_relation,
+ origTarget->name, true);
+ if (attrno == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ origTarget->name,
+ RelationGetRelationName(pstate->p_target_relation)),
+ parser_errposition(pstate, origTarget->location)));
+
+ updateTargetListEntry(pstate, tle, origTarget->name,
+ attrno,
+ origTarget->indirection,
+ origTarget->location);
+
+ /* Mark the target column as requiring update permissions */
+ rte->updatedCols = bms_add_member(rte->updatedCols,
+ attrno - FirstLowInvalidHeapAttributeNumber);
+
+ }
+
+ qual = transformWhereClause(pstate, stmt->confClause->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
+ qry->onConflictSetWhere = qual;
+ }
+
/*
* If we have a RETURNING clause, or there are inference elements used as
* for ON CONFLICT, we need to add the target relation to the query
*/
if (stmt->returningList || stmt->confClause)
{
+ /* FIXME */
pstate->p_namespace = NIL;
addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
false, true, true);
- qry->returningList = transformReturningList(pstate,
- stmt->returningList);
+
+ if (stmt->returningList)
+ qry->returningList = transformReturningList(pstate,
+ stmt->returningList);
+
+ if (stmt->confClause)
+ {
+ /*
+ * Perform parse analysis of arbiter columns/expressions. These are
+ * later used to infer a unique index which arbitrates whether or not
+ * to take the alternative ON CONFLICT path (i.e. whether or not to
+ * INSERT or take the alternative path in respect of each slot proposed
+ * for insertion).
+ */
+ transformConflictClause(pstate, stmt->confClause, &qry->arbiterElems,
+ &qry->arbiterWhere);
+ }
}
/* done building the range table and jointree */
qry->conf = conf;
qry->hasSubLinks = pstate->p_hasSubLinks;
- qry->onConflict = NULL;
-
- if (stmt->confClause)
- {
- /*
- * ON CONFLICT DO UPDATE requires special parse analysis of auxiliary
- * update Query
- */
- if (stmt->confClause->updatequery)
- {
- ParseState *sub_pstate = make_parsestate(pstate);
- Query *uqry;
-
- /*
- * The optimizer is not prepared to accept a subquery RTE for a
- * non-CMD_SELECT Query. The CMD_UPDATE Query is tracked as
- * special auxiliary state, while there is more or less analogous
- * auxiliary state tracked in later stages of query execution.
- *
- * Parent canSetTag only ever actually consulted, so no need to
- * set that here.
- */
- uqry = transformStmt(sub_pstate, stmt->confClause->updatequery);
- Assert(uqry->commandType == CMD_UPDATE &&
- uqry->conf == CONF_UPDATE);
-
- /* Save auxiliary query */
- qry->onConflict = (Node *) uqry;
-
- free_parsestate(sub_pstate);
- }
-
- /*
- * Perform parse analysis of arbiter columns/expressions. These are
- * later used to infer a unique index which arbitrates whether or not
- * to take the alternative ON CONFLICT path (i.e. whether or not to
- * INSERT or take the alternative path in respect of each slot proposed
- * for insertion).
- */
- transformConflictClause(pstate, stmt->confClause, &qry->arbiterElems,
- &qry->arbiterWhere);
- }
assign_query_collations(pstate, qry);
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->conf = CONF_NONE;
- qry->onConflict = NULL;
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->commandType = CMD_UPDATE;
pstate->p_is_update = true;
- /* for auxiliary UPDATEs, visit parent INSERT to set target table */
- pstate->p_is_on_conflict = (stmt->relation == NULL);
-
/* process the WITH clause independently of all else */
if (stmt->withClause)
{
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
- if (!pstate->p_is_on_conflict)
- {
- InhOption = interpretInhOption(stmt->relation->inhOpt);
+ InhOption = interpretInhOption(stmt->relation->inhOpt);
- qry->conf = CONF_NONE;
- }
- else
- {
- /* auxiliary UPDATE does not accept ONLY */
- InhOption = false;
-
- qry->conf = CONF_UPDATE;
- }
+ qry->conf = CONF_NONE;
qry->resultRelation = setTargetTable(pstate, stmt->relation,
InhOption,
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
- qry->onConflict = NULL;
qry->hasSubLinks = pstate->p_hasSubLinks;
%type <list> cte_list
%type <list> within_group_clause
-%type <node> OnConflictUpdateStmt
%type <node> filter_clause
%type <list> window_clause window_definition_list opt_partition_clause
%type <windef> window_definition over_clause window_specification
;
opt_on_conflict:
- ON CONFLICT opt_conf_expr DO OnConflictUpdateStmt
+ ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
$$ = makeNode(ConflictClause);
$$->conf = CONF_INSERT;
$$->infer = $3;
- $$->updatequery = $5;
+ $$->targetList = $7;
+ $$->whereClause = $8;
$$->location = @1;
}
|
$$ = makeNode(ConflictClause);
$$->conf = CONF_NOTHING;
$$->infer = $3;
- $$->updatequery = NULL;
+ $$->targetList = NIL;
+ $$->whereClause = NULL;
$$->location = @1;
}
| /*EMPTY*/
}
;
-OnConflictUpdateStmt: UPDATE
- SET set_clause_list
- where_clause
- {
- UpdateStmt *n = makeNode(UpdateStmt);
- /* NULL relation conveys auxiliary */
- n->relation = NULL;
- n->targetList = $3;
- n->fromClause = NULL;
- n->whereClause = $4;
- n->returningList = NULL;
- n->withClause = NULL;
- $$ = (Node *)n;
- }
- ;
-
set_clause_list:
set_clause { $$ = $1; }
| set_clause_list ',' set_clause { $$ = list_concat($1,$3); }
* We also open the target relation and acquire a write lock on it.
* This must be done before processing the FROM list, in case the target
* is also mentioned as a source relation --- we want to be sure to grab
- * the write lock before any read lock. Note that when called during
- * the parse analysis of an auxiliary UPDATE query, relation may be
- * NULL, and the details are acquired from the parent.
+ * the write lock before any read lock.
*
* If alsoSource is true, add the target to the query's joinlist and
* namespace. For INSERT, we don't want the target to be joined to;
/*
* Open target rel and grab suitable lock (which we will hold till end of
- * transaction), iff this is not an auxiliary ON CONFLICT DO UPDATE.
+ * transaction).
*
* free_parsestate() will eventually do the corresponding heap_close(),
- * but *not* release the lock (again, iff this is not an auxiliary ON
- * CONFLICT DO UPDATE).
+ * but *not* release the lock.
*/
- if (!pstate->p_is_on_conflict || pstate->p_is_insert)
- {
- pstate->p_target_relation = parserOpenTable(pstate, relation,
- RowExclusiveLock);
-
- /*
- * Now build an RTE.
- */
- rte = addRangeTableEntryForRelation(pstate, pstate->p_target_relation,
- relation->alias, inh, false);
+ pstate->p_target_relation = parserOpenTable(pstate, relation,
+ RowExclusiveLock);
+ /*
+ * Now build an RTE.
+ */
+ rte = addRangeTableEntryForRelation(pstate, pstate->p_target_relation,
+ relation->alias, inh, false);
- /*
- * Override addRangeTableEntry's default ACL_SELECT permissions check,
- * and instead mark target table as requiring exactly the specified
- * permissions.
- *
- * If we find an explicit reference to the rel later during parse
- * analysis, we will add the ACL_SELECT bit back again; see
- * markVarForSelectPriv and its callers.
- */
- rte->requiredPerms = requiredPerms;
- }
- else
+#ifdef FIXME
{
RangeTblEntry *exclRte;
addRTEtoQuery(pstate, exclRte, false, true, true);
/* Append parent/our target to Query rtable (should be last) */
+ /* FRACKME */
pstate->p_rtable = lappend(pstate->p_rtable, rte);
}
+#endif
pstate->p_target_rangetblentry = rte;
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
+ /*
+ * Override addRangeTableEntry's default ACL_SELECT permissions check,
+ * and instead mark target table as requiring exactly the specified
+ * permissions.
+ *
+ * If we find an explicit reference to the rel later during parse
+ * analysis, we will add the ACL_SELECT bit back again; see
+ * markVarForSelectPriv and its callers.
+ */
+ rte->requiredPerms = requiredPerms;
+
/*
* If UPDATE/DELETE, add table to joinlist and namespace.
*
parser_errposition(pstate,
exprLocation((Node *) confClause))));
- Assert(confClause->conf != CONF_INSERT ||
- confClause->updatequery != NULL);
-
/*
* To simplify certain aspects of its design, speculative insertion into
* system catalogs is disallowed
* Check to see if the sublink is in an invalid place within the query. We
* allow sublinks everywhere in SELECT/INSERT/UPDATE/DELETE, but generally
* not in utility statements. They're also disallowed within auxiliary ON
- * CONFLICT DO UPDATE commands, which we check for here.
+ * CONFLICT DO UPDATE commands, which we check for here. XXX
*/
err = NULL;
switch (pstate->p_expr_kind)
*/
}
+#ifdef FIXME
if (pstate->p_is_on_conflict && pstate->p_is_update)
err = _("cannot use subquery in ON CONFLICT DO UPDATE");
+#endif
if (err)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("target lists can have at most %d entries",
MaxTupleAttributeNumber)));
- /*
- * Don't close target relation for auxiliary ON CONFLICT DO UPDATE, since
- * it is managed by parent INSERT directly
- */
- if (pstate->p_target_relation != NULL &&
- (!pstate->p_is_on_conflict ||
- pstate->p_is_insert))
+ if (pstate->p_target_relation != NULL)
heap_close(pstate->p_target_relation, NoLock);
pfree(pstate);
bool for_execute; /* AcquireRewriteLocks' forExecute param */
} acquireLocksOnSubLinks_context;
-typedef struct excluded_replace_context
-{
- int varno; /* varno of EXCLUDED.* Vars */
- int rvarno; /* replace varno */
-} excluded_replace_context;
-
static bool acquireLocksOnSubLinks(Node *node,
acquireLocksOnSubLinks_context *context);
static Query *rewriteRuleAction(Query *parsetree,
CmdType event,
bool *returning_flag);
static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index);
-static void rewriteTargetListIU(Query *parsetree, Relation target_relation,
+static List *rewriteTargetListIU(List *targetList,
+ CmdType commandType,
+ Relation target_relation,
+ int result_rti,
List **attrno_list);
static TargetEntry *process_matched_tle(TargetEntry *src_tle,
TargetEntry *prior_tle,
bool forUpdatePushedDown);
static bool view_has_instead_trigger(Relation view, CmdType event);
static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist);
-static Node *excluded_replace_vars(Node *expr,
- excluded_replace_context * context);
-static Node *excluded_replace_vars_callback(Var *var,
- replace_rte_variables_context *context);
/*
* order of the original tlist's non-junk entries. This is needed for
* processing VALUES RTEs.
*/
-static void
-rewriteTargetListIU(Query *parsetree, Relation target_relation,
+static List*
+rewriteTargetListIU(List *targetList,
+ CmdType commandType,
+ Relation target_relation,
+ int result_rti,
List **attrno_list)
{
- CmdType commandType = parsetree->commandType;
TargetEntry **new_tles;
List *new_tlist = NIL;
List *junk_tlist = NIL;
new_tles = (TargetEntry **) palloc0(numattrs * sizeof(TargetEntry *));
next_junk_attrno = numattrs + 1;
- foreach(temp, parsetree->targetList)
+ foreach(temp, targetList)
{
TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
{
Node *new_expr;
- new_expr = (Node *) makeVar(parsetree->resultRelation,
+ new_expr = (Node *) makeVar(result_rti,
attrno,
att_tup->atttypid,
att_tup->atttypmod,
pfree(new_tles);
- parsetree->targetList = list_concat(new_tlist, junk_tlist);
+ return list_concat(new_tlist, junk_tlist);
}
List *attrnos;
/* Process the main targetlist ... */
- rewriteTargetListIU(parsetree, rt_entry_relation, &attrnos);
+ parsetree->targetList = rewriteTargetListIU(parsetree->targetList,
+ parsetree->commandType,
+ rt_entry_relation,
+ parsetree->resultRelation,
+ &attrnos);
/* ... and the VALUES expression lists */
rewriteValuesRTE(values_rte, rt_entry_relation, attrnos);
}
else
{
/* Process just the main targetlist */
- rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
+ parsetree->targetList = rewriteTargetListIU(parsetree->targetList,
+ parsetree->commandType,
+ rt_entry_relation,
+ parsetree->resultRelation,
+ NULL);
}
if (parsetree->conf == CONF_INSERT)
{
- Query *qry;
- excluded_replace_context context;
-
- /*
- * While user-defined rules will never be applied in the
- * auxiliary update query, normalization of tlist is still
- * required
- */
- qry = (Query *) parsetree->onConflict;
- rewriteTargetListIU(qry, rt_entry_relation, NULL);
-
- /*
- * Replace OLD Vars (associated with the EXCLUDED.* alias)
- * with first (and only) "real" relation RTE in rtable. This
- * allows the implementation to treat EXCLUDED.* as an alias
- * for the target relation, which is useful during parse
- * analysis, while ultimately having those references
- * rewritten as special ExcludedExpr references to the
- * corresponding Var in the target RTE.
- *
- * This is necessary because while we want a join-like syntax,
- * the resemblance is superficial. This allows the optimizer
- * to produce a simple sequential scan based auxiliary UPDATE
- * plan, sufficient only for EvalPlanQaul() execution from the
- * parent (a plan involving no actual join).
- *
- * This is a kludge, but appears necessary, since the slot
- * made available for referencing via ExcludedExpr is in fact
- * the slot just excluded from insertion by speculative
- * insertion (with the effects of BEFORE ROW INSERT triggers
- * carried). An ad-hoc method for making the EXCLUDED.* tuple
- * available within the auxiliary expression context is
- * appropriate -- the parent INSERT injects it immediately
- * before auxiliary expression evaluation, because that is the
- * only convenient time it is available. This seems far
- * preferable to teaching the ModifyTable plans to behave like
- * scan nodes (with rescanning required) just for the benefit
- * of ON CONFLICT DO UPDATE.
- */
- context.varno = PRS2_OLD_VARNO;
- context.rvarno = PRS2_NEW_VARNO;
-
- parsetree->onConflict =
- excluded_replace_vars(parsetree->onConflict, &context);
+ parsetree->onConflictSet =
+ rewriteTargetListIU(parsetree->onConflictSet,
+ CMD_UPDATE,
+ rt_entry_relation,
+ parsetree->resultRelation,
+ NULL);
}
}
else if (event == CMD_UPDATE)
{
- rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
+ parsetree->targetList = rewriteTargetListIU(parsetree->targetList,
+ parsetree->commandType,
+ rt_entry_relation,
+ parsetree->resultRelation,
+ NULL);
rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation);
}
else if (event == CMD_DELETE)
return results;
}
-
-/*
- * Apply pullup variable replacement throughout an expression tree
- *
- * Returns modified tree, with user-specified rvarno replaced with varno.
- */
-static Node *
-excluded_replace_vars(Node *expr, excluded_replace_context *context)
-{
- /*
- * Don't recurse into subqueries; they're forbidden in auxiliary ON
- * CONFLICT query
- */
- return replace_rte_variables(expr,
- context->varno, 0,
- excluded_replace_vars_callback,
- (void *) context,
- NULL);
-}
-
-static Node *
-excluded_replace_vars_callback(Var *var,
- replace_rte_variables_context *context)
-{
- ExcludedExpr *n = makeNode(ExcludedExpr);
- excluded_replace_context *rcon =
- (excluded_replace_context *) context->callback_arg;
-
- /* Replace with an enclosing ExcludedExpr */
- var->varno = rcon->rvarno;
- n->arg = (Node *) var;
-
- /*
- * Would have to adjust varlevelsup if referenced item is from higher
- * query (should not happen)
- */
- Assert(var->varlevelsup == 0);
-
- if (var->varattno < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("cannot reference system column using EXCLUDED.* alias")));
-
- if (var->varattno == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("cannot reference whole-row using EXCLUDED.* alias")));
-
- return (Node *) n;
-}
*withCheckOptions = lappend(*withCheckOptions, wco);
}
+#ifdef FIXME
/*
* ON CONFLICT DO UPDATE has an RTE that is subject to both INSERT and
* UPDATE RLS enforcement. Security quals on the auxiliary UPDATE seem
*withCheckOptions = lappend(*withCheckOptions, wco);
}
}
+#endif /* FIXME */
}
/* For SELECT, UPDATE, and DELETE, set the security quals */
case T_CoerceToDomainValue:
case T_SetToDefault:
case T_CurrentOfExpr:
- case T_ExcludedExpr:
/* single words: always simple */
return true;
}
break;
- case T_ExcludedExpr:
- {
- ExcludedExpr *excludedexpr = (ExcludedExpr *) node;
- Var *variable = (Var *) excludedexpr->arg;
- bool save_varprefix;
-
- /*
- * Force parentheses because our caller probably assumed our
- * Var is a simple expression.
- */
- appendStringInfoChar(buf, '(');
- save_varprefix = context->varprefix;
- /* Ensure EXCLUDED.* prefix is always visible */
- context->varprefix = true;
- get_rule_expr((Node *) variable, context, true);
- context->varprefix = save_varprefix;
- appendStringInfoChar(buf, ')');
- }
- break;
-
case T_List:
{
char *sep;
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
* projectReturning for computing a RETURNING list
+ * FIXME
* ----------------
*/
typedef struct ResultRelInfo
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
ProjectionInfo *ri_projectReturning;
+ List *ri_onConflictSet;
+ ProjectionInfo *ri_onConflictSetProj;
+ List *ri_onConflictSetWhere;
} ResultRelInfo;
/* ----------------
ExprState *check_expr; /* for CHECK, a boolean expression */
} DomainConstraintState;
-/* ----------------
- * ExcludedExprState node
- * ----------------
- */
-typedef struct ExcludedExprState
-{
- ExprState xprstate;
- ExprState *arg; /* the argument */
-} ExcludedExprState;
-
/* ----------------------------------------------------------------
* Executor State Trees
List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */
ConfCmd conf; /* ON CONFLICT type */
List *arbiterIndexes; /* unique index OIDs to arbitrate taking alt path */
- PlanState *onConflict; /* associated OnConflict state */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
+ TupleTableSlot *mt_outertupleslot;
+ TupleTableSlot *mt_fuck;
} ModifyTableState;
/* ----------------
T_CoerceToDomainValue,
T_SetToDefault,
T_CurrentOfExpr,
- T_ExcludedExpr,
T_InferenceElem,
T_TargetEntry,
T_RangeTblRef,
List *withCheckOptions; /* a list of WithCheckOption's */
ConfCmd conf; /* ON CONFLICT type */
+ List *onConflictSet; /* FIXME */
+ Node *onConflictSetWhere; /* FIXME */
List *arbiterElems; /* unique index arbiter list (of InferenceElem's) */
Node *arbiterWhere; /* unique index arbiter WHERE clause */
- Node *onConflict; /* ON CONFLICT Query */
List *returningList; /* return-values list (of TargetEntry) */
NodeTag type;
ConfCmd conf; /* ON CONFLICT type */
InferClause *infer; /* Optional index inference clause */
- Node *updatequery; /* Update parse stmt */
+ List *targetList; /* the target list (of ResTarget) */
+ Node *whereClause; /* qualifications */
int location; /* token location, or -1 if unknown */
} ConflictClause;
List *plans; /* plan(s) producing source data */
ConfCmd conf; /* ON CONFLICT type */
List *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs */
- Plan *onConflictPlan; /* Plan for ON CONFLICT DO UPDATE query */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
+ List *onConflictSets; /* per-target-table SET for ON CONFLICT clause */
+ List *onConflictSetWheres;/* per-target-table WHERE for ON CONFLICT clause */
+ Index excludedRel; /* Fixme */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
} ModifyTable;
Index nominalRelation,
List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, Plan *onConflictPlan, ConfCmd conf,
+ List *rowMarks,
+ List *onConflictSets, List *onConflictSetWheres,
+ ConfCmd conf,
int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
*/
extern List *preprocess_targetlist(PlannerInfo *root, List *tlist);
+List *preprocess_targetlist_cmd(PlannerInfo *root,
+ List *tlist,
+ int command_type,
+ int result_relation,
+ List *range_table);
+
+
extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
/*
bool p_hasModifyingCTE;
bool p_is_insert;
bool p_is_update;
- bool p_is_on_conflict;
bool p_locked_from_parent;
Relation p_target_relation;
RangeTblEntry *p_target_rangetblentry;