327 lines
10 KiB
C++
327 lines
10 KiB
C++
// database.cpp
|
|
|
|
/**
|
|
* Copyright (C) 2008 10gen Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "pch.h"
|
|
#include "pdfile.h"
|
|
#include "database.h"
|
|
#include "instance.h"
|
|
#include "clientcursor.h"
|
|
|
|
namespace mongo {
|
|
|
|
bool Database::_openAllFiles = false;
|
|
|
|
Database::~Database() {
|
|
magic = 0;
|
|
size_t n = files.size();
|
|
for ( size_t i = 0; i < n; i++ )
|
|
delete files[i];
|
|
if( ccByLoc.size() ) {
|
|
log() << "\n\n\nWARNING: ccByLoc not empty on database close! " << ccByLoc.size() << ' ' << name << endl;
|
|
}
|
|
}
|
|
|
|
Database::Database(const char *nm, bool& newDb, const string& _path )
|
|
: name(nm), path(_path), namespaceIndex( path, name ),
|
|
profileName(name + ".system.profile") {
|
|
try {
|
|
|
|
{
|
|
// check db name is valid
|
|
size_t L = strlen(nm);
|
|
uassert( 10028 , "db name is empty", L > 0 );
|
|
uassert( 10029 , "bad db name [1]", *nm != '.' );
|
|
uassert( 10030 , "bad db name [2]", nm[L-1] != '.' );
|
|
uassert( 10031 , "bad char(s) in db name", strchr(nm, ' ') == 0 );
|
|
uassert( 10032 , "db name too long", L < 64 );
|
|
}
|
|
|
|
newDb = namespaceIndex.exists();
|
|
profile = cmdLine.defaultProfile;
|
|
|
|
checkDuplicateUncasedNames();
|
|
|
|
// If already exists, open. Otherwise behave as if empty until
|
|
// there's a write, then open.
|
|
if ( ! newDb || cmdLine.defaultProfile ) {
|
|
namespaceIndex.init();
|
|
if( _openAllFiles )
|
|
openAllFiles();
|
|
|
|
}
|
|
|
|
|
|
magic = 781231;
|
|
} catch(...) {
|
|
// since destructor won't be called:
|
|
for ( size_t i = 0; i < files.size(); i++ )
|
|
delete files[i];
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void Database::checkDuplicateUncasedNames() const {
|
|
string duplicate = duplicateUncasedName( name, path );
|
|
if ( !duplicate.empty() ) {
|
|
stringstream ss;
|
|
ss << "db already exists with different case other: [" << duplicate << "] me [" << name << "]";
|
|
uasserted( DatabaseDifferCaseCode , ss.str() );
|
|
}
|
|
}
|
|
|
|
string Database::duplicateUncasedName( const string &name, const string &path, set< string > *duplicates ) {
|
|
if ( duplicates ) {
|
|
duplicates->clear();
|
|
}
|
|
|
|
vector<string> others;
|
|
getDatabaseNames( others , path );
|
|
|
|
set<string> allShortNames;
|
|
dbHolder.getAllShortNames( allShortNames );
|
|
|
|
others.insert( others.end(), allShortNames.begin(), allShortNames.end() );
|
|
|
|
for ( unsigned i=0; i<others.size(); i++ ) {
|
|
|
|
if ( strcasecmp( others[i].c_str() , name.c_str() ) )
|
|
continue;
|
|
|
|
if ( strcmp( others[i].c_str() , name.c_str() ) == 0 )
|
|
continue;
|
|
|
|
if ( duplicates ) {
|
|
duplicates->insert( others[i] );
|
|
} else {
|
|
return others[i];
|
|
}
|
|
}
|
|
if ( duplicates ) {
|
|
return duplicates->empty() ? "" : *duplicates->begin();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
boost::filesystem::path Database::fileName( int n ) const {
|
|
stringstream ss;
|
|
ss << name << '.' << n;
|
|
boost::filesystem::path fullName;
|
|
fullName = boost::filesystem::path(path);
|
|
if ( directoryperdb )
|
|
fullName /= name;
|
|
fullName /= ss.str();
|
|
return fullName;
|
|
}
|
|
|
|
void Database::openAllFiles() {
|
|
int n = 0;
|
|
while( exists(n) ) {
|
|
getFile(n);
|
|
n++;
|
|
}
|
|
// If last file is empty, consider it preallocated and make sure it's not mapped
|
|
// until a write is requested
|
|
if ( n > 1 && getFile( n - 1 )->getHeader()->isEmpty() ) {
|
|
delete files[ n - 1 ];
|
|
files.pop_back();
|
|
}
|
|
}
|
|
|
|
MongoDataFile* Database::getFile( int n, int sizeNeeded , bool preallocateOnly) {
|
|
assert(this);
|
|
|
|
namespaceIndex.init();
|
|
if ( n < 0 || n >= DiskLoc::MaxFiles ) {
|
|
out() << "getFile(): n=" << n << endl;
|
|
massert( 10295 , "getFile(): bad file number value (corrupt db?): run repair", false);
|
|
}
|
|
DEV {
|
|
if ( n > 100 )
|
|
out() << "getFile(): n=" << n << "?" << endl;
|
|
}
|
|
MongoDataFile* p = 0;
|
|
if ( !preallocateOnly ) {
|
|
while ( n >= (int) files.size() )
|
|
files.push_back(0);
|
|
p = files[n];
|
|
}
|
|
if ( p == 0 ) {
|
|
boost::filesystem::path fullName = fileName( n );
|
|
string fullNameString = fullName.string();
|
|
p = new MongoDataFile(n);
|
|
int minSize = 0;
|
|
if ( n != 0 && files[ n - 1 ] )
|
|
minSize = files[ n - 1 ]->getHeader()->fileLength;
|
|
if ( sizeNeeded + DataFileHeader::HeaderSize > minSize )
|
|
minSize = sizeNeeded + DataFileHeader::HeaderSize;
|
|
try {
|
|
p->open( fullNameString.c_str(), minSize, preallocateOnly );
|
|
}
|
|
catch ( AssertionException& ) {
|
|
delete p;
|
|
throw;
|
|
}
|
|
if ( preallocateOnly )
|
|
delete p;
|
|
else
|
|
files[n] = p;
|
|
}
|
|
return preallocateOnly ? 0 : p;
|
|
}
|
|
|
|
MongoDataFile* Database::addAFile( int sizeNeeded, bool preallocateNextFile ) {
|
|
int n = (int) files.size();
|
|
MongoDataFile *ret = getFile( n, sizeNeeded );
|
|
if ( preallocateNextFile )
|
|
preallocateAFile();
|
|
return ret;
|
|
}
|
|
|
|
bool fileIndexExceedsQuota( const char *ns, int fileIndex, bool enforceQuota ) {
|
|
return
|
|
cmdLine.quota &&
|
|
enforceQuota &&
|
|
fileIndex >= cmdLine.quotaFiles &&
|
|
// we don't enforce the quota on "special" namespaces as that could lead to problems -- e.g.
|
|
// rejecting an index insert after inserting the main record.
|
|
!NamespaceString::special( ns ) &&
|
|
NamespaceString( ns ).db != "local";
|
|
}
|
|
|
|
MongoDataFile* Database::suitableFile( const char *ns, int sizeNeeded, bool preallocate, bool enforceQuota ) {
|
|
|
|
// check existing files
|
|
for ( int i=numFiles()-1; i>=0; i-- ) {
|
|
MongoDataFile* f = getFile( i );
|
|
if ( f->getHeader()->unusedLength >= sizeNeeded ) {
|
|
if ( fileIndexExceedsQuota( ns, i-1, enforceQuota ) ) // NOTE i-1 is the value used historically for this check.
|
|
;
|
|
else
|
|
return f;
|
|
}
|
|
}
|
|
|
|
if ( fileIndexExceedsQuota( ns, numFiles(), enforceQuota ) )
|
|
uasserted(12501, "quota exceeded");
|
|
|
|
// allocate files until we either get one big enough or hit maxSize
|
|
for ( int i = 0; i < 8; i++ ) {
|
|
MongoDataFile* f = addAFile( sizeNeeded, preallocate );
|
|
|
|
if ( f->getHeader()->unusedLength >= sizeNeeded )
|
|
return f;
|
|
|
|
if ( f->getHeader()->fileLength >= MongoDataFile::maxSize() ) // this is as big as they get so might as well stop
|
|
return f;
|
|
}
|
|
|
|
uasserted(14810, "couldn't allocate space (suitableFile)"); // callers don't check for null return code
|
|
return 0;
|
|
}
|
|
|
|
MongoDataFile* Database::newestFile() {
|
|
int n = numFiles();
|
|
if ( n == 0 )
|
|
return 0;
|
|
return getFile(n-1);
|
|
}
|
|
|
|
|
|
Extent* Database::allocExtent( const char *ns, int size, bool capped, bool enforceQuota ) {
|
|
Extent *e = DataFileMgr::allocFromFreeList( ns, size, capped );
|
|
if( e )
|
|
return e;
|
|
return suitableFile( ns, size, !capped, enforceQuota )->createExtent( ns, size, capped );
|
|
}
|
|
|
|
|
|
bool Database::setProfilingLevel( int newLevel , string& errmsg ) {
|
|
if ( profile == newLevel )
|
|
return true;
|
|
|
|
if ( newLevel < 0 || newLevel > 2 ) {
|
|
errmsg = "profiling level has to be >=0 and <= 2";
|
|
return false;
|
|
}
|
|
|
|
if ( newLevel == 0 ) {
|
|
profile = 0;
|
|
return true;
|
|
}
|
|
|
|
assert( cc().database() == this );
|
|
|
|
if ( ! namespaceIndex.details( profileName.c_str() ) ) {
|
|
log() << "creating profile collection: " << profileName << endl;
|
|
BSONObjBuilder spec;
|
|
spec.appendBool( "capped", true );
|
|
spec.append( "size", 1024*1024 );
|
|
if ( ! userCreateNS( profileName.c_str(), spec.done(), errmsg , false /* we don't replica profile messages */ ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
profile = newLevel;
|
|
return true;
|
|
}
|
|
|
|
bool Database::validDBName( const string& ns ) {
|
|
if ( ns.size() == 0 || ns.size() > 64 )
|
|
return false;
|
|
size_t good = strcspn( ns.c_str() , "/\\. \"" );
|
|
return good == ns.size();
|
|
}
|
|
|
|
void Database::flushFiles( bool sync ) const {
|
|
dbMutex.assertAtLeastReadLocked();
|
|
for ( unsigned i=0; i<files.size(); i++ ) {
|
|
files[i]->flush( sync );
|
|
}
|
|
}
|
|
|
|
long long Database::fileSize() const {
|
|
long long size=0;
|
|
for (int n=0; exists(n); n++)
|
|
size += boost::filesystem::file_size( fileName(n) );
|
|
return size;
|
|
}
|
|
|
|
Database* DatabaseHolder::getOrCreate( const string& ns , const string& path , bool& justCreated ) {
|
|
dbMutex.assertWriteLocked();
|
|
DBs& m = _paths[path];
|
|
|
|
string dbname = _todb( ns );
|
|
|
|
Database* & db = m[dbname];
|
|
if ( db ) {
|
|
justCreated = false;
|
|
return db;
|
|
}
|
|
|
|
log(1) << "Accessing: " << dbname << " for the first time" << endl;
|
|
try {
|
|
db = new Database( dbname.c_str() , justCreated , path );
|
|
}
|
|
catch ( ... ) {
|
|
m.erase( dbname );
|
|
throw;
|
|
}
|
|
_size++;
|
|
return db;
|
|
}
|
|
|
|
} // namespace mongo
|