diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index a3e8c59..7c2216a 100644
*** a/contrib/pg_stat_statements/pg_stat_statements.c
--- b/contrib/pg_stat_statements/pg_stat_statements.c
*************** JumbleExpr(pgssJumbleState *jstate, Node
*** 2396,2401 ****
--- 2396,2402 ----
  				SubLink    *sublink = (SubLink *) node;
  
  				APP_JUMB(sublink->subLinkType);
+ 				APP_JUMB(sublink->subLinkId);
  				JumbleExpr(jstate, (Node *) sublink->testexpr);
  				JumbleQuery(jstate, (Query *) sublink->subselect);
  			}
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 5d02d94..4885ec0 100644
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
*************** ExecSubPlan(SubPlanState *node,
*** 72,78 ****
  	/* Sanity checks */
  	if (subplan->subLinkType == CTE_SUBLINK)
  		elog(ERROR, "CTE subplans should not be executed via ExecSubPlan");
! 	if (subplan->setParam != NIL)
  		elog(ERROR, "cannot set parent params from subquery");
  
  	/* Select appropriate evaluation strategy */
--- 72,78 ----
  	/* Sanity checks */
  	if (subplan->subLinkType == CTE_SUBLINK)
  		elog(ERROR, "CTE subplans should not be executed via ExecSubPlan");
! 	if (subplan->setParam != NIL && subplan->subLinkType != MULTIEXPR_SUBLINK)
  		elog(ERROR, "cannot set parent params from subquery");
  
  	/* Select appropriate evaluation strategy */
*************** ExecScanSubPlan(SubPlanState *node,
*** 231,236 ****
--- 231,259 ----
  	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
  
  	/*
+ 	 * MULTIEXPR subplans, when "executed", just return NULL; but first we
+ 	 * mark the subplan's output parameters as needing recalculation.  XXX
+ 	 * this is a bit of a hack.  Note that unlike ExecReScanSetParamPlan, we
+ 	 * do *not* set bits in the parent's chgParam, because we don't want to
+ 	 * cause a rescan of the parent.
+ 	 */
+ 	if (subLinkType == MULTIEXPR_SUBLINK)
+ 	{
+ 		EState	   *estate = node->parent->state;
+ 
+ 		foreach(l, subplan->setParam)
+ 		{
+ 			int			paramid = lfirst_int(l);
+ 			ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
+ 
+ 			prm->execPlan = node;
+ 		}
+ 		MemoryContextSwitchTo(oldcontext);
+ 		*isNull = true;
+ 		return (Datum) 0;
+ 	}
+ 
+ 	/*
  	 * Set Params of this plan from parent plan correlation values. (Any
  	 * calculation we have to do is done in the parent econtext, since the
  	 * Param values don't need to have per-query lifetime.)
*************** ExecInitSubPlan(SubPlan *subplan, PlanSt
*** 667,672 ****
--- 690,698 ----
  	sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
  											   subplan->plan_id - 1);
  
+ 	/* ... and to its parent's state */
+ 	sstate->parent = parent;
+ 
  	/* Initialize subexpressions */
  	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
  	sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
*************** ExecInitSubPlan(SubPlan *subplan, PlanSt
*** 690,695 ****
--- 716,723 ----
  	sstate->cur_eq_funcs = NULL;
  
  	/*
+ 	 * XXX comment needs update
+ 	 *
  	 * If this plan is un-correlated or undirect correlated one and want to
  	 * set params for parent plan then mark parameters as needing evaluation.
  	 *
*************** ExecInitSubPlan(SubPlan *subplan, PlanSt
*** 890,896 ****
  /* ----------------------------------------------------------------
   *		ExecSetParamPlan
   *
!  *		Executes an InitPlan subplan and sets its output parameters.
   *
   * This is called from ExecEvalParamExec() when the value of a PARAM_EXEC
   * parameter is requested and the param's execPlan field is set (indicating
--- 918,924 ----
  /* ----------------------------------------------------------------
   *		ExecSetParamPlan
   *
!  *		Executes a subplan and sets its output parameters.
   *
   * This is called from ExecEvalParamExec() when the value of a PARAM_EXEC
   * parameter is requested and the param's execPlan field is set (indicating
*************** ExecSetParamPlan(SubPlanState *node, Exp
*** 908,913 ****
--- 936,942 ----
  	SubLinkType subLinkType = subplan->subLinkType;
  	MemoryContext oldcontext;
  	TupleTableSlot *slot;
+ 	ListCell   *pvar;
  	ListCell   *l;
  	bool		found = false;
  	ArrayBuildState *astate = NULL;
*************** ExecSetParamPlan(SubPlanState *node, Exp
*** 924,929 ****
--- 953,979 ----
  	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
  
  	/*
+ 	 * Set Params of this plan from parent plan correlation values. (Any
+ 	 * calculation we have to do is done in the parent econtext, since the
+ 	 * Param values don't need to have per-query lifetime.)  Currently, we
+ 	 * expect only MULTIEXPR_SUBLINK plans to have any correlation values.
+ 	 */
+ 	Assert(subplan->parParam == NIL || subLinkType == MULTIEXPR_SUBLINK);
+ 	Assert(list_length(subplan->parParam) == list_length(node->args));
+ 
+ 	forboth(l, subplan->parParam, pvar, node->args)
+ 	{
+ 		int			paramid = lfirst_int(l);
+ 		ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
+ 
+ 		prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ 											   econtext,
+ 											   &(prm->isnull),
+ 											   NULL);
+ 		planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
+ 	}
+ 
+ 	/*
  	 * Run the plan.  (If it needs to be rescanned, the first ExecProcNode
  	 * call will take care of that.)
  	 */
*************** ExecSetParamPlan(SubPlanState *node, Exp
*** 964,969 ****
--- 1014,1020 ----
  
  		if (found &&
  			(subLinkType == EXPR_SUBLINK ||
+ 			 subLinkType == MULTIEXPR_SUBLINK ||
  			 subLinkType == ROWCOMPARE_SUBLINK))
  			ereport(ERROR,
  					(errcode(ERRCODE_CARDINALITY_VIOLATION),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 43530aa..8d3d5a7 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copySubLink(const SubLink *from)
*** 1327,1332 ****
--- 1327,1333 ----
  	SubLink    *newnode = makeNode(SubLink);
  
  	COPY_SCALAR_FIELD(subLinkType);
+ 	COPY_SCALAR_FIELD(subLinkId);
  	COPY_NODE_FIELD(testexpr);
  	COPY_NODE_FIELD(operName);
  	COPY_NODE_FIELD(subselect);
*************** _copyResTarget(const ResTarget *from)
*** 2247,2252 ****
--- 2248,2265 ----
  	return newnode;
  }
  
+ static MultiAssignRef *
+ _copyMultiAssignRef(const MultiAssignRef *from)
+ {
+ 	MultiAssignRef *newnode = makeNode(MultiAssignRef);
+ 
+ 	COPY_NODE_FIELD(source);
+ 	COPY_SCALAR_FIELD(colno);
+ 	COPY_SCALAR_FIELD(ncolumns);
+ 
+ 	return newnode;
+ }
+ 
  static TypeName *
  _copyTypeName(const TypeName *from)
  {
*************** copyObject(const void *from)
*** 4561,4566 ****
--- 4574,4582 ----
  		case T_ResTarget:
  			retval = _copyResTarget(from);
  			break;
+ 		case T_MultiAssignRef:
+ 			retval = _copyMultiAssignRef(from);
+ 			break;
  		case T_TypeCast:
  			retval = _copyTypeCast(from);
  			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2407cb7..e7b49f6 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** static bool
*** 382,387 ****
--- 382,388 ----
  _equalSubLink(const SubLink *a, const SubLink *b)
  {
  	COMPARE_SCALAR_FIELD(subLinkType);
+ 	COMPARE_SCALAR_FIELD(subLinkId);
  	COMPARE_NODE_FIELD(testexpr);
  	COMPARE_NODE_FIELD(operName);
  	COMPARE_NODE_FIELD(subselect);
*************** _equalResTarget(const ResTarget *a, cons
*** 2095,2100 ****
--- 2096,2111 ----
  }
  
  static bool
+ _equalMultiAssignRef(const MultiAssignRef *a, const MultiAssignRef *b)
+ {
+ 	COMPARE_NODE_FIELD(source);
+ 	COMPARE_SCALAR_FIELD(colno);
+ 	COMPARE_SCALAR_FIELD(ncolumns);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalTypeName(const TypeName *a, const TypeName *b)
  {
  	COMPARE_NODE_FIELD(names);
*************** equal(const void *a, const void *b)
*** 3029,3034 ****
--- 3040,3048 ----
  		case T_ResTarget:
  			retval = _equalResTarget(a, b);
  			break;
+ 		case T_MultiAssignRef:
+ 			retval = _equalMultiAssignRef(a, b);
+ 			break;
  		case T_TypeCast:
  			retval = _equalTypeCast(a, b);
  			break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index f32124b..5c09d2f 100644
*** a/src/backend/nodes/list.c
--- b/src/backend/nodes/list.c
*************** list_truncate(List *list, int new_size)
*** 385,391 ****
   * Locate the n'th cell (counting from 0) of the list.  It is an assertion
   * failure if there is no such cell.
   */
! static ListCell *
  list_nth_cell(const List *list, int n)
  {
  	ListCell   *match;
--- 385,391 ----
   * Locate the n'th cell (counting from 0) of the list.  It is an assertion
   * failure if there is no such cell.
   */
! ListCell *
  list_nth_cell(const List *list, int n)
  {
  	ListCell   *match;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 5a98bfb..546aaef 100644
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** exprType(const Node *expr)
*** 116,121 ****
--- 116,129 ----
  							format_type_be(exprType((Node *) tent->expr)))));
  					}
  				}
+ 				else if (sublink->subLinkType == MULTIEXPR_SUBLINK)
+ 				{
+ 					/*
+ 					 * We probably shouldn't ever be asked the type of a
+ 					 * MULTIEXPR subquery, but if we are, say RECORD
+ 					 */
+ 					type = RECORDOID;
+ 				}
  				else
  				{
  					/* for all other sublink types, result is boolean */
*************** exprType(const Node *expr)
*** 142,147 ****
--- 150,163 ----
  									format_type_be(subplan->firstColType))));
  					}
  				}
+ 				else if (subplan->subLinkType == MULTIEXPR_SUBLINK)
+ 				{
+ 					/*
+ 					 * We probably shouldn't ever be asked the type of a
+ 					 * MULTIEXPR subquery, but if we are, say RECORD
+ 					 */
+ 					type = RECORDOID;
+ 				}
  				else
  				{
  					/* for all other subplan types, result is boolean */
*************** exprTypmod(const Node *expr)
*** 299,304 ****
--- 315,321 ----
  					return exprTypmod((Node *) tent->expr);
  					/* note we don't need to care if it's an array */
  				}
+ 				/* otherwise, result is RECORD or BOOLEAN, typmod is -1 */
  			}
  			break;
  		case T_SubPlan:
