From 74c98a421bf8ee8a3232406f8ac78e80083a60f2 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@heroku.com>
Date: Thu, 18 Sep 2014 19:08:27 -0700
Subject: [PATCH 3/8] EXCLUDED expressions within ON CONFLICT UPDATE

EXCLUDED.* (which previously appeared as EXCLUDED(), and CONFLICTING()
before that) is an "internal" primnode expression which enables
referencing of rejected-for-insertion tuples within both the targetlist
and predicate of the UPDATE portion of an INSERT ... ON CONFLICT UPDATE
query.  The expression is invoked using an alias-like syntax (more on
how this works later).  The fact that a dedicated expression is used
(rather than a dedicated range table entry involved in query
optimization) is an implementation detail.

This additional support is particularly useful for ON CONFLICT queries
that propose multiple tuples for insertion, since it isn't otherwise
possible to succinctly decide which actual values to update each column
with (in the event of taking the update path in respect of a given
slot).

The effects of BEFORE INSERT row triggers on the slot/tuple proposed for
insertion are carried.  This seems logical, since it might be the case
that the rejected values would not have been rejected had some BEFORE
INSERT trigger been disabled.  On the other hand, the potential hazards
around equivalent modifications occurring when both INSERT and UPDATE
BEFORE triggers are fired for the same slot/tuple should be considered
by client applications.  It's possible to imagine a use case in which
this behavior is surprising and undesirable -- essentially the same
non-idempotent modification may occur twice.  (It might also be the case
that BEFORE trigger related side-effects undesirably occur twice, but
writing BEFORE triggers with external side-effects is already considered
a questionable practice for several reasons (consider commit 6868ed74),
and besides, the implementation cannot reasonably prevent this, as noted
in nodeModifyTable.c comments added by the main ON CONFLICT commit).

In this revision, the raw grammar does not generate an ExcludedExpr.
Parse analysis of ON CONFLICT UPDATE is made to add a new relation RTE
to the auxiliary sub_pstate parser state (an alias for the target).
This makes parse analysis build a query tree that is more or less
consistent with there actually being an EXCLUDED relation.  Then, as
part of query rewrite, immediately after normalizing the UPDATE
targetlist, Vars referencing the pseudo-relation (using the EXCLUDED
alias) are replaced with ExcludedExpr that references Vars in the target
relation itself.

Speculative insertion/the executor arranges to rig the Vars and
UPDATE-related/EPQ scan planstate's expression context such that values
will actually originate from the rejected tuple's slot (driven, as
always for the UPDATE's execution, by the parent INSERT ModifyTable
node, changed once per slot proposed for insertion as appropriate).
This whole mechanism is somewhat similar to the handling of trigger WHEN
clauses, where a similar dance must also occur within the executor.

