1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-09 06:21:09 +03:00

Improve the representation of FOR UPDATE/FOR SHARE so that we can

support both FOR UPDATE and FOR SHARE in one command, as well as both
NOWAIT and normal WAIT behavior.  The more general code is actually
simpler and cleaner.
This commit is contained in:
Tom Lane
2006-04-30 18:30:40 +00:00
parent 931bfc9664
commit 986085a7f0
29 changed files with 320 additions and 245 deletions

View File

@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.333 2006/04/22 01:25:59 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.334 2006/04/30 18:30:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1805,6 +1805,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
{
Query *qry = makeNode(Query);
Node *qual;
ListCell *l;
qry->commandType = CMD_SELECT;
@@ -1870,8 +1871,10 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
if (stmt->lockingClause)
transformLockingClause(qry, stmt->lockingClause);
foreach(l, stmt->lockingClause)
{
transformLockingClause(qry, (LockingClause *) lfirst(l));
}
return qry;
}
@@ -1899,10 +1902,11 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
List *sortClause;
Node *limitOffset;
Node *limitCount;
LockingClause *lockingClause;
List *lockingClause;
Node *node;
ListCell *left_tlist,
*dtlist;
*dtlist,
*l;
List *targetvars,
*targetnames,
*sv_relnamespace,
@@ -1942,7 +1946,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
stmt->sortClause = NIL;
stmt->limitOffset = NULL;
stmt->limitCount = NULL;
stmt->lockingClause = NULL;
stmt->lockingClause = NIL;
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (lockingClause)
@@ -2084,8 +2088,10 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
if (lockingClause)
transformLockingClause(qry, lockingClause);
foreach(l, lockingClause)
{
transformLockingClause(qry, (LockingClause *) lfirst(l));
}
return qry;
}
@@ -2743,44 +2749,34 @@ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
/* exported so planner can check again after rewriting, query pullup, etc */
void
CheckSelectLocking(Query *qry, bool forUpdate)
CheckSelectLocking(Query *qry)
{
const char *operation;
if (forUpdate)
operation = "SELECT FOR UPDATE";
else
operation = "SELECT FOR SHARE";
if (qry->setOperations)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT", operation)));
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
if (qry->distinctClause != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with DISTINCT clause", operation)));
errmsg("SELECT FOR UPDATE/SHARE is not allowed with DISTINCT clause")));
if (qry->groupClause != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with GROUP BY clause", operation)));
errmsg("SELECT FOR UPDATE/SHARE is not allowed with GROUP BY clause")));
if (qry->havingQual != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with HAVING clause", operation)));
errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
if (qry->hasAggs)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL command, like SELECT FOR UPDATE */
errmsg("%s is not allowed with aggregate functions", operation)));
errmsg("SELECT FOR UPDATE/SHARE is not allowed with aggregate functions")));
}
/*
* Convert FOR UPDATE/SHARE name list into rowMarks list of integer relids
* Transform a FOR UPDATE/SHARE clause
*
* This basically involves replacing names by integer relids.
*
* NB: if you need to change this, see also markQueryForLocking()
* in rewriteHandler.c.
@@ -2789,35 +2785,18 @@ static void
transformLockingClause(Query *qry, LockingClause *lc)
{
List *lockedRels = lc->lockedRels;
List *rowMarks;
ListCell *l;
ListCell *rt;
Index i;
LockingClause *allrels;
if (qry->rowMarks)
{
if (lc->forUpdate != qry->forUpdate)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot use both FOR UPDATE and FOR SHARE in one query")));
if (lc->nowait != qry->rowNoWait)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot use both wait and NOWAIT in one query")));
}
qry->forUpdate = lc->forUpdate;
qry->rowNoWait = lc->nowait;
CheckSelectLocking(qry, lc->forUpdate);
CheckSelectLocking(qry);
/* make a clause we can pass down to subqueries to select all rels */
allrels = makeNode(LockingClause);
allrels->lockedRels = NIL; /* indicates all rels */
allrels->forUpdate = lc->forUpdate;
allrels->nowait = lc->nowait;
rowMarks = qry->rowMarks;
allrels->noWait = lc->noWait;
if (lockedRels == NIL)
{
@@ -2831,8 +2810,7 @@ transformLockingClause(Query *qry, LockingClause *lc)
switch (rte->rtekind)
{
case RTE_RELATION:
/* use list_append_unique to avoid duplicates */
rowMarks = list_append_unique_int(rowMarks, i);
applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
@@ -2867,8 +2845,8 @@ transformLockingClause(Query *qry, LockingClause *lc)
switch (rte->rtekind)
{
case RTE_RELATION:
/* use list_append_unique to avoid duplicates */
rowMarks = list_append_unique_int(rowMarks, i);
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
@@ -2909,8 +2887,41 @@ transformLockingClause(Query *qry, LockingClause *lc)
relname)));
}
}
}
qry->rowMarks = rowMarks;
/*
* Record locking info for a single rangetable item
*/
void
applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
{
RowMarkClause *rc;
/* Check for pre-existing entry for same rtindex */
if ((rc = get_rowmark(qry, rtindex)) != NULL)
{
/*
* If the same RTE is specified both FOR UPDATE and FOR SHARE,
* treat it as FOR UPDATE. (Reasonable, since you can't take
* both a shared and exclusive lock at the same time; it'll
* end up being exclusive anyway.)
*
* We also consider that NOWAIT wins if it's specified both ways.
* 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.)
*/
rc->forUpdate |= forUpdate;
rc->noWait |= noWait;
return;
}
/* Make a new RowMarkClause */
rc = makeNode(RowMarkClause);
rc->rti = rtindex;
rc->forUpdate = forUpdate;
rc->noWait = noWait;
qry->rowMarks = lappend(qry->rowMarks, rc);
}