*************** exprTypmod(const Node *expr)
*** 312,322 ****
  					/* note we don't need to care if it's an array */
  					return subplan->firstColTypmod;
  				}
! 				else
! 				{
! 					/* for all other subplan types, result is boolean */
! 					return -1;
! 				}
  			}
  			break;
  		case T_AlternativeSubPlan:
--- 329,335 ----
  					/* note we don't need to care if it's an array */
  					return subplan->firstColTypmod;
  				}
! 				/* otherwise, result is RECORD or BOOLEAN, typmod is -1 */
  			}
  			break;
  		case T_AlternativeSubPlan:
*************** exprCollation(const Node *expr)
*** 784,790 ****
  				}
  				else
  				{
! 					/* for all other sublink types, result is boolean */
  					coll = InvalidOid;
  				}
  			}
--- 797,803 ----
  				}
  				else
  				{
! 					/* otherwise, result is RECORD or BOOLEAN */
  					coll = InvalidOid;
  				}
  			}
*************** exprCollation(const Node *expr)
*** 802,808 ****
  				}
  				else
  				{
! 					/* for all other subplan types, result is boolean */
  					coll = InvalidOid;
  				}
  			}
--- 815,821 ----
  				}
  				else
  				{
! 					/* otherwise, result is RECORD or BOOLEAN */
  					coll = InvalidOid;
  				}
  			}
*************** exprSetCollation(Node *expr, Oid collati
*** 1017,1023 ****
  				}
  				else
  				{
! 					/* for all other sublink types, result is boolean */
  					Assert(!OidIsValid(collation));
  				}
  			}
--- 1030,1036 ----
  				}
  				else
  				{
! 					/* otherwise, result is RECORD or BOOLEAN */
  					Assert(!OidIsValid(collation));
  				}
  			}
*************** exprLocation(const Node *expr)
*** 1420,1425 ****
--- 1433,1441 ----
  			/* we need not examine the contained expression (if any) */
  			loc = ((const ResTarget *) expr)->location;
  			break;