Note that pg_stat_statements does not fingerprint ExludedExpr, because
it cannot appear in the post-parse-analysis, pre-rewrite Query tree.
(pg_stat_statements does not fingerprint every primnode anyway, mostly
because some are only expected in utility statements).  Other existing
Node handling sites that don't expect to see primnodes that appear only
after rewriting (ExcludedExpr may be in its own subcategory here in that
it is the only such non-utility related Node) do not have an
ExcludedExpr case added either.
---
 src/backend/executor/execQual.c        | 54 +++++++++++++++++++++
 src/backend/executor/nodeModifyTable.c | 32 ++++++++++++
 src/backend/nodes/copyfuncs.c          | 16 ++++++
 src/backend/nodes/equalfuncs.c         | 11 +++++
 src/backend/nodes/nodeFuncs.c          | 38 +++++++++++++++
 src/backend/nodes/outfuncs.c           | 11 +++++
 src/backend/nodes/readfuncs.c          | 15 ++++++
 src/backend/optimizer/plan/setrefs.c   |  6 +++
 src/backend/parser/analyze.c           | 22 ++++++++-
 src/backend/rewrite/rewriteHandler.c   | 89 ++++++++++++++++++++++++++++++++++
 src/backend/utils/adt/ruleutils.c      | 39 +++++++++++++++
 src/include/nodes/execnodes.h          | 10 ++++
 src/include/nodes/nodes.h              |  2 +
 src/include/nodes/primnodes.h          | 47 ++++++++++++++++++
 14 files changed, 391 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 0e7400f..57d726e 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -182,6 +182,9 @@ static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
 						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);
 
 
 /* ----------------------------------------------------------------
@@ -4338,6 +4341,33 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 	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 UPDATE auxiliary queries' EPQ expression context (ON CONFLICT
+	 * UPDATE 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 UPDATE auxiliary queries.
+	 *
+	 * Just evaluate nested Var.
+	 */
+	return ExecEvalScalarVar(excludedExpr->arg, econtext, isNull, isDone);
+}
 
 /*
  * ExecEvalExprSwitchContext
@@ -5065,6 +5095,30 @@ ExecInitExpr(Expr *node, PlanState *parent)
 			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 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;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d03604c..05c78c9 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -57,6 +57,7 @@
 static bool ExecLockUpdateTuple(ResultRelInfo	   *resultRelInfo,
 								ItemPointer			conflictTid,
 								TupleTableSlot	   *planSlot,
+								TupleTableSlot	   *insertSlot,
 								ModifyTableState   *onConflict,
 								EState			   *estate);
 
@@ -420,6 +421,7 @@ vlock:
 			if (spec == SPEC_INSERT && !ExecLockUpdateTuple(resultRelInfo,
 															&conflictTid,
 															planSlot,
+															slot,
 															onConflict,
 															estate))
 					goto vlock;
@@ -963,6 +965,7 @@ static bool
 ExecLockUpdateTuple(ResultRelInfo *resultRelInfo,
 					ItemPointer conflictTid,
 					TupleTableSlot *planSlot,
+					TupleTableSlot *insertSlot,
 					ModifyTableState *onConflict,
 					EState			 *estate)
 {
@@ -973,6 +976,7 @@ ExecLockUpdateTuple(ResultRelInfo *resultRelInfo,
 	HTSU_Result 			test;
 	Buffer					buffer;
 	TupleTableSlot		   *slot;
+	ExprContext			   *econtext;
 
 	/*
 	 * XXX We don't have the TID of the conflicting tuple if the index
@@ -1094,12 +1098,40 @@ ExecLockUpdateTuple(ResultRelInfo *resultRelInfo,
 			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;
+
+			/*
 			 * UPDATE affects the same ResultRelation as INSERT in the context
 			 * of ON CONFLICT UPDATE, so parent's target rti is used
 			 */
 			EvalPlanQualSetTuple(&onConflict->mt_epqstate,
 								 resultRelInfo->ri_RangeTableIndex, copyTuple);
 
+			/*
+			 * Make available rejected tuple for referencing within UPDATE
+			 * expression (that is, make available a slot with the rejected
+			 * tuple, possibly already modified by BEFORE INSERT row triggers).
+			 *
+			 * This is for the benefit of any ExcludedExpr that may appear
+			 * within UPDATE's targetlist or WHERE clause.  The EXCLUDED tuple
+			 * may be referenced as an ExcludedExpr, which exist purely for our
+			 * benefit.  The nested ExcludedExpr's Var will necessarily have an
+			 * INNER_VAR varno on the assumption that the inner slot of the EPQ
+			 * scan plan state's expression context will contain the EXCLUDED
+			 * heaptuple slot (that is, on the assumption that during
+			 * expression evaluation, the ecxt_innertuple will be assigned the
+			 * insertSlot by this codepath, in advance of expression
+			 * evaluation).
+			 *
+			 * See handling of ExcludedExpr within handleRewrite.c and
+			 * execQual.c.
+			 */
+			econtext->ecxt_innertuple = insertSlot;
+
 			slot = EvalPlanQualNext(&onConflict->mt_epqstate);
 
 			if (!TupIsNull(slot))
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6c1a7f1..df611d2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1779,6 +1779,19 @@ _copyCurrentOfExpr(const CurrentOfExpr *from)
 }
 
 /*
+ * _copyExcludedExpr
+ */
+static ExcludedExpr *
+_copyExcludedExpr(const ExcludedExpr *from)
+{
+	ExcludedExpr *newnode = makeNode(ExcludedExpr);
+
+	COPY_NODE_FIELD(arg);
+
+	return newnode;
+}
+
+/*
  * _copyTargetEntry
  */
 static TargetEntry *
@@ -4287,6 +4300,9 @@ copyObject(const void *from)
 		case T_CurrentOfExpr:
 			retval = _copyCurrentOfExpr(from);
 			break;
