diff options
author | Tom Lane | 2008-11-15 19:43:47 +0000 |
---|---|---|
committer | Tom Lane | 2008-11-15 19:43:47 +0000 |
commit | 0ed988632d9a9753d4ad61e7053d60d7c60ea18e (patch) | |
tree | 8a178ca8090ef1240f16af898cb984543634d3ac | |
parent | 4932188a79102a029067429012d1ac85da59abf9 (diff) |
Make SELECT FOR UPDATE/SHARE work on inheritance trees, by having the plan
return the tableoid as well as the ctid for any FOR UPDATE targets that
have child tables. All child tables are listed in the ExecRowMark list,
but the executor just skips the ones that didn't produce the current row.
Curiously, this longstanding restriction doesn't seem to have been documented
anywhere; so no doc changes.
-rw-r--r-- | src/backend/executor/execMain.c | 55 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 2 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 2 | ||||
-rw-r--r-- | src/backend/nodes/outfuncs.c | 2 | ||||
-rw-r--r-- | src/backend/nodes/readfuncs.c | 2 | ||||
-rw-r--r-- | src/backend/optimizer/path/allpaths.c | 11 | ||||
-rw-r--r-- | src/backend/optimizer/prep/preptlist.c | 25 | ||||
-rw-r--r-- | src/backend/optimizer/prep/prepunion.c | 37 | ||||
-rw-r--r-- | src/backend/parser/analyze.c | 2 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteManip.c | 3 | ||||
-rw-r--r-- | src/include/catalog/catversion.h | 2 | ||||
-rw-r--r-- | src/include/nodes/execnodes.h | 7 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 11 | ||||
-rw-r--r-- | src/test/regress/expected/portals.out | 2 | ||||
-rw-r--r-- | src/test/regress/sql/portals.sql | 2 |
15 files changed, 137 insertions, 28 deletions
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 7ef01c2eff..7f9addd413 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -590,18 +590,25 @@ InitPlan(QueryDesc *queryDesc, int eflags) foreach(l, plannedstmt->rowMarks) { RowMarkClause *rc = (RowMarkClause *) lfirst(l); - Oid relid = getrelid(rc->rti, rangeTable); + Oid relid; Relation relation; ExecRowMark *erm; + /* ignore "parent" rowmarks; they are irrelevant at runtime */ + if (rc->isParent) + continue; + + relid = getrelid(rc->rti, rangeTable); relation = heap_open(relid, RowShareLock); erm = (ExecRowMark *) palloc(sizeof(ExecRowMark)); erm->relation = relation; erm->rti = rc->rti; + erm->prti = rc->prti; erm->forUpdate = rc->forUpdate; erm->noWait = rc->noWait; - /* We'll set up ctidAttno below */ + /* We'll locate the junk attrs below */ erm->ctidAttNo = InvalidAttrNumber; + erm->toidAttNo = InvalidAttrNumber; estate->es_rowMarks = lappend(estate->es_rowMarks, erm); } @@ -822,17 +829,29 @@ InitPlan(QueryDesc *queryDesc, int eflags) elog(ERROR, "could not find junk ctid column"); } - /* For SELECT FOR UPDATE/SHARE, find the ctid attrs now */ + /* For SELECT FOR UPDATE/SHARE, find the junk attrs now */ foreach(l, estate->es_rowMarks) { ExecRowMark *erm = (ExecRowMark *) lfirst(l); char resname[32]; - snprintf(resname, sizeof(resname), "ctid%u", erm->rti); + /* always need the ctid */ + snprintf(resname, sizeof(resname), "ctid%u", + erm->prti); erm->ctidAttNo = ExecFindJunkAttribute(j, resname); if (!AttributeNumberIsValid(erm->ctidAttNo)) elog(ERROR, "could not find junk \"%s\" column", resname); + /* if child relation, need tableoid too */ + if (erm->rti != erm->prti) + { + snprintf(resname, sizeof(resname), "tableoid%u", + erm->prti); + erm->toidAttNo = ExecFindJunkAttribute(j, resname); + if (!AttributeNumberIsValid(erm->toidAttNo)) + elog(ERROR, "could not find junk \"%s\" column", + resname); + } } } } @@ -1383,13 +1402,33 @@ lnext: ; LockTupleMode lockmode; HTSU_Result test; + /* if child rel, must check whether it produced this row */ + if (erm->rti != erm->prti) + { + Oid tableoid; + + datum = ExecGetJunkAttribute(slot, + erm->toidAttNo, + &isNull); + /* shouldn't ever get a null result... */ + if (isNull) + elog(ERROR, "tableoid is NULL"); + tableoid = DatumGetObjectId(datum); + + if (tableoid != RelationGetRelid(erm->relation)) + { + /* this child is inactive right now */ + continue; + } + } + + /* okay, fetch the tuple by ctid */ datum = ExecGetJunkAttribute(slot, erm->ctidAttNo, &isNull); /* shouldn't ever get a null result... */ if (isNull) elog(ERROR, "ctid is NULL"); - tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); if (erm->forUpdate) @@ -2122,9 +2161,11 @@ EvalPlanQual(EState *estate, Index rti, relation = NULL; foreach(l, estate->es_rowMarks) { - if (((ExecRowMark *) lfirst(l))->rti == rti) + ExecRowMark *erm = lfirst(l); + + if (erm->rti == rti) { - relation = ((ExecRowMark *) lfirst(l))->relation; + relation = erm->relation; break; } } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c4ef07ab00..fde5203ef4 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1735,8 +1735,10 @@ _copyRowMarkClause(RowMarkClause *from) RowMarkClause *newnode = makeNode(RowMarkClause); COPY_SCALAR_FIELD(rti); + COPY_SCALAR_FIELD(prti); COPY_SCALAR_FIELD(forUpdate); COPY_SCALAR_FIELD(noWait); + COPY_SCALAR_FIELD(isParent); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 7555249eb3..0da3fb40af 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2005,8 +2005,10 @@ static bool _equalRowMarkClause(RowMarkClause *a, RowMarkClause *b) { COMPARE_SCALAR_FIELD(rti); + COMPARE_SCALAR_FIELD(prti); COMPARE_SCALAR_FIELD(forUpdate); COMPARE_SCALAR_FIELD(noWait); + COMPARE_SCALAR_FIELD(isParent); return true; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 4dfd61cc96..ddd79d42ad 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1900,8 +1900,10 @@ _outRowMarkClause(StringInfo str, RowMarkClause *node) WRITE_NODE_TYPE("ROWMARKCLAUSE"); WRITE_UINT_FIELD(rti); + WRITE_UINT_FIELD(prti); WRITE_BOOL_FIELD(forUpdate); WRITE_BOOL_FIELD(noWait); + WRITE_BOOL_FIELD(isParent); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index a66087e610..48841f6317 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -226,8 +226,10 @@ _readRowMarkClause(void) READ_LOCALS(RowMarkClause); READ_UINT_FIELD(rti); + READ_UINT_FIELD(prti); READ_BOOL_FIELD(forUpdate); READ_BOOL_FIELD(noWait); + READ_BOOL_FIELD(isParent); READ_DONE(); } diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 1196d87362..f08744d3b4 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -284,17 +284,6 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, ListCell *l; /* - * XXX for now, can't handle inherited expansion of FOR UPDATE/SHARE; can - * we do better? (This will take some redesign because the executor - * currently supposes that every rowMark relation is involved in every row - * returned by the query.) - */ - if (get_rowmark(root->parse, parentRTindex)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("SELECT FOR UPDATE/SHARE is not supported for inheritance queries"))); - - /* * Initialize to compute size estimates for whole append relation. * * We handle width estimates by weighting the widths of different diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 3136410458..1d9003b55c 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -138,6 +138,11 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) char *resname; TargetEntry *tle; + /* ignore child rels */ + if (rc->rti != rc->prti) + continue; + + /* always need the ctid */ var = makeVar(rc->rti, SelfItemPointerAttributeNumber, TIDOID, @@ -153,6 +158,26 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) true); tlist = lappend(tlist, tle); + + /* if parent of inheritance tree, need the tableoid too */ + if (rc->isParent) + { + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + 0); + + resname = (char *) palloc(32); + snprintf(resname, 32, "tableoid%u", rc->rti); + + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + resname, + true); + + tlist = lappend(tlist, tle); + } } } diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index a65467d983..2f301afaa5 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1169,6 +1169,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) { Query *parse = root->parse; Oid parentOID; + RowMarkClause *oldrc; Relation oldrelation; LOCKMODE lockmode; List *inhOIDs; @@ -1209,6 +1210,15 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* + * Find out if parent relation is selected FOR UPDATE/SHARE. If so, + * we need to mark its RowMarkClause as isParent = true, and generate + * a new RowMarkClause for each child. + */ + oldrc = get_rowmark(parse, rti); + if (oldrc) + oldrc->isParent = true; + + /* * Must open the parent relation to examine its tupdesc. We need not lock * it since the rewriter already obtained at least AccessShareLock on each * relation used in the query. @@ -1221,14 +1231,15 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) * in the parse/rewrite/plan pipeline. * * If the parent relation is the query's result relation, then we need - * RowExclusiveLock. Otherwise, check to see if the relation is accessed - * FOR UPDATE/SHARE or not. We can't just grab AccessShareLock because - * then the executor would be trying to upgrade the lock, leading to - * possible deadlocks. (This code should match the parser and rewriter.) + * RowExclusiveLock. Otherwise, if it's accessed FOR UPDATE/SHARE, we + * need RowShareLock; otherwise AccessShareLock. We can't just grab + * AccessShareLock because then the executor would be trying to upgrade + * the lock, leading to possible deadlocks. (This code should match the + * parser and rewriter.) */ if (rti == parse->resultRelation) lockmode = RowExclusiveLock; - else if (get_rowmark(parse, rti)) + else if (oldrc) lockmode = RowShareLock; else lockmode = AccessShareLock; @@ -1283,6 +1294,22 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) appinfo->parent_reloid = parentOID; appinfos = lappend(appinfos, appinfo); + /* + * Build a RowMarkClause if parent is marked FOR UPDATE/SHARE. + */ + if (oldrc) + { + RowMarkClause *newrc = makeNode(RowMarkClause); + + newrc->rti = childRTindex; + newrc->prti = rti; + newrc->forUpdate = oldrc->forUpdate; + newrc->noWait = oldrc->noWait; + newrc->isParent = false; + + parse->rowMarks = lappend(parse->rowMarks, newrc); + } + /* Close child relations, but keep locks */ if (childOID != parentOID) heap_close(newrelation, NoLock); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index ac2c7ba84c..322c4bc963 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2049,8 +2049,10 @@ applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait) /* Make a new RowMarkClause */ rc = makeNode(RowMarkClause); rc->rti = rtindex; + rc->prti = rtindex; rc->forUpdate = forUpdate; rc->noWait = noWait; + rc->isParent = false; qry->rowMarks = lappend(qry->rowMarks, rc); } diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 1f72330631..376c3f6667 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -352,6 +352,7 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up) RowMarkClause *rc = (RowMarkClause *) lfirst(l); rc->rti += offset; + rc->prti += offset; } } query_tree_walker(qry, OffsetVarNodes_walker, @@ -536,6 +537,8 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up) if (rc->rti == rt_index) rc->rti = new_index; + if (rc->prti == rt_index) + rc->prti = new_index; } } query_tree_walker(qry, ChangeVarNodes_walker, diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index a9c39b7aef..76b174749a 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200811133 +#define CATALOG_VERSION_NO 200811151 #endif diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index fe74459050..349ba772de 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -368,14 +368,19 @@ typedef struct EState } EState; -/* es_rowMarks is a list of these structs: */ +/* + * es_rowMarks is a list of these structs. See RowMarkClause for details + * about rti and prti. toidAttno is not used in a "plain" rowmark. + */ typedef struct ExecRowMark { Relation relation; /* opened and RowShareLock'd relation */ Index rti; /* its range table index */ + Index prti; /* parent range table index, if child */ bool forUpdate; /* true = FOR UPDATE, false = FOR SHARE */ bool noWait; /* NOWAIT option */ AttrNumber ctidAttNo; /* resno of its ctid junk attribute */ + AttrNumber toidAttNo; /* resno of tableoid junk attribute, if any */ } ExecRowMark; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index b442121050..fff7379c7f 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -700,14 +700,23 @@ typedef struct SortGroupClause * RowMarkClause - * representation of FOR UPDATE/SHARE clauses * - * We create a separate RowMarkClause node for each target relation + * We create a separate RowMarkClause node for each target relation. In the + * output of the parser and rewriter, all RowMarkClauses have rti == prti and + * isParent == false. When the planner discovers that a target relation + * is the root of an inheritance tree, it sets isParent true, and adds an + * additional RowMarkClause to the list for each child relation (including + * the target rel itself in its role as a child). The child entries have + * rti == child rel's RT index, prti == parent's RT index, and can therefore + * be recognized as children by the fact that prti != rti. */ typedef struct RowMarkClause { NodeTag type; Index rti; /* range table index of target relation */ + Index prti; /* range table index of parent relation */ bool forUpdate; /* true = FOR UPDATE, false = FOR SHARE */ bool noWait; /* NOWAIT option */ + bool isParent; /* set by planner when expanding inheritance */ } RowMarkClause; /* diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out index 63b8a8be56..66563615d8 100644 --- a/src/test/regress/expected/portals.out +++ b/src/test/regress/expected/portals.out @@ -1118,7 +1118,7 @@ SELECT * FROM uctest; (3 rows) BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest; +DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; FETCH 1 FROM c1; f1 | f2 ----+------- diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql index 63a689666a..b53eaac786 100644 --- a/src/test/regress/sql/portals.sql +++ b/src/test/regress/sql/portals.sql @@ -393,7 +393,7 @@ INSERT INTO ucchild values(100, 'hundred'); SELECT * FROM uctest; BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest; +DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; FETCH 1 FROM c1; UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; FETCH 1 FROM c1; |