mirror of
https://github.com/mongodb/mongo.git
synced 2024-11-30 00:56:44 +01:00
0e32c98bf3
Command code cleanup --quiet for Jim
510 lines
16 KiB
C++
510 lines
16 KiB
C++
// dbcommands.cpp
|
|
|
|
/**
|
|
*
|
|
* 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 "stdafx.h"
|
|
#include "query.h"
|
|
#include "pdfile.h"
|
|
#include "jsobj.h"
|
|
#include "../util/builder.h"
|
|
#include <time.h>
|
|
#include "introspect.h"
|
|
#include "btree.h"
|
|
#include "../util/lruishmap.h"
|
|
#include "json.h"
|
|
#include "repl.h"
|
|
#include "replset.h"
|
|
#include "commands.h"
|
|
#include "db.h"
|
|
|
|
extern int queryTraceLevel;
|
|
extern int otherTraceLevel;
|
|
extern int opLogging;
|
|
void flushOpLog();
|
|
int runCount(const char *ns, BSONObj& cmd, string& err);
|
|
|
|
void clean(const char *ns, NamespaceDetails *d) {
|
|
for( int i = 0; i < Buckets; i++ )
|
|
d->deletedList[i].Null();
|
|
}
|
|
|
|
string validateNS(const char *ns, NamespaceDetails *d) {
|
|
bool valid = true;
|
|
stringstream ss;
|
|
ss << "\nvalidate\n";
|
|
ss << " details: " << hex << d << " ofs:" << nsindex(ns)->detailsOffset(d) << dec << endl;
|
|
if( d->capped )
|
|
ss << " capped:" << d->capped << " max:" << d->max << '\n';
|
|
|
|
ss << " firstExtent:" << d->firstExtent.toString() << " ns:" << d->firstExtent.ext()->ns.buf << '\n';
|
|
ss << " lastExtent:" << d->lastExtent.toString() << " ns:" << d->lastExtent.ext()->ns.buf << '\n';
|
|
try {
|
|
d->firstExtent.ext()->assertOk();
|
|
d->lastExtent.ext()->assertOk();
|
|
} catch(...) { valid=false; ss << " extent asserted "; }
|
|
|
|
ss << " datasize?:" << d->datasize << " nrecords?:" << d->nrecords << " lastExtentSize:" << d->lastExtentSize << '\n';
|
|
ss << " padding:" << d->paddingFactor << '\n';
|
|
try {
|
|
|
|
try {
|
|
ss << " first extent:\n";
|
|
d->firstExtent.ext()->dump(ss);
|
|
valid = valid && d->firstExtent.ext()->validates();
|
|
}
|
|
catch(...) {
|
|
ss << "\n exception firstextent\n" << endl;
|
|
}
|
|
|
|
auto_ptr<Cursor> c = theDataFileMgr.findAll(ns);
|
|
int n = 0;
|
|
long long len = 0;
|
|
long long nlen = 0;
|
|
set<DiskLoc> recs;
|
|
int outOfOrder = 0;
|
|
DiskLoc cl_last;
|
|
while( c->ok() ) {
|
|
n++;
|
|
|
|
DiskLoc cl = c->currLoc();
|
|
if( n < 1000000 )
|
|
recs.insert(cl);
|
|
if( d->capped ) {
|
|
if( cl < cl_last )
|
|
outOfOrder++;
|
|
cl_last = cl;
|
|
}
|
|
|
|
Record *r = c->_current();
|
|
len += r->lengthWithHeaders;
|
|
nlen += r->netLength();
|
|
c->advance();
|
|
}
|
|
if( d->capped ) {
|
|
ss << " capped outOfOrder:" << outOfOrder;
|
|
if( outOfOrder > 1 ) {
|
|
valid = false;
|
|
ss << " ???";
|
|
}
|
|
else ss << " (OK)";
|
|
ss << '\n';
|
|
}
|
|
ss << " " << n << " objects found, nobj:" << d->nrecords << "\n";
|
|
ss << " " << len << " bytes data w/headers\n";
|
|
ss << " " << nlen << " bytes data wout/headers\n";
|
|
|
|
ss << " deletedList: ";
|
|
for( int i = 0; i < Buckets; i++ ) {
|
|
ss << (d->deletedList[i].isNull() ? '0' : '1');
|
|
}
|
|
ss << endl;
|
|
int ndel = 0;
|
|
long long delSize = 0;
|
|
int incorrect = 0;
|
|
for( int i = 0; i < Buckets; i++ ) {
|
|
DiskLoc loc = d->deletedList[i];
|
|
try {
|
|
int k = 0;
|
|
while( !loc.isNull() ) {
|
|
if( recs.count(loc) )
|
|
incorrect++;
|
|
ndel++;
|
|
|
|
if( loc.questionable() ) {
|
|
if( loc.a() <= 0 || strstr(ns, "hudsonSmall") == 0 ) {
|
|
ss << " ?bad deleted loc: " << loc.toString() << " bucket:" << i << " k:" << k << endl;
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DeletedRecord *d = loc.drec();
|
|
delSize += d->lengthWithHeaders;
|
|
loc = d->nextDeleted;
|
|
k++;
|
|
}
|
|
} catch(...) { ss <<" ?exception in deleted chain for bucket " << i << endl; valid = false; }
|
|
}
|
|
ss << " deleted: n: " << ndel << " size: " << delSize << '\n';
|
|
if( incorrect ) {
|
|
ss << " ?corrupt: " << incorrect << " records from datafile are in deleted list\n";
|
|
valid = false;
|
|
}
|
|
|
|
int idxn = 0;
|
|
try {
|
|
ss << " nIndexes:" << d->nIndexes << endl;
|
|
for( ; idxn < d->nIndexes; idxn++ ) {
|
|
ss << " " << d->indexes[idxn].indexNamespace() << " keys:" <<
|
|
d->indexes[idxn].head.btree()->fullValidate(d->indexes[idxn].head) << endl;
|
|
}
|
|
}
|
|
catch(...) {
|
|
ss << "\n exception during index validate idxn:" << idxn << endl; valid=false;
|
|
}
|
|
|
|
}
|
|
catch(AssertionException) {
|
|
ss << "\n exception during validate\n" << endl;
|
|
valid = false;
|
|
}
|
|
|
|
if( !valid )
|
|
ss << " ns corrupt, requires dbchk\n";
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
class CmdDropDatabase : public Command {
|
|
public:
|
|
virtual bool logTheOp() { return true; }
|
|
virtual bool slaveOk() { return false; }
|
|
CmdDropDatabase() : Command("dropDatabase") {}
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
|
|
BSONElement e = cmdObj.findElement("dropDatabase");
|
|
log() << "dropDatabase " << ns << endl;
|
|
int p = (int) e.number();
|
|
if( p != 1 )
|
|
return false;
|
|
dropDatabase(ns);
|
|
return true;
|
|
}
|
|
} cmdDropDatabase;
|
|
|
|
/* set db profiling level
|
|
todo: how do we handle profiling information put in the db with replication?
|
|
sensibly or not?
|
|
*/
|
|
class CmdProfile : public Command {
|
|
public:
|
|
virtual bool slaveOk() { return true; }
|
|
CmdProfile() : Command("profile") {}
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
|
|
BSONElement e = cmdObj.findElement("profile");
|
|
result.append("was", (double) database->profile);
|
|
int p = (int) e.number();
|
|
bool ok = false;
|
|
if( p == -1 )
|
|
ok = true;
|
|
else if( p >= 0 && p <= 2 ) {
|
|
ok = true;
|
|
database->profile = p;
|
|
}
|
|
return ok;
|
|
}
|
|
} cmdProfile;
|
|
|
|
class CmdTimeInfo : public Command {
|
|
public:
|
|
virtual bool slaveOk() { return true; }
|
|
CmdTimeInfo() : Command("timeinfo") {}
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
|
|
unsigned long long last, start, timeLocked;
|
|
dbMutexInfo.timingInfo(start, timeLocked);
|
|
last = curTimeMicros64();
|
|
double tt = (double) last-start;
|
|
double tl = (double) timeLocked;
|
|
result.append("totalTime", tt);
|
|
result.append("lockTime", tl);
|
|
result.append("ratio", tl/tt);
|
|
return true;
|
|
}
|
|
} cmdTimeInfo;
|
|
|
|
/* just to check if the db has asserted */
|
|
class CmdAssertInfo : public Command {
|
|
public:
|
|
virtual bool slaveOk() { return true; }
|
|
CmdAssertInfo() : Command("assertinfo") {}
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
|
|
result.appendBool("dbasserted", lastAssert[0].isSet() || lastAssert[1].isSet() || lastAssert[2].isSet());
|
|
result.appendBool("asserted", lastAssert[0].isSet() || lastAssert[1].isSet() || lastAssert[2].isSet() || lastAssert[3].isSet());
|
|
result.append("assert", lastAssert[AssertRegular].toString());
|
|
result.append("assertw", lastAssert[AssertW].toString());
|
|
result.append("assertmsg", lastAssert[AssertMsg].toString());
|
|
result.append("assertuser", lastAssert[AssertUser].toString());
|
|
return true;
|
|
}
|
|
} cmdAsserts;
|
|
|
|
class CmdGetOpTime : public Command {
|
|
public:
|
|
virtual bool slaveOk() { return true; }
|
|
CmdGetOpTime() : Command("getoptime") { }
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
|
|
result.appendDate("optime", OpTime::now().asDate());
|
|
return true;
|
|
}
|
|
} cmdgetoptime;
|
|
|
|
/*
|
|
class Cmd : public Command {
|
|
public:
|
|
Cmd() : Command("") { }
|
|
bool adminOnly() { return true; }
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result) {
|
|
return true;
|
|
}
|
|
} cmd;
|
|
*/
|
|
|
|
class CmdOpLogging : public Command {
|
|
public:
|
|
virtual bool slaveOk() { return true; }
|
|
CmdOpLogging() : Command("opLogging") { }
|
|
bool adminOnly() { return true; }
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) {
|
|
opLogging = (int) cmdObj.findElement("opLogging").number();
|
|
flushOpLog();
|
|
log() << "CMD: opLogging set to " << opLogging << endl;
|
|
return true;
|
|
}
|
|
} cmdoplogging;
|
|
|
|
class CmdQueryTraceLevel : public Command {
|
|
public:
|
|
virtual bool slaveOk() { return true; }
|
|
CmdQueryTraceLevel() : Command("queryTraceLevel") { }
|
|
bool adminOnly() { return true; }
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) {
|
|
queryTraceLevel = (int) cmdObj.findElement(name.c_str()).number();
|
|
return true;
|
|
}
|
|
} cmdquerytracelevel;
|
|
|
|
class CmdTraceAll : public Command {
|
|
public:
|
|
virtual bool slaveOk() { return true; }
|
|
CmdTraceAll() : Command("traceAll") { }
|
|
bool adminOnly() { return true; }
|
|
bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) {
|
|
queryTraceLevel = otherTraceLevel = (int) cmdObj.findElement(name.c_str()).number();
|
|
return true;
|
|
}
|
|
} cmdtraceall;
|
|
|
|
class CmdDeleteIndexes : public Command {
|
|
public:
|
|
virtual bool logTheOp() { return true; }
|
|
virtual bool slaveOk() { return false; }
|
|
CmdDeleteIndexes() : Command("deleteIndexes") { }
|
|
bool run(const char *ns, BSONObj& jsobj, string& errmsg, BSONObjBuilder& anObjBuilder, bool /*fromRepl*/) {
|
|
/* note: temp implementation. space not reclaimed! */
|
|
BSONElement e = jsobj.findElement(name.c_str());
|
|
string toDeleteNs = database->name + '.' + e.valuestr();
|
|
NamespaceDetails *d = nsdetails(toDeleteNs.c_str());
|
|
log() << "CMD: deleteIndexes " << toDeleteNs << endl;
|
|
if( d ) {
|
|
BSONElement f = jsobj.findElement("index");
|
|
if( !f.eoo() ) {
|
|
|
|
d->aboutToDeleteAnIndex();
|
|
|
|
ClientCursor::invalidate(toDeleteNs.c_str());
|
|
|
|
// delete a specific index or all?
|
|
if( f.type() == String ) {
|
|
const char *idxName = f.valuestr();
|
|
if( *idxName == '*' && idxName[1] == 0 ) {
|
|
log() << " d->nIndexes was " << d->nIndexes << '\n';
|
|
anObjBuilder.append("nIndexesWas", (double)d->nIndexes);
|
|
anObjBuilder.append("msg", "all indexes deleted for collection");
|
|
for( int i = 0; i < d->nIndexes; i++ )
|
|
d->indexes[i].kill();
|
|
d->nIndexes = 0;
|
|
log() << " alpha implementation, space not reclaimed" << endl;
|
|
}
|
|
else {
|
|
// delete just one index
|
|
int x = d->findIndexByName(idxName);
|
|
if( x >= 0 ) {
|
|
cout << " d->nIndexes was " << d->nIndexes << endl;
|
|
anObjBuilder.append("nIndexesWas", (double)d->nIndexes);
|
|
|
|
/* note it is important we remove the IndexDetails with this
|
|
call, otherwise, on recreate, the old one would be reused, and its
|
|
IndexDetails::info ptr would be bad info.
|
|
*/
|
|
d->indexes[x].kill();
|
|
|
|
d->nIndexes--;
|
|
for( int i = x; i < d->nIndexes; i++ )
|
|
d->indexes[i] = d->indexes[i+1];
|
|
log() << "deleteIndexes: alpha implementation, space not reclaimed\n";
|
|
} else {
|
|
log() << "deleteIndexes: " << idxName << " not found" << endl;
|
|
errmsg = "index not found";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
errmsg = "ns not found";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
} cmdDeleteIndexes;
|
|
|
|
extern map<string,Command*> *commands;
|
|
|
|
/* TODO make these all command objects -- legacy stuff here
|
|
|
|
usage:
|
|
abc.$cmd.findOne( { ismaster:1 } );
|
|
|
|
returns true if ran a cmd
|
|
*/
|
|
bool _runCommands(const char *ns, BSONObj& jsobj, stringstream& ss, BufBuilder &b, BSONObjBuilder& anObjBuilder, bool fromRepl) {
|
|
|
|
const char *p = strchr(ns, '.');
|
|
if( !p ) return false;
|
|
if( strcmp(p, ".$cmd") != 0 ) return false;
|
|
|
|
bool ok = false;
|
|
bool valid = false;
|
|
|
|
BSONElement e;
|
|
e = jsobj.firstElement();
|
|
|
|
map<string,Command*>::iterator i;
|
|
|
|
if( e.eoo() )
|
|
;
|
|
/* check for properly registered command objects. Note that all the commands below should be
|
|
migrated over to the command object format.
|
|
*/
|
|
else if( (i = commands->find(e.fieldName())) != commands->end() ) {
|
|
valid = true;
|
|
string errmsg;
|
|
Command *c = i->second;
|
|
if( c->adminOnly() && !fromRepl && strncmp(ns, "admin", p-ns) != 0 ) {
|
|
ok = false;
|
|
errmsg = "access denied";
|
|
}
|
|
else if( !isMaster() && !c->slaveOk() ) {
|
|
/* todo: allow if Option_SlaveOk was set on the query */
|
|
ok = false;
|
|
errmsg = "not master";
|
|
}
|
|
else {
|
|
ok = c->run(ns, jsobj, errmsg, anObjBuilder, fromRepl);
|
|
if( ok && c->logTheOp() && !fromRepl )
|
|
logOp("c", ns, jsobj);
|
|
}
|
|
if( !ok )
|
|
anObjBuilder.append("errmsg", errmsg);
|
|
}
|
|
else if( e.type() == String ) {
|
|
/* { count: "collectionname"[, query: <query>] } */
|
|
string us(ns, p-ns);
|
|
|
|
if( strcmp( e.fieldName(), "count" ) == 0 ) {
|
|
valid = true;
|
|
string ns = us + '.' + e.valuestr();
|
|
string err;
|
|
int n = runCount(ns.c_str(), jsobj, err);
|
|
int nn = n;
|
|
ok = true;
|
|
if( n < 0 ) {
|
|
ok = false;
|
|
nn = 0;
|
|
if( !err.empty() )
|
|
anObjBuilder.append("errmsg", err.c_str());
|
|
}
|
|
anObjBuilder.append("n", (double) nn);
|
|
}
|
|
else if( strcmp( e.fieldName(), "create") == 0 ) {
|
|
// create a collection
|
|
valid = true;
|
|
string ns = us + '.' + e.valuestr();
|
|
string err;
|
|
ok = userCreateNS(ns.c_str(), jsobj, err, true);
|
|
if( !ok && !err.empty() )
|
|
anObjBuilder.append("errmsg", err.c_str());
|
|
}
|
|
else if( strcmp( e.fieldName(), "clean") == 0 ) {
|
|
valid = true;
|
|
string dropNs = us + '.' + e.valuestr();
|
|
NamespaceDetails *d = nsdetails(dropNs.c_str());
|
|
log() << "CMD: clean " << dropNs << endl;
|
|
if( d ) {
|
|
ok = true;
|
|
anObjBuilder.append("ns", dropNs.c_str());
|
|
clean(dropNs.c_str(), d);
|
|
}
|
|
else {
|
|
anObjBuilder.append("errmsg", "ns not found");
|
|
}
|
|
}
|
|
else if( strcmp( e.fieldName(), "drop") == 0 ) {
|
|
valid = true;
|
|
string nsToDrop = us + '.' + e.valuestr();
|
|
NamespaceDetails *d = nsdetails(nsToDrop.c_str());
|
|
log() << "CMD: drop " << nsToDrop << endl;
|
|
if( d == 0 ) {
|
|
anObjBuilder.append("errmsg", "ns not found");
|
|
}
|
|
else if( d->nIndexes != 0 ) {
|
|
// database is supposed to drop the indexes first
|
|
anObjBuilder.append("errmsg", "ns has indexes (not permitted on drop)");
|
|
}
|
|
else {
|
|
ok = true;
|
|
anObjBuilder.append("ns", nsToDrop.c_str());
|
|
ClientCursor::invalidate(nsToDrop.c_str());
|
|
dropNS(nsToDrop);
|
|
if( !fromRepl )
|
|
logOp("c", ns, jsobj);
|
|
/*
|
|
{
|
|
BSONObjBuilder b;
|
|
b.append("name", dropNs.c_str());
|
|
BSONObj cond = b.done(); // { name: "colltodropname" }
|
|
deleteObjects("system.namespaces", cond, false, true);
|
|
}
|
|
database->namespaceIndex.kill(dropNs.c_str());
|
|
*/
|
|
}
|
|
}
|
|
else if( strcmp( e.fieldName(), "validate") == 0 ) {
|
|
valid = true;
|
|
string toValidateNs = us + '.' + e.valuestr();
|
|
NamespaceDetails *d = nsdetails(toValidateNs.c_str());
|
|
log() << "CMD: validate " << toValidateNs << endl;
|
|
if( d ) {
|
|
ok = true;
|
|
anObjBuilder.append("ns", toValidateNs.c_str());
|
|
string s = validateNS(toValidateNs.c_str(), d);
|
|
anObjBuilder.append("result", s.c_str());
|
|
}
|
|
else {
|
|
anObjBuilder.append("errmsg", "ns not found");
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !valid )
|
|
anObjBuilder.append("errmsg", "no such cmd");
|
|
anObjBuilder.append("ok", ok?1.0:0.0);
|
|
BSONObj x = anObjBuilder.done();
|
|
b.append((void*) x.objdata(), x.objsize());
|
|
return true;
|
|
}
|
|
|