+		case T_ExcludedExpr:
+			retval = _copyExcludedExpr(from);
+			break;
 		case T_TargetEntry:
 			retval = _copyTargetEntry(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4127269..24e58fa 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -681,6 +681,14 @@ _equalCurrentOfExpr(const CurrentOfExpr *a, const CurrentOfExpr *b)
 }
 
 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_NODE_FIELD(expr);
@@ -2720,6 +2728,9 @@ equal(const void *a, const void *b)
 		case T_CurrentOfExpr:
 			retval = _equalCurrentOfExpr(a, b);
 			break;
+		case T_ExcludedExpr:
+			retval = _equalExcludedExpr(a, b);
+			break;
 		case T_TargetEntry:
 			retval = _equalTargetEntry(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 4107cc9..a9e1e13 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -235,6 +235,13 @@ exprType(const Node *expr)
 		case T_CurrentOfExpr:
 			type = BOOLOID;
 			break;
+		case T_ExcludedExpr:
+			{
+				const ExcludedExpr *n = (const ExcludedExpr *) expr;
+
+				type = exprType((Node *) n->arg);
+			}
+			break;
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
@@ -469,6 +476,12 @@ exprTypmod(const Node *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:
@@ -894,6 +907,9 @@ exprCollation(const Node *expr)
 		case T_CurrentOfExpr:
 			coll = InvalidOid;	/* result is always boolean */
 			break;
+		case T_ExcludedExpr:
+			coll = exprCollation((Node *) ((const ExcludedExpr *) expr)->arg);
+			break;
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
@@ -1089,6 +1105,12 @@ exprSetCollation(Node *expr, Oid collation)
 		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;
@@ -1487,6 +1509,10 @@ exprLocation(const Node *expr)
 			/* 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;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
@@ -1916,6 +1942,8 @@ expression_tree_walker(Node *node,
 			break;
 		case T_PlaceHolderVar:
 			return walker(((PlaceHolderVar *) node)->phexpr, context);
+		case T_ExcludedExpr:
+			return walker(((ExcludedExpr *) node)->arg, context);
 		case T_AppendRelInfo:
 			{
 				AppendRelInfo *appinfo = (AppendRelInfo *) node;
@@ -2632,6 +2660,16 @@ expression_tree_mutator(Node *node,
 				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_AppendRelInfo:
 			{
 				AppendRelInfo *appinfo = (AppendRelInfo *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index a32fbaa..34e9163 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1429,6 +1429,14 @@ _outCurrentOfExpr(StringInfo str, const CurrentOfExpr *node)
 }
 
 static void
+_outExcludedExpr(StringInfo str, const ExcludedExpr *node)
+{
+	WRITE_NODE_TYPE("EXCLUDED");
+
+	WRITE_NODE_FIELD(arg);
+}
+
+static void
 _outTargetEntry(StringInfo str, const TargetEntry *node)
 {
 	WRITE_NODE_TYPE("TARGETENTRY");
@@ -3069,6 +3077,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_CurrentOfExpr:
 				_outCurrentOfExpr(str, obj);
 				break;
+			case T_ExcludedExpr:
+				_outExcludedExpr(str, obj);
+				break;
 			case T_TargetEntry:
 				_outTargetEntry(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 9f6570f..b471bbf 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1132,6 +1132,19 @@ _readCurrentOfExpr(void)
 }
 
 /*
+ * _readExcludedExpr
+ */
+static ExcludedExpr *
+_readExcludedExpr(void)
+{
+	READ_LOCALS(ExcludedExpr);
+
+	READ_NODE_FIELD(arg);
+
+	READ_DONE();
+}
+
+/*
  * _readTargetEntry
  */
 static TargetEntry *
@@ -1396,6 +1409,8 @@ parseNodeString(void)
 		return_value = _readSetToDefault();
 	else if (MATCH("CURRENTOFEXPR", 13))
 		return_value = _readCurrentOfExpr();
+	else if (MATCH("EXCLUDED", 8))
+		return_value = _readExcludedExpr();
 	else if (MATCH("TARGETENTRY", 11))
 		return_value = _readTargetEntry();
 	else if (MATCH("RANGETBLREF", 11))
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 3368173..9e73d6c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -792,6 +792,12 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				}
 				else
 				{
+					/*
+					 * Decrement rtoffset, to compensate for dummy RTE left by
+					 * EXCLUDED.* alias.  Auxiliary plan will have same
+					 * resultRelation from flattened RTE as its parent.
+					 */
+					rtoffset -= PRS2_OLD_VARNO;
 					splan->onConflictPlan = (Plan *) set_plan_refs(root,
 											  (Plan *) splan->onConflictPlan,
 											  rtoffset);
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index caaa44c..e0ec207 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -779,7 +779,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 			UpdateStmt	   *pupd;
 			Query		   *dqry;
 			ParseState	   *sub_pstate = make_parsestate(pstate);
-			RangeTblEntry  *subTarget;
+			RangeTblEntry  *subTarget, *exclRte;
 
 			pupd  = (UpdateStmt *) stmt->confClause->updatequery;
 
@@ -788,6 +788,26 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 			/* Assign same target relation as parent InsertStmt */
 			pupd->relation = stmt->relation;
+			pupd->relation->alias = makeAlias("target", NIL);
+
+			/*
+			 * Create EXCLUDED alias for target relation.  This can be used to
+			 * reference the tuple originally proposed for insertion from
+			 * within the ON CONFLICT UPDATE auxiliary query.
+			 *
+			 * NOTE: 'EXCLUDED' will always have a varno equal to 1 (at least
+			 * until rewriting, where the RTE is effectively discarded).
+			 */
+			exclRte = addRangeTableEntryForRelation(sub_pstate,
+													pstate->p_target_relation,
+													makeAlias("excluded", NIL),
+													false, false);
+
+			/*
+			 * Add RTE.  Vars referencing the alias are rewritten to reference
+			 * "target", nested within an ExcludedExpr.
+			 */
+			addRTEtoQuery(sub_pstate, exclRte, false, true, true);
 
 			/*
 			 * The optimizer is not prepared to accept a subquery RTE for a
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 5ab0cba..3db5165 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -43,6 +43,12 @@ typedef struct acquireLocksOnSubLinks_context
 	bool		for_execute;	/* AcquireRewriteLocks' forExecute param */
 } acquireLocksOnSubLinks_context;
 
+typedef struct excluded_replace_context
+{
+	int			varno;			/* varno of EXLCUDED.* Vars */
+	int			rvarno;			/* replace varno */
+} excluded_replace_context;
+
 static bool acquireLocksOnSubLinks(Node *node,
 					   acquireLocksOnSubLinks_context *context);
 static Query *rewriteRuleAction(Query *parsetree,
@@ -71,6 +77,10 @@ static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
 			 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);
 
 
 /*
@@ -3099,6 +3109,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 			if (parsetree->specClause == SPEC_INSERT)
 			{
 				Query						   *qry;
+				excluded_replace_context		context;
 
 				/*
 				 * While user-defined rules will never be applied in the
@@ -3107,6 +3118,35 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 				 */
 				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
+				 * for aesthetic reasons, the resemblance is superficial.  In
+				 * fact, execution of the ModifyTable node (and its direct
+				 * child auxiliary query) manages tupleslot state directly, and
+				 * is directly tasked with making available the appropriate
+				 * tupleslot to the expression context.
+				 *
+				 * 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.
+				 */
+				context.varno = PRS2_OLD_VARNO;
+				context.rvarno = PRS2_OLD_VARNO + 1;
+
+				parsetree->onConflict =
+					excluded_replace_vars(parsetree->onConflict, &context);
 			}
 		}
 		else if (event == CMD_UPDATE)
