mirror of
https://github.com/postgres/postgres.git
synced 2025-09-02 04:21:28 +03:00
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns (but only one row) to be used to compute the new values of several columns to be updated. While the same results can be had with an independent sub-SELECT per column, such a workaround can require a great deal of duplicated computation. The standard actually says that the source for a multi-column assignment could be any row-valued expression. The implementation used here is tightly tied to our existing sub-SELECT support and can't handle other cases; the Bison grammar would have some issues with them too. However, I don't feel too bad about this since other cases can be converted into sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
This commit is contained in:
@@ -310,6 +310,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
||||
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;
|
||||
|
@@ -157,10 +157,13 @@ static bool extract_query_dependencies_walker(Node *node,
|
||||
* 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
|
||||
* 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).
|
||||
*
|
||||
* 5. We create lists of specific objects that the plan depends on.
|
||||
* 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).
|
||||
@@ -1118,11 +1121,40 @@ fix_expr_common(PlannerInfo *root, Node *node)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
@@ -1134,7 +1166,9 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
|
||||
context.root = root;
|
||||
context.rtoffset = rtoffset;
|
||||
|
||||
if (rtoffset != 0 || root->glob->lastPHId != 0)
|
||||
if (rtoffset != 0 ||
|
||||
root->multiexpr_params != NIL ||
|
||||
root->glob->lastPHId != 0)
|
||||
{
|
||||
return fix_scan_expr_mutator(node, &context);
|
||||
}
|
||||
@@ -1142,11 +1176,12 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
|
||||
{
|
||||
/*
|
||||
* 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.
|
||||
* 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;
|
||||
@@ -1176,6 +1211,8 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
|
||||
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);
|
||||
@@ -1745,6 +1782,8 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
|
||||
/* 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)
|
||||
{
|
||||
@@ -1847,6 +1886,8 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
|
||||
/* 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)
|
||||
{
|
||||
|
@@ -55,8 +55,9 @@ typedef struct finalize_primnode_context
|
||||
|
||||
static Node *build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
|
||||
List *plan_params,
|
||||
SubLinkType subLinkType, Node *testexpr,
|
||||
bool adjust_testexpr, bool unknownEqFalse);
|
||||
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,
|
||||
@@ -407,7 +408,7 @@ get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod,
|
||||
/*
|
||||
* Convert a SubLink (as created by the parser) into a SubPlan.
|
||||
*
|
||||
* We are given the SubLink's contained query, type, and testexpr. We are
|
||||
* 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,
|
||||
@@ -415,14 +416,20 @@ get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod,
|
||||
* implicit-AND form; and any SubLinks in it should already have been
|
||||
* converted to SubPlans. The subquery is as yet untouched, however.
|
||||
*
|
||||
* The result is whatever we need to substitute in place of the SubLink
|
||||
* 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.
|
||||
* The result is whatever we need to substitute in place of the SubLink node
|
||||
* in the executable expression. If we're going to do the subplan as a
|
||||
* regular subplan, this will be the constructed SubPlan node. If we're going
|
||||
* to do the subplan as an InitPlan, the SubPlan node instead goes into
|
||||
* root->init_plans, and what we return here is an expression tree
|
||||
* representing the InitPlan's result: usually just a Param node representing
|
||||
* a single scalar result, but possibly a row comparison tree containing
|
||||
* multiple Param nodes, or for a MULTIEXPR subquery a simple NULL constant
|
||||
* (since the real output Params are elsewhere in the tree, and the MULTIEXPR
|
||||
* subquery itself is in a resjunk tlist entry whose value is uninteresting).
|
||||
*/
|
||||
static Node *
|
||||
make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
|
||||
make_subplan(PlannerInfo *root, Query *orig_subquery,
|
||||
SubLinkType subLinkType, int subLinkId,
|
||||
Node *testexpr, bool isTopQual)
|
||||
{
|
||||
Query *subquery;
|
||||
@@ -452,8 +459,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
|
||||
* 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).
|
||||
* 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.
|
||||
@@ -491,7 +498,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
|
||||
|
||||
/* And convert to SubPlan or InitPlan format. */
|
||||
result = build_subplan(root, plan, subroot, plan_params,
|
||||
subLinkType, testexpr, true, isTopQual);
|
||||
subLinkType, subLinkId,
|
||||
testexpr, true, isTopQual);
|
||||
|
||||
/*
|
||||
* If it's a correlated EXISTS with an unimportant targetlist, we might be
|
||||
@@ -536,7 +544,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
|
||||
/* OK, convert to SubPlan format. */
|
||||
hashplan = (SubPlan *) build_subplan(root, plan, subroot,
|
||||
plan_params,
|
||||
ANY_SUBLINK, newtestexpr,
|
||||
ANY_SUBLINK, 0,
|
||||
newtestexpr,
|
||||
false, true);
|
||||
/* Check we got what we expected */
|
||||
Assert(IsA(hashplan, SubPlan));
|
||||
@@ -559,14 +568,15 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
|
||||
/*
|
||||
* 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.
|
||||
* Returns either the SubPlan, or a replacement expression if we decide to
|
||||
* make it an InitPlan, 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)
|
||||
SubLinkType subLinkType, int subLinkId,
|
||||
Node *testexpr, bool adjust_testexpr,
|
||||
bool unknownEqFalse)
|
||||
{
|
||||
Node *result;
|
||||
SubPlan *splan;
|
||||
@@ -615,12 +625,15 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* Un-correlated or undirect correlated plans of EXISTS, EXPR, ARRAY,
|
||||
* ROWCOMPARE, or MULTIEXPR types can be used as initPlans. For EXISTS,
|
||||
* EXPR, or ARRAY, we return 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, and then return that tree. 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.
|
||||
*/
|
||||
if (splan->parParam == NIL && subLinkType == EXISTS_SUBLINK)
|
||||
{
|
||||
@@ -687,6 +700,42 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
|
||||
* 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
|
||||
{
|
||||
/*
|
||||
@@ -760,25 +809,22 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
|
||||
splan->plan_id);
|
||||
|
||||
/* Label the subplan for EXPLAIN purposes */
|
||||
if (isInitPlan)
|
||||
splan->plan_name = palloc(32 + 12 * list_length(splan->setParam));
|
||||
sprintf(splan->plan_name, "%s %d",
|
||||
isInitPlan ? "InitPlan" : "SubPlan",
|
||||
splan->plan_id);
|
||||
if (splan->setParam)
|
||||
{
|
||||
ListCell *lc;
|
||||
int offset;
|
||||
char *ptr = splan->plan_name + strlen(splan->plan_name);
|
||||
|
||||
splan->plan_name = palloc(32 + 12 * list_length(splan->setParam));
|
||||
sprintf(splan->plan_name, "InitPlan %d (returns ", splan->plan_id);
|
||||
offset = strlen(splan->plan_name);
|
||||
ptr += sprintf(ptr, " (returns ");
|
||||
foreach(lc, splan->setParam)
|
||||
{
|
||||
sprintf(splan->plan_name + offset, "$%d%s",
|
||||
lfirst_int(lc),
|
||||
lnext(lc) ? "," : "");
|
||||
offset += strlen(splan->plan_name + offset);
|
||||
ptr += sprintf(ptr, "$%d%s",
|
||||
lfirst_int(lc),
|
||||
lnext(lc) ? "," : ")");
|
||||
}
|
||||
sprintf(splan->plan_name + offset, ")");
|
||||
}
|
||||
else
|
||||
splan->plan_name = psprintf("SubPlan %d", splan->plan_id);
|
||||
|
||||
/* Lastly, fill in the cost estimates for use later */
|
||||
cost_subplan(root, splan, plan);
|
||||
@@ -1816,6 +1862,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
|
||||
return make_subplan(context->root,
|
||||
(Query *) sublink->subselect,
|
||||
sublink->subLinkType,
|
||||
sublink->subLinkId,
|
||||
testexpr,
|
||||
context->isTopQual);
|
||||
}
|
||||
|
@@ -804,6 +804,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
|
||||
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;
|
||||
|
@@ -187,6 +187,27 @@ get_tlist_exprs(List *tlist, bool includeJunk)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* count_nonjunk_tlist_entries
|
||||
* What it says ...
|
||||
*/
|
||||
int
|
||||
count_nonjunk_tlist_entries(List *tlist)
|
||||
{
|
||||
int len = 0;
|
||||
ListCell *l;
|
||||
|
||||
foreach(l, tlist)
|
||||
{
|
||||
TargetEntry *tle = (TargetEntry *) lfirst(l);
|
||||
|
||||
if (!tle->resjunk)
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* tlist_same_exprs
|
||||
* Check whether two target lists contain the same expressions
|
||||
|
Reference in New Issue
Block a user