diff options
author | Eliot Horowitz <eliot@10gen.com> | 2009-01-26 22:19:15 -0500 |
---|---|---|
committer | Eliot Horowitz <eliot@10gen.com> | 2009-01-26 22:19:15 -0500 |
commit | 4ffda5ea25020b5b6e18392900ed52e20e7cb6e3 (patch) | |
tree | 8741dc7f1bac56cc194deb76e30757b36de30845 | |
parent | b29806e4a1821c32fc567c4208059cf1bf4a3552 (diff) | |
download | mongo-4ffda5ea25020b5b6e18392900ed52e20e7cb6e3.tar.gz |
dbshell
-rw-r--r-- | .gitignore | 13 | ||||
-rw-r--r-- | SConstruct | 97 | ||||
-rw-r--r-- | shell/MongoJS.cpp | 522 | ||||
-rw-r--r-- | shell/MongoJS.h | 49 | ||||
-rw-r--r-- | shell/ShellUtils.cpp | 188 | ||||
-rw-r--r-- | shell/ShellUtils.h | 37 | ||||
-rw-r--r-- | shell/collection.js | 269 | ||||
-rw-r--r-- | shell/db.js | 327 | ||||
-rw-r--r-- | shell/dbshell.cpp | 216 | ||||
-rw-r--r-- | shell/md5.js | 279 | ||||
-rw-r--r-- | shell/mongo.js | 60 | ||||
-rw-r--r-- | shell/query.js | 159 | ||||
-rw-r--r-- | shell/utils.js | 187 |
13 files changed, 2400 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore index 716ed63065d..34ea4c795e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .jsdbshell +.dbshell .sconsign.dblite +.sconf_temp + *~ *.o *.aps @@ -10,21 +13,31 @@ *.obj *.opt *.pch +*.jsh +*.jsall + db/Debug db/db db/dbgrid db/dbtests db/oplog* db/.gdb* +config.log #temp dirs dump log logs +docs # binaryies mongodump mongoimport firstExample +secondExample libmongoclient.* +libmongotestfiles.* test +authTest +clientTest +dbshell diff --git a/SConstruct b/SConstruct index f22d0cfcde0..c6268755e87 100644 --- a/SConstruct +++ b/SConstruct @@ -30,6 +30,12 @@ AddOption( "--32", action="store", help="whether to force 32 bit" ) +AddOption( "--release", + dest="release", + type="string", + nargs=0, + action="store", + help="relase build") AddOption('--java', dest='javaHome', @@ -40,6 +46,13 @@ AddOption('--java', metavar='DIR', help='java home') +AddOption( "--v8" , + dest="v8home", + type="string", + nargs=1, + action="store", + metavar="dir", + help="v8 location") # --- environment setup --- @@ -111,6 +124,9 @@ elif "linux2" == os.sys.platform: env.Append( LINKFLAGS="-Xlinker -rpath -Xlinker " + javaHome + "jre/lib/" + javaVersion + "/server" ) env.Append( LINKFLAGS="-Xlinker -rpath -Xlinker " + javaHome + "jre/lib/" + javaVersion ) + if force32: + env.Append( LIBPATH["/usr/lib32"] ) + nix = True elif "win32" == os.sys.platform: @@ -168,6 +184,8 @@ if nix: env.Append( CXXFLAGS="-m32" ) env.Append( LINKFLAGS="-m32" ) + if not GetOption( "release" ) is None: + env.Append( LINKFLAGS=" -static " ) # --- check system --- @@ -193,6 +211,73 @@ conf.CheckLib( "boost_system-mt" ) env = conf.Finish() +# --- v8 --- + +v8Home = GetOption( "v8home" ) + +if not v8Home: + for poss in [ "../v8" , "../open-source/v8" ]: + if os.path.exists( poss ): + v8Home = poss + break + +# --- js concat --- + +def concatjs(target, source, env): + + outFile = str( target[0] ) + + fullSource = "" + + for s in source: + f = open( str(s) , 'r' ) + for l in f: + fullSource += l + + out = open( outFile , 'w' ) + out.write( fullSource ) + + return None + +jsBuilder = Builder(action = concatjs, + suffix = '.jsall', + src_suffix = '.js') + +env.Append( BUILDERS={'JSConcat' : jsBuilder}) + +# --- jsh --- + +def jsToH(target, source, env): + + outFile = str( target[0] ) + if len( source ) != 1: + raise Exception( "wrong" ) + + h = "const char * jsconcatcode = \n" + + for l in open( str(source[0]) , 'r' ): + l = l.strip() + l = l.partition( "//" )[0] + l = l.replace( '\\' , "\\\\" ) + l = l.replace( '"' , "\\\"" ) + + + h += '"' + l + "\\n\"\n " + + h += ";\n\n" + + out = open( outFile , 'w' ) + out.write( h ) + + return None + +jshBuilder = Builder(action = jsToH, + suffix = '.jsh', + src_suffix = '.jsall') + +env.Append( BUILDERS={'JSHeader' : jshBuilder}) + + # --- targets ---- clientEnv = env.Clone(); @@ -205,9 +290,10 @@ testEnv.Append( CPPPATH=["../"] ) testEnv.Append( LIBS=[ "unittest" , "libmongotestfiles.a" ] ) testEnv.Append( LIBPATH=["."] ) -# SYSTEM CHECKS -configure = env.Configure() - +shellEnv = env.Clone(); +shellEnv.Append( CPPPATH=[ "../" , v8Home + "/include/" ] ) +shellEnv.Append( LIBS=[ "libmongoclient.a" , "v8" , "readline" , "history" ] ) +shellEnv.Append( LIBPATH=[ "." , v8Home] ) # ----- TARGETS ------ @@ -236,6 +322,11 @@ clientEnv.Program( "authTest" , [ "client/examples/authTest.cpp" ] ) test = testEnv.Program( "test" , Glob( "dbtests/*.cpp" ) ) clientEnv.Program( "clientTest" , [ "client/examples/clientTest.cpp" ] ) +# shell +shellEnv.JSConcat( "shell/mongo.jsall" , Glob( "shell/*.js" ) ) +shellEnv.JSHeader( "shell/mongo.jsall" ) +dbshell = shellEnv.Program( "dbshell" , Glob( "shell/*.cpp" ) ); + # ---- RUNNING TESTS ---- testEnv.Alias( "smoke", "test", test[ 0 ].abspath ) diff --git a/shell/MongoJS.cpp b/shell/MongoJS.cpp new file mode 100644 index 00000000000..8ad69c61758 --- /dev/null +++ b/shell/MongoJS.cpp @@ -0,0 +1,522 @@ +// MongoJS.cpp + +#include "MongoJS.h" +#include "ShellUtils.h" + +#include <iostream> + +using namespace std; +using namespace mongo; +using namespace v8; + +#define CONN_STRING (String::New( "_conn" )) + +#define DDD(x) +//#define DDD(x) ( cout << x << endl ); + +void installMongoGlobals( Handle<ObjectTemplate>& global ){ + global->Set(String::New("mongoInject"), FunctionTemplate::New(mongoInject)); + + v8::Local<v8::FunctionTemplate> mongo = FunctionTemplate::New( mongoInit ); + global->Set(String::New("Mongo") , mongo ); + + v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); + global->Set(String::New("DB") , db ); + db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + + v8::Local<v8::FunctionTemplate> dbCollection = FunctionTemplate::New( collectionInit ); + global->Set(String::New("DBCollection") , dbCollection ); + dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + + v8::Local<v8::FunctionTemplate> dbQuery = FunctionTemplate::New( dbQueryInit ); + global->Set(String::New("DBQuery") , dbQuery ); + dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); + + v8::Local<v8::FunctionTemplate> objectId = FunctionTemplate::New( objectIdInit ); + global->Set(String::New("ObjectId") , objectId ); +} + +Handle<Value> mongoInject(const Arguments& args){ + jsassert( args.Length() == 1 , "mongoInject takes exactly 1 argument" ); + jsassert( args[0]->IsObject() , "mongoInject needs to be massed a prototype" ); + + Local<v8::Object> o = args[0]->ToObject(); + + o->Set( String::New( "init" ) , FunctionTemplate::New( mongoInit )->GetFunction() ); + o->Set( String::New( "find" ) , FunctionTemplate::New( mongoFind )->GetFunction() ); + o->Set( String::New( "insert" ) , FunctionTemplate::New( mongoInsert )->GetFunction() ); + o->Set( String::New( "remove" ) , FunctionTemplate::New( mongoRemove )->GetFunction() ); + o->Set( String::New( "update" ) , FunctionTemplate::New( mongoUpdate )->GetFunction() ); + + Local<FunctionTemplate> t = FunctionTemplate::New( internalCursorCons ); + t->PrototypeTemplate()->Set( String::New("next") , FunctionTemplate::New( internalCursorNext ) ); + t->PrototypeTemplate()->Set( String::New("hasNext") , FunctionTemplate::New( internalCursorHasNext ) ); + o->Set( String::New( "internalCursor" ) , t->GetFunction() ); + + return v8::Undefined(); +} + +Handle<Value> mongoInit(const Arguments& args){ + + char host[255]; + + if ( args.Length() > 0 && args[0]->IsString() ){ + assert( args[0]->ToString()->Utf8Length() < 250 ); + args[0]->ToString()->WriteAscii( host ); + } + else { + strcpy( host , "127.0.0.1" ); + } + + DBClientConnection * conn = new DBClientConnection( true ); + + string errmsg; + if ( ! conn->connect( host , errmsg ) ){ + cout << "couldn't connect: " << errmsg << endl; + jsassert( 0 , errmsg ); + } + + args.This()->Set( CONN_STRING , External::New( conn ) ); + + + return v8::Undefined(); +} + + +// --- + +Local<v8::Object> mongoToV8( BSONObj & m , bool array ){ + Local<v8::Object> o; + if ( array ) + o = v8::Array::New(); + else + o = v8::Object::New(); + + mongo::BSONObj sub; + + for ( BSONObjIterator i(m); i.more(); ) { + BSONElement f = i.next(); + if ( f.eoo() ) + break; + + Local<Value> v; + + switch ( f.type() ){ + + case mongo::Code: + cout << "warning, code saved in database just turned into string right now" << endl; + case mongo::String: + o->Set( v8::String::New( f.fieldName() ) , v8::String::New( f.valuestr() ) ); + break; + + case mongo::jstOID: { + v8::Function * idCons = getObjectIdCons(); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::String::New( f.__oid().str().c_str() ); + o->Set( v8::String::New( f.fieldName() ) , + idCons->NewInstance( 1 , argv ) ); + break; + } + + case mongo::NumberDouble: + case mongo::NumberInt: + o->Set( v8::String::New( f.fieldName() ) , v8::Number::New( f.number() ) ); + break; + + case mongo::Array: + case mongo::Object: + sub = f.embeddedObject(); + o->Set( v8::String::New( f.fieldName() ) , mongoToV8( sub , f.type() == mongo::Array ) ); + break; + + case mongo::Date: + o->Set( v8::String::New( f.fieldName() ) , v8::Date::New( f.date() ) ); + break; + + case mongo::Bool: + o->Set( v8::String::New( f.fieldName() ) , v8::Boolean::New( f.boolean() ) ); + break; + + case mongo::jstNULL: + o->Set( v8::String::New( f.fieldName() ) , v8::Null() ); + break; + + case mongo::RegEx: { + v8::Function * regex = getNamedCons( "RegExp" ); + + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.regex() ); + argv[1] = v8::String::New( f.regexFlags() ); + + o->Set( v8::String::New( f.fieldName() ) , regex->NewInstance( 2 , argv ) ); + break; + } + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + } + + return o; +} + +void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value ){ + + if ( value->IsString() ){ + b.append( sname.c_str() , toSTLString( value ).c_str() ); + return; + } + + if ( value->IsFunction() ){ + b.appendCode( sname.c_str() , toSTLString( value ).c_str() ); + return; + } + + if ( value->IsNumber() ){ + b.append( sname.c_str() , value->ToNumber()->Value() ); + return; + } + + if ( value->IsArray() ){ + BSONObj sub = v8ToMongo( value->ToObject() ); + b.appendArray( sname.c_str() , sub ); + return; + } + + if ( value->IsDate() ){ + b.appendDate( sname.c_str() , v8::Date::Cast( *value )->NumberValue() ); + return; + } + + if ( value->IsObject() ){ + string s = toSTLString( value ); + if ( s.size() && s[0] == '/' ){ + s = s.substr( 1 ); + string r = s.substr( 0 , s.find( "/" ) ); + string o = s.substr( s.find( "/" ) + 1 ); + b.appendRegex( sname.c_str() , r.c_str() , o.c_str() ); + } + else if ( value->ToObject()->GetPrototype()->IsObject() && + value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( String::New( "isObjectId" ) ) ){ + OID oid; + oid.init( toSTLString( value ) ); + b.appendOID( sname.c_str() , &oid ); + } + else { + BSONObj sub = v8ToMongo( value->ToObject() ); + b.append( sname.c_str() , sub ); + } + return; + } + + if ( value->IsBoolean() ){ + b.appendBool( sname.c_str() , value->ToBoolean()->Value() ); + return; + } + + else if ( value->IsUndefined() ){ + return; + } + + else if ( value->IsNull() ){ + b.appendNull( sname.c_str() ); + return; + } + + cout << "don't know how to covert to mongo field [" << name << "]\t" << value << endl; +} + +BSONObj v8ToMongo( v8::Handle<v8::Object> o ){ + BSONObjBuilder b; + + v8::Handle<v8::String> idName = String::New( "_id" ); + if ( o->HasRealNamedProperty( idName ) ){ + v8ToMongoElement( b , idName , "_id" , o->Get( idName ) ); + } + + Local<v8::Array> names = o->GetPropertyNames(); + for ( unsigned int i=0; i<names->Length(); i++ ){ + v8::Local<v8::String> name = names->Get(v8::Integer::New(i) )->ToString(); + + if ( o->GetPrototype()->IsObject() && + o->GetPrototype()->ToObject()->HasRealNamedProperty( name ) ) + continue; + + v8::Local<v8::Value> value = o->Get( name ); + + const string sname = toSTLString( name ); + if ( sname == "_id" ) + continue; + + v8ToMongoElement( b , name , sname , value ); + } + return b.doneAndDecouple(); +} + +#define GETNS char ns[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); + +DBClientConnection * getConnection( const Arguments& args ){ + Local<External> c = External::Cast( *(args.This()->Get( CONN_STRING )) ); + DBClientConnection * conn = (DBClientConnection*)(c->Value()); + assert( conn ); + return conn; +} + +// ---- real methods + +/** + 0 - namespace + 1 - query + 2 - fields + 3 - limit + 4 - skip + */ +Handle<Value> mongoFind(const Arguments& args){ + jsassert( args.Length() == 5 , "find needs 5 args" ); + jsassert( args[1]->IsObject() , "needs to be an object" ); + DBClientConnection * conn = getConnection( args ); + GETNS; + + BSONObj q = v8ToMongo( args[1]->ToObject() ); + DDD( "query:" << q ); + + BSONObj fields; + bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0; + if ( haveFields ) + fields = v8ToMongo( args[2]->ToObject() ); + + auto_ptr<mongo::DBClientCursor> cursor = conn->query( ns, q , args[3]->ToNumber()->Value() , args[4]->ToNumber()->Value() , haveFields ? &fields : 0 ); + + Local<v8::Object> mongo = args.This(); + + v8::Function * cons = (v8::Function*)( *( mongo->Get( String::New( "internalCursor" ) ) ) ); + Local<v8::Object> c = cons->NewInstance(); + + c->Set( v8::String::New( "cursor" ) , External::New( cursor.release() ) ); + return c; +} + +v8::Handle<v8::Value> mongoInsert(const v8::Arguments& args){ + jsassert( args.Length() == 2 , "insert needs 2 args" ); + jsassert( args[1]->IsObject() , "have to insert an object" ); + + DBClientConnection * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + + if ( ! in->Has( String::New( "_id" ) ) ){ + v8::Handle<v8::Value> argv[0]; + in->Set( String::New( "_id" ) , getObjectIdCons()->NewInstance( 0 , argv ) ); + } + + BSONObj o = v8ToMongo( in ); + + DDD( "want to save : " << o.jsonString() ); + conn->insert( ns , o ); + + return args[1]; +} + +v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args){ + jsassert( args.Length() == 2 , "remove needs 2 args" ); + jsassert( args[1]->IsObject() , "have to remove an object template" ); + + DBClientConnection * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + BSONObj o = v8ToMongo( in ); + + DDD( "want to remove : " << o.jsonString() ); + conn->remove( ns , o ); + + return v8::Undefined(); +} + +v8::Handle<v8::Value> mongoUpdate(const v8::Arguments& args){ + jsassert( args.Length() >= 3 , "update needs at least 3 args" ); + jsassert( args[1]->IsObject() , "1st param to update has to be an object" ); + jsassert( args[2]->IsObject() , "2nd param to update has to be an object" ); + + DBClientConnection * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> q = args[1]->ToObject(); + v8::Handle<v8::Object> o = args[2]->ToObject(); + + bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value(); + + conn->update( ns , v8ToMongo( q ) , v8ToMongo( o ) , upsert ); + + return v8::Undefined(); +} + + + + +// --- cursor --- + +mongo::DBClientCursor * getCursor( const Arguments& args ){ + Local<External> c = External::Cast( *(args.This()->Get( String::New( "cursor" ) ) ) ); + mongo::DBClientCursor * cursor = (mongo::DBClientCursor*)(c->Value()); + return cursor; +} + +v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args){ + return v8::Undefined(); +} + +v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args){ + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return v8::Undefined(); + BSONObj o = cursor->next(); + return mongoToV8( o ); +} + +v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args){ + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return Boolean::New( false ); + return Boolean::New( cursor->more() ); +} + + +// --- DB ---- + +v8::Handle<v8::Value> dbInit(const v8::Arguments& args){ + assert( args.Length() == 2 ); + + args.This()->Set( String::New( "_mongo" ) , args[0] ); + args.This()->Set( String::New( "_name" ) , args[1] ); + + for ( int i=0; i<args.Length(); i++ ) + assert( ! args[i]->IsUndefined() ); + + return v8::Undefined(); +} + +v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ){ + assert( args.Length() == 4 ); + + args.This()->Set( String::New( "_mongo" ) , args[0] ); + args.This()->Set( String::New( "_db" ) , args[1] ); + args.This()->Set( String::New( "_shortName" ) , args[2] ); + args.This()->Set( String::New( "_fullName" ) , args[3] ); + + for ( int i=0; i<args.Length(); i++ ) + assert( ! args[i]->IsUndefined() ); + + return v8::Undefined(); +} + +v8::Handle<v8::Value> dbQueryInit( const v8::Arguments& args ){ + + v8::Handle<v8::Object> t = args.This(); + + assert( args.Length() >= 4 ); + + t->Set( String::New( "_mongo" ) , args[0] ); + t->Set( String::New( "_db" ) , args[1] ); + t->Set( String::New( "_collection" ) , args[2] ); + t->Set( String::New( "_ns" ) , args[3] ); + + if ( args.Length() > 4 && args[4]->IsObject() ) + t->Set( String::New( "_query" ) , args[4] ); + else + t->Set( String::New( "_query" ) , v8::Object::New() ); + + if ( args.Length() > 5 && args[5]->IsObject() ) + t->Set( String::New( "_fields" ) , args[5] ); + else + t->Set( String::New( "_fields" ) , v8::Null() ); + + + if ( args.Length() > 6 && args[6]->IsNumber() ) + t->Set( String::New( "_limit" ) , args[6] ); + else + t->Set( String::New( "_limit" ) , Number::New( 0 ) ); + + if ( args.Length() > 7 && args[7]->IsNumber() ) + t->Set( String::New( "_skip" ) , args[7] ); + else + t->Set( String::New( "_skip" ) , Number::New( 0 ) ); + + t->Set( String::New( "_cursor" ) , v8::Null() ); + t->Set( String::New( "_numReturned" ) , v8::Number::New(0) ); + t->Set( String::New( "_special" ) , Boolean::New(false) ); + + return v8::Undefined(); +} + +v8::Handle<v8::Value> collectionFallback( v8::Local<v8::String> name, const v8::AccessorInfo &info) { + DDD( "collectionFallback [" << name << "]" ); + + v8::Handle<v8::Value> real = info.This()->GetPrototype()->ToObject()->Get( name ); + if ( ! real->IsUndefined() ) + return real; + + string sname = toSTLString( name ); + if ( sname[0] == '_' ){ + if ( ! ( info.This()->HasRealNamedProperty( name ) ) ) + return v8::Undefined(); + return info.This()->GetRealNamedPropertyInPrototypeChain( name ); + } + + v8::Handle<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( String::New( "getCollection" ) ); + assert( getCollection->IsFunction() ); + + v8::Function * f = (v8::Function*)(*getCollection); + v8::Handle<v8::Value> argv[1]; + argv[0] = name; + + return f->Call( info.This() , 1 , argv ); +} + +v8::Handle<v8::Value> dbQueryIndexAccess( uint32_t index , const v8::AccessorInfo& info ){ + v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get( String::New( "arrayAccess" ) ); + assert( arrayAccess->IsFunction() ); + + v8::Function * f = (v8::Function*)(*arrayAccess); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::Number::New( index ); + + return f->Call( info.This() , 1 , argv ); +} + +v8::Function * getNamedCons( const char * name ){ + return v8::Function::Cast( *(v8::Context::GetCurrent()->Global()->Get( String::New( name ) ) ) ); +} + +v8::Function * getObjectIdCons(){ + return getNamedCons( "ObjectId" ); +} + +v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ){ + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function * f = getObjectIdCons(); + it = f->NewInstance(); + } + + OID oid; + + if ( args.Length() == 0 ){ + oid.init(); + } + else { + string s = toSTLString( args[0] ); + oid.init( s ); + } + + it->Set( String::New( "str" ) , String::New( oid.str().c_str() ) ); + + return it; +} diff --git a/shell/MongoJS.h b/shell/MongoJS.h new file mode 100644 index 00000000000..483d342c866 --- /dev/null +++ b/shell/MongoJS.h @@ -0,0 +1,49 @@ +// MongoJS.h + +#pragma once + +#include <v8.h> +#include <cstring> +#include <cstdio> +#include <cstdlib> + +#include "mongo/client/dbclient.h" + +void installMongoGlobals( v8::Handle<v8::ObjectTemplate>& global ); + +// the actual globals +v8::Handle<v8::Value> mongoInject(const v8::Arguments& args); + +// utils +v8::Local<v8::Object> mongoToV8( mongo::BSONObj & m , bool array = 0 ); +mongo::BSONObj v8ToMongo( v8::Handle<v8::Object> o ); + +mongo::DBClientConnection * getConnection( const v8::Arguments& args ); + + + +// Mongo members +v8::Handle<v8::Value> mongoInit(const v8::Arguments& args); +v8::Handle<v8::Value> mongoFind(const v8::Arguments& args); +v8::Handle<v8::Value> mongoInsert(const v8::Arguments& args); +v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args); +v8::Handle<v8::Value> mongoUpdate(const v8::Arguments& args); + + +v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args); +v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args); +v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args); + +// DB members + +v8::Handle<v8::Value> dbInit(const v8::Arguments& args); +v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ); +v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ); + +v8::Handle<v8::Value> dbQueryInit( const v8::Arguments& args ); +v8::Handle<v8::Value> dbQueryIndexAccess( uint32_t index , const v8::AccessorInfo& info ); + +v8::Handle<v8::Value> collectionFallback( v8::Local<v8::String> name, const v8::AccessorInfo &info); + +v8::Function * getNamedCons( const char * name ); +v8::Function * getObjectIdCons(); diff --git a/shell/ShellUtils.cpp b/shell/ShellUtils.cpp new file mode 100644 index 00000000000..80ddb341bb3 --- /dev/null +++ b/shell/ShellUtils.cpp @@ -0,0 +1,188 @@ +// ShellUtils.cpp + +#include "ShellUtils.h" +#include <boost/thread/thread.hpp> +#include <boost/thread/xtime.hpp> +#include <iostream> + +using namespace std; +using namespace v8; + +v8::Handle<v8::Value> Print(const v8::Arguments& args) { + bool first = true; + for (int i = 0; i < args.Length(); i++) { + v8::HandleScope handle_scope; + if (first) { + first = false; + } else { + printf(" "); + } + v8::String::Utf8Value str(args[i]); + printf("%s", *str); + } + printf("\n"); + return v8::Undefined(); +} + +std::string toSTLString( const v8::Handle<v8::Value> & o ){ + v8::String::Utf8Value str(o); + const char * foo = *str; + std::string s(foo); + return s; +} + +std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::Value> & o ){ + v8::String::Utf8Value str(o); + s << *str; + return s; +} + +std::ostream& operator<<( std::ostream &s, const v8::TryCatch * try_catch ){ + v8::HandleScope handle_scope; + v8::String::Utf8Value exception(try_catch->Exception()); + v8::Handle<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + s << *exception << endl; + } + else { + + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + cout << *filename << ":" << linenum << " " << *exception << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + cout << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + cout << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + cout << "^"; + + cout << endl; + } + + if ( try_catch->next_ ) + s << try_catch->next_; + + return s; +} + +v8::Handle<v8::Value> Load(const v8::Arguments& args) { + for (int i = 0; i < args.Length(); i++) { + v8::HandleScope handle_scope; + v8::String::Utf8Value file(args[i]); + v8::Handle<v8::String> source = ReadFile(*file); + if (source.IsEmpty()) { + return v8::ThrowException(v8::String::New("Error loading file")); + } + if (!ExecuteString(source, v8::String::New(*file), false, false)) { + return v8::ThrowException(v8::String::New("Error executing file")); + } + } + return v8::Undefined(); +} + + +v8::Handle<v8::Value> Quit(const v8::Arguments& args) { + // If not arguments are given args[0] will yield undefined which + // converts to the integer value 0. + int exit_code = args[0]->Int32Value(); + exit(exit_code); + return v8::Undefined(); +} + + +v8::Handle<v8::Value> Version(const v8::Arguments& args) { + return v8::String::New(v8::V8::GetVersion()); +} + +v8::Handle<v8::String> ReadFile(const char* name) { + FILE* file = fopen(name, "rb"); + if (file == NULL) return v8::Handle<v8::String>(); + + fseek(file, 0, SEEK_END); + int size = ftell(file); + rewind(file); + + char* chars = new char[size + 1]; + chars[size] = '\0'; + for (int i = 0; i < size;) { + int read = fread(&chars[i], 1, size - i, file); + i += read; + } + fclose(file); + v8::Handle<v8::String> result = v8::String::New(chars, size); + delete[] chars; + return result; +} + + +bool ExecuteString(v8::Handle<v8::String> source, v8::Handle<v8::Value> name, + bool print_result, bool report_exceptions ){ + + v8::HandleScope handle_scope; + v8::TryCatch try_catch; + + v8::Handle<v8::Script> script = v8::Script::Compile(source, name); + if (script.IsEmpty()) { + if (report_exceptions) + ReportException(&try_catch); + return false; + } + + v8::Handle<v8::Value> result = script->Run(); + if ( result.IsEmpty() ){ + if (report_exceptions) + ReportException(&try_catch); + return false; + } + + if ( print_result ){ + + Local<Context> current = Context::GetCurrent(); + Local<Object> global = current->Global(); + + Local<Value> shellPrint = global->Get( String::New( "shellPrint" ) ); + + if ( shellPrint->IsFunction() ){ + v8::Function * f = (v8::Function*)(*shellPrint); + v8::Handle<v8::Value> argv[1]; + argv[0] = result; + f->Call( global , 1 , argv ); + } + else if ( ! result->IsUndefined() ){ + cout << result << endl; + } + } + + return true; +} + +v8::Handle<v8::Value> JSSleep(const v8::Arguments& args){ + assert( args.Length() == 1 ); + assert( args[0]->IsNumber() ); + + + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.nsec += args[0]->ToNumber()->Value() * 1000000; + boost::thread::sleep(xt); + + return v8::Undefined(); +} + +void ReportException(v8::TryCatch* try_catch) { + cout << try_catch << endl; +} + +void installShellUtils( v8::Handle<v8::ObjectTemplate>& global ){ + global->Set(v8::String::New("sleep"), v8::FunctionTemplate::New(JSSleep)); + global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)); + global->Set(v8::String::New("load"), v8::FunctionTemplate::New(Load)); + global->Set(v8::String::New("quit"), v8::FunctionTemplate::New(Quit)); + global->Set(v8::String::New("version"), v8::FunctionTemplate::New(Version)); +} diff --git a/shell/ShellUtils.h b/shell/ShellUtils.h new file mode 100644 index 00000000000..83b2790bf26 --- /dev/null +++ b/shell/ShellUtils.h @@ -0,0 +1,37 @@ +// ShellUtils.h + +#pragma once + +#include <v8.h> + +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include <assert.h> +#include <iostream> + +// Executes a string within the current v8 context. +bool ExecuteString(v8::Handle<v8::String> source, + v8::Handle<v8::Value> name, + bool print_result, + bool report_exceptions); + +v8::Handle<v8::Value> Print(const v8::Arguments& args); +v8::Handle<v8::Value> Load(const v8::Arguments& args); +v8::Handle<v8::Value> Quit(const v8::Arguments& args); +v8::Handle<v8::Value> Version(const v8::Arguments& args); +v8::Handle<v8::Value> JSSleep(const v8::Arguments& args); + +v8::Handle<v8::String> ReadFile(const char* name); + + +void ReportException(v8::TryCatch* handler); + + +void installShellUtils( v8::Handle<v8::ObjectTemplate>& global ); + +#define jsassert(x,msg) assert(x) + +std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::Value> & o ); +std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::TryCatch> * try_catch ); +std::string toSTLString( const v8::Handle<v8::Value> & o ); diff --git a/shell/collection.js b/shell/collection.js new file mode 100644 index 00000000000..e679bad4d47 --- /dev/null +++ b/shell/collection.js @@ -0,0 +1,269 @@ +// collection.js + + +if ( ( typeof DBCollection ) == "undefined" ){ + print( "defined DBCollection" ); + + DBCollection = function( mongo , db , shortName , fullName ){ + this._mongo = mongo; + this._db = db; + this._shortName = shortName; + this._fullName = fullName; + + assert( this._mongo , "no mongo" ); + assert( this._db , "no db" ); + assert( this._shortName , "no shortName" ); + assert( this._fullName , "no fullName" ); + } +} + +DBCollection.prototype.getName = function(){ + return this._shortName; +} + +DBCollection.prototype.help = function(){ + print("DBCollection help"); + print("\tdb.foo.getDB() get DB object associated with collection"); + print("\tdb.foo.findOne(...)"); + print("\tdb.foo.find(...)"); + print("\tdb.foo.find(...).sort(...)"); + print("\tdb.foo.find(...).limit(n)"); + print("\tdb.foo.find(...).skip(n)"); + print("\tdb.foo.find(...).count()"); + print("\tdb.foo.count()"); + print("\tdb.foo.save(obj)"); + print("\tdb.foo.update(query, object[, upsert_bool])"); + print("\tdb.foo.ensureIndex(keypattern)"); + print("\tdb.foo.dropIndexes()"); + print("\tdb.foo.dropIndex(name)"); + print("\tdb.foo.getIndexes()"); + print("\tdb.foo.drop() drop the collection"); + print("\tdb.foo.validate()"); +} + +DBCollection.prototype.getFullName = function(){ + return this._fullName; +} +DBCollection.prototype.getDB = function(){ + return this._db; +} + +DBCollection.prototype._dbCommand = function( cmd ){ + return this._db._dbCommand( cmd ); +} + +DBCollection.prototype._massageObject = function( q ){ + if ( ! q ) + return {}; + + var type = typeof q; + + if ( type == "function" ) + return { $where : q }; + + if ( q.isObjectId ) + return { _id : q }; + + if ( type == "object" ) + return q; + + if ( type == "string" ){ + if ( q.length == 24 ) + return { _id : q }; + + throw "don't know how to handle string [" + q + "]"; + } + + throw "don't know how to massage : " + type; + +} + +DBCollection.prototype._validateForStorage = function( o ){ + for ( k in o ){ + if ( k.indexOf( "." ) >= 0 ) + throw "can't have . in field names [" + k + "]" ; + } +} + +DBCollection.prototype.find = function( query , fields , limit , skip ){ + return new DBQuery( this._mongo , this._db , this , + this._fullName , this._massageObject( query ) , fields , limit , skip ); +} + +DBCollection.prototype.findOne = function( query , fields ){ + var cursor = this._mongo.find( this._fullName , this._massageObject( query ) || {} , fields , 1 , 0 ); + if ( ! cursor.hasNext() ) + return null; + var ret = cursor.next(); + if ( cursor.hasNext() ) throw "something is wrong"; + if ( ret.$err ) + throw "error " + tojson( ret ); + return ret; +} + +DBCollection.prototype.insert = function( obj ){ + if ( ! obj ) + throw "no object!"; + this._validateForStorage( obj ); + this._mongo.insert( this._fullName , obj ); +} + +DBCollection.prototype.remove = function( t ){ + this._mongo.remove( this._fullName , this._massageObject( t ) ); +} + +DBCollection.prototype.update = function( query , obj , upsert ){ + assert( query , "need a query" ); + assert( obj , "need an object" ); + return this._mongo.update( this._fullName , query , obj , upsert ? true : false ); +} + +DBCollection.prototype.save = function( obj ){ + if ( ! obj._id ){ + this.insert( obj ); + } + else { + this.update( { _id : obj._id } , obj , true ); + } +} + +DBCollection.prototype._genIndexName = function( keys ){ + var name = ""; + for ( k in keys ){ + if ( name.length > 0 ) + name += "_"; + name += k + "_"; + + var v = keys[k]; + if ( typeof v == "number" ) + name += v; + } + return name; +} + +DBCollection.prototype.createIndex = function( keys , name ){ + name = name || this._genIndexName( keys ); + var o = { ns : this._fullName , key : keys , name : name }; + this._db.getCollection( "system.indexes" ).insert( o ); +} + +DBCollection.prototype.ensureIndex = function( keys , name ){ + name = name || this._genIndexName( keys ); + this._indexCache = this._indexCache || {}; + if ( this._indexCache[ name ] ) + return false; + + this.createIndex( keys , name ); + this._indexCache[name] = true; + return true; +} + +DBCollection.prototype.resetIndexCache = function(){ + this._indexCache = {}; +} + +DBCollection.prototype.dropIndexes = function() { + this.resetIndexCache(); + + var res = this._db.runCommand( { deleteIndexes: this.getName(), index: "*" } ); + assert( res , "no result from dropIndex result" ); + if ( res.ok ) + return res; + + if ( res.errmsg.match( /not found/ ) ) + return res; + + throw "error dropping indexes : " + tojson( res ); +} + + +DBCollection.prototype.drop = function(){ + var res = this.dropIndexes(); + if( ! res ) + return res; + + if( ! res.ok ) { + res.errmsg = "dropping indexes..." + res.errmsg; + return res; + } + + res = this._db.runCommand( { drop: this.getName() } ); + return res; +} + +DBCollection.prototype.validate = function() { + var res = this._db.runCommand( { validate: this.getName() } ); + + res.valid = false; + + if ( res.result ){ + var str = "-" + tojson( res.result ); + res.valid = ! ( str.match( /exception/ ) || str.match( /corrupt/ ) ); + + var p = /lastExtentSize:(\d+)/; + var r = p.exec( str ); + if ( r ){ + res.lastExtentSize = Number( r[1] ); + } + } + + return res; +} + +DBCollection.prototype.getIndexes = function(){ + return this.getDB().getCollection( "system.indexes" ).find( { ns : this.getFullName() } ); +} + +DBCollection.prototype.getIndexKeys = function(){ + return this.getIndexes().toArray().map( + function(i){ + return i.key; + } + ); +} + + +DBCollection.prototype.count = function(){ + return this.find().count(); +} + +/** + * Drop free lists. Normally not used. + * Note this only does the collection itself, not the namespaces of its indexes (see cleanAll). + */ +DBCollection.prototype.clean = function() { + return this._dbCommand( { clean: this.getName() } ); +} + + + +/** + * <p>Drop a specified index.</p> + * + * <p> + * Name is the name of the index in the system.indexes name field. (Run db.system.indexes.find() to + * see example data.) + * </p> + * + * <p>Note : alpha: space is not reclaimed </p> + * @param {String} name of index to delete. + * @return A result object. result.ok will be true if successful. + */ +DBCollection.prototype.dropIndex = function(index) { + assert(index , "need to specify index to dropIndex" ); + + if ( ! isString( index ) && isObject( index ) ) + index = this._genIndexName( index ); + + var res = this._dbCommand( { deleteIndexes: this.getName(), index: index } ); + this.resetIndexCache(); + return res; +} + +DBCollection.prototype.getCollection = function( subName ){ + return this._db.getCollection( this._shortName + "." + subName ); +} + +DBCollection.prototype.toString = function(){ + return this.getFullName(); +} diff --git a/shell/db.js b/shell/db.js new file mode 100644 index 00000000000..ef2d6bf274c --- /dev/null +++ b/shell/db.js @@ -0,0 +1,327 @@ +// db.js + +if ( typeof DB == "undefined" ){ + DB = function( mongo , name ){ + this._mongo = mongo; + this._name = name; + } +} + +DB.prototype.getMongo = function(){ + assert( this._mongo , "why no mongo!" ); + return this._mongo; +} + +DB.prototype.getName = function(){ + return this._name; +} + +DB.prototype.getCollection = function( name ){ + return new DBCollection( this._mongo , this , name , this._name + "." + name ); +} + +DB.prototype.runCommand = function( obj ){ + if ( typeof( obj ) == "string" ){ + var n = {}; + n[obj] = 1; + obj = n; + } + return this.getCollection( "$cmd" ).findOne( obj ); +} + +DB.prototype._dbCommand = DB.prototype.runCommand; + +DB.prototype.addUser = function( username , pass ){ + var c = this.getCollection( "system.users" ); + + var u = c.findOne( { user : username } ) || { user : username }; + u.pwd = hex_md5( "mongo" + pass ); + print( tojson( u ) ); + + c.save( u ); +} + +DB.prototype.auth = function( username , pass ){ + var n = this.runCommand( { getnonce : 1 } ); + + var a = this.runCommand( + { + authenticate : 1 , + user : username , + nonce : n.nonce , + key : hex_md5( n.nonce + username + hex_md5( "mongo" + pass ) ) + } + ); + + return a.ok; +} + +/** + Create a new collection in the database. Normally, collection creation is automatic. You would + use this function if you wish to specify special options on creation. + + If the collection already exists, no action occurs. + + <p>Options:</p> + <ul> + <li> + size: desired initial extent size for the collection. Must be <= 1000000000. + for fixed size (capped) collections, this size is the total/max size of the + collection. + </li> + <li> + capped: if true, this is a capped collection (where old data rolls out). + </li> + <li> max: maximum number of objects if capped (optional).</li> + </ul> + + <p>Example: </p> + + <code>db.createCollection("movies", { size: 10 * 1024 * 1024, capped:true } );</code> + + * @param {String} name Name of new collection to create + * @param {Object} options Object with options for call. Options are listed above. + * @return SOMETHING_FIXME +*/ +DB.prototype.createCollection = function(name, opt) { + var options = opt || {}; + var cmd = { create: name, capped: options.capped, size: options.size, max: options.max }; + var res = this._dbCommand(cmd); + return res; +} + +/** + * Returns the current profiling level of this database + * @return SOMETHING_FIXME or null on error + */ + DB.prototype.getProfilingLevel = function() { + var res = this._dbCommand( { profile: -1 } ); + return res ? res.was : null; +} + + +/** + Erase the entire database. (!) + + * @return Object returned has member ok set to true if operation succeeds, false otherwise. +*/ +DB.prototype.dropDatabase = function() { + return this._dbCommand( { dropDatabase: 1 } ); +} + + +DB.prototype.help = function() { + print("DB methods:"); + print("\tdb.auth(username, password)"); + print("\tdb.getMongo() get the server connection object"); + print("\tdb.getName()"); + print("\tdb.getCollection(cname) same as db['cname'] or db.cname"); + print("\tdb.runCommand(cmdObj) run a database command. if cmdObj is a string, turns it into { cmdObj : 1 }"); + print("\tdb.addUser(username, password)"); + print("\tdb.createCollection(name, { size : ..., capped : ..., max : ... } )"); + print("\tdb.getProfilingLevel()"); + print("\tdb.setProfilingLevel(level) 0=off 1=slow 2=all"); + print("\tdb.eval(func, args) run code server-side"); + print("\tdb.getLastError()"); + print("\tdb.getPrevError()"); + print("\tdb.resetError()"); + print("\tdb.getCollectionNames()"); + print("\tdb.group(ns, key[, keyf], cond, reduce, initial)"); +} + +/** + * <p> Set profiling level for your db. Profiling gathers stats on query performance. </p> + * + * <p>Default is off, and resets to off on a database restart -- so if you want it on, + * turn it on periodically. </p> + * + * <p>Levels :</p> + * <ul> + * <li>0=off</li> + * <li>1=log very slow (>100ms) operations</li> + * <li>2=log all</li> + * @param {String} level Desired level of profiling + * @return SOMETHING_FIXME or null on error + */ +DB.prototype.setProfilingLevel = function(level) { + + if (level < 0 || level > 2) { + throw { dbSetProfilingException : "input level " + level + " is out of range [0..2]" }; + } + + if (level) { + // if already exists does nothing + this.createCollection("system.profile", { capped: true, size: 128 * 1024 } ); + } + return this._dbCommand( { profile: level } ); +} + + +/** + * <p> Evaluate a js expression at the database server.</p> + * + * <p>Useful if you need to touch a lot of data lightly; in such a scenario + * the network transfer of the data could be a bottleneck. A good example + * is "select count(*)" -- can be done server side via this mechanism. + * </p> + * + * <p> + * If the eval fails, an exception is thrown of the form: + * </p> + * <code>{ dbEvalException: { retval: functionReturnValue, ok: num [, errno: num] [, errmsg: str] } }</code> + * + * <p>Example: </p> + * <code>print( "mycount: " + db.eval( function(){db.mycoll.find({},{_id:ObjId()}).length();} );</code> + * + * @param {Function} jsfunction Javascript function to run on server. Note this it not a closure, but rather just "code". + * @return result of your function, or null if error + * + */ +DB.prototype.eval = function(jsfunction) { + var cmd = { $eval : jsfunction }; + if ( arguments.length > 1 ) { + cmd.args = argumentsToArray( arguments ).slice(1); + } + + var res = this._dbCommand( cmd ); + + if (!res.ok) + throw tojson( res ); + + return res.retval; +} + +DB.prototype.dbEval = DB.prototype.eval; + + +/** + * + * <p> + * Similar to SQL group by. For example: </p> + * + * <code>select a,b,sum(c) csum from coll where active=1 group by a,b</code> + * + * <p> + * corresponds to the following in 10gen: + * </p> + * + * <code> + db.group( + { + ns: "coll", + key: { a:true, b:true }, + // keyf: ..., + cond: { active:1 }, + reduce: function(obj,prev) { prev.csum += obj.c; } , + initial: { csum: 0 } + }); + </code> + * + * + * <p> + * An array of grouped items is returned. The array must fit in RAM, thus this function is not + * suitable when the return set is extremely large. + * </p> + * <p> + * To order the grouped data, simply sort it client side upon return. + * <p> + Defaults + cond may be null if you want to run against all rows in the collection + keyf is a function which takes an object and returns the desired key. set either key or keyf (not both). + * </p> +*/ +DB.prototype.group = function(parmsObj) { + + var groupFunction = function() { + var parms = args[0]; + var c = db[parms.ns].find(parms.cond||{}); + var map = new Map(); + + while( c.hasNext() ) { + var obj = c.next(); + + var key = {}; + if( parms.key ) { + for( var i in parms.key ) + key[i] = obj[i]; + } + else { + key = parms.$keyf(obj); + } + + var aggObj = map[key]; + if( aggObj == null ) { + var newObj = Object.extend({}, key); // clone + aggObj = map[key] = Object.extend(newObj, parms.initial) + } + parms.$reduce(obj, aggObj); + } + + var ret = map.values(); + return ret; + } + + var parms = Object.extend({}, parmsObj); + + if( parms.reduce ) { + parms.$reduce = parms.reduce; // must have $ to pass to db + delete parms.reduce; + } + + if( parms.keyf ) { + parms.$keyf = parms.keyf; + delete parms.keyf; + } + + return this.eval(groupFunction, parms); +} + +DB.prototype.resetError = function(){ + return this.runCommand( { reseterror : 1 } ); +} + +DB.prototype.forceError = function(){ + return this.runCommand( { forceerror : 1 } ); +} + +DB.prototype.getLastError = function(){ + return this.runCommand( { getlasterror : 1 } ).err; +} + +/* Return the last error which has occurred, even if not the very last error. + + Returns: + { err : <error message>, nPrev : <how_many_ops_back_occurred>, ok : 1 } + + result.err will be null if no error has occurred. + */ +DB.prototype.getPrevError = function(){ + return this.runCommand( { getpreverror : 1 } ); +} + +DB.prototype.getCollectionNames = function(){ + var all = []; + + var nsLength = this._name.length + 1; + + this.getCollection( "system.namespaces" ).find().forEach( + function(z){ + var name = z.name; + + if ( name.indexOf( "$" ) >= 0 ) + return; + + all.push( name.substring( nsLength ) ); + } + ); + return all; +} + +DB.prototype.tojson = function(){ + return this.toString(); +} + +DB.prototype.toString = function(){ + return this._name; +} + diff --git a/shell/dbshell.cpp b/shell/dbshell.cpp new file mode 100644 index 00000000000..4685056b2ae --- /dev/null +++ b/shell/dbshell.cpp @@ -0,0 +1,216 @@ +// dbshell.cpp + +#include <v8.h> + +#include <readline/readline.h> +#include <readline/history.h> + +#include "ShellUtils.h" +#include "MongoJS.h" + +#include "mongo.jsh" + + +void quitNicely( int sig ){ + write_history( ".dbshell" ); + exit(0); +} + +int main(int argc, char* argv[]) { + signal( SIGINT , quitNicely ); + + //v8::V8::SetFlagsFromCommandLine(&argc, argv, true); + + v8::HandleScope handle_scope; + + v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(); + + installShellUtils( global ); + installMongoGlobals( global ); + + + v8::Handle<v8::Context> context = v8::Context::New(NULL, global); + v8::Context::Scope context_scope(context); + + { // init mongo code + v8::HandleScope handle_scope; + if ( ! ExecuteString( v8::String::New( jsconcatcode ) , v8::String::New( "(mongo init)" ) , false , true ) ) + return -1; + } + + const char * host = "test"; + + string username; + string password; + + bool runShell = false; + + int argNumber = 1; + for ( ; argNumber < argc; argNumber++) { + const char* str = argv[argNumber]; + + if (strcmp(str, "--shell") == 0) { + runShell = true; + continue; + } + + if ( strcmp( str , "-u" ) == 0 ){ + username = argv[argNumber+1]; + argNumber++; + continue; + } + + if ( strcmp( str , "-p" ) == 0 ){ + password = argv[argNumber+1]; + argNumber++; + continue; + } + + if ( strstr( str , "-p" ) == str ){ + password = str; + password = password.substr( 2 ); + continue; + } + + if ( strcmp(str, "--help") == 0 || + strcmp(str, "-h" ) == 0 ) { + + cout + << "usage: ./dbshell [options] <db address> [file names]\n" + << "db address can be:\n" + << " foo = foo database on local machine\n" + << " 192.169.0.5/foo = foo database on 192.168.0.5 machine\n" + << " 192.169.0.5:9999/foo = foo database on 192.168.0.5 machine on port 9999\n" + << "options\n" + << " --shell run the shell after executing files\n" + << " -u <username>\n" + << " -p<password> - notice no space\n" + << "file names: a list of files to run. will exit after unless --shell is specified\n" + ; + + return 0; + } + + if (strcmp(str, "-f") == 0) { + continue; + } + + if (strncmp(str, "--", 2) == 0) { + printf("Warning: unknown flag %s.\nTry --help for options\n", str); + continue; + } + + { + const char * last = strstr( str , "/" ); + if ( last ) + last++; + else + last = str; + + if ( ! strstr( last , "." ) ){ + host = str; + continue; + } + + } + + break; + } + + { // init mongo code + v8::HandleScope handle_scope; + string setup = (string)"db = connect( \"" + host + "\")"; + if ( ! ExecuteString( v8::String::New( setup.c_str() ) , v8::String::New( "(connect)" ) , false , true ) ){ + cout << "error connecting!" << endl; + return -1; + } + + if ( username.size() && password.size() ){ + stringstream ss; + ss << "if ( ! db.auth( \"" << username << "\" , \"" << password << "\" ) ){ throw 'login failed'; }"; + + if ( ! ExecuteString( v8::String::New( ss.str().c_str() ) , v8::String::New( "(auth)" ) , true , true ) ){ + cout << "login failed" << endl; + return -1; + } + + + } + + } + + int numFiles = 0; + + for ( ; argNumber < argc; argNumber++) { + const char* str = argv[argNumber]; + + v8::HandleScope handle_scope; + v8::Handle<v8::String> file_name = v8::String::New(str); + v8::Handle<v8::String> source = ReadFile(str); + if (source.IsEmpty()) { + printf("Error reading '%s'\n", str); + return 1; + } + + if (!ExecuteString(source, file_name, false, true)){ + cout << "error processing: " << file_name << endl; + return 1; + } + + numFiles++; + } + + if ( numFiles == 0 ) + runShell = true; + + if ( runShell ){ + + using_history(); + read_history( ".dbshell" ); + + cout << "type \"help\" for help" << endl; + + v8::Handle<v8::Object> shellHelper = context->Global()->Get( v8::String::New( "shellHelper" ) )->ToObject(); + + while ( 1 ){ + + char * line = readline( "> " ); + + if ( ! line || ( strlen(line) == 4 && strstr( line , "exit" ) ) ){ + cout << "bye" << endl; + break; + } + + string code = line; + + { + string cmd = line; + if ( cmd.find( " " ) > 0 ) + cmd = cmd.substr( 0 , cmd.find( " " ) ); + + if ( shellHelper->HasRealNamedProperty( v8::String::New( cmd.c_str() ) ) ){ + stringstream ss; + ss << "shellHelper( \"" << cmd << "\" , \"" << code.substr( cmd.size() ) << "\" )"; + code = ss.str(); + } + + } + + v8::HandleScope handle_scope; + ExecuteString(v8::String::New( code.c_str() ), + v8::String::New("(shell)"), + true, + true); + + + if ( strlen( line ) ) + add_history( line ); + } + + write_history( ".dbshell" ); + } + + return 0; +} + + diff --git a/shell/md5.js b/shell/md5.js new file mode 100644 index 00000000000..9610e726432 --- /dev/null +++ b/shell/md5.js @@ -0,0 +1,279 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ +var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_md5(s){ + var theCore = core_md5(str2binl(s), s.length * chrsz); + return binl2hex( theCore ); +} +function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));} +function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));} +function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); } +function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); } +function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); } + +/* + * Perform a simple self-test to see if the VM is working + */ +function md5_vm_test() +{ + var actual = hex_md5("abc"); + return actual == "900150983cd24fb0d6963f7d28e17f72"; +} + +/* + * Calculate the MD5 of an array of little-endian words, and a bit length + */ +function core_md5(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + + a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + + c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + + a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + } + + return Array(a, b, c, d); + +} + +/* + * These functions implement the four basic operations the algorithm uses. + */ +function md5_cmn(q, a, b, x, s, t) +{ + return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); +} +function md5_ff(a, b, c, d, x, s, t) +{ + return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); +} +function md5_gg(a, b, c, d, x, s, t) +{ + return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); +} +function md5_hh(a, b, c, d, x, s, t) +{ + return md5_cmn(b ^ c ^ d, a, b, x, s, t); +} +function md5_ii(a, b, c, d, x, s, t) +{ + return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); +} + +/* + * Calculate the HMAC-MD5, of a key and some data + */ +function core_hmac_md5(key, data) +{ + var bkey = str2binl(key); + if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i = i + 1) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); + return core_md5(opad.concat(hash), 512 + 128); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function bit_rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert a string to an array of little-endian words + * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. + */ +function str2binl(str) +{ + var bin = Array(); + var mask = (1 << chrsz) - 1; + for(var i = 0; i < str.length * chrsz; i += chrsz) + bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); + return bin; +} + +/* + * Convert an array of little-endian words to a string + */ +function binl2str(bin) +{ + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); + return str; +} + +/* + * Convert an array of little-endian words to a hex string. + */ +function binl2hex(binarray) +{ + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i = 1 + i) + { + str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); + } + return str; +} + +/* + * Convert an array of little-endian words to a base-64 string + */ +function binl2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j = j + 1) + { + if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; +} diff --git a/shell/mongo.js b/shell/mongo.js new file mode 100644 index 00000000000..001b6ff66be --- /dev/null +++ b/shell/mongo.js @@ -0,0 +1,60 @@ +// mongo.js + +if ( typeof Mongo == "undefined" ){ + Mongo = function( host ){ + this.init( host ); + } +} + +Mongo.prototype.find = function( ns , query , fields , limit , skip ){ throw "find not implemented"; } +Mongo.prototype.insert = function( ns , obj ){ throw "insert not implemented"; } +Mongo.prototype.remove = function( ns , pattern ){ throw "remove not implemented;" } +Mongo.prototype.update = function( ns , query , obj ){ throw "update not implemented;" } + +mongoInject( Mongo.prototype ); + +Mongo.prototype.getDB = function( name ){ + return new DB( this , name ); +} + +Mongo.prototype.getDBs = function(){ + var res = this.getDB( "admin" ).runCommand( { "listDatabases" : 1 } ); + assert( res.ok == 1 , "listDatabases failed" ); + return res; +} + +Mongo.prototype.getDBNames = function(){ + return this.getDBs().databases.map( + function(z){ + return z.name; + } + ); +} + +Mongo.prototype.toString = function(){ + return "mongo connection"; +} + +connect = function( url , user , pass ){ + if ( user && ! pass ) + throw "you specified a user and not a password. either you need a password, or you're using the old connect api"; + + var idx = url.indexOf( "/" ); + + var db; + + if ( idx < 0 ) + db = new Mongo().getDB( url ); + else + db = new Mongo( url.substring( 0 , idx ) ).getDB( url.substring( idx + 1 ) ); + + if ( user && pass ){ + if ( ! db.auth( user , pass ) ){ + throw "couldn't login"; + } + } + + return db; +} + + diff --git a/shell/query.js b/shell/query.js new file mode 100644 index 00000000000..cce25b6edef --- /dev/null +++ b/shell/query.js @@ -0,0 +1,159 @@ +// query.js + +if ( typeof DBQuery == "undefined" ){ + print( "defining DBQuery" ); + DBQuery = function( mongo , db , collection , ns , query , fields , limit , skip ){ + + this._mongo = mongo; // 0 + this._db = db; // 1 + this._collection = collection; // 2 + this._ns = ns; // 3 + + this._query = query || {}; // 4 + this._fields = fields; // 5 + this._limit = limit || 0; // 6 + this._skip = skip || 0; // 7 + + this._cursor = null; + this._numReturned = 0; + this._special = false; + } +} + + +DBQuery.prototype.clone = function(){ + var q = new DBQuery( this._mongo , this._db , this._collection , this._ns , + this._query , this._fields , + this._limit , this._skip ); + q._special = this._special; + return q; +} + +DBQuery.prototype._ensureSpecial = function(){ + if ( this._special ) + return; + + var n = { query : this._query }; + this._query = n; + this._special = true; +} + +DBQuery.prototype._checkModify = function(){ + if ( this._cursor ) + throw "query already executed"; +} + +DBQuery.prototype._exec = function(){ + if ( ! this._cursor ){ + assert.eq( 0 , this._numReturned ); + this._cursor = this._mongo.find( this._ns , this._query , this._fields , this._limit , this._skip ); + this._cursorSeen = 0; + } + return this._cursor; +} + +DBQuery.prototype.limit = function( limit ){ + this._checkModify(); + this._limit = limit; + return this; +} + +DBQuery.prototype.skip = function( skip ){ + this._checkModify(); + this._skip = skip; + return this; +} + +DBQuery.prototype.hasNext = function(){ + this._exec(); + + if ( this._limit > 0 && this._cursorSeen >= this._limit ) + return false; + var o = this._cursor.hasNext(); + if ( o ) + this._cursorSeen++; + return o; +} + +DBQuery.prototype.next = function(){ + this._exec(); + + var ret = this._cursor.next(); + if ( ret.$err && this._numReturned == 0 && ! this.hasNext() ) + throw "error: " + tojson( ret ); + + this._numReturned++; + return ret; +} + +DBQuery.prototype.toArray = function(){ + if ( this._arr ) + return this._arr; + + var a = []; + while ( this.hasNext() ) + a.push( this.next() ); + this._arr = a; + return a; +} + +DBQuery.prototype.count = function(){ + var cmd = { count: this._collection.getName() }; + if ( this._query ){ + if ( this._special ) + cmd.query = this._query.query; + else + cmd.query = this._query; + } + + var res = this._db.runCommand( cmd ); + if( res && res.n != null ) return res.n; + throw { exception: "count failed", res: res }; +} + +DBQuery.prototype.length = function(){ + return this.toArray().length; +} + +DBQuery.prototype.sort = function( sortBy ){ + this._ensureSpecial(); + this._query.orderby = sortBy; + return this; +} + +DBQuery.prototype.forEach = function( func ){ + while ( this.hasNext() ) + func( this.next() ); +} + +DBQuery.prototype.arrayAccess = function( idx ){ + return this.toArray()[idx]; +} + +DBQuery.prototype.explain = function(){ + var n = this.clone(); + n._ensureSpecial(); + n._query.$explain = true; + return n.next(); +} + +DBQuery.prototype.shellPrint = function(){ + try { + var n = 0; + while ( this.hasNext() && n < 10 ){ + var s = tojson( this.next() ); + print( s ); + n++; + } + if ( this.hasNext() ) + print( "has more" ); + } + catch ( e ){ + print( e ); + } + +} + +DBQuery.prototype.toString = function(){ + return "DBQuery: " + this._ns + " -> " + tojson( this.query ); +} diff --git a/shell/utils.js b/shell/utils.js new file mode 100644 index 00000000000..a49d7a7f63b --- /dev/null +++ b/shell/utils.js @@ -0,0 +1,187 @@ + +assert = function( b , msg ){ + if ( b ) + return; + + throw "assert failed : " + msg; +} + +assert.eq = function( a , b , msg ){ + if ( a == b ) + return; + + throw "[" + a + "] != [" + b + "] are not equal : " + msg; +} + +Object.extend = function( dst , src ){ + for ( var k in src ){ + dst[k] = src[k]; + } + return dst; +} + +argumentsToArray = function( a ){ + var arr = []; + for ( var i=0; i<a.length; i++ ) + arr[i] = a[i]; + return arr; +} + +isString = function( x ){ + return typeof( x ) == "string"; +} + +isObject = function( x ){ + return typeof( x ) == "object"; +} + +String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g,""); +} +String.prototype.ltrim = function() { + return this.replace(/^\s+/,""); +} +String.prototype.rtrim = function() { + return this.replace(/\s+$/,""); +} + +Date.prototype.tojson = function(){ + return "\"" + this.toString() + "\""; +} + +RegExp.prototype.tojson = RegExp.prototype.toString; + +Array.prototype.tojson = function(){ + var s = "["; + for ( var i=0; i<this.length; i++){ + if ( i > 0 ) + s += ","; + s += tojson( this[i] ); + } + s += "]"; + return s; +} + +ObjectId.prototype.toString = function(){ + return this.str; +} + +ObjectId.prototype.tojson = function(){ + return "\"" + this.str + "\""; +} + +ObjectId.prototype.isObjectId = true; + +tojson = function( x ){ + if ( x == null || x == undefined ) + return ""; + + switch ( typeof x ){ + + case "string": + return "\"" + x + "\""; + + case "number": + case "boolean": + return "" + x; + + case "object": + return tojsonObject( x ); + + default: + throw "can't handle type " + ( typeof v ); + } + +} + +tojsonObject = function( x ){ + assert( typeof x == "object" , "tojsonObject needs object" ); + + if ( x.tojson ) + return x.tojson(); + + var s = "{"; + + var first = true; + for ( var k in x ){ + if ( first ) first = false; + else s += " , "; + + s += "\"" + k + "\" : " + tojson( x[k] ); + } + + return s + "}"; +} + +shellPrint = function( x ){ + if ( x != undefined ) + shellPrintHelper( x ); + + if ( db ){ + var e = db.getPrevError(); + if ( e.err ) { + if( e.nPrev <= 1 ) + print( "error on last call: " + tojson( e.err ) ); + else + print( "an error " + tojson(e.err) + " occurred " + e.nPrev + " operations back in the command invocation" ); + } + db.resetError(); + } +} + +shellPrintHelper = function( x ){ + + if ( typeof x != "object" ) + return print( x ); + + var p = x.shellPrint; + if ( typeof p == "function" ) + return x.shellPrint(); + + var p = x.tojson; + if ( typeof p == "function" ) + print( x.tojson() ); + else + print( tojson( x ) ); +} + +shellHelper = function( command , rest ){ + command = command.trim(); + var args = rest.trim().replace(/;$/,"").split( "\s+" ); + + if ( ! shellHelper[command] ) + throw "no command [" + command + "]"; + + return shellHelper[command].apply( null , args ); +} + +help = shellHelper.help = function(){ + print( "HELP" ); + print( "\t" + "show (dbs|collections|users)" ); + print( "\t" + "use <db name>" ); + print( "\t" + "db.help() help on DB methods"); + print( "\t" + "db.foo.find()" ); + print( "\t" + "db.foo.find( { a : 1 } )" ); + print( "\t" + "db.foo.help() help on collection methods"); +} + +shellHelper.use = function( dbname ){ + db = db.getMongo().getDB( dbname ); + print( "switched to db " + db.getName() ); +} + +shellHelper.show = function( what ){ + assert( typeof what == "string" ); + + if ( what == "users" ) + return db.system.users.find(); + + if ( what == "collections" || what == "tables" ) + return db.getCollectionNames(); + + if ( what == "dbs" ) + return db.getMongo().getDBNames(); + + throw "don't know how to show [" + what + "]"; + +} |