@@ -3428,3 +3468,52 @@ QueryRewrite(Query *parsetree)
 
 	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)
+{
+	excluded_replace_context *rcon = (excluded_replace_context *) context->callback_arg;
+	ExcludedExpr *n = makeNode(ExcludedExpr);
+
+	/* 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;
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index dd748ac..84f344a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5618,6 +5618,24 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 
 		return NULL;
 	}
+	else if (var->varno == INNER_VAR)
+	{
+		/* Assume an EXCLUDED variable */
+		rte = rt_fetch(PRS2_OLD_VARNO, dpns->rtable);
+
+		/*
+		 * Sanity check:  EXCLUDED.* Vars should only appear in auxiliary ON
+		 * CONFLICT UPDATE queries.  Assert that rte and planstate are
+		 * consistent with that.
+		 */
+		Assert(rte->rtekind == RTE_RELATION);
+		Assert(IsA(dpns->planstate, SeqScanState) ||
+			   IsA(dpns->planstate, ResultState));
+
+		refname = "excluded";
+		colinfo = deparse_columns_fetch(PRS2_OLD_VARNO, dpns);
+		attnum = var->varattno;
+	}
 	else
 	{
 		elog(ERROR, "bogus varno: %d", var->varno);
@@ -6358,6 +6376,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 		case T_CoerceToDomainValue:
 		case T_SetToDefault:
 		case T_CurrentOfExpr:
+		case T_ExcludedExpr:
 			/* single words: always simple */
 			return true;
 
@@ -7583,6 +7602,26 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			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;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 19b5e29..0274ebc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -973,6 +973,16 @@ typedef struct DomainConstraintState
 	ExprState  *check_expr;		/* for CHECK, a boolean expression */
 } DomainConstraintState;
 
