Rethink the locking mechanisms used for CREATE/DROP/RENAME DATABASE.

The former approach used ExclusiveLock on pg_database, which being a
cluster-wide lock meant only one of these operations could proceed at
a time; worse, it also blocked all incoming connections in ReverifyMyDatabase.
Now that we have LockSharedObject(), we can use locks of different types
applied to databases considered as objects.  This allows much more
flexible management of the interlocking: two CREATE DATABASEs need not
block each other, and need not block connections except to the template
database being used.  Similarly DROP DATABASE doesn't block unrelated
operations.  The locking used in flatfiles.c is also much narrower in
scope than before.  Per recent proposal.
This commit is contained in:
Tom Lane 2006-05-04 16:07:29 +00:00
parent cb98e6fb8f
commit 52667d56a3
9 changed files with 454 additions and 408 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/manage-ag.sgml,v 2.45 2006/03/10 19:10:48 momjian Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/manage-ag.sgml,v 2.46 2006/05/04 16:07:28 tgl Exp $ -->
<chapter id="managing-databases">
<title>Managing Databases</title>
@ -166,6 +166,7 @@ CREATE DATABASE <replaceable>dbname</> OWNER <replaceable>rolename</>;
<programlisting>
createdb -O <replaceable>rolename</> <replaceable>dbname</>
</programlisting>
from the shell.
You must be a superuser to be allowed to create a database for
someone else (that is, for a role you are not a member of).
</para>
@ -220,19 +221,15 @@ createdb -T template0 <replaceable>dbname</>
<para>
It is possible to create additional template databases, and indeed
one might copy any database in a cluster by specifying its name
one may copy any database in a cluster by specifying its name
as the template for <command>CREATE DATABASE</>. It is important to
understand, however, that this is not (yet) intended as
a general-purpose <quote><command>COPY DATABASE</command></quote> facility. In particular, it is
essential that the source database be idle (no data-altering transactions
in progress)
for the duration of the copying operation. <command>CREATE DATABASE</>
will check
that no session (other than itself) is connected to
the source database at the start of the operation, but this does not
guarantee that changes cannot be made while the copy proceeds, which
would result in an inconsistent copied database. Therefore,
we recommend that databases used as templates be treated as read-only.
a general-purpose <quote><command>COPY DATABASE</command></quote> facility.
The principal limitation is that no other sessions can be connected to
the source database while it is being copied. <command>CREATE
DATABASE</> will fail if any other connection exists when it starts;
otherwise, new connections to the source database are locked out
until <command>CREATE DATABASE</> completes.
</para>
<para>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_database.sgml,v 1.44 2005/07/31 17:19:17 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_database.sgml,v 1.45 2006/05/04 16:07:29 tgl Exp $
PostgreSQL documentation
-->
@ -45,7 +45,7 @@ CREATE DATABASE <replaceable class="PARAMETER">name</replaceable>
<para>
Normally, the creator becomes the owner of the new database.
Superusers can create databases owned by other users using the
Superusers can create databases owned by other users, by using the
<literal>OWNER</> clause. They can even create databases owned by
users with no special privileges. Non-superusers with <literal>CREATEDB</>
privilege can only create databases owned by themselves.
@ -104,7 +104,8 @@ CREATE DATABASE <replaceable class="PARAMETER">name</replaceable>
Character set encoding to use in the new database. Specify
a string constant (e.g., <literal>'SQL_ASCII'</literal>),
or an integer encoding number, or <literal>DEFAULT</literal>
to use the default encoding. The character sets supported by the
to use the default encoding (namely, the encoding of the
template database). The character sets supported by the
<productname>PostgreSQL</productname> server are described in
<xref linkend="multibyte-charset-supported">.
</para>
@ -169,7 +170,11 @@ CREATE DATABASE <replaceable class="PARAMETER">name</replaceable>
Although it is possible to copy a database other than <literal>template1</>
by specifying its name as the template, this is not (yet) intended as
a general-purpose <quote><command>COPY DATABASE</command></quote> facility.
We recommend that databases used as templates be treated as read-only.
The principal limitation is that no other sessions can be connected to
the template database while it is being copied. <command>CREATE
DATABASE</> will fail if any other connection exists when it starts;
otherwise, new connections to the template database are locked out
until <command>CREATE DATABASE</> completes.
See <xref linkend="manage-ag-templatedbs"> for more information.
</para>
@ -220,6 +225,16 @@ CREATE DATABASE music ENCODING 'LATIN1';
implementation-defined.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-alterdatabase" endterm="sql-alterdatabase-title"></member>
<member><xref linkend="sql-dropdatabase" endterm="sql-dropdatabase-title"></member>
</simplelist>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/drop_database.sgml,v 1.21 2005/11/22 15:24:17 adunstan Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/drop_database.sgml,v 1.22 2006/05/04 16:07:29 tgl Exp $
PostgreSQL documentation
-->
@ -86,7 +86,7 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
<title>Compatibility</title>
<para>
The is no <command>DROP DATABASE</command> statement in the SQL standard.
There is no <command>DROP DATABASE</command> statement in the SQL standard.
</para>
</refsect1>

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.8 2006/03/05 15:58:23 momjian Exp $
* $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.9 2006/05/04 16:07:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -37,7 +37,6 @@
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/syscache.h"
@ -911,12 +910,6 @@ shdepLockAndCheckObject(Oid classId, Oid objectId)
/* AccessShareLock should be OK, since we are not modifying the object */
LockSharedObject(classId, objectId, 0, AccessShareLock);
/*
* We have to recognize sinval updates here, else our local syscache may
* still contain the object even if it was just dropped.
*/
AcceptInvalidationMessages();
switch (classId)
{
case AuthIdRelationId:

View File

@ -3,19 +3,17 @@
* dbcommands.c
* Database management commands (create/drop database).
*
* Note: database creation/destruction commands take ExclusiveLock on
* pg_database to ensure that no two proceed in parallel. We must use
* at least this level of locking to ensure that no two backends try to
* write the flat-file copy of pg_database at once. We avoid using
* AccessExclusiveLock since there's no need to lock out ordinary readers
* of pg_database.
* Note: database creation/destruction commands use exclusive locks on
* the database objects (as expressed by LockSharedObject()) to avoid
* stepping on each others' toes. Formerly we used table-level locks
* on pg_database, but that's too coarse-grained.
*
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.180 2006/05/03 22:45:26 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.181 2006/05/04 16:07:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,7 +51,8 @@
/* non-export function prototypes */
static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
static bool get_db_info(const char *name, LOCKMODE lockmode,
Oid *dbIdP, Oid *ownerIdP,
int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
Oid *dbLastSysOidP,
TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
@ -80,13 +79,12 @@ createdb(const CreatedbStmt *stmt)
TransactionId src_frozenxid;
Oid src_deftablespace;
volatile Oid dst_deftablespace;
volatile Relation pg_database_rel;
Relation pg_database_rel;
HeapTuple tuple;
TupleDesc pg_database_dsc;
Datum new_record[Natts_pg_database];
char new_record_nulls[Natts_pg_database];
Oid dboid;
volatile Oid datdba;
Oid datdba;
ListCell *option;
DefElem *dtablespacename = NULL;
DefElem *downer = NULL;
@ -96,8 +94,8 @@ createdb(const CreatedbStmt *stmt)
char *dbname = stmt->dbname;
char *dbowner = NULL;
const char *dbtemplate = NULL;
volatile int encoding = -1;
volatile int dbconnlimit = -1;
int encoding = -1;
int dbconnlimit = -1;
/* don't call this in a transaction block */
PreventTransactionChain((void *) stmt, "CREATE DATABASE");
@ -216,31 +214,25 @@ createdb(const CreatedbStmt *stmt)
check_is_member_of_role(GetUserId(), datdba);
/*
* Check for db name conflict. There is a race condition here, since
* another backend could create the same DB name before we commit.
* However, holding an exclusive lock on pg_database for the whole time we
* are copying the source database doesn't seem like a good idea, so
* accept possibility of race to create. We will check again after we
* grab the exclusive lock.
*/
if (get_db_info(dbname, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_DATABASE),
errmsg("database \"%s\" already exists", dbname)));
/*
* Lookup database (template) to be cloned.
* Lookup database (template) to be cloned, and obtain share lock on it.
* ShareLock allows two CREATE DATABASEs to work from the same template
* concurrently, while ensuring no one is busy dropping it in parallel
* (which would be Very Bad since we'd likely get an incomplete copy
* without knowing it). This also prevents any new connections from being
* made to the source until we finish copying it, so we can be sure it
* won't change underneath us.
*/
if (!dbtemplate)
dbtemplate = "template1"; /* Default template database name */
if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
if (!get_db_info(dbtemplate, ShareLock,
&src_dboid, &src_owner, &src_encoding,
&src_istemplate, &src_allowconn, &src_lastsysoid,
&src_vacuumxid, &src_frozenxid, &src_deftablespace))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("template database \"%s\" does not exist", dbtemplate)));
errmsg("template database \"%s\" does not exist",
dbtemplate)));
/*
* Permission check: to copy a DB that's not marked datistemplate, you
@ -258,8 +250,7 @@ createdb(const CreatedbStmt *stmt)
/*
* The source DB can't have any active backends, except this one
* (exception is to allow CREATE DB while connected to template1).
* Otherwise we might copy inconsistent data. This check is not
* bulletproof, since someone might connect while we are copying...
* Otherwise we might copy inconsistent data.
*/
if (DatabaseHasActiveBackends(src_dboid, true))
ereport(ERROR,
@ -346,14 +337,65 @@ createdb(const CreatedbStmt *stmt)
src_vacuumxid = src_frozenxid = GetCurrentTransactionId();
/*
* Preassign OID for pg_database tuple, so that we can compute db path. We
* have to open pg_database to do this, but we don't want to take
* ExclusiveLock yet, so just do it and close again.
* Check for db name conflict. This is just to give a more friendly
* error message than "unique index violation". There's a race condition
* but we're willing to accept the less friendly message in that case.
*/
pg_database_rel = heap_open(DatabaseRelationId, AccessShareLock);
dboid = GetNewOid(pg_database_rel);
heap_close(pg_database_rel, AccessShareLock);
pg_database_rel = NULL;
if (OidIsValid(get_database_oid(dbname)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_DATABASE),
errmsg("database \"%s\" already exists", dbname)));
/*
* Insert a new tuple into pg_database. This establishes our ownership
* of the new database name (anyone else trying to insert the same name
* will block on the unique index, and fail after we commit). It also
* assigns the OID that the new database will have.
*/
pg_database_rel = heap_open(DatabaseRelationId, RowExclusiveLock);
/* Form tuple */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
new_record[Anum_pg_database_datname - 1] =
DirectFunctionCall1(namein, CStringGetDatum(dbname));
new_record[Anum_pg_database_datdba - 1] = ObjectIdGetDatum(datdba);
new_record[Anum_pg_database_encoding - 1] = Int32GetDatum(encoding);
new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(false);
new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(true);
new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit);
new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid);
new_record[Anum_pg_database_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid);
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace);
/*
* We deliberately set datconfig and datacl to defaults (NULL), rather
* than copying them from the template database. Copying datacl would
* be a bad idea when the owner is not the same as the template's
* owner. It's more debatable whether datconfig should be copied.
*/
new_record_nulls[Anum_pg_database_datconfig - 1] = 'n';
new_record_nulls[Anum_pg_database_datacl - 1] = 'n';
tuple = heap_formtuple(RelationGetDescr(pg_database_rel),
new_record, new_record_nulls);
dboid = simple_heap_insert(pg_database_rel, tuple);
/* Update indexes */
CatalogUpdateIndexes(pg_database_rel, tuple);
/*
* Now generate additional catalog entries associated with the new DB
*/
/* Register owner dependency */
recordDependencyOnOwner(DatabaseRelationId, dboid, datdba);
/* Create pg_shdepend entries for objects within database */
copyTemplateDependencies(src_dboid, dboid);
/*
* Force dirty buffers out to disk, to ensure source database is
@ -434,64 +476,6 @@ createdb(const CreatedbStmt *stmt)
heap_endscan(scan);
heap_close(rel, AccessShareLock);
/*
* Now OK to grab exclusive lock on pg_database.
*/
pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);
/* Check to see if someone else created same DB name meanwhile. */
if (get_db_info(dbname, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_DATABASE),
errmsg("database \"%s\" already exists", dbname)));
/*
* Insert a new tuple into pg_database
*/
pg_database_dsc = RelationGetDescr(pg_database_rel);
/* Form tuple */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
new_record[Anum_pg_database_datname - 1] =
DirectFunctionCall1(namein, CStringGetDatum(dbname));
new_record[Anum_pg_database_datdba - 1] = ObjectIdGetDatum(datdba);
new_record[Anum_pg_database_encoding - 1] = Int32GetDatum(encoding);
new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(false);
new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(true);
new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit);
new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid);
new_record[Anum_pg_database_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid);
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace);
/*
* We deliberately set datconfig and datacl to defaults (NULL), rather
* than copying them from the template database. Copying datacl would
* be a bad idea when the owner is not the same as the template's
* owner. It's more debatable whether datconfig should be copied.
*/
new_record_nulls[Anum_pg_database_datconfig - 1] = 'n';
new_record_nulls[Anum_pg_database_datacl - 1] = 'n';
tuple = heap_formtuple(pg_database_dsc, new_record, new_record_nulls);
HeapTupleSetOid(tuple, dboid); /* override heap_insert's OID
* selection */
simple_heap_insert(pg_database_rel, tuple);
/* Update indexes */
CatalogUpdateIndexes(pg_database_rel, tuple);
/* Register owner dependency */
recordDependencyOnOwner(DatabaseRelationId, dboid, datdba);
/* Create pg_shdepend entries for objects within database */
copyTemplateDependencies(src_dboid, dboid);
/*
* We force a checkpoint before committing. This effectively means
* that committed XLOG_DBASE_CREATE operations will never need to be
@ -523,6 +507,12 @@ createdb(const CreatedbStmt *stmt)
*/
RequestCheckpoint(true, false);
/*
* Close pg_database, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_database_rel, NoLock);
/*
* Set flag to update flat database file at commit.
*/
@ -530,9 +520,9 @@ createdb(const CreatedbStmt *stmt)
}
PG_CATCH();
{
/* Don't hold pg_database lock while doing recursive remove */
if (pg_database_rel != NULL)
heap_close(pg_database_rel, ExclusiveLock);
/* Release lock on source database before doing recursive remove */
UnlockSharedObject(DatabaseRelationId, src_dboid, 0,
ShareLock);
/* Throw away any successfully copied subdirectories */
remove_dbtablespaces(dboid);
@ -540,10 +530,6 @@ createdb(const CreatedbStmt *stmt)
PG_RE_THROW();
}
PG_END_TRY();
/* Close pg_database, but keep exclusive lock till commit */
/* This has to be outside the PG_TRY */
heap_close(pg_database_rel, NoLock);
}
@ -568,20 +554,15 @@ dropdb(const char *dbname, bool missing_ok)
errmsg("cannot drop the currently open database")));
/*
* Obtain exclusive lock on pg_database. We need this to ensure that no
* new backend starts up in the target database while we are deleting it.
* (Actually, a new backend might still manage to start up, because it
* isn't able to lock pg_database while starting. But it will detect its
* error in ReverifyMyDatabase and shut down before any serious damage is
* done. See postinit.c.)
*
* An ExclusiveLock, rather than AccessExclusiveLock, is sufficient since
* ReverifyMyDatabase takes RowShareLock. This allows ordinary readers of
* pg_database to proceed in parallel.
* Look up the target database's OID, and get exclusive lock on it.
* We need this to ensure that no new backend starts up in the target
* database while we are deleting it (see postinit.c), and that no one is
* using it as a CREATE DATABASE template or trying to delete it for
* themselves.
*/
pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);
pgdbrel = heap_open(DatabaseRelationId, RowExclusiveLock);
if (!get_db_info(dbname, &db_id, NULL, NULL,
if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
&db_istemplate, NULL, NULL, NULL, NULL, NULL))
{
if (!missing_ok)
@ -592,17 +573,18 @@ dropdb(const char *dbname, bool missing_ok)
}
else
{
/* Close pg_database, release the lock, since we changed nothing */
heap_close(pgdbrel, ExclusiveLock);
heap_close(pgdbrel, RowExclusiveLock);
ereport(NOTICE,
(errmsg("database \"%s\" does not exist, skipping",
dbname)));
return;
}
}
/*
* Permission checks
*/
if (!pg_database_ownercheck(db_id, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
dbname);
@ -618,7 +600,8 @@ dropdb(const char *dbname, bool missing_ok)
errmsg("cannot drop a template database")));
/*
* Check for active backends in the target database.
* Check for active backends in the target database. (Because we hold
* the database lock, no new ones can start after this.)
*/
if (DatabaseHasActiveBackends(db_id, false))
ereport(ERROR,
@ -640,8 +623,7 @@ dropdb(const char *dbname, bool missing_ok)
ReleaseSysCache(tup);
/*
* Delete any comments associated with the database
*
* Delete any comments associated with the database.
*/
DeleteSharedComments(db_id, DatabaseRelationId);
@ -675,7 +657,10 @@ dropdb(const char *dbname, bool missing_ok)
*/
remove_dbtablespaces(db_id);
/* Close pg_database, but keep exclusive lock till commit */
/*
* Close pg_database, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pgdbrel, NoLock);
/*
@ -691,29 +676,18 @@ dropdb(const char *dbname, bool missing_ok)
void
RenameDatabase(const char *oldname, const char *newname)
{
HeapTuple tup,
newtup;
Oid db_id;
HeapTuple newtup;
Relation rel;
SysScanDesc scan,
scan2;
ScanKeyData key,
key2;
/*
* Obtain ExclusiveLock so that no new session gets started while the
* rename is in progress.
* Look up the target database's OID, and get exclusive lock on it.
* We need this for the same reasons as DROP DATABASE.
*/
rel = heap_open(DatabaseRelationId, ExclusiveLock);
rel = heap_open(DatabaseRelationId, RowExclusiveLock);
ScanKeyInit(&key,
Anum_pg_database_datname,
BTEqualStrategyNumber, F_NAMEEQ,
NameGetDatum(oldname));
scan = systable_beginscan(rel, DatabaseNameIndexId, true,
SnapshotNow, 1, &key);
tup = systable_getnext(scan);
if (!HeapTupleIsValid(tup))
if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" does not exist", oldname)));
@ -724,36 +698,29 @@ RenameDatabase(const char *oldname, const char *newname)
* be an actual problem besides a little confusion, so think about this
* and decide.
*/
if (HeapTupleGetOid(tup) == MyDatabaseId)
if (db_id == MyDatabaseId)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("current database may not be renamed")));
/*
* Make sure the database does not have active sessions. Might not be
* necessary, but it's consistent with other database operations.
* Make sure the database does not have active sessions. This is the
* same concern as above, but applied to other sessions.
*/
if (DatabaseHasActiveBackends(HeapTupleGetOid(tup), false))
if (DatabaseHasActiveBackends(db_id, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
oldname)));
/* make sure the new name doesn't exist */
ScanKeyInit(&key2,
Anum_pg_database_datname,
BTEqualStrategyNumber, F_NAMEEQ,
NameGetDatum(newname));
scan2 = systable_beginscan(rel, DatabaseNameIndexId, true,
SnapshotNow, 1, &key2);
if (HeapTupleIsValid(systable_getnext(scan2)))
if (OidIsValid(get_database_oid(newname)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_DATABASE),
errmsg("database \"%s\" already exists", newname)));
systable_endscan(scan2);
/* must be owner */
if (!pg_database_ownercheck(HeapTupleGetOid(tup), GetUserId()))
if (!pg_database_ownercheck(db_id, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
oldname);
@ -764,14 +731,19 @@ RenameDatabase(const char *oldname, const char *newname)
errmsg("permission denied to rename database")));
/* rename */
newtup = heap_copytuple(tup);
newtup = SearchSysCacheCopy(DATABASEOID,
ObjectIdGetDatum(db_id),
0, 0, 0);
if (!HeapTupleIsValid(newtup))
elog(ERROR, "cache lookup failed for database %u", db_id);
namestrcpy(&(((Form_pg_database) GETSTRUCT(newtup))->datname), newname);
simple_heap_update(rel, &newtup->t_self, newtup);
CatalogUpdateIndexes(rel, newtup);
systable_endscan(scan);
/* Close pg_database, but keep exclusive lock till commit */
/*
* Close pg_database, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(rel, NoLock);
/*
@ -821,7 +793,9 @@ AlterDatabase(AlterDatabaseStmt *stmt)
connlimit = intVal(dconnlimit->arg);
/*
* We don't need ExclusiveLock since we aren't updating the flat file.
* Get the old tuple. We don't need a lock on the database per se,
* because we're not going to do anything that would mess up incoming
* connections.
*/
rel = heap_open(DatabaseRelationId, RowExclusiveLock);
ScanKeyInit(&scankey,
@ -891,7 +865,9 @@ AlterDatabaseSet(AlterDatabaseSetStmt *stmt)
valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
/*
* We don't need ExclusiveLock since we aren't updating the flat file.
* Get the old tuple. We don't need a lock on the database per se,
* because we're not going to do anything that would mess up incoming
* connections.
*/
rel = heap_open(DatabaseRelationId, RowExclusiveLock);
ScanKeyInit(&scankey,
@ -974,7 +950,9 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
Form_pg_database datForm;
/*
* We don't need ExclusiveLock since we aren't updating the flat file.
* Get the old tuple. We don't need a lock on the database per se,
* because we're not going to do anything that would mess up incoming
* connections.
*/
rel = heap_open(DatabaseRelationId, RowExclusiveLock);
ScanKeyInit(&scankey,
@ -1077,72 +1055,127 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
* Helper functions
*/
/*
* Look up info about the database named "name". If the database exists,
* obtain the specified lock type on it, fill in any of the remaining
* parameters that aren't NULL, and return TRUE. If no such database,
* return FALSE.
*/
static bool
get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
get_db_info(const char *name, LOCKMODE lockmode,
Oid *dbIdP, Oid *ownerIdP,
int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
Oid *dbLastSysOidP,
TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
Oid *dbTablespace)
{
bool result = false;
Relation relation;
ScanKeyData scanKey;
SysScanDesc scan;
HeapTuple tuple;
bool gottuple;
AssertArg(name);
/* Caller may wish to grab a better lock on pg_database beforehand... */
relation = heap_open(DatabaseRelationId, AccessShareLock);
ScanKeyInit(&scanKey,
Anum_pg_database_datname,
BTEqualStrategyNumber, F_NAMEEQ,
NameGetDatum(name));
scan = systable_beginscan(relation, DatabaseNameIndexId, true,
SnapshotNow, 1, &scanKey);
tuple = systable_getnext(scan);
gottuple = HeapTupleIsValid(tuple);
if (gottuple)
/*
* Loop covers the rare case where the database is renamed before we
* can lock it. We try again just in case we can find a new one of
* the same name.
*/
for (;;)
{
Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
ScanKeyData scanKey;
SysScanDesc scan;
HeapTuple tuple;
Oid dbOid;
/* oid of the database */
if (dbIdP)
*dbIdP = HeapTupleGetOid(tuple);
/* oid of the owner */
if (ownerIdP)
*ownerIdP = dbform->datdba;
/* character encoding */
if (encodingP)
*encodingP = dbform->encoding;
/* allowed as template? */
if (dbIsTemplateP)
*dbIsTemplateP = dbform->datistemplate;
/* allowing connections? */
if (dbAllowConnP)
*dbAllowConnP = dbform->datallowconn;
/* last system OID used in database */
if (dbLastSysOidP)
*dbLastSysOidP = dbform->datlastsysoid;
/* limit of vacuumed XIDs */
if (dbVacuumXidP)
*dbVacuumXidP = dbform->datvacuumxid;
/* limit of frozen XIDs */
if (dbFrozenXidP)
*dbFrozenXidP = dbform->datfrozenxid;
/* default tablespace for this database */
if (dbTablespace)
*dbTablespace = dbform->dattablespace;
/*
* there's no syscache for database-indexed-by-name,
* so must do it the hard way
*/
ScanKeyInit(&scanKey,
Anum_pg_database_datname,
BTEqualStrategyNumber, F_NAMEEQ,
NameGetDatum(name));
scan = systable_beginscan(relation, DatabaseNameIndexId, true,
SnapshotNow, 1, &scanKey);
tuple = systable_getnext(scan);
if (!HeapTupleIsValid(tuple))
{
/* definitely no database of that name */
systable_endscan(scan);
break;
}
dbOid = HeapTupleGetOid(tuple);
systable_endscan(scan);
/*
* Now that we have a database OID, we can try to lock the DB.
*/
if (lockmode != NoLock)
LockSharedObject(DatabaseRelationId, dbOid, 0, lockmode);
/*
* And now, re-fetch the tuple by OID. If it's still there and
* still the same name, we win; else, drop the lock and loop
* back to try again.
*/
tuple = SearchSysCache(DATABASEOID,
ObjectIdGetDatum(dbOid),
0, 0, 0);
if (HeapTupleIsValid(tuple))
{
Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
if (strcmp(name, NameStr(dbform->datname)) == 0)
{
/* oid of the database */
if (dbIdP)
*dbIdP = dbOid;
/* oid of the owner */
if (ownerIdP)
*ownerIdP = dbform->datdba;
/* character encoding */
if (encodingP)
*encodingP = dbform->encoding;
/* allowed as template? */
if (dbIsTemplateP)
*dbIsTemplateP = dbform->datistemplate;
/* allowing connections? */
if (dbAllowConnP)
*dbAllowConnP = dbform->datallowconn;
/* last system OID used in database */
if (dbLastSysOidP)
*dbLastSysOidP = dbform->datlastsysoid;
/* limit of vacuumed XIDs */
if (dbVacuumXidP)
*dbVacuumXidP = dbform->datvacuumxid;
/* limit of frozen XIDs */
if (dbFrozenXidP)
*dbFrozenXidP = dbform->datfrozenxid;
/* default tablespace for this database */
if (dbTablespace)
*dbTablespace = dbform->dattablespace;
ReleaseSysCache(tuple);
result = true;
break;
}
/* can only get here if it was just renamed */
ReleaseSysCache(tuple);
}
if (lockmode != NoLock)
UnlockSharedObject(DatabaseRelationId, dbOid, 0, lockmode);
}
systable_endscan(scan);
heap_close(relation, AccessShareLock);
return gottuple;
return result;
}
/* Check if current user has createdb privileges */
@ -1234,8 +1267,6 @@ remove_dbtablespaces(Oid db_id)
* get_database_oid - given a database name, look up the OID
*
* Returns InvalidOid if database name not found.
*
* This is not actually used in this file, but is exported for use elsewhere.
*/
Oid
get_database_oid(const char *dbname)
@ -1277,8 +1308,6 @@ get_database_oid(const char *dbname)
* get_database_name - given a database OID, look up the name
*
* Returns a palloc'd string, or NULL if no such database.
*
* This is not actually used in this file, but is exported for use elsewhere.
*/
char *
get_database_name(Oid dbid)

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/commands/user.c,v 1.170 2006/03/05 15:58:25 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.171 2006/05/04 16:07:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -274,10 +274,9 @@ CreateRole(CreateRoleStmt *stmt)
/*
* Check the pg_authid relation to be certain the role doesn't already
* exist. Note we secure exclusive lock because we need to protect our
* eventual update of the flat auth file.
* exist.
*/
pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
tuple = SearchSysCache(AUTHNAME,
@ -377,8 +376,8 @@ CreateRole(CreateRoleStmt *stmt)
GetUserId(), false);
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
* Close pg_authid, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authid_rel, NoLock);
@ -538,10 +537,9 @@ AlterRole(AlterRoleStmt *stmt)
validUntil = strVal(dvalidUntil->arg);
/*
* Scan the pg_authid relation to be certain the user exists. Note we
* secure exclusive lock to protect our update of the flat auth file.
* Scan the pg_authid relation to be certain the user exists.
*/
pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
tuple = SearchSysCache(AUTHNAME,
@ -697,8 +695,8 @@ AlterRole(AlterRoleStmt *stmt)
false);
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
* Close pg_authid, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authid_rel, NoLock);
@ -726,10 +724,6 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
/*
* RowExclusiveLock is sufficient, because we don't need to update the
* flat auth file.
*/
rel = heap_open(AuthIdRelationId, RowExclusiveLock);
oldtuple = SearchSysCache(AUTHNAME,
PointerGetDatum(stmt->role),
@ -799,6 +793,7 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
CatalogUpdateIndexes(rel, newtuple);
ReleaseSysCache(oldtuple);
/* needn't keep lock since we won't be updating the flat file */
heap_close(rel, RowExclusiveLock);
}
@ -820,11 +815,9 @@ DropRole(DropRoleStmt *stmt)
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
* deleted. Note we secure exclusive lock on pg_authid, because we need
* to protect our update of the flat auth file. A regular writer's lock
* on pg_auth_members is sufficient though.
* deleted.
*/
pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
foreach(item, stmt->roles)
@ -960,7 +953,7 @@ DropRole(DropRoleStmt *stmt)
/*
* Now we can clean up; but keep locks until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
* deadlock failure while updating flat file)
*/
heap_close(pg_auth_members_rel, NoLock);
heap_close(pg_authid_rel, NoLock);
@ -989,8 +982,7 @@ RenameRole(const char *oldname, const char *newname)
int i;
Oid roleid;
/* ExclusiveLock because we need to update the flat auth file */
rel = heap_open(AuthIdRelationId, ExclusiveLock);
rel = heap_open(AuthIdRelationId, RowExclusiveLock);
dsc = RelationGetDescr(rel);
oldtuple = SearchSysCache(AUTHNAME,
@ -1080,6 +1072,11 @@ RenameRole(const char *oldname, const char *newname)
CatalogUpdateIndexes(rel, newtuple);
ReleaseSysCache(oldtuple);
/*
* Close pg_authid, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(rel, NoLock);
/*
@ -1108,11 +1105,8 @@ GrantRole(GrantRoleStmt *stmt)
grantee_ids = roleNamesToIds(stmt->grantee_roles);
/*
* Even though this operation doesn't change pg_authid, we must secure
* exclusive lock on it to protect our update of the flat auth file.
*/
pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
/* AccessShareLock is enough since we aren't modifying pg_authid */
pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
/*
* Step through all of the granted roles and add/remove entries for the
@ -1136,6 +1130,10 @@ GrantRole(GrantRoleStmt *stmt)
stmt->admin_opt);
}
/*
* Close pg_authid, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authid_rel, NoLock);
/*
@ -1237,8 +1235,7 @@ roleNamesToIds(List *memberNames)
* grantorId: who is granting the membership
* admin_opt: granting admin option?
*
* Note: caller is responsible for holding ExclusiveLock on pg_authid,
* and for calling auth_file_update_needed().
* Note: caller is responsible for calling auth_file_update_needed().
*/
static void
AddRoleMems(const char *rolename, Oid roleid,
@ -1283,7 +1280,6 @@ AddRoleMems(const char *rolename, Oid roleid,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to set grantor")));
/* We need only regular writer's lock on pg_auth_members */
pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
@ -1363,8 +1359,8 @@ AddRoleMems(const char *rolename, Oid roleid,
}
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
* Close pg_authmem, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authmem_rel, NoLock);
}
@ -1378,8 +1374,7 @@ AddRoleMems(const char *rolename, Oid roleid,
* memberIds: OIDs of roles to del
* admin_opt: remove admin option only?
*
* Note: caller is responsible for holding ExclusiveLock on pg_authid,
* and for calling auth_file_update_needed().
* Note: caller is responsible for calling auth_file_update_needed().
*/
static void
DelRoleMems(const char *rolename, Oid roleid,
@ -1418,7 +1413,6 @@ DelRoleMems(const char *rolename, Oid roleid,
rolename)));
}
/* We need only regular writer's lock on pg_auth_members */
pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
@ -1478,8 +1472,8 @@ DelRoleMems(const char *rolename, Oid roleid,
}
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
* Close pg_authmem, but keep lock till commit (this is important
* to prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authmem_rel, NoLock);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.82 2006/03/05 15:58:38 momjian Exp $
* $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.83 2006/05/04 16:07:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -478,6 +478,9 @@ LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
objsubid);
(void) LockAcquire(&tag, false, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
}
/*

View File

@ -23,7 +23,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.17 2006/03/05 15:58:46 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.18 2006/05/04 16:07:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -768,25 +768,49 @@ AtEOXact_UpdateFlatFiles(bool isCommit)
CommandCounterIncrement();
/*
* We use ExclusiveLock to ensure that only one backend writes the flat
* file(s) at a time. That's sufficient because it's okay to allow plain
* reads of the tables in parallel. There is some chance of a deadlock
* here (if we were triggered by a user update of one of the tables, which
* likely won't have gotten a strong enough lock), so get the locks we
* need before writing anything.
* Open and lock the needed catalog(s).
*
* For writing the auth file, it's sufficient to ExclusiveLock pg_authid;
* we take just regular AccessShareLock on pg_auth_members.
* Even though we only need AccessShareLock, this could theoretically fail
* due to deadlock. In practice, however, our transaction already holds
* RowExclusiveLock or better (it couldn't have updated the catalog
* without such a lock). This implies that dbcommands.c and other places
* that force flat-file updates must not follow the common practice of
* dropping catalog locks before commit.
*/
if (database_file_update_subid != InvalidSubTransactionId)
drel = heap_open(DatabaseRelationId, ExclusiveLock);
drel = heap_open(DatabaseRelationId, AccessShareLock);
if (auth_file_update_subid != InvalidSubTransactionId)
{
arel = heap_open(AuthIdRelationId, ExclusiveLock);
arel = heap_open(AuthIdRelationId, AccessShareLock);
mrel = heap_open(AuthMemRelationId, AccessShareLock);
}
/*
* Obtain special locks to ensure that two transactions don't try to write
* the same flat file concurrently. Quite aside from any direct risks of
* corrupted output, the winning writer probably wouldn't have seen the
* other writer's updates. By taking a lock and holding it till commit,
* we ensure that whichever updater goes second will see the other
* updater's changes as committed, and thus the final state of the file
* will include all updates.
*
* We use a lock on "database 0" to protect writing the pg_database flat
* file, and a lock on "role 0" to protect the auth file. This is a bit
* ugly but it's not worth inventing any more-general convention. (Any
* two locktags that are never used for anything else would do.)
*
* This is safe against deadlock as long as these are the very last locks
* acquired during the transaction.
*/
if (database_file_update_subid != InvalidSubTransactionId)
LockSharedObject(DatabaseRelationId, InvalidOid, 0,
AccessExclusiveLock);
if (auth_file_update_subid != InvalidSubTransactionId)
LockSharedObject(AuthIdRelationId, InvalidOid, 0,
AccessExclusiveLock);
/* Okay to write the files */
if (database_file_update_subid != InvalidSubTransactionId)
{

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.165 2006/05/03 22:45:26 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.166 2006/05/04 16:07:29 tgl Exp $
*
*
*-------------------------------------------------------------------------
@ -16,14 +16,10 @@
#include "postgres.h"
#include <fcntl.h>
#include <sys/file.h>
#include <math.h>
#include <unistd.h>
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/catalog.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
@ -42,7 +38,6 @@
#include "storage/smgr.h"
#include "utils/acl.h"
#include "utils/flatfiles.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/portal.h"
#include "utils/relcache.h"
@ -51,7 +46,7 @@
static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace);
static void ReverifyMyDatabase(const char *name, bool am_superuser);
static void CheckMyDatabase(const char *name, bool am_superuser);
static void InitCommunication(void);
static void ShutdownPostgres(int code, Datum arg);
static bool ThereIsAtLeastOneRole(void);
@ -72,7 +67,7 @@ static bool ThereIsAtLeastOneRole(void);
* file" copy of pg_database that is helpfully maintained by flatfiles.c.
* This is subject to various race conditions, so after we have the
* transaction infrastructure started, we have to recheck the information;
* see ReverifyMyDatabase.
* see InitPostgres.
*/
static bool
FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace)
@ -108,76 +103,35 @@ FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace)
}
/*
* ReverifyMyDatabase -- recheck info obtained by FindMyDatabase
*
* Since FindMyDatabase cannot lock pg_database, the information it read
* could be stale; for example we might have attached to a database that's in
* process of being destroyed by dropdb(). This routine is called after
* we have all the locking and other infrastructure running --- now we can
* check that we are really attached to a valid database.
*
* In reality, if dropdb() is running in parallel with our startup,
* it's pretty likely that we will have failed before now, due to being
* unable to read some of the system tables within the doomed database.
* This routine just exists to make *sure* we have not started up in an
* invalid database. If we quit now, we should have managed to avoid
* creating any serious problems.
*
* This is also a handy place to fetch the database encoding info out
* of pg_database.
*
* To avoid having to read pg_database more times than necessary
* during session startup, this place is also fitting to check CONNECT
* privilege and set up any database-specific configuration variables.
* CheckMyDatabase -- fetch information from the pg_database entry for our DB
*/
static void
ReverifyMyDatabase(const char *name, bool am_superuser)
CheckMyDatabase(const char *name, bool am_superuser)
{
Relation pgdbrel;
SysScanDesc pgdbscan;
ScanKeyData key;
HeapTuple tup;
Form_pg_database dbform;
/*
* Because we grab RowShareLock here, we can be sure that dropdb() is not
* running in parallel with us (any more).
*/
pgdbrel = heap_open(DatabaseRelationId, RowShareLock);
ScanKeyInit(&key,
Anum_pg_database_datname,
BTEqualStrategyNumber, F_NAMEEQ,
NameGetDatum(name));
pgdbscan = systable_beginscan(pgdbrel, DatabaseNameIndexId, true,
SnapshotNow, 1, &key);
tup = systable_getnext(pgdbscan);
if (!HeapTupleIsValid(tup) ||
HeapTupleGetOid(tup) != MyDatabaseId)
{
/* OOPS */
heap_close(pgdbrel, RowShareLock);
/*
* The only real problem I could have created is to load dirty buffers
* for the dead database into shared buffer cache; if I did, some
* other backend will eventually try to write them and die in
* mdblindwrt. Flush any such pages to forestall trouble.
*/
DropDatabaseBuffers(MyDatabaseId);
/* Now I can commit hara-kiri with a clear conscience... */
ereport(FATAL,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\", OID %u, has disappeared from pg_database",
name, MyDatabaseId)));
}
/* Fetch our real pg_database row */
tup = SearchSysCache(DATABASEOID,
ObjectIdGetDatum(MyDatabaseId),
0, 0, 0);
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
dbform = (Form_pg_database) GETSTRUCT(tup);
/* This recheck is strictly paranoia */
if (strcmp(name, NameStr(dbform->datname)) != 0)
ereport(FATAL,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" has disappeared from pg_database",
name),
errdetail("Database OID %u now seems to belong to \"%s\".",
MyDatabaseId, NameStr(dbform->datname))));
/*
* These next checks are not enforced when in standalone mode, so that
* Check permissions to connect to the database.
*
* These checks are not enforced when in standalone mode, so that
* there is a way to recover from disabling all access to all databases,
* for example "UPDATE pg_database SET datallowconn = false;".
*
@ -246,8 +200,8 @@ ReverifyMyDatabase(const char *name, bool am_superuser)
Datum datum;
bool isnull;
datum = heap_getattr(tup, Anum_pg_database_datconfig,
RelationGetDescr(pgdbrel), &isnull);
datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_datconfig,
&isnull);
if (!isnull)
{
ArrayType *a = DatumGetArrayTypeP(datum);
@ -256,8 +210,7 @@ ReverifyMyDatabase(const char *name, bool am_superuser)
}
}
systable_endscan(pgdbscan);
heap_close(pgdbrel, RowShareLock);
ReleaseSysCache(tup);
}
@ -337,9 +290,11 @@ InitPostgres(const char *dbname, const char *username)
bool bootstrap = IsBootstrapProcessingMode();
bool autovacuum = IsAutoVacuumProcess();
bool am_superuser;
char *fullpath;
/*
* Set up the global variables holding database id and path.
* Set up the global variables holding database id and path. But note
* we won't actually try to touch the database just yet.
*
* We take a shortcut in the bootstrap case, otherwise we have to look up
* the db name in pg_database.
@ -348,55 +303,24 @@ InitPostgres(const char *dbname, const char *username)
{
MyDatabaseId = TemplateDbOid;
MyDatabaseTableSpace = DEFAULTTABLESPACE_OID;
SetDatabasePath(GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace));
}
else
{
char *fullpath;
/*
* Formerly we validated DataDir here, but now that's done earlier.
*/
/*
* Find oid and tablespace of the database we're about to open. Since
* we're not yet up and running we have to use the hackish
* FindMyDatabase.
* FindMyDatabase, which looks in the flat-file copy of pg_database.
*/
if (!FindMyDatabase(dbname, &MyDatabaseId, &MyDatabaseTableSpace))
ereport(FATAL,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" does not exist",
dbname)));
fullpath = GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace);
/* Verify the database path */
if (access(fullpath, F_OK) == -1)
{
if (errno == ENOENT)
ereport(FATAL,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" does not exist",
dbname),
errdetail("The database subdirectory \"%s\" is missing.",
fullpath)));
else
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not access directory \"%s\": %m",
fullpath)));
}
ValidatePgVersion(fullpath);
SetDatabasePath(fullpath);
}
/*
* Code after this point assumes we are in the proper directory!
*/
fullpath = GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace);
SetDatabasePath(fullpath);
/*
* Finish filling in the PGPROC struct, and add it to the ProcArray.
@ -459,10 +383,78 @@ InitPostgres(const char *dbname, const char *username)
*/
on_shmem_exit(ShutdownPostgres, 0);
/* start a new transaction here before access to db */
/*
* Start a new transaction here before first access to db
*/
if (!bootstrap)
StartTransactionCommand();
/*
* Now that we have a transaction, we can take locks. Take a writer's
* lock on the database we are trying to connect to. If there is
* a concurrently running DROP DATABASE on that database, this will
* block us until it finishes (and has updated the flat file copy
* of pg_database).
*
* Note that the lock is not held long, only until the end of this
* startup transaction. This is OK since we are already advertising
* our use of the database in the PGPROC array; anyone trying a DROP
* DATABASE after this point will see us there.
*
* Note: use of RowExclusiveLock here is reasonable because we envision
* our session as being a concurrent writer of the database. If we had
* a way of declaring a session as being guaranteed-read-only, we could
* use AccessShareLock for such sessions and thereby not conflict against
* CREATE DATABASE.
*/
if (!bootstrap)
LockSharedObject(DatabaseRelationId, MyDatabaseId, 0,
RowExclusiveLock);
/*
* Recheck the flat file copy of pg_database to make sure the target
* database hasn't gone away. If there was a concurrent DROP DATABASE,
* this ensures we will die cleanly without creating a mess.
*/
if (!bootstrap)
{
Oid dbid2;
Oid tsid2;
if (!FindMyDatabase(dbname, &dbid2, &tsid2) ||
dbid2 != MyDatabaseId || tsid2 != MyDatabaseTableSpace)
ereport(FATAL,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" does not exist",
dbname),
errdetail("It seems to have just been dropped or renamed.")));
}
/*
* Now we should be able to access the database directory safely.
* Verify it's there and looks reasonable.
*/
if (!bootstrap)
{
if (access(fullpath, F_OK) == -1)
{
if (errno == ENOENT)
ereport(FATAL,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" does not exist",
dbname),
errdetail("The database subdirectory \"%s\" is missing.",
fullpath)));
else
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not access directory \"%s\": %m",
fullpath)));
}
ValidatePgVersion(fullpath);
}
/*
* It's now possible to do real access to the system catalogs.
*
@ -499,22 +491,21 @@ InitPostgres(const char *dbname, const char *username)
am_superuser = superuser();
}
/* set up ACL framework (so ReverifyMyDatabase can check permissions) */
/* set up ACL framework (so CheckMyDatabase can check permissions) */
initialize_acl();
/*
* Unless we are bootstrapping, double-check that InitMyDatabaseInfo() got
* a correct result. We can't do this until all the database-access
* infrastructure is up. (Also, it wants to know if the user is a
* superuser, so the above stuff has to happen first.)
* Read the real pg_database row for our database, check permissions
* and set up database-specific GUC settings. We can't do this until all
* the database-access infrastructure is up. (Also, it wants to know if
* the user is a superuser, so the above stuff has to happen first.)
*/
if (!bootstrap)
ReverifyMyDatabase(dbname, am_superuser);
CheckMyDatabase(dbname, am_superuser);
/*
* Final phase of relation cache startup: write a new cache file if
* necessary. This is done after ReverifyMyDatabase to avoid writing a
* cache file into a dead database.
* necessary. (XXX this could be folded back into Phase2)
*/
RelationCacheInitializePhase3();
@ -530,7 +521,7 @@ InitPostgres(const char *dbname, const char *username)
/*
* Initialize various default states that can't be set up until we've
* selected the active user and done ReverifyMyDatabase.
* selected the active user and gotten the right GUC settings.
*/
/* set default namespace search path */
@ -587,13 +578,13 @@ ThereIsAtLeastOneRole(void)
HeapScanDesc scan;
bool result;
pg_authid_rel = heap_open(AuthIdRelationId, AccessExclusiveLock);
pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
scan = heap_beginscan(pg_authid_rel, SnapshotNow, 0, NULL);
result = (heap_getnext(scan, ForwardScanDirection) != NULL);
heap_endscan(scan);
heap_close(pg_authid_rel, AccessExclusiveLock);
heap_close(pg_authid_rel, AccessShareLock);
return result;
}