1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-20 05:03:10 +03:00

Move the handling of SELECT FOR UPDATE locking and rechecking out of

execMain.c and into a new plan node type LockRows.  Like the recent change
to put table updating into a ModifyTable plan node, this increases planning
flexibility by allowing the operations to occur below the top level of the
plan tree.  It's necessary in any case to restore the previous behavior of
having FOR UPDATE locking occur before ModifyTable does.

This partially refactors EvalPlanQual to allow multiple rows-under-test
to be inserted into the EPQ machinery before starting an EPQ test query.
That isn't sufficient to fix EPQ's general bogosity in the face of plans
that return multiple rows per test row, though.  Since this patch is
mostly about getting some plan node infrastructure in place and not about
fixing ten-year-old bugs, I will leave EPQ improvements for another day.

Another behavioral change that we could now think about is doing FOR UPDATE
before LIMIT, but that too seems like it should be treated as a followon
patch.
This commit is contained in:
Tom Lane
2009-10-12 18:10:51 +00:00
parent 05d249717d
commit 0adaf4cb31
32 changed files with 882 additions and 320 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.186 2009/09/17 20:49:28 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.187 2009/10/12 18:10:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -632,6 +632,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
false, tuple_fraction,
&subroot);
rel->subrtable = subroot->parse->rtable;
rel->subrowmark = subroot->parse->rowMarks;
/* Copy number of output rows from subplan */
rel->tuples = rel->subplan->plan_rows;
@ -971,10 +972,10 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
* since that could change the set of rows returned.
*
* 2. If the subquery contains any window functions, we can't push quals
* into it, because that would change the results.
* into it, because that could change the results.
*
* 3. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
* quals into it, because that would change the results.
* quals into it, because that could change the results.
*
* 4. For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
* push quals into each component query, but the quals can only reference

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.264 2009/10/10 01:43:49 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.265 2009/10/12 18:10:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1336,7 +1336,8 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
scan_clauses,
scan_relid,
best_path->parent->subplan,
best_path->parent->subrtable);
best_path->parent->subrtable,
best_path->parent->subrowmark);
copy_path_costsize(&scan_plan->scan.plan, best_path);
@ -2508,7 +2509,8 @@ make_subqueryscan(List *qptlist,
List *qpqual,
Index scanrelid,
Plan *subplan,
List *subrtable)
List *subrtable,
List *subrowmark)
{
SubqueryScan *node = makeNode(SubqueryScan);
Plan *plan = &node->scan.plan;
@ -2528,6 +2530,7 @@ make_subqueryscan(List *qptlist,
node->scan.scanrelid = scanrelid;
node->subplan = subplan;
node->subrtable = subrtable;
node->subrowmark = subrowmark;
return node;
}
@ -3590,6 +3593,31 @@ make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
return node;
}
/*
* make_lockrows
* Build a LockRows plan node
*/
LockRows *
make_lockrows(Plan *lefttree, List *rowMarks)
{
LockRows *node = makeNode(LockRows);
Plan *plan = &node->plan;
copy_plan_costsize(plan, lefttree);
/* charge cpu_tuple_cost to reflect locking costs (underestimate?) */
plan->total_cost += cpu_tuple_cost * plan->plan_rows;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->rowMarks = rowMarks;
return node;
}
/*
* Note: offset_est and count_est are passed in to save having to repeat
* work already done to estimate the values of the limitOffset and limitCount
@ -3792,6 +3820,7 @@ is_projection_capable_plan(Plan *plan)
case T_Sort:
case T_Unique:
case T_SetOp:
case T_LockRows:
case T_Limit:
case T_ModifyTable:
case T_Append:

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.258 2009/10/10 01:43:49 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.259 2009/10/12 18:10:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -132,7 +132,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
PlannerInfo *root;
Plan *top_plan;
ListCell *lp,
*lr;
*lrt,
*lrm;
/* Cursor options may come from caller or from DECLARE CURSOR stmt */
if (parse->utilityStmt &&
@ -151,11 +152,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
glob->paramlist = NIL;
glob->subplans = NIL;
glob->subrtables = NIL;
glob->subrowmarks = NIL;
glob->rewindPlanIDs = NULL;
glob->finalrtable = NIL;
glob->finalrowmarks = NIL;
glob->relationOids = NIL;
glob->invalItems = NIL;
glob->lastPHId = 0;
glob->lastRowmarkId = 0;
glob->transientPlan = false;
/* Determine what fraction of the plan is likely to be scanned */
@ -202,15 +206,25 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
/* final cleanup of the plan */
Assert(glob->finalrtable == NIL);
top_plan = set_plan_references(glob, top_plan, root->parse->rtable);
Assert(glob->finalrowmarks == NIL);
top_plan = set_plan_references(glob, top_plan,
root->parse->rtable,
root->parse->rowMarks);
/* ... and the subplans (both regular subplans and initplans) */
Assert(list_length(glob->subplans) == list_length(glob->subrtables));
forboth(lp, glob->subplans, lr, glob->subrtables)
Assert(list_length(glob->subplans) == list_length(glob->subrowmarks));
lrt = list_head(glob->subrtables);
lrm = list_head(glob->subrowmarks);
foreach(lp, glob->subplans)
{
Plan *subplan = (Plan *) lfirst(lp);
List *subrtable = (List *) lfirst(lr);
List *subrtable = (List *) lfirst(lrt);
List *subrowmark = (List *) lfirst(lrm);
lfirst(lp) = set_plan_references(glob, subplan, subrtable);
lfirst(lp) = set_plan_references(glob, subplan,
subrtable, subrowmark);
lrt = lnext(lrt);
lrm = lnext(lrm);
}
/* build the PlannedStmt result */
@ -227,7 +241,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->intoClause = parse->intoClause;
result->subplans = glob->subplans;
result->rewindPlanIDs = glob->rewindPlanIDs;
result->rowMarks = parse->rowMarks;
result->rowMarks = glob->finalrowmarks;
result->relationOids = glob->relationOids;
result->invalItems = glob->invalItems;
result->nParamExec = list_length(glob->paramlist);
@ -349,6 +363,21 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
}
}
/*
* Assign unique IDs (unique within this planner run) to RowMarkClauses.
* We can't identify them just by RT index because that will change
* during final rtable flattening, and we don't want to have to go back
* and change the resnames assigned to junk CTID tlist entries at that
* point. Do it now before expanding inheritance sets, because child
* relations should inherit their parents' rowmarkId.
*/
foreach(l, parse->rowMarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(l);
rc->rowmarkId = ++(root->glob->lastRowmarkId);
}
/*
* Expand any rangetable entries that are inheritance sets into "append
* relations". This can add entries to the rangetable, but they must be
@ -1588,7 +1617,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
}
/*
* Finally, if there is a LIMIT/OFFSET clause, add the LIMIT node.
* If there is a LIMIT/OFFSET clause, add the LIMIT node.
*/
if (parse->limitCount || parse->limitOffset)
{
@ -1599,6 +1628,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
count_est);
}
/*
* Finally, if there is a FOR UPDATE/SHARE clause, add the LockRows node.
*/
if (parse->rowMarks)
{
result_plan = (Plan *) make_lockrows(result_plan,
parse->rowMarks);
}
/* Compute result-relations list if needed */
if (parse->resultRelation)
root->resultRelations = list_make1_int(parse->resultRelation);

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.151 2009/10/10 01:43:49 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.152 2009/10/12 18:10:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -166,12 +166,14 @@ static bool extract_query_dependencies_walker(Node *node,
* glob: global data for planner run
* plan: the topmost node of the plan
* rtable: the rangetable for the current subquery
* rowmarks: the RowMarkClause list for the current subquery
*
* The return value is normally the same Plan node passed in, but can be
* different when the passed-in Plan is a SubqueryScan we decide isn't needed.
*
* The flattened rangetable entries are appended to glob->finalrtable, and
* plan dependencies are appended to glob->relationOids (for relations)
* The flattened rangetable entries are appended to glob->finalrtable,
* and we also append rowmarks entries to glob->finalrowmarks.
* Plan dependencies are appended to glob->relationOids (for relations)
* and glob->invalItems (for everything else).
*
* Notice that we modify Plan nodes in-place, but use expression_tree_mutator
@ -180,7 +182,8 @@ static bool extract_query_dependencies_walker(Node *node,
* it's not so safe to assume that for expression tree nodes.
*/
Plan *
set_plan_references(PlannerGlobal *glob, Plan *plan, List *rtable)
set_plan_references(PlannerGlobal *glob, Plan *plan,
List *rtable, List *rowmarks)
{
int rtoffset = list_length(glob->finalrtable);
ListCell *lc;
@ -230,6 +233,26 @@ set_plan_references(PlannerGlobal *glob, Plan *plan, List *rtable)
newrte->relid);
}
/*
* Adjust RT indexes of RowMarkClauses and add to final rowmarks list
*/
foreach(lc, rowmarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(lc);
RowMarkClause *newrc;
/* flat copy to duplicate all the scalar fields */
newrc = (RowMarkClause *) palloc(sizeof(RowMarkClause));
memcpy(newrc, rc, sizeof(RowMarkClause));
/* adjust indexes */
newrc->rti += rtoffset;
newrc->prti += rtoffset;
/* rowmarkId must NOT be adjusted */
glob->finalrowmarks = lappend(glob->finalrowmarks, newrc);
}
/* Now fix the Plan tree */
return set_plan_refs(glob, plan, rtoffset);
}
@ -396,6 +419,27 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
*/
Assert(plan->qual == NIL);
break;
case T_LockRows:
{
LockRows *splan = (LockRows *) plan;
/*
* Like the plan types above, LockRows doesn't evaluate its
* tlist or quals. But we have to fix up the RT indexes
* in its rowmarks.
*/
set_dummy_tlist_references(plan, rtoffset);
Assert(splan->plan.qual == NIL);
foreach(l, splan->rowMarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(l);
rc->rti += rtoffset;
rc->prti += rtoffset;
}
}
break;
case T_Limit:
{
Limit *splan = (Limit *) plan;
@ -553,10 +597,12 @@ set_subqueryscan_references(PlannerGlobal *glob,
Plan *result;
/* First, recursively process the subplan */
plan->subplan = set_plan_references(glob, plan->subplan, plan->subrtable);
plan->subplan = set_plan_references(glob, plan->subplan,
plan->subrtable, plan->subrowmark);
/* subrtable is no longer needed in the plan tree */
/* subrtable/subrowmark are no longer needed in the plan tree */
plan->subrtable = NIL;
plan->subrowmark = NIL;
if (trivial_subqueryscan(plan))
{

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.154 2009/10/10 01:43:49 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.155 2009/10/12 18:10:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,7 +53,8 @@ typedef struct finalize_primnode_context
} finalize_primnode_context;
static Node *build_subplan(PlannerInfo *root, Plan *plan, List *rtable,
static Node *build_subplan(PlannerInfo *root, Plan *plan,
List *rtable, List *rowmarks,
SubLinkType subLinkType, Node *testexpr,
bool adjust_testexpr, bool unknownEqFalse);
static List *generate_subquery_params(PlannerInfo *root, List *tlist,
@ -333,7 +334,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
&subroot);
/* And convert to SubPlan or InitPlan format. */
result = build_subplan(root, plan, subroot->parse->rtable,
result = build_subplan(root, plan,
subroot->parse->rtable, subroot->parse->rowMarks,
subLinkType, testexpr, true, isTopQual);
/*
@ -375,6 +377,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
/* OK, convert to SubPlan format. */
hashplan = (SubPlan *) build_subplan(root, plan,
subroot->parse->rtable,
subroot->parse->rowMarks,
ANY_SUBLINK, newtestexpr,
false, true);
/* Check we got what we expected */
@ -402,7 +405,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
* as explained in the comments for make_subplan.
*/
static Node *
build_subplan(PlannerInfo *root, Plan *plan, List *rtable,
build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks,
SubLinkType subLinkType, Node *testexpr,
bool adjust_testexpr, bool unknownEqFalse)
{
@ -585,6 +588,7 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable,
*/
root->glob->subplans = lappend(root->glob->subplans, plan);
root->glob->subrtables = lappend(root->glob->subrtables, rtable);
root->glob->subrowmarks = lappend(root->glob->subrowmarks, rowmarks);
splan->plan_id = list_length(root->glob->subplans);
if (isInitPlan)
@ -944,6 +948,8 @@ SS_process_ctes(PlannerInfo *root)
root->glob->subplans = lappend(root->glob->subplans, plan);
root->glob->subrtables = lappend(root->glob->subrtables,
subroot->parse->rtable);
root->glob->subrowmarks = lappend(root->glob->subrowmarks,
subroot->parse->rowMarks);
splan->plan_id = list_length(root->glob->subplans);
root->init_plans = lappend(root->init_plans, splan);
@ -2035,6 +2041,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params)
case T_Unique:
case T_SetOp:
case T_Group:
case T_LockRows:
break;
default:
@ -2191,6 +2198,8 @@ SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan,
plan);
root->glob->subrtables = lappend(root->glob->subrtables,
root->parse->rtable);
root->glob->subrowmarks = lappend(root->glob->subrowmarks,
root->parse->rowMarks);
/*
* Create a SubPlan node and add it to the outer list of InitPlans. Note

View File

@ -16,7 +16,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.96 2009/04/19 19:46:33 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.97 2009/10/12 18:10:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -123,25 +123,20 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
*/
CheckSelectLocking(parse);
/*
* Currently the executor only supports FOR UPDATE/SHARE at top level
*/
if (root->query_level > 1)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed in subqueries")));
foreach(l, parse->rowMarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(l);
Var *var;
char *resname;
char resname[32];
TargetEntry *tle;
/* ignore child rels */
if (rc->rti != rc->prti)
continue;
/* we should have an ID for the RowMarkClause */
Assert(rc->rowmarkId != 0);
/* always need the ctid */
var = makeVar(rc->rti,
SelfItemPointerAttributeNumber,
@ -149,12 +144,12 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
-1,
0);
resname = (char *) palloc(32);
snprintf(resname, 32, "ctid%u", rc->rti);
snprintf(resname, sizeof(resname),
"ctid%u", rc->rowmarkId);
tle = makeTargetEntry((Expr *) var,
list_length(tlist) + 1,
resname,
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
@ -168,12 +163,12 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
-1,
0);
resname = (char *) palloc(32);
snprintf(resname, 32, "tableoid%u", rc->rti);
snprintf(resname, sizeof(resname),
"tableoid%u", rc->rowmarkId);
tle = makeTargetEntry((Expr *) var,
list_length(tlist) + 1,
resname,
pstrdup(resname),
true);
tlist = lappend(tlist, tle);

View File

@ -22,7 +22,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.175 2009/10/10 01:43:49 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.176 2009/10/12 18:10:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -247,7 +247,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
NIL,
rtr->rtindex,
subplan,
subroot->parse->rtable);
subroot->parse->rtable,
subroot->parse->rowMarks);
/*
* We don't bother to determine the subquery's output ordering since
@ -1281,6 +1282,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
/* children use the same rowmarkId as their parent */
newrc->rowmarkId = oldrc->rowmarkId;
newrc->forUpdate = oldrc->forUpdate;
newrc->noWait = oldrc->noWait;
newrc->isParent = false;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.94 2009/06/11 14:48:59 momjian Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.95 2009/10/12 18:10:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -84,6 +84,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->tuples = 0;
rel->subplan = NULL;
rel->subrtable = NIL;
rel->subrowmark = NIL;
rel->baserestrictinfo = NIL;
rel->baserestrictcost.startup = 0;
rel->baserestrictcost.per_tuple = 0;
@ -337,6 +338,7 @@ build_join_rel(PlannerInfo *root,
joinrel->tuples = 0;
joinrel->subplan = NULL;
joinrel->subrtable = NIL;
joinrel->subrowmark = NIL;
joinrel->baserestrictinfo = NIL;
joinrel->baserestrictcost.startup = 0;
joinrel->baserestrictcost.per_tuple = 0;