+/* ----------------
+ *		ExcludedExprState node
+ * ----------------
+ */
+typedef struct ExcludedExprState
+{
+	ExprState	xprstate;
+	ExprState  *arg;			/* the argument */
+} ExcludedExprState;
+
 
 /* ----------------------------------------------------------------
  *				 Executor State Trees
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cac6b15..ca568a2 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -168,6 +168,7 @@ typedef enum NodeTag
 	T_CoerceToDomainValue,
 	T_SetToDefault,
 	T_CurrentOfExpr,
+	T_ExcludedExpr,
 	T_TargetEntry,
 	T_RangeTblRef,
 	T_JoinExpr,
@@ -207,6 +208,7 @@ typedef enum NodeTag
 	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
+	T_ExcludedExprState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1d06f42..21c39dc 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1147,6 +1147,53 @@ typedef struct CurrentOfExpr
 	int			cursor_param;	/* refcursor parameter number, or 0 */
 } CurrentOfExpr;
 
+/*
+ * ExcludedExpr - an EXCLUDED.* expression
+ *
+ * During parse analysis of ON CONFLICT UPDATE auxiliary queries, a dummy
+ * EXCLUDED range table entry is generated, which is actually just an alias for
+ * the target relation.  This is useful during parse analysis, allowing the
+ * parser to produce simple error messages, for example.  There is the
+ * appearance of a join within the auxiliary ON CONFLICT UPDATE, superficially
+ * similar to a join in an UPDATE ... FROM;  this is a limited, ad-hoc join
+ * though, as the executor needs to tightly control the referenced tuple/slot
+ * through which update evaluation references excluded values originally
+ * proposed for insertion.  Note that EXCLUDED.* values carry forward the
+ * effects of BEFORE ROW INSERT triggers.
+ *
+ * To implement a limited "join" for ON CONFLICT UPDATE auxiliary queries,
+ * during the rewrite stage, Vars referencing the alias EXCLUDED.* RTE are
+ * swapped with ExcludedExprs, which also contain Vars;  their Vars are
+ * equivalent, but reference the target instead.  The ExcludedExpr Var actually
+ * evaluates against varno INNER_VAR during expression evaluation (and not a
+ * varno INDEX_VAR associated with an entry in the flattened range table
+ * representing the target, which is necessarily being scanned whenever an
+ * ExcludedExpr is evaluated) while still being logically associated with the
+ * target.  The Var is only rigged to reference the inner slot during
+ * ExcludedExpr initialization.  The executor closely controls the evaluation
+ * expression, installing the EXCLUDED slot actually excluded from insertion
+ * into the inner slot of the child/auxiliary evaluation context in an ad-hoc
+ * fashion, which, after ExcludedExpr initialization, is expected (i.e. it is
+ * expected during ExcludedExpr evaluation that the parent insert will make
+ * each excluded tuple available in the inner slot in turn).  ExcludedExpr are
+ * only ever evaluated during special speculative insertion related EPQ
+ * expression evaluation, purely for the benefit of auxiliary UPDATE
+ * expressions.
+ *
+ * Aside from representing a logical choke point for this special expression
+ * evaluation, having a dedicated primnode also prevents the optimizer from
+ * considering various optimization that might otherwise be attempted.
+ * Obviously there is no useful join optimization possible within the auxiliary
+ * query, and an ExcludedExpr based post-rewrite query tree representation is a
+ * convenient way of preventing that, as well as related inapplicable
+ * optimizations concerning the equivalence of Vars.
+ */
+typedef struct ExcludedExpr
+{
+	Expr		xpr;
+	Node	   *arg;			/* argument (Var) */
+} ExcludedExpr;
+
 /*--------------------
  * TargetEntry -
  *	   a target entry (used in query target lists)
-- 
1.9.1