View File

@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.543 2006/04/27 00:33:45 momjian Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.544 2006/04/30 18:30:39 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -96,7 +96,7 @@ static List *check_func_name(List *names);
static List *extractArgTypes(List *parameters);
static SelectStmt *findLeftmostSelect(SelectStmt *node);
static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, Node *lockingClause,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount);
static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
static Node *doNegate(Node *n, int location);
@@ -253,7 +253,8 @@ static void doNegateFloat(Value *v);
%type <oncommit> OnCommitOption
%type <withoids> OptWithOids
%type <node> for_locking_clause opt_for_locking_clause
%type <node> for_locking_item
%type <list> for_locking_clause opt_for_locking_clause for_locking_items
%type <list> locked_rels_list
%type <boolean> opt_all
@@ -5400,7 +5401,7 @@ select_no_parens:
simple_select { $$ = $1; }
| select_clause sort_clause
{
insertSelectOptions((SelectStmt *) $1, $2, NULL,
insertSelectOptions((SelectStmt *) $1, $2, NIL,
NULL, NULL);
$$ = $1;
}
@@ -5644,12 +5645,27 @@ having_clause:
;
for_locking_clause:
for_locking_items { $$ = $1; }
| FOR READ ONLY { $$ = NIL; }
;
opt_for_locking_clause:
for_locking_clause { $$ = $1; }
| /* EMPTY */ { $$ = NIL; }
;
for_locking_items:
for_locking_item { $$ = list_make1($1); }
| for_locking_items for_locking_item { $$ = lappend($1, $2); }
;
for_locking_item:
FOR UPDATE locked_rels_list opt_nowait
{
LockingClause *n = makeNode(LockingClause);
n->lockedRels = $3;
n->forUpdate = TRUE;
n->nowait = $4;
n->noWait = $4;
$$ = (Node *) n;
}
| FOR SHARE locked_rels_list opt_nowait
@@ -5657,15 +5673,9 @@ for_locking_clause:
LockingClause *n = makeNode(LockingClause);
n->lockedRels = $3;
n->forUpdate = FALSE;
n->nowait = $4;
n->noWait = $4;
$$ = (Node *) n;
}
| FOR READ ONLY { $$ = NULL; }
;
opt_for_locking_clause:
for_locking_clause { $$ = $1; }
| /* EMPTY */ { $$ = NULL; }
;
locked_rels_list:
@@ -8976,7 +8986,7 @@ findLeftmostSelect(SelectStmt *node)
*/
static void
insertSelectOptions(SelectStmt *stmt,
List *sortClause, Node *lockingClause,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount)
{
/*
@@ -8991,14 +9001,8 @@ insertSelectOptions(SelectStmt *stmt,
errmsg("multiple ORDER BY clauses not allowed")));
stmt->sortClause = sortClause;
}
if (lockingClause)
{
if (stmt->lockingClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple FOR UPDATE/FOR SHARE clauses not allowed")));
stmt->lockingClause = (LockingClause *) lockingClause;
}
/* We can handle multiple locking clauses, though */
stmt->lockingClause = list_concat(stmt->lockingClause, lockingClause);
if (limitOffset)
{
if (stmt->limitOffset)

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.122 2006/03/23 00:19:30 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.123 2006/04/30 18:30:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1001,6 +1001,8 @@ addRangeTableEntryForJoin(ParseState *pstate,
/*
* Has the specified refname been selected FOR UPDATE/FOR SHARE?
*
* Note: we pay no attention to whether it's FOR UPDATE vs FOR SHARE.
*/
static bool
isLockedRel(ParseState *pstate, char *refname)
@@ -1008,9 +1010,13 @@ isLockedRel(ParseState *pstate, char *refname)
/* Outer loop to check parent query levels as well as this one */
while (pstate != NULL)
{
if (pstate->p_locking_clause)
ListCell *l;
foreach(l, pstate->p_locking_clause)
{
if (pstate->p_locking_clause->lockedRels == NIL)
LockingClause *lc = (LockingClause *) lfirst(l);
if (lc->lockedRels == NIL)
{
/* all tables used in query */
return true;
@@ -1018,11 +1024,11 @@ isLockedRel(ParseState *pstate, char *refname)
else
{
/* just the named tables */
ListCell *l;
ListCell *l2;
foreach(l, pstate->p_locking_clause->lockedRels)
foreach(l2, lc->lockedRels)
{
char *rname = strVal(lfirst(l));
char *rname = strVal(lfirst(l2));
if (strcmp(refname, rname) == 0)
return true;
@@ -1702,6 +1708,26 @@ get_tle_by_resno(List *tlist, AttrNumber resno)
return NULL;
}
/*
* Given a Query and rangetable index, return relation's RowMarkClause if any
*
* Returns NULL if relation is not selected FOR UPDATE/SHARE
*/
RowMarkClause *
get_rowmark(Query *qry, Index rtindex)
{
ListCell *l;
foreach(l, qry->rowMarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(l);
if (rc->rti == rtindex)
return rc;
}
return NULL;
}
/*
* given relation and att name, return attnum of variable
*

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.80 2006/04/04 19:35:35 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.81 2006/04/30 18:30:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -432,7 +432,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod)
stmt->sortClause != NIL ||
stmt->limitOffset != NULL ||
stmt->limitCount != NULL ||
stmt->lockingClause != NULL ||
stmt->lockingClause != NIL ||
stmt->op != SETOP_NONE)
goto fail;
if (list_length(stmt->targetList) != 1)