+ 		case T_MultiAssignRef:
+ 			loc = exprLocation(((const MultiAssignRef *) expr)->source);
+ 			break;
  		case T_TypeCast:
  			{
  				const TypeCast *tc = (const TypeCast *) expr;
*************** raw_expression_tree_walker(Node *node,
*** 3099,3104 ****
--- 3115,3122 ----
  					return true;
  			}
  			break;
+ 		case T_MultiAssignRef:
+ 			return walker(((MultiAssignRef *) node)->source, context);
  		case T_TypeCast:
  			{
  				TypeCast   *tc = (TypeCast *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 11c7486..d25641f 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outSubLink(StringInfo str, const SubLin
*** 1115,1120 ****
--- 1115,1121 ----
  	WRITE_NODE_TYPE("SUBLINK");
  
  	WRITE_ENUM_FIELD(subLinkType, SubLinkType);
+ 	WRITE_INT_FIELD(subLinkId);
  	WRITE_NODE_FIELD(testexpr);
  	WRITE_NODE_FIELD(operName);
  	WRITE_NODE_FIELD(subselect);
*************** _outPlannerInfo(StringInfo str, const Pl
*** 1701,1706 ****
--- 1702,1708 ----
  	WRITE_INT_FIELD(join_cur_level);
  	WRITE_NODE_FIELD(init_plans);
  	WRITE_NODE_FIELD(cte_plan_ids);
+ 	WRITE_NODE_FIELD(multiexpr_params);
  	WRITE_NODE_FIELD(eq_classes);
  	WRITE_NODE_FIELD(canon_pathkeys);
  	WRITE_NODE_FIELD(left_join_clauses);
*************** _outResTarget(StringInfo str, const ResT
*** 2590,2595 ****
--- 2592,2607 ----
  }
  
  static void
+ _outMultiAssignRef(StringInfo str, const MultiAssignRef *node)
+ {
+ 	WRITE_NODE_TYPE("MULTIASSIGNREF");
+ 
+ 	WRITE_NODE_FIELD(source);
+ 	WRITE_INT_FIELD(colno);
+ 	WRITE_INT_FIELD(ncolumns);
+ }
+ 
+ static void
  _outSortBy(StringInfo str, const SortBy *node)
  {
  	WRITE_NODE_TYPE("SORTBY");
*************** _outNode(StringInfo str, const void *obj
*** 3200,3205 ****
--- 3212,3220 ----
  			case T_ResTarget:
  				_outResTarget(str, obj);
  				break;
+ 			case T_MultiAssignRef:
+ 				_outMultiAssignRef(str, obj);
+ 				break;
  			case T_SortBy:
  				_outSortBy(str, obj);
  				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1ec4f3c..69d9989 100644
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readSubLink(void)
*** 744,749 ****
--- 744,750 ----
  	READ_LOCALS(SubLink);
  
  	READ_ENUM_FIELD(subLinkType, SubLinkType);
+ 	READ_INT_FIELD(subLinkId);
  	READ_NODE_FIELD(testexpr);
  	READ_NODE_FIELD(operName);
  	READ_NODE_FIELD(subselect);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 0f1e2e4..f2c9c99 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 310,315 ****
--- 310,316 ----
  	root->planner_cxt = CurrentMemoryContext;
  	root->init_plans = NIL;
  	root->cte_plan_ids = NIL;
+ 	root->multiexpr_params = NIL;
  	root->eq_classes = NIL;
  	root->append_rel_list = NIL;
  	root->rowMarks = NIL;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 768c5c7..4d717df 100644
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** static bool extract_query_dependencies_w
*** 157,166 ****
   * 3. We adjust Vars in upper plan nodes to refer to the outputs of their
   * subplans.
   *
!  * 4. We compute regproc OIDs for operators (ie, we look up the function
   * that implements each op).
   *
!  * 5. We create lists of specific objects that the plan depends on.
   * This will be used by plancache.c to drive invalidation of cached plans.
   * Relation dependencies are represented by OIDs, and everything else by
   * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
--- 157,169 ----
   * 3. We adjust Vars in upper plan nodes to refer to the outputs of their
   * subplans.
   *
!  * 4. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
!  * now that we have finished planning all MULTIEXPR subplans.
!  *
!  * 5. We compute regproc OIDs for operators (ie, we look up the function
   * that implements each op).
   *
!  * 6. We create lists of specific objects that the plan depends on.
   * This will be used by plancache.c to drive invalidation of cached plans.
   * Relation dependencies are represented by OIDs, and everything else by
   * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
*************** fix_expr_common(PlannerInfo *root, Node 
*** 1119,1128 ****
--- 1122,1160 ----
  }
  
  /*
+  * fix_param_node
+  *		Do set_plan_references processing on a Param
+  *
+  * If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
+  * root->multiexpr_params; otherwise no change is needed.
+  * Just for paranoia's sake, we make a copy of the node in either case.
+  */
+ static Node *
+ fix_param_node(PlannerInfo *root, Param *p)
+ {
+ 	if (p->paramkind == PARAM_MULTIEXPR)
+ 	{
+ 		int			subqueryid = p->paramid >> 16;
+ 		int			colno = p->paramid & 0xFFFF;
+ 		List	   *params;
+ 
+ 		if (subqueryid <= 0 ||
+ 			subqueryid > list_length(root->multiexpr_params))
+ 			elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
+ 		params = (List *) list_nth(root->multiexpr_params, subqueryid - 1);
+ 		if (colno <= 0 || colno > list_length(params))
+ 			elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
+ 		return copyObject(list_nth(params, colno - 1));
+ 	}
+ 	return copyObject(p);
+ }
+ 
+ /*
   * fix_scan_expr
   *		Do set_plan_references processing on a scan-level expression
   *
   * This consists of incrementing all Vars' varnos by rtoffset,
+  * replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars,
   * looking up operator opcode info for OpExpr and related nodes,
   * and adding OIDs from regclass Const nodes into root->glob->relationOids.
   */
*************** fix_scan_expr(PlannerInfo *root, Node *n
*** 1134,1140 ****
  	context.root = root;
  	context.rtoffset = rtoffset;
  
! 	if (rtoffset != 0 || root->glob->lastPHId != 0)
  	{
  		return fix_scan_expr_mutator(node, &context);
  	}
--- 1166,1174 ----
  	context.root = root;
  	context.rtoffset = rtoffset;
  
! 	if (rtoffset != 0 ||
! 		root->multiexpr_params != NIL ||
! 		root->glob->lastPHId != 0)
  	{
  		return fix_scan_expr_mutator(node, &context);
  	}
*************** fix_scan_expr(PlannerInfo *root, Node *n
*** 1142,1152 ****
  	{
  		/*
  		 * If rtoffset == 0, we don't need to change any Vars, and if there
! 		 * are no placeholders anywhere we won't need to remove them.  Then
! 		 * it's OK to just scribble on the input node tree instead of copying
! 		 * (since the only change, filling in any unset opfuncid fields, is
! 		 * harmless).  This saves just enough cycles to be noticeable on
! 		 * trivial queries.
  		 */
  		(void) fix_scan_expr_walker(node, &context);
  		return node;
--- 1176,1187 ----
  	{
  		/*
  		 * If rtoffset == 0, we don't need to change any Vars, and if there
! 		 * are no MULTIEXPR subqueries then we don't need to replace
! 		 * PARAM_MULTIEXPR Params, and if there are no placeholders anywhere
! 		 * we won't need to remove them.  Then it's OK to just scribble on the
! 		 * input node tree instead of copying (since the only change, filling
! 		 * in any unset opfuncid fields, is harmless).  This saves just enough
! 		 * cycles to be noticeable on trivial queries.
  		 */
  		(void) fix_scan_expr_walker(node, &context);
  		return node;
*************** fix_scan_expr_mutator(Node *node, fix_sc
*** 1176,1181 ****
--- 1211,1218 ----
  			var->varnoold += context->rtoffset;
  		return (Node *) var;
  	}
+ 	if (IsA(node, Param))
+ 		return fix_param_node(context->root, (Param *) node);
  	if (IsA(node, CurrentOfExpr))
  	{
  		CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 1745,1750 ****
--- 1782,1789 ----
  		/* If not supplied by input plans, evaluate the contained expr */
  		return fix_join_expr_mutator((Node *) phv->phexpr, context);
  	}
+ 	if (IsA(node, Param))
+ 		return fix_param_node(context->root, (Param *) node);
  	/* Try matching more complex expressions too, if tlists have any */
  	if (context->outer_itlist->has_non_vars)
  	{
*************** fix_upper_expr_mutator(Node *node, fix_u
*** 1847,1852 ****
--- 1886,1893 ----
  		/* If not supplied by input plan, evaluate the contained expr */
  		return fix_upper_expr_mutator((Node *) phv->phexpr, context);
  	}
+ 	if (IsA(node, Param))
+ 		return fix_param_node(context->root, (Param *) node);
  	/* Try matching more complex expressions too, if tlist has any */
  	if (context->subplan_itlist->has_non_vars)
  	{
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index be92049..4694ec4 100644
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
*************** typedef struct finalize_primnode_context
*** 55,62 ****
  
  static Node *build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
  			  List *plan_params,
! 			  SubLinkType subLinkType, Node *testexpr,
! 			  bool adjust_testexpr, bool unknownEqFalse);
  static List *generate_subquery_params(PlannerInfo *root, List *tlist,
  						 List **paramIds);
  static List *generate_subquery_vars(PlannerInfo *root, List *tlist,
--- 55,63 ----
  
  static Node *build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
  			  List *plan_params,
! 			  SubLinkType subLinkType, int subLinkId,
! 			  Node *testexpr, bool adjust_testexpr,
! 			  bool unknownEqFalse);
  static List *generate_subquery_params(PlannerInfo *root, List *tlist,
  						 List **paramIds);
  static List *generate_subquery_vars(PlannerInfo *root, List *tlist,
*************** get_first_col_type(Plan *plan, Oid *colt
*** 407,413 ****
  /*
   * Convert a SubLink (as created by the parser) into a SubPlan.
   *
!  * We are given the SubLink's contained query, type, and testexpr.  We are
   * also told if this expression appears at top level of a WHERE/HAVING qual.
   *
   * Note: we assume that the testexpr has been AND/OR flattened (actually,
--- 408,414 ----
  /*
   * Convert a SubLink (as created by the parser) into a SubPlan.
   *
!  * We are given the SubLink's contained query, type, ID, and testexpr.  We are
   * also told if this expression appears at top level of a WHERE/HAVING qual.
   *
   * Note: we assume that the testexpr has been AND/OR flattened (actually,
*************** get_first_col_type(Plan *plan, Oid *colt
*** 419,428 ****
   * node in the executable expression.  This will be either the SubPlan
   * node (if we have to do the subplan as a subplan), or a Param node
   * representing the result of an InitPlan, or a row comparison expression
!  * tree containing InitPlan Param nodes.
   */
  static Node *
! make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
  			 Node *testexpr, bool isTopQual)
  {
  	Query	   *subquery;
--- 420,430 ----
   * node in the executable expression.  This will be either the SubPlan
   * node (if we have to do the subplan as a subplan), or a Param node
   * representing the result of an InitPlan, or a row comparison expression
!  * tree containing InitPlan Param nodes.  XXX describe MULTIEXPR case
   */
  static Node *
! make_subplan(PlannerInfo *root, Query *orig_subquery,
! 			 SubLinkType subLinkType, int subLinkId,
  			 Node *testexpr, bool isTopQual)
  {
  	Query	   *subquery;
*************** make_subplan(PlannerInfo *root, Query *o
*** 452,459 ****
  	 * first tuple will be retrieved.  For ALL and ANY subplans, we will be
  	 * able to stop evaluating if the test condition fails or matches, so very
  	 * often not all the tuples will be retrieved; for lack of a better idea,
! 	 * specify 50% retrieval.  For EXPR and ROWCOMPARE subplans, use default
! 	 * behavior (we're only expecting one row out, anyway).
  	 *
  	 * NOTE: if you change these numbers, also change cost_subplan() in
  	 * path/costsize.c.
--- 454,461 ----
  	 * first tuple will be retrieved.  For ALL and ANY subplans, we will be
  	 * able to stop evaluating if the test condition fails or matches, so very
  	 * often not all the tuples will be retrieved; for lack of a better idea,
! 	 * specify 50% retrieval.  For EXPR, MULTIEXPR, and ROWCOMPARE subplans,
! 	 * use default behavior (we're only expecting one row out, anyway).
  	 *
  	 * NOTE: if you change these numbers, also change cost_subplan() in
  	 * path/costsize.c.
*************** make_subplan(PlannerInfo *root, Query *o
*** 491,497 ****
  
  	/* And convert to SubPlan or InitPlan format. */
  	result = build_subplan(root, plan, subroot, plan_params,
! 						   subLinkType, testexpr, true, isTopQual);
  
  	/*
  	 * If it's a correlated EXISTS with an unimportant targetlist, we might be
--- 493,500 ----
  
  	/* And convert to SubPlan or InitPlan format. */
  	result = build_subplan(root, plan, subroot, plan_params,
! 						   subLinkType, subLinkId,
! 						   testexpr, true, isTopQual);
  
  	/*
  	 * If it's a correlated EXISTS with an unimportant targetlist, we might be
*************** make_subplan(PlannerInfo *root, Query *o
*** 536,542 ****
  				/* OK, convert to SubPlan format. */
  				hashplan = (SubPlan *) build_subplan(root, plan, subroot,
  													 plan_params,
! 													 ANY_SUBLINK, newtestexpr,
  													 false, true);
  				/* Check we got what we expected */
  				Assert(IsA(hashplan, SubPlan));
--- 539,546 ----
  				/* OK, convert to SubPlan format. */
  				hashplan = (SubPlan *) build_subplan(root, plan, subroot,
  													 plan_params,
! 													 ANY_SUBLINK, 0,
! 													 newtestexpr,
  													 false, true);
  				/* Check we got what we expected */
  				Assert(IsA(hashplan, SubPlan));
*************** make_subplan(PlannerInfo *root, Query *o
*** 560,572 ****
   * Build a SubPlan node given the raw inputs --- subroutine for make_subplan
   *
   * Returns either the SubPlan, or an expression using initplan output Params,
!  * as explained in the comments for make_subplan.
   */
  static Node *
  build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
  			  List *plan_params,
! 			  SubLinkType subLinkType, Node *testexpr,
! 			  bool adjust_testexpr, bool unknownEqFalse)
  {
  	Node	   *result;
  	SubPlan    *splan;
--- 564,577 ----
   * Build a SubPlan node given the raw inputs --- subroutine for make_subplan
   *
   * Returns either the SubPlan, or an expression using initplan output Params,
!  * as explained in the comments for make_subplan. XXX more commentary
   */
  static Node *
  build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
  			  List *plan_params,
! 			  SubLinkType subLinkType, int subLinkId,
! 			  Node *testexpr, bool adjust_testexpr,
! 			  bool unknownEqFalse)
  {
  	Node	   *result;
  	SubPlan    *splan;
*************** build_subplan(PlannerInfo *root, Plan *p
*** 615,626 ****
  	}
  
  	/*
! 	 * Un-correlated or undirect correlated plans of EXISTS, EXPR, ARRAY, or
! 	 * ROWCOMPARE types can be used as initPlans.  For EXISTS, EXPR, or ARRAY,
! 	 * we just produce a Param referring to the result of evaluating the
! 	 * initPlan.  For ROWCOMPARE, we must modify the testexpr tree to contain
! 	 * PARAM_EXEC Params instead of the PARAM_SUBLINK Params emitted by the
! 	 * parser.
  	 */
  	if (splan->parParam == NIL && subLinkType == EXISTS_SUBLINK)
  	{
--- 620,634 ----
  	}
  
  	/*
! 	 * Un-correlated or undirect correlated plans of EXISTS, EXPR, MULTIEXPR,
! 	 * ARRAY, or ROWCOMPARE types can be used as initPlans.  For EXISTS, EXPR,
! 	 * or ARRAY, we return a Param referring to the result of evaluating the
! 	 * initPlan.  For MULTIEXPR, we return a null constant: the resjunk
! 	 * targetlist item containing the SubLink does not need to return anything
! 	 * useful, since the referencing Params are elsewhere.  For ROWCOMPARE, we
! 	 * must modify the testexpr tree to contain PARAM_EXEC Params instead of
! 	 * the PARAM_SUBLINK Params emitted by the parser, and then return that
! 	 * tree.
  	 */
  	if (splan->parParam == NIL && subLinkType == EXISTS_SUBLINK)
  	{
*************** build_subplan(PlannerInfo *root, Plan *p
*** 687,692 ****
--- 695,736 ----
  		 * plan's expression tree; it is not kept in the initplan node.
  		 */
  	}
+ 	else if (subLinkType == MULTIEXPR_SUBLINK)
+ 	{
+ 		/*
+ 		 * Whether it's an initplan or not, it needs to set a PARAM_EXEC Param
+ 		 * for each output column.
+ 		 */
+ 		List	   *params;
+ 
+ 		Assert(testexpr == NULL);
+ 		params = generate_subquery_params(root,
+ 										  plan->targetlist,
+ 										  &splan->setParam);
+ 
+ 		/*
+ 		 * Save the list of replacement Params in the n'th cell of
+ 		 * root->multiexpr_params; setrefs.c will use it to replace
+ 		 * PARAM_MULTIEXPR Params.
+ 		 */
+ 		while (list_length(root->multiexpr_params) < subLinkId)
+ 			root->multiexpr_params = lappend(root->multiexpr_params, NIL);
+ 		lc = list_nth_cell(root->multiexpr_params, subLinkId - 1);
+ 		Assert(lfirst(lc) == NIL);
+ 		lfirst(lc) = params;
+ 
+ 		/* It can be an initplan if there are no parParams. */
+ 		if (splan->parParam == NIL)
+ 		{
+ 			isInitPlan = true;
+ 			result = (Node *) makeNullConst(RECORDOID, -1, InvalidOid);
+ 		}
+ 		else
+ 		{
+ 			isInitPlan = false;
+ 			result = (Node *) splan;
+ 		}
+ 	}
  	else
  	{
  		/*
*************** process_sublinks_mutator(Node *node, pro
*** 1816,1821 ****
--- 1860,1866 ----
  		return make_subplan(context->root,
  							(Query *) sublink->subselect,
  							sublink->subLinkType,
+ 							sublink->subLinkId,
  							testexpr,
  							context->isTopQual);
  	}
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 776fe42..1030c78 100644
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
*************** pull_up_simple_subquery(PlannerInfo *roo
*** 804,809 ****
--- 804,810 ----
  	subroot->planner_cxt = CurrentMemoryContext;
  	subroot->init_plans = NIL;
  	subroot->cte_plan_ids = NIL;
+ 	subroot->multiexpr_params = NIL;
  	subroot->eq_classes = NIL;
  	subroot->append_rel_list = NIL;
  	subroot->rowMarks = NIL;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7b9895d..5b33818 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** single_set_clause:
*** 9231,9236 ****
--- 9231,9244 ----
  				}
  		;
  
+ /*
+  * Ideally, we'd accept any row-valued a_expr as RHS of a multiple_set_clause.
+  * However, per SQL spec the row-constructor case must allow DEFAULT as a row
+  * member, and it's pretty unclear how to do that (unless perhaps we allow
+  * DEFAULT in any a_expr and let parse analysis sort it out later?).  For the
+  * moment, the planner/executor only support a subquery as a MultiAssignment
+  * source anyhow, so we need only accept ctext_row and subqueries here.
+  */
  multiple_set_clause:
  			'(' set_target_list ')' '=' ctext_row
  				{
*************** multiple_set_clause:
*** 9239,9252 ****
  
  					/*
  					 * Break the ctext_row apart, merge individual expressions
! 					 * into the destination ResTargets.  XXX this approach
! 					 * cannot work for general row expressions as sources.
  					 */
  					if (list_length($2) != list_length($5))
  						ereport(ERROR,
  								(errcode(ERRCODE_SYNTAX_ERROR),
  								 errmsg("number of columns does not match number of values"),
! 								 parser_errposition(@1)));
  					forboth(col_cell, $2, val_cell, $5)
  					{
  						ResTarget *res_col = (ResTarget *) lfirst(col_cell);
--- 9247,9261 ----
  
  					/*
  					 * Break the ctext_row apart, merge individual expressions
! 					 * into the destination ResTargets.  This is semantically
! 					 * equivalent to, and much cheaper to process than, the
! 					 * general case.
  					 */
  					if (list_length($2) != list_length($5))
  						ereport(ERROR,
  								(errcode(ERRCODE_SYNTAX_ERROR),
  								 errmsg("number of columns does not match number of values"),
! 								 parser_errposition(@5)));
  					forboth(col_cell, $2, val_cell, $5)
  					{
  						ResTarget *res_col = (ResTarget *) lfirst(col_cell);
*************** multiple_set_clause:
*** 9257,9262 ****
--- 9266,9301 ----
  
  					$$ = $2;
  				}
+ 			| '(' set_target_list ')' '=' select_with_parens
+ 				{
+ 					SubLink *sl = makeNode(SubLink);
+ 					int ncolumns = list_length($2);
+ 					int i = 1;
+ 					ListCell *col_cell;
+ 
+ 					/* First, convert bare SelectStmt into a SubLink */
+ 					sl->subLinkType = MULTIEXPR_SUBLINK;
+ 					sl->subLinkId = 0;		/* will be assigned later */
+ 					sl->testexpr = NULL;
+ 					sl->operName = NIL;
+ 					sl->subselect = $5;
+ 					sl->location = @5;
+ 
+ 					/* Create a MultiAssignRef source for each target */
+ 					foreach(col_cell, $2)
+ 					{
+ 						ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+ 						MultiAssignRef *r = makeNode(MultiAssignRef);
+ 
+ 						r->source = (Node *) sl;
+ 						r->colno = i;
+ 						r->ncolumns = ncolumns;
+ 						res_col->val = (Node *) r;
+ 						i++;
+ 					}
+ 
+ 					$$ = $2;
+ 				}
  		;
  
  set_target:
*************** a_expr:		c_expr									{ $$ = $1; }
*** 11090,11095 ****
--- 11129,11135 ----
  						/* generate foo = ANY (subquery) */
  						SubLink *n = (SubLink *) $3;
  						n->subLinkType = ANY_SUBLINK;
+ 						n->subLinkId = 0;
  						n->testexpr = $1;
  						n->operName = list_make1(makeString("="));
  						n->location = @2;
*************** a_expr:		c_expr									{ $$ = $1; }
*** 11110,11115 ****
--- 11150,11156 ----
  						/* Make an = ANY node */
  						SubLink *n = (SubLink *) $4;
  						n->subLinkType = ANY_SUBLINK;
+ 						n->subLinkId = 0;
  						n->testexpr = $1;
  						n->operName = list_make1(makeString("="));
  						n->location = @3;
*************** a_expr:		c_expr									{ $$ = $1; }
*** 11126,11131 ****
--- 11167,11173 ----
  				{
  					SubLink *n = makeNode(SubLink);
  					n->subLinkType = $3;
+ 					n->subLinkId = 0;
  					n->testexpr = $1;
  					n->operName = $2;
  					n->subselect = $4;
*************** c_expr:		columnref								{ $$ = $1; }
*** 11286,11291 ****
--- 11328,11334 ----
  				{
  					SubLink *n = makeNode(SubLink);
  					n->subLinkType = EXPR_SUBLINK;
+ 					n->subLinkId = 0;
  					n->testexpr = NULL;
  					n->operName = NIL;
  					n->subselect = $1;
*************** c_expr:		columnref								{ $$ = $1; }
*** 11307,11312 ****
--- 11350,11356 ----
  					SubLink *n = makeNode(SubLink);
  					A_Indirection *a = makeNode(A_Indirection);
  					n->subLinkType = EXPR_SUBLINK;
+ 					n->subLinkId = 0;
  					n->testexpr = NULL;
  					n->operName = NIL;
  					n->subselect = $1;
*************** c_expr:		columnref								{ $$ = $1; }
*** 11319,11324 ****
--- 11363,11369 ----
  				{
  					SubLink *n = makeNode(SubLink);
  					n->subLinkType = EXISTS_SUBLINK;
+ 					n->subLinkId = 0;
  					n->testexpr = NULL;
  					n->operName = NIL;
  					n->subselect = $2;
*************** c_expr:		columnref								{ $$ = $1; }
*** 11329,11334 ****
--- 11374,11380 ----
  				{
  					SubLink *n = makeNode(SubLink);
  					n->subLinkType = ARRAY_SUBLINK;
+ 					n->subLinkId = 0;
  					n->testexpr = NULL;
  					n->operName = NIL;
  					n->subselect = $2;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 0882245..cf87830 100644
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
*************** static Node *transformAExprNullIf(ParseS
*** 51,56 ****
--- 51,57 ----
  static Node *transformAExprOf(ParseState *pstate, A_Expr *a);
  static Node *transformAExprIn(ParseState *pstate, A_Expr *a);
  static Node *transformFuncCall(ParseState *pstate, FuncCall *fn);
+ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
  static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c);
  static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
  static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
*************** static Node *make_row_distinct_op(ParseS
*** 75,80 ****
--- 76,82 ----
  					 RowExpr *lrow, RowExpr *rrow, int location);
  static Expr *make_distinct_op(ParseState *pstate, List *opname,
  				 Node *ltree, Node *rtree, int location);
+ static int	count_output_columns(List *targetlist);
  
  
  /*
*************** transformExprRecurse(ParseState *pstate,
*** 262,267 ****
--- 264,273 ----
  			result = transformFuncCall(pstate, (FuncCall *) expr);
  			break;
  
+ 		case T_MultiAssignRef:
+ 			result = transformMultiAssignRef(pstate, (MultiAssignRef *) expr);
+ 			break;
+ 
  		case T_NamedArgExpr:
  			{
  				NamedArgExpr *na = (NamedArgExpr *) expr;
*************** transformFuncCall(ParseState *pstate, Fu
*** 1280,1285 ****
--- 1286,1365 ----
  }
  
  static Node *
+ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
+ {
+ 	SubLink    *sublink;
+ 	Query	   *qtree;
+ 	TargetEntry *tle;
+ 	Param	   *param;
+ 
+ 	/* We should only see this in first-stage processing of UPDATE tlists */
+ 	Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
+ 
+ 	/* We only need to transform the source if this is the first column */
+ 	if (maref->colno == 1)
+ 	{
+ 		sublink = (SubLink *) transformExprRecurse(pstate, maref->source);
+ 		/* Currently, the grammar only allows a SubLink as source */
+ 		Assert(IsA(sublink, SubLink));
+ 		Assert(sublink->subLinkType == MULTIEXPR_SUBLINK);
+ 		qtree = (Query *) sublink->subselect;
+ 		Assert(IsA(qtree, Query));
+ 
+ 		/* Check subquery returns required number of columns */
+ 		if (count_output_columns(qtree->targetList) != maref->ncolumns)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg("number of columns does not match number of values"),
+ 					 parser_errposition(pstate, sublink->location)));
+ 
+ 		/*
+ 		 * Build a resjunk tlist item containing the MULTIEXPR SubLink, and
+ 		 * add it to pstate->p_multiassign_exprs, whence it will later get
+ 		 * appended to the completed targetlist.  We needn't worry about
+ 		 * selecting a resno for it; transformUpdateStmt will do that.
+ 		 */
+ 		tle = makeTargetEntry((Expr *) sublink, 0, NULL, true);
+ 		pstate->p_multiassign_exprs = lappend(pstate->p_multiassign_exprs, tle);
+ 
+ 		/*
+ 		 * Assign a unique-within-this-targetlist ID to the MULTIEXPR SubLink.
+ 		 * We can just use its position in the p_multiassign_exprs list.
+ 		 */
+ 		sublink->subLinkId = list_length(pstate->p_multiassign_exprs);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Second or later column in a multiassignment.  Re-fetch the
+ 		 * transformed query, which we assume is still the last entry in
+ 		 * p_multiassign_exprs.
+ 		 */
+ 		Assert(pstate->p_multiassign_exprs != NIL);
+ 		tle = (TargetEntry *) llast(pstate->p_multiassign_exprs);
+ 		sublink = (SubLink *) tle->expr;
+ 		Assert(IsA(sublink, SubLink));
+ 		Assert(sublink->subLinkType == MULTIEXPR_SUBLINK);
+ 		qtree = (Query *) sublink->subselect;
+ 		Assert(IsA(qtree, Query));
+ 	}
+ 
+ 	/* Build a Param representing the appropriate subquery output column */
+ 	tle = (TargetEntry *) list_nth(qtree->targetList, maref->colno - 1);
+ 	Assert(!tle->resjunk);
+ 
+ 	param = makeNode(Param);
+ 	param->paramkind = PARAM_MULTIEXPR;
+ 	param->paramid = (sublink->subLinkId << 16) | maref->colno;
+ 	param->paramtype = exprType((Node *) tle->expr);
+ 	param->paramtypmod = exprTypmod((Node *) tle->expr);
+ 	param->paramcollid = exprCollation((Node *) tle->expr);
+ 	param->location = exprLocation((Node *) tle->expr);
+ 
+ 	return (Node *) param;
+ }
+ 
+ static Node *
  transformCaseExpr(ParseState *pstate, CaseExpr *c)
  {
  	CaseExpr   *newc;
*************** transformSubLink(ParseState *pstate, Sub
*** 1532,1557 ****
  	else if (sublink->subLinkType == EXPR_SUBLINK ||
  			 sublink->subLinkType == ARRAY_SUBLINK)
  	{
- 		ListCell   *tlist_item = list_head(qtree->targetList);
- 
  		/*
  		 * Make sure the subselect delivers a single column (ignoring resjunk
  		 * targets).
  		 */
! 		if (tlist_item == NULL ||
! 			((TargetEntry *) lfirst(tlist_item))->resjunk)
  			ereport(ERROR,
  					(errcode(ERRCODE_SYNTAX_ERROR),
! 					 errmsg("subquery must return a column"),
  					 parser_errposition(pstate, sublink->location)));
- 		while ((tlist_item = lnext(tlist_item)) != NULL)
- 		{
- 			if (!((TargetEntry *) lfirst(tlist_item))->resjunk)
- 				ereport(ERROR,
- 						(errcode(ERRCODE_SYNTAX_ERROR),
- 						 errmsg("subquery must return only one column"),
- 						 parser_errposition(pstate, sublink->location)));
- 		}
  
  		/*
  		 * EXPR and ARRAY need no test expression or combining operator. These
--- 1612,1626 ----
  	else if (sublink->subLinkType == EXPR_SUBLINK ||
  			 sublink->subLinkType == ARRAY_SUBLINK)
  	{
  		/*
  		 * Make sure the subselect delivers a single column (ignoring resjunk
  		 * targets).
  		 */
! 		if (count_output_columns(qtree->targetList) != 1)
  			ereport(ERROR,
  					(errcode(ERRCODE_SYNTAX_ERROR),
! 					 errmsg("subquery must return only one column"),
  					 parser_errposition(pstate, sublink->location)));
  
  		/*
  		 * EXPR and ARRAY need no test expression or combining operator. These
*************** transformSubLink(ParseState *pstate, Sub
*** 1560,1565 ****
--- 1629,1640 ----
  		sublink->testexpr = NULL;
  		sublink->operName = NIL;
  	}
+ 	else if (sublink->subLinkType == MULTIEXPR_SUBLINK)
+ 	{
+ 		/* Same as EXPR case, except no restriction on number of columns */
+ 		sublink->testexpr = NULL;
+ 		sublink->operName = NIL;
+ 	}
  	else
  	{
  		/* ALL, ANY, or ROWCOMPARE: generate row-comparing expression */
*************** make_distinct_op(ParseState *pstate, Lis
*** 2569,2574 ****
--- 2644,2669 ----
  }
  
  /*
+  * Count non-resjunk columns in a targetlist
+  */
+ static int
+ count_output_columns(List *targetlist)
+ {
+ 	int			len = 0;
+ 	ListCell   *lc;
+ 
+ 	foreach(lc, targetlist)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 
+ 		Assert(IsA(tle, TargetEntry));
+ 		if (!tle->resjunk)
+ 			len++;
+ 	}
+ 	return len;
+ }
+ 
+ /*
   * Produce a string identifying an expression by kind.
   *
   * Note: when practical, use a simple SQL keyword for the result.  If that
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2ee1270..328e0c6 100644
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
*************** transformTargetEntry(ParseState *pstate,
*** 113,121 ****
   * transformTargetList()
   * Turns a list of ResTarget's into a list of TargetEntry's.
   *
!  * At this point, we don't care whether we are doing SELECT, UPDATE,
!  * or RETURNING; we just transform the given expressions (the "val" fields).
!  * However, our subroutines care, so we need the exprKind parameter.
   */
  List *
  transformTargetList(ParseState *pstate, List *targetlist,
--- 113,121 ----
   * transformTargetList()
   * Turns a list of ResTarget's into a list of TargetEntry's.
   *
!  * This code acts mostly the same for SELECT, UPDATE, or RETURNING lists;
!  * the main thing is to transform the given expressions (the "val" fields).
!  * The exprKind parameter distinguishes these cases when necesssary.
   */
  List *
  transformTargetList(ParseState *pstate, List *targetlist,
*************** transformTargetList(ParseState *pstate, 
*** 124,129 ****
--- 124,132 ----
  	List	   *p_target = NIL;
  	ListCell   *o_target;
  
+ 	/* Shouldn't have any leftover multiassign items at start */
+ 	Assert(pstate->p_multiassign_exprs == NIL);
+ 
  	foreach(o_target, targetlist)
  	{
  		ResTarget  *res = (ResTarget *) lfirst(o_target);
*************** transformTargetList(ParseState *pstate, 
*** 172,177 ****
--- 175,193 ----
  												false));
  	}
  
+ 	/*
+ 	 * If any multiassign resjunk items were created, attach them to the end
+ 	 * of the targetlist.  This should only happen in an UPDATE tlist.  We
+ 	 * don't need to worry about numbering of these items; transformUpdateStmt
+ 	 * will set their resnos.
+ 	 */
+ 	if (pstate->p_multiassign_exprs)
+ 	{
+ 		Assert(exprKind == EXPR_KIND_UPDATE_SOURCE);
+ 		p_target = list_concat(p_target, pstate->p_multiassign_exprs);
+ 		pstate->p_multiassign_exprs = NIL;
+ 	}
+ 
  	return p_target;
  }
  
*************** transformExpressionList(ParseState *psta
*** 234,239 ****
--- 250,258 ----
  						 transformExpr(pstate, e, exprKind));
  	}
  
+ 	/* Shouldn't have any multiassign items here */
+ 	Assert(pstate->p_multiassign_exprs == NIL);
+ 
  	return result;
  }
  
*************** FigureColnameInternal(Node *node, char *
*** 1691,1696 ****
--- 1710,1716 ----
  					}
  					break;
  					/* As with other operator-like nodes, these have no names */
+ 				case MULTIEXPR_SUBLINK:
  				case ALL_SUBLINK:
  				case ANY_SUBLINK:
  				case ROWCOMPARE_SUBLINK:
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0ab2a13..1f7c6d1 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct SubPlanState
*** 728,733 ****
--- 728,734 ----
  {
  	ExprState	xprstate;
  	struct PlanState *planstate;	/* subselect plan's state tree */
+ 	struct PlanState *parent;	/* parent plan node's state tree */
  	ExprState  *testexpr;		/* state of combining expression */
  	List	   *args;			/* states of argument expression(s) */
  	HeapTuple	curTuple;		/* copy of most recent tuple from subplan */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bc58e16..7b0088f 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 379,384 ****
--- 379,385 ----
  	T_A_Indirection,
  	T_A_ArrayExpr,
  	T_ResTarget,
+ 	T_MultiAssignRef,
  	T_TypeCast,
  	T_CollateClause,
  	T_SortBy,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7e560a1..4758466 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct ResTarget
*** 388,393 ****
--- 388,410 ----
  } ResTarget;
  
  /*
+  * MultiAssignRef - element of a row source expression for UPDATE
+  *
+  * In an UPDATE target list, when we have SET (a,b,c) = row-valued-expression,
+  * we generate separate ResTarget items for each of a,b,c.  Their "val" trees
+  * are MultiAssignRef nodes numbered 1..n, linking to a common copy of the
+  * row-valued-expression (which parse analysis will process only once, when
+  * handling the MultiAssignRef with colno=1).
+  */
+ typedef struct MultiAssignRef
+ {
+ 	NodeTag		type;
+ 	Node	   *source;			/* the row-valued expression */
+ 	int			colno;			/* column number for this target (1..n) */
+ 	int			ncolumns;		/* number of targets in the construct */
+ } MultiAssignRef;
+ 
+ /*
   * SortBy - for ORDER BY clause
   */
  typedef struct SortBy
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 4167680..c545115 100644
*** a/src/include/nodes/pg_list.h
--- b/src/include/nodes/pg_list.h
*************** extern List *lcons_oid(Oid datum, List *
*** 206,211 ****
--- 206,212 ----
  extern List *list_concat(List *list1, List *list2);
  extern List *list_truncate(List *list, int new_size);
  
+ extern ListCell *list_nth_cell(const List *list, int n);
  extern void *list_nth(const List *list, int n);
  extern int	list_nth_int(const List *list, int n);
  extern Oid	list_nth_oid(const List *list, int n);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 4f03ef9..6949989 100644
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Const
*** 198,214 ****
   *				`paramid' field.  (This type of Param is converted to
   *				PARAM_EXEC during planning.)
   *
!  * Note: currently, paramtypmod is valid for PARAM_SUBLINK Params, and for
!  * PARAM_EXEC Params generated from them; it is always -1 for PARAM_EXTERN
!  * params, since the APIs that supply values for such parameters don't carry
!  * any typmod info.
   * ----------------
   */
  typedef enum ParamKind
  {
  	PARAM_EXTERN,
  	PARAM_EXEC,
! 	PARAM_SUBLINK
  } ParamKind;
  
  typedef struct Param
--- 198,222 ----
   *				`paramid' field.  (This type of Param is converted to
   *				PARAM_EXEC during planning.)
   *
!  *		PARAM_MULTIEXPR:  Like PARAM_SUBLINK, the parameter represents an
!  *				output column of a SubLink node's sub-select, but here, the
!  *				SubLink is always a MULTIEXPR SubLink.  The high-order 16 bits
!  *				of the `paramid' field contain the SubLink's subLinkId, and
!  *				the low-order 16 bits contain the column number.  (This type
!  *				of Param is also converted to PARAM_EXEC during planning.)
!  *
!  * Note: currently, paramtypmod is always -1 for PARAM_EXTERN params, since
!  * the APIs that supply values for such parameters don't carry any typmod
!  * info.  It is valid in other types of Params, if they represent expressions
!  * with determinable typmod.
   * ----------------
   */
  typedef enum ParamKind
  {
  	PARAM_EXTERN,
  	PARAM_EXEC,
! 	PARAM_SUBLINK,
! 	PARAM_MULTIEXPR
  } ParamKind;
  
  typedef struct Param
*************** typedef struct BoolExpr
*** 489,502 ****
   *	ANY_SUBLINK			(lefthand) op ANY (SELECT ...)
   *	ROWCOMPARE_SUBLINK	(lefthand) op (SELECT ...)
   *	EXPR_SUBLINK		(SELECT with single targetlist item ...)
   *	ARRAY_SUBLINK		ARRAY(SELECT with single targetlist item ...)
   *	CTE_SUBLINK			WITH query (never actually part of an expression)
   * For ALL, ANY, and ROWCOMPARE, the lefthand is a list of expressions of the
   * same length as the subselect's targetlist.  ROWCOMPARE will *always* have
   * a list with more than one entry; if the subselect has just one target
   * then the parser will create an EXPR_SUBLINK instead (and any operator
!  * above the subselect will be represented separately).  Note that both
!  * ROWCOMPARE and EXPR require the subselect to deliver only one row.
   * ALL, ANY, and ROWCOMPARE require the combining operators to deliver boolean
   * results.  ALL and ANY combine the per-row results using AND and OR
   * semantics respectively.
--- 497,512 ----
   *	ANY_SUBLINK			(lefthand) op ANY (SELECT ...)
   *	ROWCOMPARE_SUBLINK	(lefthand) op (SELECT ...)
   *	EXPR_SUBLINK		(SELECT with single targetlist item ...)
+  *	MULTIEXPR_SUBLINK	(SELECT with multiple targetlist items ...)
   *	ARRAY_SUBLINK		ARRAY(SELECT with single targetlist item ...)
   *	CTE_SUBLINK			WITH query (never actually part of an expression)
   * For ALL, ANY, and ROWCOMPARE, the lefthand is a list of expressions of the
   * same length as the subselect's targetlist.  ROWCOMPARE will *always* have
   * a list with more than one entry; if the subselect has just one target
   * then the parser will create an EXPR_SUBLINK instead (and any operator
!  * above the subselect will be represented separately).
!  * ROWCOMPARE, EXPR, and MULTIEXPR require the subselect to deliver at most
!  * one row (if it returns no rows, the result is NULL).
   * ALL, ANY, and ROWCOMPARE require the combining operators to deliver boolean
   * results.  ALL and ANY combine the per-row results using AND and OR
   * semantics respectively.
*************** typedef struct BoolExpr
*** 515,522 ****
   * output columns of the subselect.  And subselect is transformed to a Query.
   * This is the representation seen in saved rules and in the rewriter.
   *
!  * In EXISTS, EXPR, and ARRAY SubLinks, testexpr and operName are unused and
!  * are always null.
   *
   * The CTE_SUBLINK case never occurs in actual SubLink nodes, but it is used
   * in SubPlans generated for WITH subqueries.
--- 525,538 ----
   * output columns of the subselect.  And subselect is transformed to a Query.
   * This is the representation seen in saved rules and in the rewriter.
   *
!  * In EXISTS, EXPR, MULTIEXPR, and ARRAY SubLinks, testexpr and operName
!  * are unused and are always null.
!  *
!  * subLinkId is currently used only for MULTIEXPR SubLinks, and is zero in
!  * other SubLinks.  This number identifies different multiple-assignment
!  * subqueries within an UPDATE statement's SET list.  It is unique only
!  * within a particular targetlist.  The output column(s) of the MULTIEXPR
!  * are referenced by PARAM_MULTIEXPR Params appearing elsewhere in the tlist.
   *
   * The CTE_SUBLINK case never occurs in actual SubLink nodes, but it is used
   * in SubPlans generated for WITH subqueries.
*************** typedef enum SubLinkType
*** 528,533 ****
--- 544,550 ----
  	ANY_SUBLINK,
  	ROWCOMPARE_SUBLINK,
  	EXPR_SUBLINK,
+ 	MULTIEXPR_SUBLINK,
  	ARRAY_SUBLINK,
  	CTE_SUBLINK					/* for SubPlans only */
  } SubLinkType;
*************** typedef struct SubLink
*** 537,545 ****
  {
  	Expr		xpr;
  	SubLinkType subLinkType;	/* see above */
  	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
  	List	   *operName;		/* originally specified operator name */
! 	Node	   *subselect;		/* subselect as Query* or parsetree */
  	int			location;		/* token location, or -1 if unknown */
  } SubLink;
  
--- 554,563 ----
  {
  	Expr		xpr;
  	SubLinkType subLinkType;	/* see above */
+ 	int			subLinkId;		/* ID (1..n); 0 if not MULTIEXPR */
  	Node	   *testexpr;		/* outer-query test for ALL/ANY/ROWCOMPARE */
  	List	   *operName;		/* originally specified operator name */
! 	Node	   *subselect;		/* subselect as Query* or raw parsetree */
  	int			location;		/* token location, or -1 if unknown */
  } SubLink;
  
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 300136e..dacbe9c 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 190,195 ****
--- 190,198 ----
  
  	List	   *cte_plan_ids;	/* per-CTE-item list of subplan IDs */
  
+ 	List	   *multiexpr_params;		/* List of Lists of Params for
+ 										 * MULTIEXPR subquery outputs */
+ 
  	List	   *eq_classes;		/* list of active EquivalenceClasses */
  
  	List	   *canon_pathkeys; /* list of "canonical" PathKeys */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 4ce802a..b32ddf7 100644
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
*************** struct ParseState
*** 144,149 ****
--- 144,150 ----
  	List	   *p_windowdefs;	/* raw representations of window clauses */
  	ParseExprKind p_expr_kind;	/* what kind of expression we're parsing */
  	int			p_next_resno;	/* next targetlist resno to assign */
+ 	List	   *p_multiassign_exprs;	/* junk tlist entries for multiassign */
  	List	   *p_locking_clause;		/* raw FOR UPDATE/FOR SHARE info */
  	Node	   *p_value_substitute;		/* what to replace VALUE with, if any */
  	bool		p_hasAggs;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 71b856f..1de2a86 100644
*** a/src/test/regress/expected/update.out
--- b/src/test/regress/expected/update.out
*************** SELECT * FROM update_test;
*** 55,85 ****
  --
  -- Test multiple-set-clause syntax
  --
  UPDATE update_test SET (c,b,a) = ('bugle', b+11, DEFAULT) WHERE c = 'foo';
  SELECT * FROM update_test;
    a  | b  |   c   
  -----+----+-------
   100 | 20 | 
    10 | 31 | bugle
! (2 rows)
  
  UPDATE update_test SET (c,b) = ('car', a+b), a = a + 1 WHERE a = 10;
  SELECT * FROM update_test;
    a  | b  |  c  
  -----+----+-----
   100 | 20 | 
    11 | 41 | car
! (2 rows)
  
  -- fail, multi assignment to same column:
  UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
  ERROR:  multiple assignments to same column "b"
! -- XXX this should work, but doesn't yet:
! UPDATE update_test SET (a,b) = (select a,b FROM update_test where c = 'foo')
!   WHERE a = 10;
! ERROR:  syntax error at or near "select"
! LINE 1: UPDATE update_test SET (a,b) = (select a,b FROM update_test ...
!                                         ^
  -- if an alias for the target table is specified, don't allow references
  -- to the original table name
  UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a = 10;
--- 55,134 ----
  --
  -- Test multiple-set-clause syntax
  --
+ INSERT INTO update_test SELECT a,b+1,c FROM update_test;
+ SELECT * FROM update_test;
+   a  | b  |  c  
+ -----+----+-----
+  100 | 20 | foo
+  100 | 20 | 
+  100 | 21 | foo
+  100 | 21 | 
+ (4 rows)
+ 
  UPDATE update_test SET (c,b,a) = ('bugle', b+11, DEFAULT) WHERE c = 'foo';
  SELECT * FROM update_test;
    a  | b  |   c   
  -----+----+-------
   100 | 20 | 
+  100 | 21 | 
    10 | 31 | bugle
!   10 | 32 | bugle
! (4 rows)
  
  UPDATE update_test SET (c,b) = ('car', a+b), a = a + 1 WHERE a = 10;
  SELECT * FROM update_test;
    a  | b  |  c  
  -----+----+-----
   100 | 20 | 
+  100 | 21 | 
    11 | 41 | car
!   11 | 42 | car
! (4 rows)
  
  -- fail, multi assignment to same column:
  UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
  ERROR:  multiple assignments to same column "b"
! -- uncorrelated sub-select:
! UPDATE update_test
!   SET (b,a) = (select a,b from update_test where b = 41 and c = 'car')
!   WHERE a = 100 AND b = 20;
! SELECT * FROM update_test;
!   a  | b  |  c  
! -----+----+-----
!  100 | 21 | 
!   11 | 41 | car
!   11 | 42 | car
!   41 | 11 | 
! (4 rows)
! 
! -- correlated sub-select:
! UPDATE update_test o
!   SET (b,a) = (select a+1,b from update_test i
!                where i.a=o.a and i.b=o.b and i.c is not distinct from o.c);
! SELECT * FROM update_test;
!  a  |  b  |  c  
! ----+-----+-----
!  21 | 101 | 
!  41 |  12 | car
!  42 |  12 | car
!  11 |  42 | 
! (4 rows)
! 
! -- fail, multiple rows supplied:
! UPDATE update_test SET (b,a) = (select a+1,b from update_test);
! ERROR:  more than one row returned by a subquery used as an expression
! -- set to null if no rows supplied:
! UPDATE update_test SET (b,a) = (select a+1,b from update_test where a = 1000)
!   WHERE a = 11;
! SELECT * FROM update_test;
!  a  |  b  |  c  
! ----+-----+-----
!  21 | 101 | 
!  41 |  12 | car
!  42 |  12 | car
!     |     | 
! (4 rows)
! 
  -- if an alias for the target table is specified, don't allow references
  -- to the original table name
  UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a = 10;
*************** HINT:  Perhaps you meant to reference th
*** 90,99 ****
  -- Make sure that we can update to a TOASTed value.
  UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car';
  SELECT a, b, char_length(c) FROM update_test;
!   a  | b  | char_length 
! -----+----+-------------
!  100 | 20 |            
!   11 | 41 |       10000
! (2 rows)
  
  DROP TABLE update_test;
--- 139,150 ----
  -- Make sure that we can update to a TOASTed value.
  UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car';
  SELECT a, b, char_length(c) FROM update_test;
!  a  |  b  | char_length 
! ----+-----+-------------
!  21 | 101 |            
!     |     |            
!  41 |  12 |       10000
!  42 |  12 |       10000
! (4 rows)
  
  DROP TABLE update_test;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index a8a028f..e71128c 100644
*** a/src/test/regress/sql/update.sql
--- b/src/test/regress/sql/update.sql
*************** SELECT * FROM update_test;
*** 39,44 ****
--- 39,47 ----
  -- Test multiple-set-clause syntax
  --
  
+ INSERT INTO update_test SELECT a,b+1,c FROM update_test;
+ SELECT * FROM update_test;
+ 
  UPDATE update_test SET (c,b,a) = ('bugle', b+11, DEFAULT) WHERE c = 'foo';
  SELECT * FROM update_test;
  UPDATE update_test SET (c,b) = ('car', a+b), a = a + 1 WHERE a = 10;
*************** SELECT * FROM update_test;
*** 46,54 ****
  -- fail, multi assignment to same column:
  UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
  
! -- XXX this should work, but doesn't yet:
! UPDATE update_test SET (a,b) = (select a,b FROM update_test where c = 'foo')
!   WHERE a = 10;
  
  -- if an alias for the target table is specified, don't allow references
  -- to the original table name
--- 49,70 ----
  -- fail, multi assignment to same column:
  UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10;
  
! -- uncorrelated sub-select:
! UPDATE update_test
!   SET (b,a) = (select a,b from update_test where b = 41 and c = 'car')
!   WHERE a = 100 AND b = 20;
! SELECT * FROM update_test;
! -- correlated sub-select:
! UPDATE update_test o
!   SET (b,a) = (select a+1,b from update_test i
!                where i.a=o.a and i.b=o.b and i.c is not distinct from o.c);
! SELECT * FROM update_test;
! -- fail, multiple rows supplied:
! UPDATE update_test SET (b,a) = (select a+1,b from update_test);
! -- set to null if no rows supplied:
! UPDATE update_test SET (b,a) = (select a+1,b from update_test where a = 1000)
!   WHERE a = 11;
! SELECT * FROM update_test;
  
  -- if an alias for the target table is specified, don't allow references
  -- to the original table name
