mirror of
https://github.com/postgres/postgres.git
synced 2025-10-18 04:29:09 +03:00
In the previous coding, we would look up a relation in RangeVarGetRelid, lock the resulting OID, and then AcceptInvalidationMessages(). While this was sufficient to ensure that we noticed any changes to the relation definition before building the relcache entry, it didn't handle the possibility that the name we looked up no longer referenced the same OID. This was particularly problematic in the case where a table had been dropped and recreated: we'd latch on to the entry for the old relation and fail later on. Now, we acquire the relation lock inside RangeVarGetRelid, and retry the name lookup if we notice that invalidation messages have been processed meanwhile. Many operations that would previously have failed with an error in the presence of concurrent DDL will now succeed. There is a good deal of work remaining to be done here: many callers of RangeVarGetRelid still pass NoLock for one reason or another. In addition, nothing in this patch guards against the possibility that the meaning of an unqualified name might change due to the creation of a relation in a schema earlier in the user's search path than the one where it was previously found. Furthermore, there's nothing at all here to guard against similar race conditions for non-relations. For all that, it's a start. Noah Misch and Robert Haas
149 lines
3.9 KiB
C
149 lines
3.9 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* lockcmds.c
|
|
* LOCK command support code
|
|
*
|
|
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/commands/lockcmds.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_inherits_fn.h"
|
|
#include "commands/lockcmds.h"
|
|
#include "miscadmin.h"
|
|
#include "parser/parse_clause.h"
|
|
#include "storage/lmgr.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/lsyscache.h"
|
|
|
|
static void LockTableRecurse(Relation rel, LOCKMODE lockmode, bool nowait,
|
|
bool recurse);
|
|
|
|
|
|
/*
|
|
* LOCK TABLE
|
|
*/
|
|
void
|
|
LockTableCommand(LockStmt *lockstmt)
|
|
{
|
|
ListCell *p;
|
|
|
|
/*
|
|
* During recovery we only accept these variations: LOCK TABLE foo IN
|
|
* ACCESS SHARE MODE LOCK TABLE foo IN ROW SHARE MODE LOCK TABLE foo
|
|
* IN ROW EXCLUSIVE MODE This test must match the restrictions defined
|
|
* in LockAcquire()
|
|
*/
|
|
if (lockstmt->mode > RowExclusiveLock)
|
|
PreventCommandDuringRecovery("LOCK TABLE");
|
|
|
|
/*
|
|
* Iterate over the list and process the named relations one at a time
|
|
*/
|
|
foreach(p, lockstmt->relations)
|
|
{
|
|
RangeVar *rv = (RangeVar *) lfirst(p);
|
|
Relation rel;
|
|
bool recurse = interpretInhOption(rv->inhOpt);
|
|
Oid reloid;
|
|
|
|
reloid = RangeVarGetRelid(rv, lockstmt->mode, false, lockstmt->nowait);
|
|
rel = relation_open(reloid, NoLock);
|
|
|
|
LockTableRecurse(rel, lockstmt->mode, lockstmt->nowait, recurse);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Apply LOCK TABLE recursively over an inheritance tree
|
|
*/
|
|
static void
|
|
LockTableRecurse(Relation rel, LOCKMODE lockmode, bool nowait, bool recurse)
|
|
{
|
|
AclResult aclresult;
|
|
Oid reloid = RelationGetRelid(rel);
|
|
|
|
/* Verify adequate privilege */
|
|
if (lockmode == AccessShareLock)
|
|
aclresult = pg_class_aclcheck(reloid, GetUserId(),
|
|
ACL_SELECT);
|
|
else
|
|
aclresult = pg_class_aclcheck(reloid, GetUserId(),
|
|
ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_CLASS,
|
|
RelationGetRelationName(rel));
|
|
|
|
/* Currently, we only allow plain tables to be locked */
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a table",
|
|
RelationGetRelationName(rel))));
|
|
|
|
/*
|
|
* If requested, recurse to children. We use find_inheritance_children
|
|
* not find_all_inheritors to avoid taking locks far in advance of
|
|
* checking privileges. This means we'll visit multiply-inheriting
|
|
* children more than once, but that's no problem.
|
|
*/
|
|
if (recurse)
|
|
{
|
|
List *children = find_inheritance_children(reloid, NoLock);
|
|
ListCell *lc;
|
|
Relation childrel;
|
|
|
|
foreach(lc, children)
|
|
{
|
|
Oid childreloid = lfirst_oid(lc);
|
|
|
|
/*
|
|
* Acquire the lock, to protect against concurrent drops. Note
|
|
* that a lock against an already-dropped relation's OID won't
|
|
* fail.
|
|
*/
|
|
if (!nowait)
|
|
LockRelationOid(childreloid, lockmode);
|
|
else if (!ConditionalLockRelationOid(childreloid, lockmode))
|
|
{
|
|
/* try to throw error by name; relation could be deleted... */
|
|
char *relname = get_rel_name(childreloid);
|
|
|
|
if (relname)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
|
|
errmsg("could not obtain lock on relation \"%s\"",
|
|
relname)));
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
|
|
errmsg("could not obtain lock on relation with OID %u",
|
|
reloid)));
|
|
}
|
|
|
|
/*
|
|
* Now that we have the lock, check to see if the relation really
|
|
* exists or not.
|
|
*/
|
|
childrel = try_relation_open(childreloid, NoLock);
|
|
if (!childrel)
|
|
{
|
|
/* Release useless lock */
|
|
UnlockRelationOid(childreloid, lockmode);
|
|
}
|
|
|
|
LockTableRecurse(childrel, lockmode, nowait, recurse);
|
|
}
|
|
}
|
|
|
|
relation_close(rel, NoLock); /* close rel, keep lock */
|
|
}
|