diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 63f057704e65db38117469c6c3e42de01592bed4..478e12484b940ad9474b3604bc1615883018cde4 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -822,13 +822,14 @@ postgresGetForeignPlan(PlannerInfo *root, } else { - RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid); + PlanRowMark *rc = get_plan_rowmark(root->rowMarks, baserel->relid); if (rc) { /* * Relation is specified as a FOR UPDATE/SHARE target, so handle - * that. + * that. (But we could also see LCS_NONE, meaning this isn't a + * target relation after all.) * * For now, just ignore any [NO] KEY specification, since (a) it's * not clear what that means for a remote table that we don't have @@ -837,6 +838,9 @@ postgresGetForeignPlan(PlannerInfo *root, */ switch (rc->strength) { + case LCS_NONE: + /* No locking needed */ + break; case LCS_FORKEYSHARE: case LCS_FORSHARE: appendStringInfoString(&sql, " FOR SHARE"); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 291e6a7c39113fbad8ab4a6cce862d6eb8fa300f..3c6a964a6599b585f969df4eba716b27af7787b3 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -991,6 +991,8 @@ _copyPlanRowMark(const PlanRowMark *from) COPY_SCALAR_FIELD(prti); COPY_SCALAR_FIELD(rowmarkId); COPY_SCALAR_FIELD(markType); + COPY_SCALAR_FIELD(allMarkTypes); + COPY_SCALAR_FIELD(strength); COPY_SCALAR_FIELD(waitPolicy); COPY_SCALAR_FIELD(isParent); @@ -2510,7 +2512,7 @@ _copyXmlSerialize(const XmlSerialize *from) static RoleSpec * _copyRoleSpec(const RoleSpec *from) { - RoleSpec *newnode = makeNode(RoleSpec); + RoleSpec *newnode = makeNode(RoleSpec); COPY_SCALAR_FIELD(roletype); COPY_STRING_FIELD(rolename); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index fc418fcf9de7882437e1e7d0cb62a9a068ef1ac7..385b289bed2ff17e4ec4382aca632be16ffad384 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -852,7 +852,9 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node) WRITE_UINT_FIELD(prti); WRITE_UINT_FIELD(rowmarkId); WRITE_ENUM_FIELD(markType, RowMarkType); - WRITE_BOOL_FIELD(waitPolicy); + WRITE_INT_FIELD(allMarkTypes); + WRITE_ENUM_FIELD(strength, LockClauseStrength); + WRITE_ENUM_FIELD(waitPolicy, LockWaitPolicy); WRITE_BOOL_FIELD(isParent); } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 88b91f1b205a2f9ec4cc26a3dbf7e8f5c3a39582..05687a48c9a4a721e2885e499ce59a5bc643dcf8 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -2219,35 +2219,42 @@ preprocess_rowmarks(PlannerInfo *root) if (rte->rtekind != RTE_RELATION) continue; - /* - * Similarly, ignore RowMarkClauses for foreign tables; foreign tables - * will instead get ROW_MARK_COPY items in the next loop. (FDWs might - * choose to do something special while fetching their rows, but that - * is of no concern here.) - */ - if (rte->relkind == RELKIND_FOREIGN_TABLE) - continue; - rels = bms_del_member(rels, rc->rti); newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = rc->rti; newrc->rowmarkId = ++(root->glob->lastRowMarkId); - switch (rc->strength) + if (rte->relkind == RELKIND_FOREIGN_TABLE) { - case LCS_FORUPDATE: - newrc->markType = ROW_MARK_EXCLUSIVE; - break; - case LCS_FORNOKEYUPDATE: - newrc->markType = ROW_MARK_NOKEYEXCLUSIVE; - break; - case LCS_FORSHARE: - newrc->markType = ROW_MARK_SHARE; - break; - case LCS_FORKEYSHARE: - newrc->markType = ROW_MARK_KEYSHARE; - break; + /* For now, we force all foreign tables to use ROW_MARK_COPY */ + newrc->markType = ROW_MARK_COPY; + } + else + { + /* regular table, apply the appropriate lock type */ + switch (rc->strength) + { + case LCS_NONE: + /* we intentionally throw an error for LCS_NONE */ + elog(ERROR, "unrecognized LockClauseStrength %d", + (int) rc->strength); + break; + case LCS_FORKEYSHARE: + newrc->markType = ROW_MARK_KEYSHARE; + break; + case LCS_FORSHARE: + newrc->markType = ROW_MARK_SHARE; + break; + case LCS_FORNOKEYUPDATE: + newrc->markType = ROW_MARK_NOKEYEXCLUSIVE; + break; + case LCS_FORUPDATE: + newrc->markType = ROW_MARK_EXCLUSIVE; + break; + } } + newrc->allMarkTypes = (1 << newrc->markType); + newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; newrc->isParent = false; @@ -2276,6 +2283,8 @@ preprocess_rowmarks(PlannerInfo *root) newrc->markType = ROW_MARK_REFERENCE; else newrc->markType = ROW_MARK_COPY; + newrc->allMarkTypes = (1 << newrc->markType); + newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ newrc->isParent = false; diff --git a/src/backend/optimizer/prep/prepsecurity.c b/src/backend/optimizer/prep/prepsecurity.c index b382f13504e944351e9c5ccb5b8bd6da5cdab147..0be44c1c2f7ccca8f8d1e546964939d3591999c4 100644 --- a/src/backend/optimizer/prep/prepsecurity.c +++ b/src/backend/optimizer/prep/prepsecurity.c @@ -73,8 +73,8 @@ expand_security_quals(PlannerInfo *root, List *tlist) rt_index = 0; foreach(cell, parse->rtable) { - bool targetRelation = false; - RangeTblEntry *rte = (RangeTblEntry *) lfirst(cell); + bool targetRelation = false; + RangeTblEntry *rte = (RangeTblEntry *) lfirst(cell); rt_index++; @@ -241,30 +241,10 @@ expand_security_qual(PlannerInfo *root, List *tlist, int rt_index, rc = get_plan_rowmark(root->rowMarks, rt_index); if (rc != NULL) { - switch (rc->markType) - { - case ROW_MARK_EXCLUSIVE: - applyLockingClause(subquery, 1, LCS_FORUPDATE, - rc->waitPolicy, false); - break; - case ROW_MARK_NOKEYEXCLUSIVE: - applyLockingClause(subquery, 1, LCS_FORNOKEYUPDATE, - rc->waitPolicy, false); - break; - case ROW_MARK_SHARE: - applyLockingClause(subquery, 1, LCS_FORSHARE, - rc->waitPolicy, false); - break; - case ROW_MARK_KEYSHARE: - applyLockingClause(subquery, 1, LCS_FORKEYSHARE, - rc->waitPolicy, false); - break; - case ROW_MARK_REFERENCE: - case ROW_MARK_COPY: - /* No locking needed */ - break; - } - root->rowMarks = list_delete(root->rowMarks, rc); + if (rc->strength != LCS_NONE) + applyLockingClause(subquery, 1, rc->strength, + rc->waitPolicy, false); + root->rowMarks = list_delete_ptr(root->rowMarks, rc); } /* @@ -276,6 +256,7 @@ expand_security_qual(PlannerInfo *root, List *tlist, int rt_index, if (targetRelation) applyLockingClause(subquery, 1, LCS_FORUPDATE, LockWaitBlock, false); + /* * Replace any variables in the outer query that refer to the * original relation RTE with references to columns that we will diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8a6c3cc5e5c644de516a32a68e9b81898d2a65a8..08e7c446d880c0c082436a71013a6569b84a2643 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -92,9 +92,9 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) if (rc->rti != rc->prti) continue; - if (rc->markType != ROW_MARK_COPY) + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { - /* It's a regular table, so fetch its TID */ + /* Need to fetch TID */ var = makeVar(rc->rti, SelfItemPointerAttributeNumber, TIDOID, @@ -125,9 +125,9 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) tlist = lappend(tlist, tle); } } - else + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) { - /* Not a table, so we need the whole row as a junk var */ + /* Need the whole row as a junk var */ var = makeWholeRowVar(rt_fetch(rc->rti, range_table), rc->rti, 0, diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index b90fee387b4c73af26ef1e2235fd92a7546da98e..cd40afce767cc45fdb3397bec65c2d71a7659ce6 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1389,9 +1389,14 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) newrc->prti = rti; newrc->rowmarkId = oldrc->rowmarkId; newrc->markType = oldrc->markType; + newrc->allMarkTypes = (1 << newrc->markType); + newrc->strength = oldrc->strength; newrc->waitPolicy = oldrc->waitPolicy; newrc->isParent = false; + /* Include child's rowmark type in parent's allMarkTypes */ + oldrc->allMarkTypes |= newrc->allMarkTypes; + root->rowMarks = lappend(root->rowMarks, newrc); } diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index a68f2e8bb1406d117ab505382f2af9bdbbf4d3c2..4a5a5205391b932d76f71f2e34660f724426543a 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2254,11 +2254,18 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) } -char * +/* + * Produce a string representation of a LockClauseStrength value. + * This should only be applied to valid values (not LCS_NONE). + */ +const char * LCS_asString(LockClauseStrength strength) { switch (strength) { + case LCS_NONE: + Assert(false); + break; case LCS_FORKEYSHARE: return "FOR KEY SHARE"; case LCS_FORSHARE: @@ -2279,6 +2286,8 @@ LCS_asString(LockClauseStrength strength) void CheckSelectLocking(Query *qry, LockClauseStrength strength) { + Assert(strength != LCS_NONE); /* else caller error */ + if (qry->setOperations) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -2498,6 +2507,8 @@ applyLockingClause(Query *qry, Index rtindex, { RowMarkClause *rc; + Assert(strength != LCS_NONE); /* else caller error */ + /* If it's an explicit clause, make sure hasForUpdate gets set */ if (!pushedDown) qry->hasForUpdate = true; @@ -2506,20 +2517,21 @@ applyLockingClause(Query *qry, Index rtindex, if ((rc = get_parse_rowmark(qry, rtindex)) != NULL) { /* - * If the same RTE is specified for more than one locking strength, - * treat is as the strongest. (Reasonable, since you can't take both - * a shared and exclusive lock at the same time; it'll end up being - * exclusive anyway.) + * If the same RTE is specified with more than one locking strength, + * use the strongest. (Reasonable, since you can't take both a shared + * and exclusive lock at the same time; it'll end up being exclusive + * anyway.) * - * Similarly, if the same RTE is specified with more than one lock wait - * policy, consider that NOWAIT wins over SKIP LOCKED, which in turn - * wins over waiting for the lock (the default). This is a bit more - * debatable but raising an error doesn't seem helpful. (Consider for - * instance SELECT FOR UPDATE NOWAIT from a view that internally + * Similarly, if the same RTE is specified with more than one lock + * wait policy, consider that NOWAIT wins over SKIP LOCKED, which in + * turn wins over waiting for the lock (the default). This is a bit + * more debatable but raising an error doesn't seem helpful. (Consider + * for instance SELECT FOR UPDATE NOWAIT from a view that internally * contains a plain FOR UPDATE spec.) Having NOWAIT win over SKIP * LOCKED is reasonable since the former throws an error in case of - * coming across a locked tuple, which may be undesirable in some cases - * but it seems better than silently returning inconsistent results. + * coming across a locked tuple, which may be undesirable in some + * cases but it seems better than silently returning inconsistent + * results. * * And of course pushedDown becomes false if any clause is explicit. */ diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 126e38d7f73935812c15bbaf2076137fb97fd146..065475dda2a0ba75736e6935a82b17bf0ff54b13 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -2395,26 +2395,22 @@ CreateCommandTag(Node *parsetree) else if (stmt->rowMarks != NIL) { /* not 100% but probably close enough */ - switch (((PlanRowMark *) linitial(stmt->rowMarks))->markType) + switch (((PlanRowMark *) linitial(stmt->rowMarks))->strength) { - case ROW_MARK_EXCLUSIVE: - tag = "SELECT FOR UPDATE"; - break; - case ROW_MARK_NOKEYEXCLUSIVE: - tag = "SELECT FOR NO KEY UPDATE"; + case LCS_FORKEYSHARE: + tag = "SELECT FOR KEY SHARE"; break; - case ROW_MARK_SHARE: + case LCS_FORSHARE: tag = "SELECT FOR SHARE"; break; - case ROW_MARK_KEYSHARE: - tag = "SELECT FOR KEY SHARE"; + case LCS_FORNOKEYUPDATE: + tag = "SELECT FOR NO KEY UPDATE"; break; - case ROW_MARK_REFERENCE: - case ROW_MARK_COPY: - tag = "SELECT"; + case LCS_FORUPDATE: + tag = "SELECT FOR UPDATE"; break; default: - tag = "???"; + tag = "SELECT"; break; } } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2fa30be401f77be32fbc12e5d15dd63a985050d2..28e1acfb86a9d9e704a04336fb5c0a1364b312fa 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -4453,6 +4453,11 @@ get_select_query_def(Query *query, deparse_context *context, switch (rc->strength) { + case LCS_NONE: + /* we intentionally throw an error for LCS_NONE */ + elog(ERROR, "unrecognized LockClauseStrength %d", + (int) rc->strength); + break; case LCS_FORKEYSHARE: appendContextKeyword(context, " FOR KEY SHARE", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 76c64cd1227337c7c76e0cf8a35fb5ca4e1ad7db..479fc91d4662b66e0d34898d0e8c0c84aa23b43c 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201503061 +#define CATALOG_VERSION_NO 201503151 #endif diff --git a/src/include/nodes/lockoptions.h b/src/include/nodes/lockoptions.h index 55324baf40f7618520ce4a34df526a81d957f83d..2e55622b043089032dd089474999d6b19ce10721 100644 --- a/src/include/nodes/lockoptions.h +++ b/src/include/nodes/lockoptions.h @@ -20,6 +20,7 @@ */ typedef enum LockClauseStrength { + LCS_NONE, /* no such clause - only used in PlanRowMark */ LCS_FORKEYSHARE, /* FOR KEY SHARE */ LCS_FORSHARE, /* FOR SHARE */ LCS_FORNOKEYUPDATE, /* FOR NO KEY UPDATE */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index af44ddf5dc552db88a8e32418003c08a6bd2aa9b..21cbfa8cf0febf77d67e6b82b85a07aaa8cf746d 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -820,7 +820,7 @@ typedef enum RowMarkType ROW_MARK_NOKEYEXCLUSIVE, /* obtain no-key exclusive tuple lock */ ROW_MARK_SHARE, /* obtain shared tuple lock */ ROW_MARK_KEYSHARE, /* obtain keyshare tuple lock */ - ROW_MARK_REFERENCE, /* just fetch the TID */ + ROW_MARK_REFERENCE, /* just fetch the TID, don't lock it */ ROW_MARK_COPY /* physically copy the row value */ } RowMarkType; @@ -841,7 +841,9 @@ typedef enum RowMarkType * 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 and * prti == parent's RT index, and can therefore be recognized as children by - * the fact that prti != rti. + * the fact that prti != rti. The parent's allMarkTypes field gets the OR + * of (1<<markType) across all its children (this definition allows children + * to use different markTypes). * * The planner also adds resjunk output columns to the plan that carry * information sufficient to identify the locked or fetched rows. For @@ -851,6 +853,8 @@ typedef enum RowMarkType * The tableoid column is only present for an inheritance hierarchy. * When markType == ROW_MARK_COPY, there is instead a single column named * wholerow%u whole-row value of relation + * (An inheritance hierarchy could have all three resjunk output columns, + * if some children use a different markType than others.) * In all three cases, %u represents the rowmark ID number (rowmarkId). * This number is unique within a plan tree, except that child relation * entries copy their parent's rowmarkId. (Assigning unique numbers @@ -867,6 +871,8 @@ typedef struct PlanRowMark Index prti; /* range table index of parent relation */ Index rowmarkId; /* unique identifier for resjunk columns */ RowMarkType markType; /* see enum above */ + int allMarkTypes; /* OR of (1<<markType) for all children */ + LockClauseStrength strength; /* LockingClause's strength, or LCS_NONE */ LockWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED options */ bool isParent; /* true if this is a "dummy" parent entry */ } PlanRowMark; diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index 4a31cbf9b034e247f7026c8b37c7a02424efdc97..258d9d9b3eb80cf683fb0fc0948e89922dd61b96 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -36,7 +36,7 @@ extern Query *transformStmt(ParseState *pstate, Node *parseTree); extern bool analyze_requires_snapshot(Node *parseTree); -extern char *LCS_asString(LockClauseStrength strength); +extern const char *LCS_asString(LockClauseStrength strength); extern void CheckSelectLocking(Query *qry, LockClauseStrength strength); extern void applyLockingClause(Query *qry, Index rtindex, LockClauseStrength strength,