diff options
author | Antonin Kral <a.kral@bobek.cz> | 2011-03-17 00:05:43 +0100 |
---|---|---|
committer | Antonin Kral <a.kral@bobek.cz> | 2011-03-17 00:05:43 +0100 |
commit | 582fc32574a3b158c81e49cb00e6ae59205e66ba (patch) | |
tree | ac64a3243e0d2121709f685695247052858115c8 /scripting | |
parent | 2761bffa96595ac1698d86bbc2e95ebb0d4d6e93 (diff) | |
download | mongodb-582fc32574a3b158c81e49cb00e6ae59205e66ba.tar.gz |
Imported Upstream version 1.8.0
Diffstat (limited to 'scripting')
-rw-r--r-- | scripting/bench.cpp | 173 | ||||
-rw-r--r-- | scripting/engine.cpp | 253 | ||||
-rw-r--r-- | scripting/engine.h | 134 | ||||
-rw-r--r-- | scripting/engine_java.cpp | 77 | ||||
-rw-r--r-- | scripting/engine_java.h | 16 | ||||
-rw-r--r-- | scripting/engine_none.cpp | 2 | ||||
-rw-r--r-- | scripting/engine_spidermonkey.cpp | 624 | ||||
-rw-r--r-- | scripting/engine_spidermonkey.h | 16 | ||||
-rw-r--r-- | scripting/engine_v8.cpp | 323 | ||||
-rw-r--r-- | scripting/engine_v8.h | 55 | ||||
-rw-r--r-- | scripting/sm_db.cpp | 628 | ||||
-rw-r--r-- | scripting/utils.cpp | 23 | ||||
-rw-r--r-- | scripting/v8_db.cpp | 498 | ||||
-rw-r--r-- | scripting/v8_db.h | 92 | ||||
-rw-r--r-- | scripting/v8_utils.cpp | 141 | ||||
-rw-r--r-- | scripting/v8_utils.h | 4 | ||||
-rw-r--r-- | scripting/v8_wrapper.cpp | 282 | ||||
-rw-r--r-- | scripting/v8_wrapper.h | 4 |
18 files changed, 2007 insertions, 1338 deletions
diff --git a/scripting/bench.cpp b/scripting/bench.cpp new file mode 100644 index 0000000..2723985 --- /dev/null +++ b/scripting/bench.cpp @@ -0,0 +1,173 @@ +/** @file bench.cpp */ + +/* + * Copyright (C) 2010 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 "engine.h" +#include "../util/md5.hpp" +#include "../util/version.h" +#include "../client/dbclient.h" +#include "../client/connpool.h" +// --------------------------------- +// ---- benchmarking system -------- +// --------------------------------- + + +namespace mongo { + + + /** + * benchQuery( "foo" , { _id : 1 } ) + */ + BSONObj benchQuery( const BSONObj& args ) { + return BSONObj(); + } + + struct BenchRunConfig { + BenchRunConfig() { + host = "localhost"; + db = "test"; + + parallel = 1; + seconds = 1; + + active = true; + threadsReady = 0; + error = false; + } + + string host; + string db; + + unsigned parallel; + int seconds; + + BSONObj ops; + + bool active; // true at starts, gets set to false when should stop + AtomicUInt threadsReady; + + bool error; + }; + + static void benchThread( BenchRunConfig * config ) { + ScopedDbConnection conn( config->host ); + config->threadsReady++; + + while ( config->active ) { + BSONObjIterator i( config->ops ); + while ( i.more() ) { + BSONElement e = i.next(); + string ns = e["ns"].String(); + string op = e["op"].String(); + + if ( op == "findOne" ) { + conn->findOne( ns , e["query"].Obj() ); + } + else { + log() << "don't understand op: " << op << endl; + config->error = true; + return; + } + + } + } + + conn.done(); + } + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchRun( const BSONObj& argsFake ) { + assert( argsFake.firstElement().isABSONObj() ); + BSONObj args = argsFake.firstElement().Obj(); + + // setup + + BenchRunConfig config; + + if ( args["host"].type() == String ) + config.host = args["host"].String(); + if ( args["db"].type() == String ) + config.db = args["db"].String(); + + if ( args["parallel"].isNumber() ) + config.parallel = args["parallel"].numberInt(); + if ( args["seconds"].isNumber() ) + config.seconds = args["seconds"].numberInt(); + + + config.ops = args["ops"].Obj(); + + // execute + + ScopedDbConnection conn( config.host ); + + // start threads + vector<boost::thread*> all; + for ( unsigned i=0; i<config.parallel; i++ ) + all.push_back( new boost::thread( boost::bind( benchThread , &config ) ) ); + + // give them time to init + while ( config.threadsReady < config.parallel ) + sleepmillis( 1 ); + + BSONObj before; + conn->simpleCommand( "admin" , &before , "serverStatus" ); + + sleepsecs( config.seconds ); + + BSONObj after; + conn->simpleCommand( "admin" , &after , "serverStatus" ); + + conn.done(); + + config.active = false; + + for ( unsigned i=0; i<all.size(); i++ ) + all[i]->join(); + + if ( config.error ) + return BSON( "err" << 1 ); + + // compute actual ops/sec + + before = before["opcounters"].Obj(); + after = after["opcounters"].Obj(); + + BSONObjBuilder buf; + buf.append( "note" , "values per second" ); + + { + BSONObjIterator i( after ); + while ( i.more() ) { + BSONElement e = i.next(); + double x = e.number(); + x = x - before[e.fieldName()].number(); + buf.append( e.fieldName() , x / config.seconds ); + } + } + BSONObj zoo = buf.obj(); + return BSON( "" << zoo ); + } + + void installBenchmarkSystem( Scope& scope ) { + scope.injectNative( "benchRun" , benchRun ); + } + +} diff --git a/scripting/engine.cpp b/scripting/engine.cpp index da108c6..60e56ae 100644 --- a/scripting/engine.cpp +++ b/scripting/engine.cpp @@ -23,27 +23,27 @@ namespace mongo { long long Scope::_lastVersion = 1; - + int Scope::_numScopes = 0; - Scope::Scope() : _localDBName("") , _loadedVersion(0){ + Scope::Scope() : _localDBName("") , _loadedVersion(0) { _numScopes++; } - Scope::~Scope(){ + Scope::~Scope() { _numScopes--; } ScriptEngine::ScriptEngine() : _scopeInitCallback() { } - ScriptEngine::~ScriptEngine(){ + ScriptEngine::~ScriptEngine() { } - void Scope::append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ){ + void Scope::append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ) { int t = type( scopeName ); - - switch ( t ){ + + switch ( t ) { case Object: builder.append( fieldName , getObject( scopeName ) ); break; @@ -74,7 +74,7 @@ namespace mongo { builder.appendDate( fieldName , Date_t((unsigned long long)getNumber( scopeName )) ); break; case Code: - builder.appendCode( fieldName , getString( scopeName ).c_str() ); + builder.appendCode( fieldName , getString( scopeName ) ); break; default: stringstream temp; @@ -82,20 +82,20 @@ namespace mongo { temp << t; uassert( 10206 , temp.str() , 0 ); } - + } - int Scope::invoke( const char* code , const BSONObj& args, int timeoutMs ){ + int Scope::invoke( const char* code , const BSONObj& args, int timeoutMs ) { ScriptingFunction func = createFunction( code ); uassert( 10207 , "compile failed" , func ); return invoke( func , args, timeoutMs ); } - - bool Scope::execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs ){ - + + bool Scope::execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs ) { + path p( filename ); - if ( ! exists( p ) ){ + if ( ! exists( p ) ) { log() << "file [" << filename << "] doesn't exist" << endl; if ( assertOnError ) assert( 0 ); @@ -103,10 +103,10 @@ namespace mongo { } // iterate directories and recurse using all *.js files in the directory - if ( is_directory( p ) ){ + if ( is_directory( p ) ) { directory_iterator end; bool empty = true; - for (directory_iterator it (p); it != end; it++){ + for (directory_iterator it (p); it != end; it++) { empty = false; path sub (*it); if (!endsWith(sub.string().c_str(), ".js")) @@ -115,7 +115,7 @@ namespace mongo { return false; } - if (empty){ + if (empty) { log() << "directory [" << filename << "] doesn't have any *.js files" << endl; if ( assertOnError ) assert( 0 ); @@ -124,83 +124,97 @@ namespace mongo { return true; } - + File f; f.open( filename.c_str() , true ); - fileofs L = f.len(); - assert( L <= 0x7ffffffe ); - char * data = (char*)malloc( (size_t) L+1 ); + unsigned L; + { + fileofs fo = f.len(); + assert( fo <= 0x7ffffffe ); + L = (unsigned) fo; + } + boost::scoped_array<char> data (new char[L+1]); data[L] = 0; - f.read( 0 , data , (size_t) L ); - - return exec( data , filename , printResult , reportError , assertOnError, timeoutMs ); + f.read( 0 , data.get() , L ); + + int offset = 0; + if (data[0] == '#' && data[1] == '!') { + const char* newline = strchr(data.get(), '\n'); + if (! newline) + return true; // file of just shebang treated same as empty file + offset = newline - data.get(); + } + + StringData code (data.get() + offset, L - offset); + + return exec( code , filename , printResult , reportError , assertOnError, timeoutMs ); } - void Scope::storedFuncMod(){ + void Scope::storedFuncMod() { _lastVersion++; } - + void Scope::validateObjectIdString( const string &str ) { massert( 10448 , "invalid object id: length", str.size() == 24 ); - for ( string::size_type i=0; i<str.size(); i++ ){ + for ( string::size_type i=0; i<str.size(); i++ ) { char c = str[i]; if ( ( c >= '0' && c <= '9' ) || - ( c >= 'a' && c <= 'f' ) || - ( c >= 'A' && c <= 'F' ) ){ + ( c >= 'a' && c <= 'f' ) || + ( c >= 'A' && c <= 'F' ) ) { continue; } massert( 10430 , "invalid object id: not hex", false ); - } + } } - void Scope::loadStored( bool ignoreNotConnected ){ - if ( _localDBName.size() == 0 ){ + void Scope::loadStored( bool ignoreNotConnected ) { + if ( _localDBName.size() == 0 ) { if ( ignoreNotConnected ) return; uassert( 10208 , "need to have locallyConnected already" , _localDBName.size() ); } if ( _loadedVersion == _lastVersion ) return; - + _loadedVersion = _lastVersion; string coll = _localDBName + ".system.js"; - + static DBClientBase * db = createDirectClient(); - auto_ptr<DBClientCursor> c = db->query( coll , Query() ); + auto_ptr<DBClientCursor> c = db->query( coll , Query(), 0, 0, NULL, QueryOption_SlaveOk, 0 ); assert( c.get() ); - + set<string> thisTime; - - while ( c->more() ){ + + while ( c->more() ) { BSONObj o = c->next(); BSONElement n = o["_id"]; BSONElement v = o["value"]; - + uassert( 10209 , "name has to be a string" , n.type() == String ); uassert( 10210 , "value has to be set" , v.type() != EOO ); - + setElement( n.valuestr() , v ); thisTime.insert( n.valuestr() ); _storedNames.insert( n.valuestr() ); - + } // --- remove things from scope that were removed list<string> toremove; - for ( set<string>::iterator i=_storedNames.begin(); i!=_storedNames.end(); i++ ){ + for ( set<string>::iterator i=_storedNames.begin(); i!=_storedNames.end(); i++ ) { string n = *i; if ( thisTime.count( n ) == 0 ) toremove.push_back( n ); } - - for ( list<string>::iterator i=toremove.begin(); i!=toremove.end(); i++ ){ + + for ( list<string>::iterator i=toremove.begin(); i!=toremove.end(); i++ ) { string n = *i; _storedNames.erase( n ); execSetup( (string)"delete " + n , "clean up scope" ); @@ -208,11 +222,11 @@ namespace mongo { } - ScriptingFunction Scope::createFunction( const char * code ){ - if ( code[0] == '/' && code [1] == '*' ){ + ScriptingFunction Scope::createFunction( const char * code ) { + if ( code[0] == '/' && code [1] == '*' ) { code += 2; - while ( code[0] && code[1] ){ - if ( code[0] == '*' && code[1] == '/' ){ + while ( code[0] && code[1] ) { + if ( code[0] == '*' && code[1] == '/' ) { code += 2; break; } @@ -226,7 +240,7 @@ namespace mongo { _cachedFunctions[code] = f; return f; } - + typedef map< string , list<Scope*> > PoolToScopes; class ScopeCache { @@ -235,21 +249,21 @@ namespace mongo { ScopeCache() : _mutex("ScopeCache") { _magic = 17; } - - ~ScopeCache(){ + + ~ScopeCache() { assert( _magic == 17 ); _magic = 1; if ( inShutdown() ) return; - + clear(); } - void done( const string& pool , Scope * s ){ + void done( const string& pool , Scope * s ) { scoped_lock lk( _mutex ); list<Scope*> & l = _pools[pool]; - if ( l.size() > 10 ){ + if ( l.size() > 10 ) { delete s; } else { @@ -257,31 +271,31 @@ namespace mongo { s->reset(); } } - - Scope * get( const string& pool ){ + + Scope * get( const string& pool ) { scoped_lock lk( _mutex ); list<Scope*> & l = _pools[pool]; if ( l.size() == 0 ) return 0; - + Scope * s = l.back(); l.pop_back(); s->reset(); return s; } - - void clear(){ + + void clear() { set<Scope*> seen; - - for ( PoolToScopes::iterator i=_pools.begin() ; i != _pools.end(); i++ ){ - for ( list<Scope*>::iterator j=i->second.begin(); j != i->second.end(); j++ ){ + + for ( PoolToScopes::iterator i=_pools.begin() ; i != _pools.end(); i++ ) { + for ( list<Scope*>::iterator j=i->second.begin(); j != i->second.end(); j++ ) { Scope * s = *j; assert( ! seen.count( s ) ); delete s; seen.insert( s ); } } - + _pools.clear(); } @@ -295,12 +309,12 @@ namespace mongo { class PooledScope : public Scope { public: - PooledScope( const string pool , Scope * real ) : _pool( pool ) , _real( real ){ + PooledScope( const string pool , Scope * real ) : _pool( pool ) , _real( real ) { _real->loadStored( true ); }; - virtual ~PooledScope(){ + virtual ~PooledScope() { ScopeCache * sc = scopeCache.get(); - if ( sc ){ + if ( sc ) { sc->done( _pool , _real ); _real = 0; } @@ -312,88 +326,92 @@ namespace mongo { _real = 0; } } - - void reset(){ + + void reset() { _real->reset(); } - void init( BSONObj * data ){ + void init( const BSONObj * data ) { _real->init( data ); } - - void localConnect( const char * dbName ){ + + void localConnect( const char * dbName ) { _real->localConnect( dbName ); } - void externalSetup(){ + void externalSetup() { _real->externalSetup(); } - - double getNumber( const char *field ){ + + double getNumber( const char *field ) { return _real->getNumber( field ); } - string getString( const char *field ){ + string getString( const char *field ) { return _real->getString( field ); } - bool getBoolean( const char *field ){ + bool getBoolean( const char *field ) { return _real->getBoolean( field ); } - BSONObj getObject( const char *field ){ + BSONObj getObject( const char *field ) { return _real->getObject( field ); } - int type( const char *field ){ + int type( const char *field ) { return _real->type( field ); } - void setElement( const char *field , const BSONElement& val ){ + void setElement( const char *field , const BSONElement& val ) { _real->setElement( field , val ); } - void setNumber( const char *field , double val ){ + void setNumber( const char *field , double val ) { _real->setNumber( field , val ); } - void setString( const char *field , const char * val ){ + void setString( const char *field , const char * val ) { _real->setString( field , val ); } - void setObject( const char *field , const BSONObj& obj , bool readOnly=true ){ + void setObject( const char *field , const BSONObj& obj , bool readOnly=true ) { _real->setObject( field , obj , readOnly ); } - void setBoolean( const char *field , bool val ){ + void setBoolean( const char *field , bool val ) { _real->setBoolean( field , val ); } - void setThis( const BSONObj * obj ){ + void setThis( const BSONObj * obj ) { _real->setThis( obj ); } - - ScriptingFunction createFunction( const char * code ){ + + ScriptingFunction createFunction( const char * code ) { return _real->createFunction( code ); } - ScriptingFunction _createFunction( const char * code ){ + ScriptingFunction _createFunction( const char * code ) { return _real->createFunction( code ); } + void rename( const char * from , const char * to ) { + _real->rename( from , to ); + } + /** * @return 0 on success */ - int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs , bool ignoreReturn ){ + int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs , bool ignoreReturn ) { return _real->invoke( func , args , timeoutMs , ignoreReturn ); } - string getError(){ + string getError() { return _real->getError(); } - - bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ){ + + bool exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) { return _real->exec( code , name , printResult , reportError , assertOnError , timeoutMs ); } - bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ){ + bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) { return _real->execFile( filename , printResult , reportError , assertOnError , timeoutMs ); } - - void injectNative( const char *field, NativeFunction func ){ + + void injectNative( const char *field, NativeFunction func ) { _real->injectNative( field , func ); } - - void gc(){ + + void gc() { _real->gc(); } @@ -402,40 +420,57 @@ namespace mongo { Scope * _real; }; - auto_ptr<Scope> ScriptEngine::getPooledScope( const string& pool ){ - if ( ! scopeCache.get() ){ + auto_ptr<Scope> ScriptEngine::getPooledScope( const string& pool ) { + if ( ! scopeCache.get() ) { scopeCache.reset( new ScopeCache() ); } Scope * s = scopeCache->get( pool ); - if ( ! s ){ + if ( ! s ) { s = newScope(); } - + auto_ptr<Scope> p; p.reset( new PooledScope( pool , s ) ); return p; } - - void ScriptEngine::threadDone(){ + + void ScriptEngine::threadDone() { ScopeCache * sc = scopeCache.get(); - if ( sc ){ + if ( sc ) { sc->clear(); } } - + void ( *ScriptEngine::_connectCallback )( DBClientWithCommands & ) = 0; - - ScriptEngine * globalScriptEngine; + const char * ( *ScriptEngine::_checkInterruptCallback )() = 0; + unsigned ( *ScriptEngine::_getInterruptSpecCallback )() = 0; + + ScriptEngine * globalScriptEngine = 0; - bool hasJSReturn( const string& code ){ + bool hasJSReturn( const string& code ) { size_t x = code.find( "return" ); if ( x == string::npos ) return false; - return + return ( x == 0 || ! isalpha( code[x-1] ) ) && ! isalpha( code[x+6] ); } + + const char * jsSkipWhiteSpace( const char * raw ) { + while ( raw[0] ) { + while (isspace(*raw)) { + raw++; + } + + if ( raw[0] != '/' || raw[1] != '/' ) + break; + + while ( raw[0] && raw[0] != '\n' ) + raw++; + } + return raw; + } } - + diff --git a/scripting/engine.h b/scripting/engine.h index e097401..62afd77 100644 --- a/scripting/engine.h +++ b/scripting/engine.h @@ -20,10 +20,23 @@ #include "../pch.h" #include "../db/jsobj.h" -extern const char * jsconcatcode; // TODO: change name to mongoJSCode - namespace mongo { + struct JSFile { + const char* name; + const StringData& source; + }; + + namespace JSFiles { + extern const JSFile collection; + extern const JSFile db; + extern const JSFile mongo; + extern const JSFile mr; + extern const JSFile query; + extern const JSFile servers; + extern const JSFile utils; + } + typedef unsigned long long ScriptingFunction; typedef BSONObj (*NativeFunction) ( const BSONObj &args ); @@ -31,20 +44,35 @@ namespace mongo { public: Scope(); virtual ~Scope(); - + virtual void reset() = 0; - virtual void init( BSONObj * data ) = 0; - void init( const char * data ){ + virtual void init( const BSONObj * data ) = 0; + void init( const char * data ) { BSONObj o( data , 0 ); init( &o ); } - + virtual void localConnect( const char * dbName ) = 0; virtual void externalSetup() = 0; - + + class NoDBAccess { + Scope * _s; + public: + NoDBAccess( Scope * s ) { + _s = s; + } + ~NoDBAccess() { + _s->rename( "____db____" , "db" ); + } + }; + NoDBAccess disableDBAccess( const char * why ) { + rename( "db" , "____db____" ); + return NoDBAccess( this ); + } + virtual double getNumber( const char *field ) = 0; - virtual int getNumberInt( const char *field ){ return (int)getNumber( field ); } - virtual long long getNumberLongLong( const char *field ){ return (long long)getNumber( field ); } + virtual int getNumberInt( const char *field ) { return (int)getNumber( field ); } + virtual long long getNumberLongLong( const char *field ) { return (long long)getNumber( field ); } virtual string getString( const char *field ) = 0; virtual bool getBoolean( const char *field ) = 0; virtual BSONObj getObject( const char *field ) = 0; @@ -59,52 +87,68 @@ namespace mongo { virtual void setObject( const char *field , const BSONObj& obj , bool readOnly=true ) = 0; virtual void setBoolean( const char *field , bool val ) = 0; virtual void setThis( const BSONObj * obj ) = 0; - + virtual ScriptingFunction createFunction( const char * code ); - + + virtual void rename( const char * from , const char * to ) = 0; /** * @return 0 on success */ virtual int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = false ) = 0; - void invokeSafe( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 ){ + void invokeSafe( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 ) { int res = invoke( func , args , timeoutMs ); if ( res == 0 ) return; throw UserException( 9004 , (string)"invoke failed: " + getError() ); } virtual string getError() = 0; - + int invoke( const char* code , const BSONObj& args, int timeoutMs = 0 ); - void invokeSafe( const char* code , const BSONObj& args, int timeoutMs = 0 ){ + void invokeSafe( const char* code , const BSONObj& args, int timeoutMs = 0 ) { if ( invoke( code , args , timeoutMs ) == 0 ) return; throw UserException( 9005 , (string)"invoke failed: " + getError() ); } - virtual bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) = 0; - virtual void execSetup( const string& code , const string& name = "setup" ){ + virtual bool exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) = 0; + virtual void execSetup( const StringData& code , const string& name = "setup" ) { exec( code , name , false , true , true , 0 ); } + + void execSetup( const JSFile& file) { + execSetup(file.source, file.name); + } + + void execCoreFiles() { + // keeping same order as in SConstruct + execSetup(JSFiles::utils); + execSetup(JSFiles::db); + execSetup(JSFiles::mongo); + execSetup(JSFiles::mr); + execSetup(JSFiles::query); + execSetup(JSFiles::collection); + } + virtual bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ); - + virtual void injectNative( const char *field, NativeFunction func ) = 0; virtual void gc() = 0; void loadStored( bool ignoreNotConnected = false ); - + /** if any changes are made to .system.js, call this right now its just global - slightly inefficient, but a lot simpler */ static void storedFuncMod(); - - static int getNumScopes(){ + + static int getNumScopes() { return _numScopes; } - + static void validateObjectIdString( const string &str ); - + protected: virtual ScriptingFunction _createFunction( const char * code ) = 0; @@ -117,16 +161,16 @@ namespace mongo { static int _numScopes; }; - + void installGlobalUtils( Scope& scope ); class DBClientWithCommands; - + class ScriptEngine : boost::noncopyable { public: ScriptEngine(); virtual ~ScriptEngine(); - + virtual Scope * newScope() { Scope *s = createScope(); if ( s && _scopeInitCallback ) @@ -134,35 +178,63 @@ namespace mongo { installGlobalUtils( *s ); return s; } - + virtual void runTest() = 0; - + virtual bool utf8Ok() const = 0; static void setup(); auto_ptr<Scope> getPooledScope( const string& pool ); void threadDone(); - + struct Unlocker { virtual ~Unlocker() {} }; virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new Unlocker ); } - + void setScopeInitCallback( void ( *func )( Scope & ) ) { _scopeInitCallback = func; } static void setConnectCallback( void ( *func )( DBClientWithCommands& ) ) { _connectCallback = func; } static void runConnectCallback( DBClientWithCommands &c ) { if ( _connectCallback ) _connectCallback( c ); } - + + // engine implementation may either respond to interrupt events or + // poll for interrupts + + // the interrupt functions must not wait indefinitely on a lock + virtual void interrupt( unsigned opSpec ) {} + virtual void interruptAll() {} + + static void setGetInterruptSpecCallback( unsigned ( *func )() ) { _getInterruptSpecCallback = func; } + static bool haveGetInterruptSpecCallback() { return _getInterruptSpecCallback; } + static unsigned getInterruptSpec() { + massert( 13474, "no _getInterruptSpecCallback", _getInterruptSpecCallback ); + return _getInterruptSpecCallback(); + } + + static void setCheckInterruptCallback( const char * ( *func )() ) { _checkInterruptCallback = func; } + static bool haveCheckInterruptCallback() { return _checkInterruptCallback; } + static const char * checkInterrupt() { + return _checkInterruptCallback ? _checkInterruptCallback() : ""; + } + static bool interrupted() { + const char *r = checkInterrupt(); + return r && r[ 0 ]; + } + protected: virtual Scope * createScope() = 0; - + private: void ( *_scopeInitCallback )( Scope & ); static void ( *_connectCallback )( DBClientWithCommands & ); + static const char * ( *_checkInterruptCallback )(); + static unsigned ( *_getInterruptSpecCallback )(); }; bool hasJSReturn( const string& s ); + const char * jsSkipWhiteSpace( const char * raw ); + extern ScriptEngine * globalScriptEngine; } diff --git a/scripting/engine_java.cpp b/scripting/engine_java.cpp index dacf532..fc8945f 100644 --- a/scripting/engine_java.cpp +++ b/scripting/engine_java.cpp @@ -55,19 +55,19 @@ namespace mongo { no tss cleanup on windows for boost lib? we don't care for now esp on windows only - the boost source says: - - This function's sole purpose is to cause a link error in cases where - automatic tss cleanup is not implemented by Boost.Threads as a - reminder that user code is responsible for calling the necessary - functions at the appropriate times (and for implementing an a - tss_cleanup_implemented() function to eliminate the linker's - missing symbol error). - - If Boost.Threads later implements automatic tss cleanup in cases - where it currently doesn't (which is the plan), the duplicate - symbol error will warn the user that their custom solution is no - longer needed and can be removed. + the boost source says: + + This function's sole purpose is to cause a link error in cases where + automatic tss cleanup is not implemented by Boost.Threads as a + reminder that user code is responsible for calling the necessary + functions at the appropriate times (and for implementing an a + tss_cleanup_implemented() function to eliminate the linker's + missing symbol error). + + If Boost.Threads later implements automatic tss cleanup in cases + where it currently doesn't (which is the plan), the duplicate + symbol error will warn the user that their custom solution is no + longer needed and can be removed. */ extern "C" void tss_cleanup_implemented(void) { //out() << "tss_cleanup_implemented called" << endl; @@ -185,10 +185,10 @@ namespace mongo { if ( res ) { log() << "using classpath: " << q << endl; log() - << " res : " << (unsigned) res << " " - << "_jvm : " << _jvm << " " - << "_env : " << _mainEnv << " " - << endl; + << " res : " << (unsigned) res << " " + << "_jvm : " << _jvm << " " + << "_env : " << _mainEnv << " " + << endl; problem() << "Couldn't create JVM res:" << (int) res << " terminating" << endl; log() << "(try --nojni if you do not require that functionality)" << endl; exit(22); @@ -397,12 +397,11 @@ namespace mongo { return retStr; } - BSONObj JavaJSImpl::scopeGetObject( jlong id , const char * field ) - { + BSONObj JavaJSImpl::scopeGetObject( jlong id , const char * field ) { jstring s1 = _getEnv()->NewStringUTF( field ); int guess = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGuessObjectSize , id , _getEnv()->NewStringUTF( field ) ); _getEnv()->DeleteLocalRef( s1 ); - + if ( guess == 0 ) return BSONObj(); @@ -471,12 +470,12 @@ namespace mongo { return env; } - Scope * JavaJSImpl::createScope(){ + Scope * JavaJSImpl::createScope() { return new JavaScope(); } - void ScriptEngine::setup(){ - if ( ! JavaJS ){ + void ScriptEngine::setup() { + if ( ! JavaJS ) { JavaJS = new JavaJSImpl(); globalScriptEngine = JavaJS; } @@ -564,40 +563,40 @@ namespace mongo { if ( ! possible.size() ) { possible.push_back( "./" ); possible.push_back( "../" ); - + log(2) << "dbExecCommand: " << dbExecCommand << endl; - + string dbDir = dbExecCommand; #ifdef WIN32 - if ( dbDir.find( "\\" ) != string::npos ){ + if ( dbDir.find( "\\" ) != string::npos ) { dbDir = dbDir.substr( 0 , dbDir.find_last_of( "\\" ) ); } else { dbDir = "."; } #else - if ( dbDir.find( "/" ) != string::npos ){ + if ( dbDir.find( "/" ) != string::npos ) { dbDir = dbDir.substr( 0 , dbDir.find_last_of( "/" ) ); } else { bool found = false; - - if ( getenv( "PATH" ) ){ + + if ( getenv( "PATH" ) ) { string s = getenv( "PATH" ); s += ":"; pcrecpp::StringPiece input( s ); string dir; pcrecpp::RE re("(.*?):"); - while ( re.Consume( &input, &dir ) ){ + while ( re.Consume( &input, &dir ) ) { string test = dir + "/" + dbExecCommand; - if ( boost::filesystem::exists( test ) ){ - while ( boost::filesystem::symbolic_link_exists( test ) ){ + if ( boost::filesystem::exists( test ) ) { + while ( boost::filesystem::symbolic_link_exists( test ) ) { char tmp[2048]; int len = readlink( test.c_str() , tmp , 2048 ); tmp[len] = 0; log(5) << " symlink " << test << " -->> " << tmp << endl; test = tmp; - + dir = test.substr( 0 , test.rfind( "/" ) ); } dbDir = dir; @@ -606,12 +605,12 @@ namespace mongo { } } } - + if ( ! found ) dbDir = "."; } #endif - + log(2) << "dbDir [" << dbDir << "]" << endl; possible.push_back( ( dbDir + "/../lib/mongo/" )); possible.push_back( ( dbDir + "/../lib64/mongo/" )); @@ -624,7 +623,7 @@ namespace mongo { for ( list<string>::iterator i = possible.begin() ; i != possible.end(); i++ ) { const string temp = *i; const string jarDir = ((string)temp) + "jars/"; - + log(5) << "possible jarDir [" << jarDir << "]" << endl; path p(jarDir ); @@ -641,7 +640,7 @@ namespace mongo { }; - + // --- JNIEXPORT void JNICALL java_native_say(JNIEnv * env , jclass, jobject outBuffer ) { @@ -692,7 +691,7 @@ namespace mongo { jlong func1 = JavaJS.functionCreate( "foo = 5.6; bar = \"eliot\"; abc = { foo : 517 }; " ); - jassert( ! JavaJS.invoke( scope , func1 ) ); + jassert( ! JavaJS.invoke( scope , func1 ) ); if ( debug ) out() << "func3 start" << endl; @@ -757,7 +756,7 @@ namespace mongo { assert( 12 == JavaJS.scopeGetNumber( scope , "return" ) ); } - + #endif } // namespace mongo diff --git a/scripting/engine_java.h b/scripting/engine_java.h index 5c6bc3b..b8245ba 100644 --- a/scripting/engine_java.h +++ b/scripting/engine_java.h @@ -163,10 +163,10 @@ namespace mongo { JavaJS->scopeInit( s , o ); } - void localConnect( const char * dbName ){ + void localConnect( const char * dbName ) { setString("$client", dbName ); } - + double getNumber(const char *field) { return JavaJS->scopeGetNumber(s,field); } @@ -183,7 +183,7 @@ namespace mongo { return JavaJS->scopeGetType(s,field); } - void setThis( const BSONObj * obj ){ + void setThis( const BSONObj * obj ) { JavaJS->scopeSetThis( s , obj ); } @@ -200,17 +200,17 @@ namespace mongo { void setBoolean(const char *field, bool val ) { JavaJS->scopeSetBoolean(s,field,val); } - - ScriptingFunction createFunction( const char * code ){ + + ScriptingFunction createFunction( const char * code ) { return JavaJS->functionCreate( code ); } - int invoke( ScriptingFunction function , const BSONObj& args ){ + int invoke( ScriptingFunction function , const BSONObj& args ) { setObject( "args" , args , true ); return JavaJS->invoke(s,function); } - - string getError(){ + + string getError() { return getString( "error" ); } diff --git a/scripting/engine_none.cpp b/scripting/engine_none.cpp index 2320d0e..d13dbec 100644 --- a/scripting/engine_none.cpp +++ b/scripting/engine_none.cpp @@ -18,7 +18,7 @@ #include "engine.h" namespace mongo { - void ScriptEngine::setup(){ + void ScriptEngine::setup() { // noop } } diff --git a/scripting/engine_spidermonkey.cpp b/scripting/engine_spidermonkey.cpp index c8f2eca..73ebfaa 100644 --- a/scripting/engine_spidermonkey.cpp +++ b/scripting/engine_spidermonkey.cpp @@ -26,31 +26,34 @@ #endif #define smuassert( cx , msg , val ) \ - if ( ! ( val ) ){ \ - JS_ReportError( cx , msg ); \ - return JS_FALSE; \ - } + if ( ! ( val ) ){ \ + JS_ReportError( cx , msg ); \ + return JS_FALSE; \ + } #define CHECKNEWOBJECT(xx,ctx,w) \ if ( ! xx ){ \ massert(13072,(string)"JS_NewObject failed: " + w ,xx); \ } +#define CHECKJSALLOC( newthing ) \ + massert( 13615 , "JS allocation failed, either memory leak or using too much memory" , newthing ) + namespace mongo { - + class InvalidUTF8Exception : public UserException { public: - InvalidUTF8Exception() : UserException( 9006 , "invalid utf8" ){ + InvalidUTF8Exception() : UserException( 9006 , "invalid utf8" ) { } }; - string trim( string s ){ + string trim( string s ) { while ( s.size() && isspace( s[0] ) ) s = s.substr( 1 ); - + while ( s.size() && isspace( s[s.size()-1] ) ) s = s.substr( 0 , s.size() - 1 ); - + return s; } @@ -65,18 +68,18 @@ namespace mongo { class BSONHolder { public: - BSONHolder( BSONObj obj ){ + BSONHolder( BSONObj obj ) { _obj = obj.getOwned(); _inResolve = false; _modified = false; _magic = 17; } - - ~BSONHolder(){ + + ~BSONHolder() { _magic = 18; } - void check(){ + void check() { uassert( 10212 , "holder magic value is wrong" , _magic == 17 && _obj.isValid() ); } @@ -89,24 +92,24 @@ namespace mongo { set<string> _removed; bool _modified; }; - + class BSONFieldIterator { public: - BSONFieldIterator( BSONHolder * holder ){ + BSONFieldIterator( BSONHolder * holder ) { set<string> added; BSONObjIterator it( holder->_obj ); - while ( it.more() ){ + while ( it.more() ) { BSONElement e = it.next(); if ( holder->_removed.count( e.fieldName() ) ) continue; _names.push_back( e.fieldName() ); added.insert( e.fieldName() ); } - - for ( list<string>::iterator i = holder->_extra.begin(); i != holder->_extra.end(); i++ ){ + + for ( list<string>::iterator i = holder->_extra.begin(); i != holder->_extra.end(); i++ ) { if ( ! added.count( *i ) ) _names.push_back( *i ); } @@ -114,11 +117,11 @@ namespace mongo { _it = _names.begin(); } - bool more(){ + bool more() { return _it != _names.end(); } - string next(){ + string next() { string s = *_it; _it++; return s; @@ -129,24 +132,24 @@ namespace mongo { list<string>::iterator _it; }; - BSONFieldIterator * BSONHolder::it(){ + BSONFieldIterator * BSONHolder::it() { return new BSONFieldIterator( this ); } class TraverseStack { public: - TraverseStack(){ + TraverseStack() { _o = 0; _parent = 0; } - TraverseStack( JSObject * o , const TraverseStack * parent ){ + TraverseStack( JSObject * o , const TraverseStack * parent ) { _o = o; _parent = parent; } TraverseStack dive( JSObject * o ) const { - if ( o ){ + if ( o ) { uassert( 13076 , (string)"recursive toObject" , ! has( o ) ); } return TraverseStack( o , this ); @@ -155,7 +158,7 @@ namespace mongo { int depth() const { int d = 0; const TraverseStack * s = _parent; - while ( s ){ + while ( s ) { s = s->_parent; d++; } @@ -165,12 +168,12 @@ namespace mongo { bool isTop() const { return _parent == 0; } - + bool has( JSObject * o ) const { if ( ! o ) return false; const TraverseStack * s = this; - while ( s ){ + while ( s ) { if ( s->_o == o ) return true; s = s->_parent; @@ -184,11 +187,11 @@ namespace mongo { class Convertor : boost::noncopyable { public: - Convertor( JSContext * cx ){ + Convertor( JSContext * cx ) { _context = cx; } - string toString( JSString * so ){ + string toString( JSString * so ) { jschar * s = JS_GetStringChars( so ); size_t srclen = JS_GetStringLength( so ); if( srclen == 0 ) @@ -202,7 +205,16 @@ namespace mongo { // units, but experiments suggest 8bit units expected. We allocate // enough memory that either will work. - assert( JS_EncodeCharacters( _context , s , srclen , dst , &len) ); + if ( !JS_EncodeCharacters( _context , s , srclen , dst , &len) ) { + StringBuilder temp; + temp << "Not proper UTF-16: "; + for ( size_t i=0; i<srclen; i++ ) { + if ( i > 0 ) + temp << ","; + temp << s[i]; + } + uasserted( 13498 , temp.str() ); + } string ss( dst , len ); free( dst ); @@ -212,7 +224,7 @@ namespace mongo { return ss; } - string toString( jsval v ){ + string toString( jsval v ) { return toString( JS_ValueToString( _context , v ) ); } @@ -221,27 +233,28 @@ namespace mongo { boost::uint64_t val; if ( hasProperty( o, "top" ) ) { val = - ( (boost::uint64_t)(boost::uint32_t)getNumber( o , "top" ) << 32 ) + - ( boost::uint32_t)( getNumber( o , "bottom" ) ); - } else { + ( (boost::uint64_t)(boost::uint32_t)getNumber( o , "top" ) << 32 ) + + ( boost::uint32_t)( getNumber( o , "bottom" ) ); + } + else { val = (boost::uint64_t)(boost::int64_t) getNumber( o, "floatApprox" ); } return val; } - - double toNumber( jsval v ){ + + double toNumber( jsval v ) { double d; uassert( 10214 , "not a number" , JS_ValueToNumber( _context , v , &d ) ); return d; } - bool toBoolean( jsval v ){ + bool toBoolean( jsval v ) { JSBool b; assert( JS_ValueToBoolean( _context, v , &b ) ); return b; } - OID toOID( jsval v ){ + OID toOID( jsval v ) { JSContext * cx = _context; assert( JSVAL_IS_OID( v ) ); @@ -251,21 +264,21 @@ namespace mongo { return oid; } - BSONObj toObject( JSObject * o , const TraverseStack& stack=TraverseStack() ){ + BSONObj toObject( JSObject * o , const TraverseStack& stack=TraverseStack() ) { if ( ! o ) return BSONObj(); - if ( JS_InstanceOf( _context , o , &bson_ro_class , 0 ) ){ + if ( JS_InstanceOf( _context , o , &bson_ro_class , 0 ) ) { BSONHolder * holder = GETHOLDER( _context , o ); assert( holder ); return holder->_obj.getOwned(); } BSONObj orig; - if ( JS_InstanceOf( _context , o , &bson_class , 0 ) ){ + if ( JS_InstanceOf( _context , o , &bson_class , 0 ) ) { BSONHolder * holder = GETHOLDER(_context,o); assert( holder ); - if ( ! holder->_modified ){ + if ( ! holder->_modified ) { return holder->_obj; } orig = holder->_obj; @@ -273,26 +286,26 @@ namespace mongo { BSONObjBuilder b; - if ( ! appendSpecialDBObject( this , b , "value" , OBJECT_TO_JSVAL( o ) , o ) ){ + if ( ! appendSpecialDBObject( this , b , "value" , OBJECT_TO_JSVAL( o ) , o ) ) { - if ( stack.isTop() ){ + if ( stack.isTop() ) { jsval theid = getProperty( o , "_id" ); - if ( ! JSVAL_IS_VOID( theid ) ){ + if ( ! JSVAL_IS_VOID( theid ) ) { append( b , "_id" , theid , EOO , stack.dive( o ) ); } } - + JSIdArray * properties = JS_Enumerate( _context , o ); assert( properties ); - - for ( jsint i=0; i<properties->length; i++ ){ + + for ( jsint i=0; i<properties->length; i++ ) { jsid id = properties->vector[i]; jsval nameval; assert( JS_IdToValue( _context ,id , &nameval ) ); string name = toString( nameval ); if ( stack.isTop() && name == "_id" ) continue; - + append( b , name , getProperty( o , name.c_str() ) , orig[name].type() , stack.dive( o ) ); } @@ -302,34 +315,34 @@ namespace mongo { return b.obj(); } - BSONObj toObject( jsval v ){ + BSONObj toObject( jsval v ) { if ( JSVAL_IS_NULL( v ) || - JSVAL_IS_VOID( v ) ) + JSVAL_IS_VOID( v ) ) return BSONObj(); uassert( 10215 , "not an object" , JSVAL_IS_OBJECT( v ) ); return toObject( JSVAL_TO_OBJECT( v ) ); } - string getFunctionCode( JSFunction * func ){ + string getFunctionCode( JSFunction * func ) { return toString( JS_DecompileFunction( _context , func , 0 ) ); } - string getFunctionCode( jsval v ){ + string getFunctionCode( jsval v ) { uassert( 10216 , "not a function" , JS_TypeOfValue( _context , v ) == JSTYPE_FUNCTION ); return getFunctionCode( JS_ValueToFunction( _context , v ) ); } - - void appendRegex( BSONObjBuilder& b , const string& name , string s ){ + + void appendRegex( BSONObjBuilder& b , const string& name , string s ) { assert( s[0] == '/' ); s = s.substr(1); string::size_type end = s.rfind( '/' ); - b.appendRegex( name , s.substr( 0 , end ).c_str() , s.substr( end + 1 ).c_str() ); + b.appendRegex( name , s.substr( 0 , end ) , s.substr( end + 1 ) ); } - void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO , const TraverseStack& stack=TraverseStack() ){ + void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO , const TraverseStack& stack=TraverseStack() ) { //cout << "name: " << name << "\t" << typeString( val ) << " oldType: " << oldType << endl; - switch ( JS_TypeOfValue( _context , val ) ){ + switch ( JS_TypeOfValue( _context , val ) ) { case JSTYPE_VOID: b.appendUndefined( name ); break; case JSTYPE_NULL: b.appendNull( name ); break; @@ -347,12 +360,12 @@ namespace mongo { case JSTYPE_OBJECT: { JSObject * o = JSVAL_TO_OBJECT( val ); - if ( ! o || o == JSVAL_NULL ){ + if ( ! o || o == JSVAL_NULL ) { b.appendNull( name ); } - else if ( ! appendSpecialDBObject( this , b , name , val , o ) ){ + else if ( ! appendSpecialDBObject( this , b , name , val , o ) ) { BSONObj sub = toObject( o , stack ); - if ( JS_IsArrayObject( _context , o ) ){ + if ( JS_IsArrayObject( _context , o ) ) { b.appendArray( name , sub ); } else { @@ -364,11 +377,11 @@ namespace mongo { case JSTYPE_FUNCTION: { string s = toString(val); - if ( s[0] == '/' ){ + if ( s[0] == '/' ) { appendRegex( b , name , s ); } else { - b.appendCode( name , getFunctionCode( val ).c_str() ); + b.appendCode( name , getFunctionCode( val ) ); } break; } @@ -379,25 +392,28 @@ namespace mongo { // ---------- to spider monkey --------- - bool hasFunctionIdentifier( const string& code ){ + bool hasFunctionIdentifier( const string& code ) { if ( code.size() < 9 || code.find( "function" ) != 0 ) return false; return code[8] == ' ' || code[8] == '('; } - bool isSimpleStatement( const string& code ){ + bool isSimpleStatement( const string& code ) { if ( hasJSReturn( code ) ) return false; - if ( code.find( ";" ) != string::npos && - code.find( ";" ) != code.rfind( ";" ) ) + if ( code.find( ';' ) != string::npos && + code.find( ';' ) != code.rfind( ';' ) ) + return false; + + if ( code.find( '\n') != string::npos ) return false; if ( code.find( "for(" ) != string::npos || - code.find( "for (" ) != string::npos || - code.find( "while (" ) != string::npos || - code.find( "while(" ) != string::npos ) + code.find( "for (" ) != string::npos || + code.find( "while (" ) != string::npos || + code.find( "while(" ) != string::npos ) return false; return true; @@ -405,20 +421,20 @@ namespace mongo { void addRoot( JSFunction * f , const char * name ); - JSFunction * compileFunction( const char * code, JSObject * assoc = 0 ){ + JSFunction * compileFunction( const char * code, JSObject * assoc = 0 ) { const char * gcName = "unknown"; JSFunction * f = _compileFunction( code , assoc , gcName ); //addRoot( f , gcName ); return f; } - JSFunction * _compileFunction( const char * raw , JSObject * assoc , const char *& gcName ){ + JSFunction * _compileFunction( const char * raw , JSObject * assoc , const char *& gcName ) { if ( ! assoc ) assoc = JS_GetGlobalObject( _context ); - while (isspace(*raw)) { - raw++; - } + raw = jsSkipWhiteSpace( raw ); + + //cout << "RAW\n---\n" << raw << "\n---" << endl; stringstream fname; fname << "cf_"; @@ -426,34 +442,34 @@ namespace mongo { fname << "_" << fnum++ << "_"; - if ( ! hasFunctionIdentifier( raw ) ){ + if ( ! hasFunctionIdentifier( raw ) ) { string s = raw; - if ( isSimpleStatement( s ) ){ + if ( isSimpleStatement( s ) ) { s = "return " + s; } gcName = "cf anon"; fname << "anon"; - return JS_CompileFunction( _context , assoc , fname.str().c_str() , 0 , 0 , s.c_str() , strlen( s.c_str() ) , "nofile_a" , 0 ); + return JS_CompileFunction( _context , assoc , fname.str().c_str() , 0 , 0 , s.c_str() , s.size() , "nofile_a" , 0 ); } string code = raw; - + size_t start = code.find( '(' ); assert( start != string::npos ); - + fname << "_f_" << trim( code.substr( 9 , start - 9 ) ); code = code.substr( start + 1 ); size_t end = code.find( ')' ); assert( end != string::npos ); - + string paramString = trim( code.substr( 0 , end ) ); code = code.substr( end + 1 ); - + vector<string> params; - while ( paramString.size() ){ + while ( paramString.size() ) { size_t c = paramString.find( ',' ); - if ( c == string::npos ){ + if ( c == string::npos ) { params.push_back( paramString ); break; } @@ -461,14 +477,14 @@ namespace mongo { paramString = trim( paramString.substr( c + 1 ) ); paramString = trim( paramString ); } - + boost::scoped_array<const char *> paramArray (new const char*[params.size()]); for ( size_t i=0; i<params.size(); i++ ) paramArray[i] = params[i].c_str(); - - JSFunction * func = JS_CompileFunction( _context , assoc , fname.str().c_str() , params.size() , paramArray.get() , code.c_str() , strlen( code.c_str() ) , "nofile_b" , 0 ); - if ( ! func ){ + JSFunction * func = JS_CompileFunction( _context , assoc , fname.str().c_str() , params.size() , paramArray.get() , code.c_str() , code.size() , "nofile_b" , 0 ); + + if ( ! func ) { log() << "compile failed for: " << raw << endl; return 0; } @@ -477,31 +493,31 @@ namespace mongo { } - jsval toval( double d ){ + jsval toval( double d ) { jsval val; assert( JS_NewNumberValue( _context, d , &val ) ); return val; } - jsval toval( const char * c ){ + jsval toval( const char * c ) { JSString * s = JS_NewStringCopyZ( _context , c ); if ( s ) return STRING_TO_JSVAL( s ); - + // possibly unicode, try manual - + size_t len = strlen( c ); size_t dstlen = len * 4; jschar * dst = (jschar*)malloc( dstlen ); - + JSBool res = JS_DecodeBytes( _context , c , len , dst, &dstlen ); - if ( res ){ + if ( res ) { s = JS_NewUCStringCopyN( _context , dst , dstlen ); } free( dst ); - if ( ! res ){ + if ( ! res ) { tlog() << "decode failed. probably invalid utf-8 string [" << c << "]" << endl; jsval v; if ( JS_GetPendingException( _context , &v ) ) @@ -509,13 +525,13 @@ namespace mongo { throw InvalidUTF8Exception(); } - assert( s ); + CHECKJSALLOC( s ); return STRING_TO_JSVAL( s ); } - JSObject * toJSObject( const BSONObj * obj , bool readOnly=false ){ + JSObject * toJSObject( const BSONObj * obj , bool readOnly=false ) { static string ref = "$ref"; - if ( ref == obj->firstElement().fieldName() ){ + if ( ref == obj->firstElement().fieldName() ) { JSObject * o = JS_NewObject( _context , &dbref_class , NULL, NULL); CHECKNEWOBJECT(o,_context,"toJSObject1"); assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); @@ -527,7 +543,7 @@ namespace mongo { return o; } - jsval toval( const BSONObj* obj , bool readOnly=false ){ + jsval toval( const BSONObj* obj , bool readOnly=false ) { JSObject * o = toJSObject( obj , readOnly ); return OBJECT_TO_JSVAL( o ); } @@ -535,7 +551,7 @@ namespace mongo { void makeLongObj( long long n, JSObject * o ) { boost::uint64_t val = (boost::uint64_t)n; CHECKNEWOBJECT(o,_context,"NumberLong1"); - setProperty( o , "floatApprox" , toval( (double)(boost::int64_t)( val ) ) ); + setProperty( o , "floatApprox" , toval( (double)(boost::int64_t)( val ) ) ); if ( (boost::int64_t)val != (boost::int64_t)(double)(boost::int64_t)( val ) ) { // using 2 doubles here instead of a single double because certain double // bit patterns represent undefined values and sm might trash them @@ -543,16 +559,16 @@ namespace mongo { setProperty( o , "bottom" , toval( (double)(boost::uint32_t)( val & 0x00000000ffffffff ) ) ); } } - + jsval toval( long long n ) { JSObject * o = JS_NewObject( _context , &numberlong_class , 0 , 0 ); makeLongObj( n, o ); return OBJECT_TO_JSVAL( o ); } - - jsval toval( const BSONElement& e ){ - switch( e.type() ){ + jsval toval( const BSONElement& e ) { + + switch( e.type() ) { case EOO: case jstNULL: case Undefined: @@ -565,50 +581,50 @@ namespace mongo { return toval( e.valuestr() ); case Bool: return e.boolean() ? JSVAL_TRUE : JSVAL_FALSE; - case Object:{ + case Object: { BSONObj embed = e.embeddedObject().getOwned(); return toval( &embed ); } - case Array:{ + case Array: { BSONObj embed = e.embeddedObject().getOwned(); - if ( embed.isEmpty() ){ + if ( embed.isEmpty() ) { return OBJECT_TO_JSVAL( JS_NewArrayObject( _context , 0 , 0 ) ); } - - int n = embed.nFields(); - - JSObject * array = JS_NewArrayObject( _context , n , 0 ); - assert( array ); + + JSObject * array = JS_NewArrayObject( _context , 1 , 0 ); + CHECKJSALLOC( array ); jsval myarray = OBJECT_TO_JSVAL( array ); - for ( int i=0; i<n; i++ ){ - jsval v = toval( embed[i] ); - assert( JS_SetElement( _context , array , i , &v ) ); + BSONObjIterator i( embed ); + while ( i.more() ){ + const BSONElement& e = i.next(); + jsval v = toval( e ); + assert( JS_SetElement( _context , array , atoi(e.fieldName()) , &v ) ); } return myarray; } - case jstOID:{ + case jstOID: { OID oid = e.__oid(); JSObject * o = JS_NewObject( _context , &object_id_class , 0 , 0 ); CHECKNEWOBJECT(o,_context,"jstOID"); setProperty( o , "str" , toval( oid.str().c_str() ) ); return OBJECT_TO_JSVAL( o ); } - case RegEx:{ + case RegEx: { const char * flags = e.regexFlags(); uintN flagNumber = 0; - while ( *flags ){ - switch ( *flags ){ + while ( *flags ) { + switch ( *flags ) { case 'g': flagNumber |= JSREG_GLOB; break; case 'i': flagNumber |= JSREG_FOLD; break; case 'm': flagNumber |= JSREG_MULTILINE; break; //case 'y': flagNumber |= JSREG_STICKY; break; - - default: + + default: log() << "warning: unknown regex flag:" << *flags << endl; } flags++; @@ -618,17 +634,17 @@ namespace mongo { assert( r ); return OBJECT_TO_JSVAL( r ); } - case Code:{ + case Code: { JSFunction * func = compileFunction( e.valuestr() ); if ( func ) return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); return JSVAL_NULL; } - case CodeWScope:{ + case CodeWScope: { JSFunction * func = compileFunction( e.codeWScopeCode() ); BSONObj extraScope = e.codeWScopeObject(); - if ( ! extraScope.isEmpty() ){ + if ( ! extraScope.isEmpty() ) { log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; } @@ -665,7 +681,7 @@ namespace mongo { setProperty( o , "id" , OBJECT_TO_JSVAL( oid ) ); return OBJECT_TO_JSVAL( o ); } - case BinData:{ + case BinData: { JSObject * o = JS_NewObject( _context , &bindata_class , 0 , 0 ); CHECKNEWOBJECT(o,_context,"Bindata_BinData1"); int len; @@ -686,55 +702,55 @@ namespace mongo { // ------- object helpers ------ - JSObject * getJSObject( JSObject * o , const char * name ){ + JSObject * getJSObject( JSObject * o , const char * name ) { jsval v; assert( JS_GetProperty( _context , o , name , &v ) ); return JSVAL_TO_OBJECT( v ); } - JSObject * getGlobalObject( const char * name ){ + JSObject * getGlobalObject( const char * name ) { return getJSObject( JS_GetGlobalObject( _context ) , name ); } - JSObject * getGlobalPrototype( const char * name ){ + JSObject * getGlobalPrototype( const char * name ) { return getJSObject( getGlobalObject( name ) , "prototype" ); } - bool hasProperty( JSObject * o , const char * name ){ + bool hasProperty( JSObject * o , const char * name ) { JSBool res; assert( JS_HasProperty( _context , o , name , & res ) ); return res; } - jsval getProperty( JSObject * o , const char * field ){ + jsval getProperty( JSObject * o , const char * field ) { uassert( 10219 , "object passed to getPropery is null" , o ); jsval v; assert( JS_GetProperty( _context , o , field , &v ) ); return v; } - void setProperty( JSObject * o , const char * field , jsval v ){ + void setProperty( JSObject * o , const char * field , jsval v ) { assert( JS_SetProperty( _context , o , field , &v ) ); } - string typeString( jsval v ){ + string typeString( jsval v ) { JSType t = JS_TypeOfValue( _context , v ); return JS_GetTypeName( _context , t ); } - bool getBoolean( JSObject * o , const char * field ){ + bool getBoolean( JSObject * o , const char * field ) { return toBoolean( getProperty( o , field ) ); } - double getNumber( JSObject * o , const char * field ){ + double getNumber( JSObject * o , const char * field ) { return toNumber( getProperty( o , field ) ); } - string getString( JSObject * o , const char * field ){ + string getString( JSObject * o , const char * field ) { return toString( getProperty( o , field ) ); } - JSClass * getClass( JSObject * o , const char * field ){ + JSClass * getClass( JSObject * o , const char * field ) { jsval v; assert( JS_GetProperty( _context , o , field , &v ) ); if ( ! JSVAL_IS_OBJECT( v ) ) @@ -748,25 +764,25 @@ namespace mongo { }; - void bson_finalize( JSContext * cx , JSObject * obj ){ + void bson_finalize( JSContext * cx , JSObject * obj ) { BSONHolder * o = GETHOLDER( cx , obj ); - if ( o ){ + if ( o ) { delete o; assert( JS_SetPrivate( cx , obj , 0 ) ); } } - JSBool bson_enumerate( JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp ){ + JSBool bson_enumerate( JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp ) { BSONHolder * o = GETHOLDER( cx , obj ); - - if ( enum_op == JSENUMERATE_INIT ){ - if ( o ){ + + if ( enum_op == JSENUMERATE_INIT ) { + if ( o ) { BSONFieldIterator * it = o->it(); *statep = PRIVATE_TO_JSVAL( it ); } else { - *statep = 0; + *statep = 0; } if ( idp ) *idp = JSVAL_ZERO; @@ -774,13 +790,13 @@ namespace mongo { } BSONFieldIterator * it = (BSONFieldIterator*)JSVAL_TO_PRIVATE( *statep ); - if ( ! it ){ + if ( ! it ) { *statep = 0; return JS_TRUE; } - if ( enum_op == JSENUMERATE_NEXT ){ - if ( it->more() ){ + if ( enum_op == JSENUMERATE_NEXT ) { + if ( it->more() ) { string name = it->next(); Convertor c(cx); assert( JS_ValueToId( cx , c.toval( name.c_str() ) , idp ) ); @@ -792,7 +808,7 @@ namespace mongo { return JS_TRUE; } - if ( enum_op == JSENUMERATE_DESTROY ){ + if ( enum_op == JSENUMERATE_DESTROY ) { if ( it ) delete it; return JS_TRUE; @@ -802,9 +818,9 @@ namespace mongo { return JS_FALSE; } - JSBool noaccess( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + JSBool noaccess( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { BSONHolder * holder = GETHOLDER( cx , obj ); - if ( ! holder ){ + if ( ! holder ) { // in init code still return JS_TRUE; } @@ -821,7 +837,7 @@ namespace mongo { JSCLASS_NO_OPTIONAL_MEMBERS }; - JSBool bson_cons( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool bson_cons( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { cerr << "bson_cons : shouldn't be here!" << endl; JS_ReportError( cx , "can't construct bson object" ); return JS_FALSE; @@ -830,26 +846,26 @@ namespace mongo { JSFunctionSpec bson_functions[] = { { 0 } }; - - JSBool bson_add_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + + JSBool bson_add_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { BSONHolder * holder = GETHOLDER( cx , obj ); - if ( ! holder ){ + if ( ! holder ) { // static init return JS_TRUE; } - if ( ! holder->_inResolve ){ + if ( ! holder->_inResolve ) { Convertor c(cx); string name = c.toString( idval ); - if ( holder->_obj[name].eoo() ){ + if ( holder->_obj[name].eoo() ) { holder->_extra.push_back( name ); } holder->_modified = true; } return JS_TRUE; } - - JSBool mark_modified( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + + JSBool mark_modified( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { Convertor c(cx); BSONHolder * holder = GETHOLDER( cx , obj ); if ( !holder ) // needed when we're messing with DBRef.prototype @@ -860,8 +876,8 @@ namespace mongo { holder->_removed.erase( c.toString( idval ) ); return JS_TRUE; } - - JSBool mark_modified_remove( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + + JSBool mark_modified_remove( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { Convertor c(cx); BSONHolder * holder = GETHOLDER( cx , obj ); if ( holder->_inResolve ) @@ -887,10 +903,10 @@ namespace mongo { // --- global helpers --- - JSBool native_print( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ){ + JSBool native_print( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { stringstream ss; Convertor c( cx ); - for ( uintN i=0; i<argc; i++ ){ + for ( uintN i=0; i<argc; i++ ) { if ( i > 0 ) ss << " "; ss << c.toString( argv[i] ); @@ -900,32 +916,32 @@ namespace mongo { return JS_TRUE; } - JSBool native_helper( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + JSBool native_helper( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { Convertor c(cx); - + NativeFunction func = (NativeFunction)((long long)c.getNumber( obj , "x" ) ); assert( func ); - + BSONObj a; - if ( argc > 0 ){ + if ( argc > 0 ) { BSONObjBuilder args; - for ( uintN i=0; i<argc; i++ ){ + for ( uintN i=0; i<argc; i++ ) { c.append( args , args.numStr( i ) , argv[i] ); } - + a = args.obj(); } - + BSONObj out; try { out = func( a ); } - catch ( std::exception& e ){ + catch ( std::exception& e ) { JS_ReportError( cx , e.what() ); return JS_FALSE; } - - if ( out.isEmpty() ){ + + if ( out.isEmpty() ) { *rval = JSVAL_VOID; } else { @@ -937,7 +953,7 @@ namespace mongo { JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ); - JSBool native_gc( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + JSBool native_gc( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { JS_GC( cx ); return JS_TRUE; } @@ -953,22 +969,28 @@ namespace mongo { // ----END global helpers ---- // Object helpers - - JSBool bson_get_size(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + + JSBool bson_get_size(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { if ( argc != 1 || !JSVAL_IS_OBJECT( argv[ 0 ] ) ) { JS_ReportError( cx , "bsonsize requires one valid object" ); return JS_FALSE; } - - JSObject * o = JSVAL_TO_OBJECT( argv[0] ); Convertor c(cx); + + if ( argv[0] == JSVAL_VOID || argv[0] == JSVAL_NULL ) { + *rval = c.toval( 0.0 ); + return JS_TRUE; + } + + JSObject * o = JSVAL_TO_OBJECT( argv[0] ); + double size = 0; if ( JS_InstanceOf( cx , o , &bson_ro_class , 0 ) || - JS_InstanceOf( cx , o , &bson_class , 0 ) ){ + JS_InstanceOf( cx , o , &bson_class , 0 ) ) { BSONHolder * h = GETHOLDER( cx , o ); - if ( h ){ + if ( h ) { size = h->_obj.objsize(); } } @@ -976,36 +998,36 @@ namespace mongo { BSONObj temp = c.toObject( o ); size = temp.objsize(); } - + *rval = c.toval( size ); - return JS_TRUE; + return JS_TRUE; } - + JSFunctionSpec objectHelpers[] = { - { "bsonsize" , &bson_get_size , 1 , 0 , 0 } , - { 0 , 0 , 0 , 0 , 0 } + { "bsonsize" , &bson_get_size , 1 , 0 , 0 } , + { 0 , 0 , 0 , 0 , 0 } }; - + // end Object helpers - JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { assert( JS_EnterLocalRootScope( cx ) ); Convertor c( cx ); BSONHolder * holder = GETHOLDER( cx , obj ); - if ( ! holder ){ + if ( ! holder ) { // static init *objp = 0; JS_LeaveLocalRootScope( cx ); return JS_TRUE; } holder->check(); - + string s = c.toString( id ); BSONElement e = holder->_obj[ s.c_str() ]; - - if ( e.type() == EOO || holder->_removed.count( s ) ){ + + if ( e.type() == EOO || holder->_removed.count( s ) ) { *objp = 0; JS_LeaveLocalRootScope( cx ); return JS_TRUE; @@ -1025,12 +1047,12 @@ namespace mongo { holder->_inResolve = true; assert( JS_SetProperty( cx , obj , s.c_str() , &val ) ); holder->_inResolve = false; - - if ( val != JSVAL_NULL && val != JSVAL_VOID && JSVAL_IS_OBJECT( val ) ){ + + if ( val != JSVAL_NULL && val != JSVAL_VOID && JSVAL_IS_OBJECT( val ) ) { // TODO: this is a hack to get around sub objects being modified JSObject * oo = JSVAL_TO_OBJECT( val ); - if ( JS_InstanceOf( cx , oo , &bson_class , 0 ) || - JS_IsArrayObject( cx , oo ) ){ + if ( JS_InstanceOf( cx , oo , &bson_class , 0 ) || + JS_IsArrayObject( cx , oo ) ) { holder->_modified = true; } } @@ -1046,15 +1068,15 @@ namespace mongo { class SMEngine : public ScriptEngine { public: - SMEngine(){ + SMEngine() { #ifdef SM18 JS_SetCStringsAreUTF8(); #endif _runtime = JS_NewRuntime(8L * 1024L * 1024L); uassert( 10221 , "JS_NewRuntime failed" , _runtime ); - - if ( ! utf8Ok() ){ + + if ( ! utf8Ok() ) { log() << "*** warning: spider monkey build without utf8 support. consider rebuilding with utf8 support" << endl; } @@ -1063,7 +1085,7 @@ namespace mongo { uassert( 10222 , "assert not being executed" , x == 1 ); } - ~SMEngine(){ + ~SMEngine() { JS_DestroyRuntime( _runtime ); JS_ShutDown(); } @@ -1088,7 +1110,7 @@ namespace mongo { SMEngine * globalSMEngine; - void ScriptEngine::setup(){ + void ScriptEngine::setup() { globalSMEngine = new SMEngine(); globalScriptEngine = globalSMEngine; } @@ -1097,11 +1119,11 @@ namespace mongo { // ------ scope ------ - JSBool no_gc(JSContext *cx, JSGCStatus status){ + JSBool no_gc(JSContext *cx, JSGCStatus status) { return JS_FALSE; } - JSBool yes_gc(JSContext *cx, JSGCStatus status){ + JSBool yes_gc(JSContext *cx, JSGCStatus status) { return JS_TRUE; } @@ -1125,64 +1147,65 @@ namespace mongo { JS_SetOptions( _context , JS_GetOptions( _context ) | JSOPTION_VAROBJFIX ); JS_DefineFunctions( _context , _global , globalHelpers ); - + JS_DefineFunctions( _context , _convertor->getGlobalObject( "Object" ), objectHelpers ); //JS_SetGCCallback( _context , no_gc ); // this is useful for seeing if something is a gc problem _postCreateHacks(); } - - ~SMScope(){ + + ~SMScope() { smlock; uassert( 10223 , "deleted SMScope twice?" , _convertor ); - for ( list<void*>::iterator i=_roots.begin(); i != _roots.end(); i++ ){ + for ( list<void*>::iterator i=_roots.begin(); i != _roots.end(); i++ ) { JS_RemoveRoot( _context , *i ); } _roots.clear(); - - if ( _this ){ + + if ( _this ) { JS_RemoveRoot( _context , &_this ); _this = 0; } - if ( _convertor ){ + if ( _convertor ) { delete _convertor; _convertor = 0; } - - if ( _context ){ + + if ( _context ) { + // This is expected to reclaim _global as well. JS_DestroyContext( _context ); _context = 0; } } - - void reset(){ + + void reset() { smlock; assert( _convertor ); return; - if ( _this ){ + if ( _this ) { JS_RemoveRoot( _context , &_this ); _this = 0; } currentScope.reset( this ); _error = ""; } - - void addRoot( void * root , const char * name ){ + + void addRoot( void * root , const char * name ) { JS_AddNamedRoot( _context , root , name ); _roots.push_back( root ); } - void init( BSONObj * data ){ + void init( const BSONObj * data ) { smlock; if ( ! data ) return; BSONObjIterator i( *data ); - while ( i.more() ){ + while ( i.more() ) { BSONElement e = i.next(); _convertor->setProperty( _global , e.fieldName() , _convertor->toval( e ) ); _initFieldNames.insert( e.fieldName() ); @@ -1190,7 +1213,7 @@ namespace mongo { } - void externalSetup(){ + void externalSetup() { smlock; uassert( 10224 , "already local connected" , ! _localConnect ); if ( _externalSetup ) @@ -1199,20 +1222,20 @@ namespace mongo { _externalSetup = true; } - void localConnect( const char * dbName ){ + void localConnect( const char * dbName ) { { smlock; uassert( 10225 , "already setup for external db" , ! _externalSetup ); - if ( _localConnect ){ + if ( _localConnect ) { uassert( 10226 , "connected to different db" , _localDBName == dbName ); return; } - + initMongoJS( this , _context , _global , true ); - + exec( "_mongo = new Mongo();" ); exec( ((string)"db = _mongo.getDB( \"" + dbName + "\" ); ").c_str() ); - + _localConnect = true; _localDBName = dbName; } @@ -1220,14 +1243,14 @@ namespace mongo { } // ----- getters ------ - double getNumber( const char *field ){ + double getNumber( const char *field ) { smlock; jsval val; assert( JS_GetProperty( _context , _global , field , &val ) ); return _convertor->toNumber( val ); } - string getString( const char *field ){ + string getString( const char *field ) { smlock; jsval val; assert( JS_GetProperty( _context , _global , field , &val ) ); @@ -1235,27 +1258,27 @@ namespace mongo { return _convertor->toString( s ); } - bool getBoolean( const char *field ){ + bool getBoolean( const char *field ) { smlock; return _convertor->getBoolean( _global , field ); } - BSONObj getObject( const char *field ){ + BSONObj getObject( const char *field ) { smlock; return _convertor->toObject( _convertor->getProperty( _global , field ) ); } - JSObject * getJSObject( const char * field ){ + JSObject * getJSObject( const char * field ) { smlock; return _convertor->getJSObject( _global , field ); } - int type( const char *field ){ + int type( const char *field ) { smlock; jsval val; assert( JS_GetProperty( _context , _global , field , &val ) ); - switch ( JS_TypeOfValue( _context , val ) ){ + switch ( JS_TypeOfValue( _context , val ) ) { case JSTYPE_VOID: return Undefined; case JSTYPE_NULL: return jstNULL; case JSTYPE_OBJECT: { @@ -1280,52 +1303,61 @@ namespace mongo { // ----- setters ------ - void setElement( const char *field , const BSONElement& val ){ + void setElement( const char *field , const BSONElement& val ) { smlock; jsval v = _convertor->toval( val ); assert( JS_SetProperty( _context , _global , field , &v ) ); } - void setNumber( const char *field , double val ){ + void setNumber( const char *field , double val ) { smlock; jsval v = _convertor->toval( val ); assert( JS_SetProperty( _context , _global , field , &v ) ); } - void setString( const char *field , const char * val ){ + void setString( const char *field , const char * val ) { smlock; jsval v = _convertor->toval( val ); assert( JS_SetProperty( _context , _global , field , &v ) ); } - void setObject( const char *field , const BSONObj& obj , bool readOnly ){ + void setObject( const char *field , const BSONObj& obj , bool readOnly ) { smlock; jsval v = _convertor->toval( &obj , readOnly ); JS_SetProperty( _context , _global , field , &v ); } - void setBoolean( const char *field , bool val ){ + void setBoolean( const char *field , bool val ) { smlock; jsval v = BOOLEAN_TO_JSVAL( val ); assert( JS_SetProperty( _context , _global , field , &v ) ); } - void setThis( const BSONObj * obj ){ + void setThis( const BSONObj * obj ) { smlock; - if ( _this ){ + if ( _this ) { JS_RemoveRoot( _context , &_this ); _this = 0; } - - if ( obj ){ + + if ( obj ) { _this = _convertor->toJSObject( obj ); JS_AddNamedRoot( _context , &_this , "scope this" ); } } + void rename( const char * from , const char * to ) { + smlock; + jsval v; + assert( JS_GetProperty( _context , _global , from , &v ) ); + assert( JS_SetProperty( _context , _global , to , &v ) ); + v = JSVAL_VOID; + assert( JS_SetProperty( _context , _global , from , &v ) ); + } + // ---- functions ----- - ScriptingFunction _createFunction( const char * code ){ + ScriptingFunction _createFunction( const char * code ) { smlock; precall(); return (ScriptingFunction)_convertor->compileFunction( code ); @@ -1337,40 +1369,49 @@ namespace mongo { int count; }; - static JSBool _checkTimeout( JSContext *cx ){ + // should not generate exceptions, as those can be caught in + // javascript code; returning false without an exception exits + // immediately + static JSBool _interrupt( JSContext *cx ) { TimeoutSpec &spec = *(TimeoutSpec *)( JS_GetContextPrivate( cx ) ); if ( ++spec.count % 1000 != 0 ) return JS_TRUE; + const char * interrupt = ScriptEngine::checkInterrupt(); + if ( interrupt && interrupt[ 0 ] ) { + return JS_FALSE; + } + if ( spec.timeout.ticks() == 0 ) { + return JS_TRUE; + } boost::posix_time::time_duration elapsed = ( boost::posix_time::microsec_clock::local_time() - spec.start ); if ( elapsed < spec.timeout ) { return JS_TRUE; } - JS_ReportError( cx, "Timeout exceeded" ); return JS_FALSE; } - static JSBool checkTimeout( JSContext *cx, JSScript *script ){ - return _checkTimeout( cx ); - } + static JSBool interrupt( JSContext *cx, JSScript *script ) { + return _interrupt( cx ); + } - void installCheckTimeout( int timeoutMs ) { - if ( timeoutMs > 0 ) { + void installInterrupt( int timeoutMs ) { + if ( timeoutMs != 0 || ScriptEngine::haveCheckInterruptCallback() ) { TimeoutSpec *spec = new TimeoutSpec; spec->timeout = boost::posix_time::millisec( timeoutMs ); spec->start = boost::posix_time::microsec_clock::local_time(); spec->count = 0; JS_SetContextPrivate( _context, (void*)spec ); #if defined(SM181) && !defined(XULRUNNER190) - JS_SetOperationCallback( _context, _checkTimeout ); + JS_SetOperationCallback( _context, _interrupt ); #else - JS_SetBranchCallback( _context, checkTimeout ); + JS_SetBranchCallback( _context, interrupt ); #endif } } - void uninstallCheckTimeout( int timeoutMs ) { - if ( timeoutMs > 0 ) { + void uninstallInterrupt( int timeoutMs ) { + if ( timeoutMs != 0 || ScriptEngine::haveCheckInterruptCallback() ) { #if defined(SM181) && !defined(XULRUNNER190) JS_SetOperationCallback( _context , 0 ); #else @@ -1381,34 +1422,33 @@ namespace mongo { } } - void precall(){ + void precall() { _error = ""; currentScope.reset( this ); } - bool exec( const string& code , const string& name = "(anon)" , bool printResult = false , bool reportError = true , bool assertOnError = true, int timeoutMs = 0 ){ + bool exec( const StringData& code , const string& name = "(anon)" , bool printResult = false , bool reportError = true , bool assertOnError = true, int timeoutMs = 0 ) { smlock; precall(); jsval ret = JSVAL_VOID; - installCheckTimeout( timeoutMs ); - JSBool worked = JS_EvaluateScript( _context , _global , code.c_str() , strlen( code.c_str() ) , name.c_str() , 0 , &ret ); - uninstallCheckTimeout( timeoutMs ); + installInterrupt( timeoutMs ); + JSBool worked = JS_EvaluateScript( _context , _global , code.data() , code.size() , name.c_str() , 1 , &ret ); + uninstallInterrupt( timeoutMs ); - if ( ! worked && _error.size() == 0 ){ + if ( ! worked && _error.size() == 0 ) { jsval v; - if ( JS_GetPendingException( _context , &v ) ){ + if ( JS_GetPendingException( _context , &v ) ) { _error = _convertor->toString( v ); if ( reportError ) cout << _error << endl; } } - if ( assertOnError ) - uassert( 10228 , name + " exec failed" , worked ); + uassert( 10228 , str::stream() << name + " exec failed: " << _error , worked || ! assertOnError ); - if ( reportError && ! _error.empty() ){ + if ( reportError && ! _error.empty() ) { // cout << "exec error: " << _error << endl; // already printed in reportError, so... TODO } @@ -1421,23 +1461,23 @@ namespace mongo { return worked; } - - int invoke( JSFunction * func , const BSONObj& args, int timeoutMs , bool ignoreReturn ){ + + int invoke( JSFunction * func , const BSONObj& args, int timeoutMs , bool ignoreReturn ) { smlock; precall(); assert( JS_EnterLocalRootScope( _context ) ); - + int nargs = args.nFields(); scoped_array<jsval> smargsPtr( new jsval[nargs] ); - if ( nargs ){ + if ( nargs ) { BSONObjIterator it( args ); - for ( int i=0; i<nargs; i++ ){ + for ( int i=0; i<nargs; i++ ) { smargsPtr[i] = _convertor->toval( it.next() ); } } - if ( args.isEmpty() ){ + if ( args.isEmpty() ) { _convertor->setProperty( _global , "args" , JSVAL_NULL ); } else { @@ -1446,35 +1486,35 @@ namespace mongo { JS_LeaveLocalRootScope( _context ); - installCheckTimeout( timeoutMs ); + installInterrupt( timeoutMs ); jsval rval; JSBool ret = JS_CallFunction( _context , _this ? _this : _global , func , nargs , smargsPtr.get() , &rval ); - uninstallCheckTimeout( timeoutMs ); + uninstallInterrupt( timeoutMs ); if ( !ret ) { return -3; } - - if ( ! ignoreReturn ){ + + if ( ! ignoreReturn ) { assert( JS_SetProperty( _context , _global , "return" , &rval ) ); } return 0; } - int invoke( ScriptingFunction funcAddr , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = 0 ){ + int invoke( ScriptingFunction funcAddr , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = 0 ) { return invoke( (JSFunction*)funcAddr , args , timeoutMs , ignoreReturn ); } - void gotError( string s ){ + void gotError( string s ) { _error = s; } - string getError(){ + string getError() { return _error; } - void injectNative( const char *field, NativeFunction func ){ + void injectNative( const char *field, NativeFunction func ) { smlock; string name = field; _convertor->setProperty( _global , (name + "_").c_str() , _convertor->toval( (double)(long long)func ) ); @@ -1482,19 +1522,19 @@ namespace mongo { stringstream code; code << field << "_" << " = { x : " << field << "_ }; "; code << field << " = function(){ return nativeHelper.apply( " << field << "_ , arguments ); }"; - exec( code.str().c_str() ); + exec( code.str() ); } - virtual void gc(){ + virtual void gc() { smlock; JS_GC( _context ); } JSContext *SavedContext() const { return _context; } - + private: - void _postCreateHacks(){ + void _postCreateHacks() { #ifdef XULRUNNER exec( "__x__ = new Date(1);" ); globalSMEngine->_dateClass = _convertor->getClass( _global , "__x__" ); @@ -1502,7 +1542,7 @@ namespace mongo { globalSMEngine->_regexClass = _convertor->getClass( _global , "__x__" ); #endif } - + JSContext * _context; Convertor * _convertor; @@ -1514,41 +1554,41 @@ namespace mongo { bool _externalSetup; bool _localConnect; - + set<string> _initFieldNames; - + }; /* used to make the logging not overly chatty in the mongo shell. */ extern bool isShell; - void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ){ + void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ) { stringstream ss; - if( !isShell ) + if( !isShell ) ss << "JS Error: "; ss << message; - if ( report && report->filename ){ + if ( report && report->filename ) { ss << " " << report->filename << ":" << report->lineno; } tlog() << ss.str() << endl; - if ( currentScope.get() ){ + if ( currentScope.get() ) { currentScope->gotError( ss.str() ); } } - JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { Convertor c(cx); Scope * s = currentScope.get(); - for ( uintN i=0; i<argc; i++ ){ + for ( uintN i=0; i<argc; i++ ) { string filename = c.toString( argv[i] ); //cout << "load [" << filename << "]" << endl; - if ( ! s->execFile( filename , false , true , false ) ){ + if ( ! s->execFile( filename , false , true , false ) ) { JS_ReportError( cx , ((string)"error loading js file: " + filename ).c_str() ); return JS_FALSE; } @@ -1559,7 +1599,7 @@ namespace mongo { - void SMEngine::runTest(){ + void SMEngine::runTest() { SMScope s; s.localConnect( "foo" ); @@ -1589,17 +1629,17 @@ namespace mongo { } - Scope * SMEngine::createScope(){ + Scope * SMEngine::createScope() { return new SMScope(); } - void Convertor::addRoot( JSFunction * f , const char * name ){ + void Convertor::addRoot( JSFunction * f , const char * name ) { if ( ! f ) return; SMScope * scope = currentScope.get(); uassert( 10229 , "need a scope" , scope ); - + JSObject * o = JS_GetFunctionObject( f ); assert( o ); scope->addRoot( &o , name ); diff --git a/scripting/engine_spidermonkey.h b/scripting/engine_spidermonkey.h index 4617b5d..3ee7495 100644 --- a/scripting/engine_spidermonkey.h +++ b/scripting/engine_spidermonkey.h @@ -37,7 +37,7 @@ #include "jstypes.h" #undef JS_PUBLIC_API #undef JS_PUBLIC_DATA -#define JS_PUBLIC_API(t) t __cdecl +#define JS_PUBLIC_API(t) t __cdecl #define JS_PUBLIC_DATA(t) t #endif @@ -64,7 +64,7 @@ #define JSCLASS_GLOBAL_FLAGS 0 -JSBool JS_CStringsAreUTF8(){ +JSBool JS_CStringsAreUTF8() { return false; } @@ -85,7 +85,7 @@ namespace mongo { class SMScope; class Convertor; - + extern JSClass bson_class; extern JSClass bson_ro_class; @@ -99,10 +99,10 @@ namespace mongo { extern JSClass maxkey_class; // internal things - void dontDeleteScope( SMScope * s ){} + void dontDeleteScope( SMScope * s ) {} void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ); extern boost::thread_specific_ptr<SMScope> currentScope; - + // bson JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ); @@ -112,14 +112,14 @@ namespace mongo { bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ); #define JSVAL_IS_OID(v) ( JSVAL_IS_OBJECT( v ) && JS_InstanceOf( cx , JSVAL_TO_OBJECT( v ) , &object_id_class , 0 ) ) - + bool isDate( JSContext * cx , JSObject * o ); // JS private data must be 2byte aligned, so we use a holder to refer to an unaligned pointer. struct BinDataHolder { BinDataHolder( const char *c, int copyLen = -1 ) : - c_( const_cast< char * >( c ) ), - iFree_( copyLen != -1 ) { + c_( const_cast< char * >( c ) ), + iFree_( copyLen != -1 ) { if ( copyLen != -1 ) { c_ = (char*)malloc( copyLen ); memcpy( c_, c, copyLen ); diff --git a/scripting/engine_v8.cpp b/scripting/engine_v8.cpp index 08826b1..cd186b4 100644 --- a/scripting/engine_v8.cpp +++ b/scripting/engine_v8.cpp @@ -1,4 +1,4 @@ -//engine_v8.cpp +//engine_v8.cpp /* Copyright 2009 10gen Inc. * @@ -21,54 +21,74 @@ #include "v8_utils.h" #include "v8_db.h" -#define V8_SIMPLE_HEADER Locker l; HandleScope handle_scope; Context::Scope context_scope( _context ); +#define V8_SIMPLE_HEADER V8Lock l; HandleScope handle_scope; Context::Scope context_scope( _context ); namespace mongo { + // guarded by v8 mutex + map< unsigned, int > __interruptSpecToThreadId; + // --- engine --- V8ScriptEngine::V8ScriptEngine() {} - - V8ScriptEngine::~V8ScriptEngine(){ + + V8ScriptEngine::~V8ScriptEngine() { } - void ScriptEngine::setup(){ - if ( !globalScriptEngine ){ + void ScriptEngine::setup() { + if ( !globalScriptEngine ) { globalScriptEngine = new V8ScriptEngine(); } } + void V8ScriptEngine::interrupt( unsigned opSpec ) { + v8::Locker l; + if ( __interruptSpecToThreadId.count( opSpec ) ) { + V8::TerminateExecution( __interruptSpecToThreadId[ opSpec ] ); + } + } + void V8ScriptEngine::interruptAll() { + v8::Locker l; + vector< int > toKill; // v8 mutex could potentially be yielded during the termination call + for( map< unsigned, int >::const_iterator i = __interruptSpecToThreadId.begin(); i != __interruptSpecToThreadId.end(); ++i ) { + toKill.push_back( i->second ); + } + for( vector< int >::const_iterator i = toKill.begin(); i != toKill.end(); ++i ) { + V8::TerminateExecution( *i ); + } + } + // --- scope --- - - V8Scope::V8Scope( V8ScriptEngine * engine ) - : _engine( engine ) , - _connectState( NOT ){ - Locker l; - HandleScope handleScope; + V8Scope::V8Scope( V8ScriptEngine * engine ) + : _engine( engine ) , + _connectState( NOT ) { + + V8Lock l; + HandleScope handleScope; _context = Context::New(); Context::Scope context_scope( _context ); _global = Persistent< v8::Object >::New( _context->Global() ); _this = Persistent< v8::Object >::New( v8::Object::New() ); - _global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)->GetFunction() ); - _global->Set(v8::String::New("version"), v8::FunctionTemplate::New(Version)->GetFunction() ); + _global->Set(v8::String::New("print"), newV8Function< Print >()->GetFunction() ); + _global->Set(v8::String::New("version"), newV8Function< Version >()->GetFunction() ); _global->Set(v8::String::New("load"), - v8::FunctionTemplate::New(loadCallback, v8::External::New(this))->GetFunction() ); - + v8::FunctionTemplate::New( v8Callback< loadCallback >, v8::External::New(this))->GetFunction() ); + _wrapper = Persistent< v8::Function >::New( getObjectWrapperTemplate()->GetFunction() ); - - _global->Set(v8::String::New("gc"), v8::FunctionTemplate::New(GCV8)->GetFunction() ); + + _global->Set(v8::String::New("gc"), newV8Function< GCV8 >()->GetFunction() ); installDBTypes( _global ); } - V8Scope::~V8Scope(){ - Locker l; - Context::Scope context_scope( _context ); + V8Scope::~V8Scope() { + V8Lock l; + Context::Scope context_scope( _context ); _wrapper.Dispose(); _this.Dispose(); for( unsigned i = 0; i < _funcs.size(); ++i ) @@ -79,7 +99,7 @@ namespace mongo { } Handle< Value > V8Scope::nativeCallback( const Arguments &args ) { - Locker l; + V8Lock l; HandleScope handle_scope; Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_native_function" ) ) ); NativeFunction function = (NativeFunction)(f->Value()); @@ -93,16 +113,18 @@ namespace mongo { BSONObj ret; try { ret = function( nativeArgs ); - } catch( const std::exception &e ) { + } + catch( const std::exception &e ) { return v8::ThrowException(v8::String::New(e.what())); - } catch( ... ) { - return v8::ThrowException(v8::String::New("unknown exception")); + } + catch( ... ) { + return v8::ThrowException(v8::String::New("unknown exception")); } return handle_scope.Close( mongoToV8Element( ret.firstElement() ) ); } Handle< Value > V8Scope::loadCallback( const Arguments &args ) { - Locker l; + V8Lock l; HandleScope handle_scope; Handle<External> field = Handle<External>::Cast(args.Data()); void* ptr = field->Value(); @@ -120,46 +142,46 @@ namespace mongo { // ---- global stuff ---- - void V8Scope::init( BSONObj * data ){ - Locker l; + void V8Scope::init( const BSONObj * data ) { + V8Lock l; if ( ! data ) return; - + BSONObjIterator i( *data ); - while ( i.more() ){ + while ( i.more() ) { BSONElement e = i.next(); setElement( e.fieldName() , e ); } } - - void V8Scope::setNumber( const char * field , double val ){ + + void V8Scope::setNumber( const char * field , double val ) { V8_SIMPLE_HEADER _global->Set( v8::String::New( field ) , v8::Number::New( val ) ); } - void V8Scope::setString( const char * field , const char * val ){ + void V8Scope::setString( const char * field , const char * val ) { V8_SIMPLE_HEADER _global->Set( v8::String::New( field ) , v8::String::New( val ) ); } - void V8Scope::setBoolean( const char * field , bool val ){ + void V8Scope::setBoolean( const char * field , bool val ) { V8_SIMPLE_HEADER _global->Set( v8::String::New( field ) , v8::Boolean::New( val ) ); } - void V8Scope::setElement( const char *field , const BSONElement& e ){ + void V8Scope::setElement( const char *field , const BSONElement& e ) { V8_SIMPLE_HEADER _global->Set( v8::String::New( field ) , mongoToV8Element( e ) ); } - void V8Scope::setObject( const char *field , const BSONObj& obj , bool readOnly){ + void V8Scope::setObject( const char *field , const BSONObj& obj , bool readOnly) { V8_SIMPLE_HEADER // Set() accepts a ReadOnly parameter, but this just prevents the field itself // from being overwritten and doesn't protect the object stored in 'field'. _global->Set( v8::String::New( field ) , mongoToV8( obj, false, readOnly) ); } - int V8Scope::type( const char *field ){ + int V8Scope::type( const char *field ) { V8_SIMPLE_HEADER Handle<Value> v = get( field ); if ( v->IsNull() ) @@ -178,7 +200,7 @@ namespace mongo { return NumberInt; if ( v->IsNumber() ) return NumberDouble; - if ( v->IsExternal() ){ + if ( v->IsExternal() ) { uassert( 10230 , "can't handle external yet" , 0 ); return -1; } @@ -190,36 +212,36 @@ namespace mongo { throw UserException( 12509, (string)"don't know what this is: " + field ); } - v8::Handle<v8::Value> V8Scope::get( const char * field ){ + v8::Handle<v8::Value> V8Scope::get( const char * field ) { return _global->Get( v8::String::New( field ) ); } - double V8Scope::getNumber( const char *field ){ + double V8Scope::getNumber( const char *field ) { V8_SIMPLE_HEADER return get( field )->ToNumber()->Value(); } - int V8Scope::getNumberInt( const char *field ){ + int V8Scope::getNumberInt( const char *field ) { V8_SIMPLE_HEADER return get( field )->ToInt32()->Value(); } - long long V8Scope::getNumberLongLong( const char *field ){ + long long V8Scope::getNumberLongLong( const char *field ) { V8_SIMPLE_HEADER return get( field )->ToInteger()->Value(); } - string V8Scope::getString( const char *field ){ + string V8Scope::getString( const char *field ) { V8_SIMPLE_HEADER return toSTLString( get( field ) ); } - bool V8Scope::getBoolean( const char *field ){ + bool V8Scope::getBoolean( const char *field ) { V8_SIMPLE_HEADER return get( field )->ToBoolean()->Value(); } - - BSONObj V8Scope::getObject( const char * field ){ + + BSONObj V8Scope::getObject( const char * field ) { V8_SIMPLE_HEADER Handle<Value> v = get( field ); if ( v->IsNull() || v->IsUndefined() ) @@ -227,21 +249,28 @@ namespace mongo { uassert( 10231 , "not an object" , v->IsObject() ); return v8ToMongo( v->ToObject() ); } - + // --- functions ----- - Local< v8::Function > V8Scope::__createFunction( const char * raw ){ - for(; isspace( *raw ); ++raw ); // skip whitespace + bool hasFunctionIdentifier( const string& code ) { + if ( code.size() < 9 || code.find( "function" ) != 0 ) + return false; + + return code[8] == ' ' || code[8] == '('; + } + + Local< v8::Function > V8Scope::__createFunction( const char * raw ) { + raw = jsSkipWhiteSpace( raw ); string code = raw; - if ( code.find( "function" ) == string::npos ){ - if ( code.find( "\n" ) == string::npos && - ! hasJSReturn( code ) && - ( code.find( ";" ) == string::npos || code.find( ";" ) == code.size() - 1 ) ){ + if ( !hasFunctionIdentifier( code ) ) { + if ( code.find( "\n" ) == string::npos && + ! hasJSReturn( code ) && + ( code.find( ";" ) == string::npos || code.find( ";" ) == code.size() - 1 ) ) { code = "return " + code; } code = "function(){ " + code + "}"; } - + int num = _funcs.size() + 1; string fn; @@ -250,29 +279,30 @@ namespace mongo { ss << "_funcs" << num; fn = ss.str(); } - + code = fn + " = " + code; TryCatch try_catch; - Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) , - v8::String::New( fn.c_str() ) ); - if ( script.IsEmpty() ){ + // this might be time consuming, consider allowing an interrupt + Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) , + v8::String::New( fn.c_str() ) ); + if ( script.IsEmpty() ) { _error = (string)"compile error: " + toSTLString( &try_catch ); log() << _error << endl; return Local< v8::Function >(); } - + Local<Value> result = script->Run(); - if ( result.IsEmpty() ){ + if ( result.IsEmpty() ) { _error = (string)"compile error: " + toSTLString( &try_catch ); log() << _error << endl; return Local< v8::Function >(); - } - + } + return v8::Function::Cast( *_global->Get( v8::String::New( fn.c_str() ) ) ); } - - ScriptingFunction V8Scope::_createFunction( const char * raw ){ + + ScriptingFunction V8Scope::_createFunction( const char * raw ) { V8_SIMPLE_HEADER Local< Value > ret = __createFunction( raw ); if ( ret.IsEmpty() ) @@ -284,9 +314,9 @@ namespace mongo { return num; } - void V8Scope::setThis( const BSONObj * obj ){ + void V8Scope::setThis( const BSONObj * obj ) { V8_SIMPLE_HEADER - if ( ! obj ){ + if ( ! obj ) { _this = Persistent< v8::Object >::New( v8::Object::New() ); return; } @@ -296,57 +326,80 @@ namespace mongo { argv[0] = v8::External::New( createWrapperHolder( obj , true , false ) ); _this = Persistent< v8::Object >::New( _wrapper->NewInstance( 1, argv ) ); } - - int V8Scope::invoke( ScriptingFunction func , const BSONObj& argsObject, int timeoutMs , bool ignoreReturn ){ + + void V8Scope::rename( const char * from , const char * to ) { + V8_SIMPLE_HEADER; + v8::Local<v8::String> f = v8::String::New( from ); + v8::Local<v8::String> t = v8::String::New( to ); + _global->Set( t , _global->Get( f ) ); + _global->Set( f , v8::Undefined() ); + } + + int V8Scope::invoke( ScriptingFunction func , const BSONObj& argsObject, int timeoutMs , bool ignoreReturn ) { V8_SIMPLE_HEADER Handle<Value> funcValue = _funcs[func-1]; - - TryCatch try_catch; + + TryCatch try_catch; int nargs = argsObject.nFields(); scoped_array< Handle<Value> > args; - if ( nargs ){ + if ( nargs ) { args.reset( new Handle<Value>[nargs] ); BSONObjIterator it( argsObject ); - for ( int i=0; i<nargs; i++ ){ + for ( int i=0; i<nargs; i++ ) { BSONElement next = it.next(); args[i] = mongoToV8Element( next ); } setObject( "args", argsObject, true ); // for backwards compatibility - } else { + } + else { _global->Set( v8::String::New( "args" ), v8::Undefined() ); } + if ( globalScriptEngine->interrupted() ) { + stringstream ss; + ss << "error in invoke: " << globalScriptEngine->checkInterrupt(); + _error = ss.str(); + log() << _error << endl; + return 1; + } + enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable Local<Value> result = ((v8::Function*)(*funcValue))->Call( _this , nargs , args.get() ); - - if ( result.IsEmpty() ){ + disableV8Interrupt(); + + if ( result.IsEmpty() ) { stringstream ss; - ss << "error in invoke: " << toSTLString( &try_catch ); + if ( try_catch.HasCaught() && !try_catch.CanContinue() ) { + ss << "error in invoke: " << globalScriptEngine->checkInterrupt(); + } + else { + ss << "error in invoke: " << toSTLString( &try_catch ); + } _error = ss.str(); log() << _error << endl; return 1; } - if ( ! ignoreReturn ){ + if ( ! ignoreReturn ) { _global->Set( v8::String::New( "return" ) , result ); } return 0; } - bool V8Scope::exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ){ - if ( timeoutMs ){ + bool V8Scope::exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ) { + if ( timeoutMs ) { static bool t = 1; - if ( t ){ - log() << "timeoutMs not support for v8 yet" << endl; + if ( t ) { + log() << "timeoutMs not support for v8 yet code: " << code << endl; t = 0; } } - + V8_SIMPLE_HEADER - + TryCatch try_catch; - - Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) , - v8::String::New( name.c_str() ) ); + + Handle<Script> script = v8::Script::Compile( v8::String::New( code.data() ) , + v8::String::New( name.c_str() ) ); if (script.IsEmpty()) { stringstream ss; ss << "compile error: " << toSTLString( &try_catch ); @@ -356,65 +409,87 @@ namespace mongo { if ( assertOnError ) uassert( 10233 , _error , 0 ); return false; - } - + } + + if ( globalScriptEngine->interrupted() ) { + _error = (string)"exec error: " + globalScriptEngine->checkInterrupt(); + if ( reportError ) { + log() << _error << endl; + } + if ( assertOnError ) { + uassert( 13475 , _error , 0 ); + } + return false; + } + enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable Handle<v8::Value> result = script->Run(); - if ( result.IsEmpty() ){ - _error = (string)"exec error: " + toSTLString( &try_catch ); + disableV8Interrupt(); + if ( result.IsEmpty() ) { + if ( try_catch.HasCaught() && !try_catch.CanContinue() ) { + _error = (string)"exec error: " + globalScriptEngine->checkInterrupt(); + } + else { + _error = (string)"exec error: " + toSTLString( &try_catch ); + } if ( reportError ) log() << _error << endl; if ( assertOnError ) uassert( 10234 , _error , 0 ); return false; - } - + } + _global->Set( v8::String::New( "__lastres__" ) , result ); - if ( printResult && ! result->IsUndefined() ){ + if ( printResult && ! result->IsUndefined() ) { cout << toSTLString( result ) << endl; } - + return true; } - - void V8Scope::injectNative( const char *field, NativeFunction func ){ + + void V8Scope::injectNative( const char *field, NativeFunction func ) { V8_SIMPLE_HEADER - - Handle< FunctionTemplate > f( v8::FunctionTemplate::New( nativeCallback ) ); + + Handle< FunctionTemplate > f( newV8Function< nativeCallback >() ); f->Set( v8::String::New( "_native_function" ), External::New( (void*)func ) ); _global->Set( v8::String::New( field ), f->GetFunction() ); - } - + } + void V8Scope::gc() { cout << "in gc" << endl; - Locker l; - while( V8::IdleNotification() ); + V8Lock l; + while( !V8::IdleNotification() ); } // ----- db access ----- - void V8Scope::localConnect( const char * dbName ){ - V8_SIMPLE_HEADER + void V8Scope::localConnect( const char * dbName ) { + { + V8_SIMPLE_HEADER + + if ( _connectState == EXTERNAL ) + throw UserException( 12510, "externalSetup already called, can't call externalSetup" ); + if ( _connectState == LOCAL ) { + if ( _localDBName == dbName ) + return; + throw UserException( 12511, "localConnect called with a different name previously" ); + } - if ( _connectState == EXTERNAL ) - throw UserException( 12510, "externalSetup already called, can't call externalSetup" ); - if ( _connectState == LOCAL ){ - if ( _localDBName == dbName ) - return; - throw UserException( 12511, "localConnect called with a different name previously" ); - } + // needed for killop / interrupt support + v8::Locker::StartPreemption( 50 ); - //_global->Set( v8::String::New( "Mongo" ) , _engine->_externalTemplate->GetFunction() ); - _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( true )->GetFunction() ); - exec( jsconcatcode , "localConnect 1" , false , true , true , 0 ); - exec( "_mongo = new Mongo();" , "local connect 2" , false , true , true , 0 ); - exec( (string)"db = _mongo.getDB(\"" + dbName + "\");" , "local connect 3" , false , true , true , 0 ); - _connectState = LOCAL; - _localDBName = dbName; + //_global->Set( v8::String::New( "Mongo" ) , _engine->_externalTemplate->GetFunction() ); + _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( true )->GetFunction() ); + execCoreFiles(); + exec( "_mongo = new Mongo();" , "local connect 2" , false , true , true , 0 ); + exec( (string)"db = _mongo.getDB(\"" + dbName + "\");" , "local connect 3" , false , true , true , 0 ); + _connectState = LOCAL; + _localDBName = dbName; + } loadStored(); } - - void V8Scope::externalSetup(){ + + void V8Scope::externalSetup() { V8_SIMPLE_HEADER if ( _connectState == EXTERNAL ) return; @@ -423,18 +498,18 @@ namespace mongo { installFork( _global, _context ); _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( false )->GetFunction() ); - exec( jsconcatcode , "shell setup" , false , true , true , 0 ); + execCoreFiles(); _connectState = EXTERNAL; } // ----- internal ----- - void V8Scope::reset(){ + void V8Scope::reset() { _startCall(); } - void V8Scope::_startCall(){ + void V8Scope::_startCall() { _error = ""; } - + } // namespace mongo diff --git a/scripting/engine_v8.h b/scripting/engine_v8.h index 9d86d92..c770955 100644 --- a/scripting/engine_v8.h +++ b/scripting/engine_v8.h @@ -19,6 +19,7 @@ #include <vector> #include "engine.h" +#include "v8_db.h" #include <v8.h> using namespace v8; @@ -26,19 +27,19 @@ using namespace v8; namespace mongo { class V8ScriptEngine; - + class V8Scope : public Scope { public: - + V8Scope( V8ScriptEngine * engine ); ~V8Scope(); - + virtual void reset(); - virtual void init( BSONObj * data ); + virtual void init( const BSONObj * data ); virtual void localConnect( const char * dbName ); virtual void externalSetup(); - + v8::Handle<v8::Value> get( const char * field ); // caller must create context and handle scopes virtual double getNumber( const char *field ); virtual int getNumberInt( const char *field ); @@ -46,7 +47,7 @@ namespace mongo { virtual string getString( const char *field ); virtual bool getBoolean( const char *field ); virtual BSONObj getObject( const char *field ); - + virtual int type( const char *field ); virtual void setNumber( const char *field , double val ); @@ -55,22 +56,24 @@ namespace mongo { virtual void setElement( const char *field , const BSONElement& e ); virtual void setObject( const char *field , const BSONObj& obj , bool readOnly); virtual void setThis( const BSONObj * obj ); - + + virtual void rename( const char * from , const char * to ); + virtual ScriptingFunction _createFunction( const char * code ); Local< v8::Function > __createFunction( const char * code ); virtual int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = false ); - virtual bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ); - virtual string getError(){ return _error; } - + virtual bool exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ); + virtual string getError() { return _error; } + virtual void injectNative( const char *field, NativeFunction func ); void gc(); Handle< Context > context() const { return _context; } - + private: void _startCall(); - + static Handle< Value > nativeCallback( const Arguments &args ); static Handle< Value > loadCallback( const Arguments &args ); @@ -89,28 +92,32 @@ namespace mongo { enum ConnectState { NOT , LOCAL , EXTERNAL }; ConnectState _connectState; }; - + class V8ScriptEngine : public ScriptEngine { public: V8ScriptEngine(); virtual ~V8ScriptEngine(); - - virtual Scope * createScope(){ return new V8Scope( this ); } - - virtual void runTest(){} + + virtual Scope * createScope() { return new V8Scope( this ); } + + virtual void runTest() {} bool utf8Ok() const { return true; } - class V8Unlocker : public Unlocker { - v8::Unlocker u_; + class V8UnlockForClient : public Unlocker { + V8Unlock u_; }; - - virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new V8Unlocker ); } - + + virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new V8UnlockForClient ); } + + virtual void interrupt( unsigned opSpec ); + virtual void interruptAll(); + private: friend class V8Scope; }; - - + + extern ScriptEngine * globalScriptEngine; + extern map< unsigned, int > __interruptSpecToThreadId; } diff --git a/scripting/sm_db.cpp b/scripting/sm_db.cpp index 8ba612b..4c9d541 100644 --- a/scripting/sm_db.cpp +++ b/scripting/sm_db.cpp @@ -34,15 +34,15 @@ namespace mongo { bool haveLocalShardingInfo( const string& ns ); // ------------ some defs needed --------------- - + JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ); - + // ------------ utils ------------------ - - bool isSpecialName( const string& name ){ + + bool isSpecialName( const string& name ) { static set<string> names; - if ( names.size() == 0 ){ + if ( names.size() == 0 ) { names.insert( "tojson" ); names.insert( "toJson" ); names.insert( "toString" ); @@ -50,10 +50,10 @@ namespace mongo { if ( name.length() == 0 ) return false; - + if ( name[0] == '_' ) return true; - + return names.count( name ) > 0; } @@ -63,8 +63,8 @@ namespace mongo { class CursorHolder { public: CursorHolder( auto_ptr< DBClientCursor > &cursor, const shared_ptr< DBClientWithCommands > &connection ) : - connection_( connection ), - cursor_( cursor ) { + connection_( connection ), + cursor_( cursor ) { assert( cursor_.get() ); } DBClientCursor *get() const { return cursor_.get(); } @@ -72,60 +72,60 @@ namespace mongo { shared_ptr< DBClientWithCommands > connection_; auto_ptr< DBClientCursor > cursor_; }; - + DBClientCursor *getCursor( JSContext *cx, JSObject *obj ) { CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj ); uassert( 10235 , "no cursor!" , holder ); return holder->get(); } - - JSBool internal_cursor_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + + JSBool internal_cursor_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { uassert( 10236 , "no args to internal_cursor_constructor" , argc == 0 ); assert( JS_SetPrivate( cx , obj , 0 ) ); // just for safety return JS_TRUE; } - void internal_cursor_finalize( JSContext * cx , JSObject * obj ){ + void internal_cursor_finalize( JSContext * cx , JSObject * obj ) { CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj ); - if ( holder ){ + if ( holder ) { delete holder; assert( JS_SetPrivate( cx , obj , 0 ) ); } } - JSBool internal_cursor_hasNext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool internal_cursor_hasNext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { DBClientCursor *cursor = getCursor( cx, obj ); try { *rval = cursor->more() ? JSVAL_TRUE : JSVAL_FALSE; } - catch ( std::exception& e ){ + catch ( std::exception& e ) { JS_ReportError( cx , e.what() ); return JS_FALSE; } return JS_TRUE; } - JSBool internal_cursor_objsLeftInBatch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool internal_cursor_objsLeftInBatch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { DBClientCursor *cursor = getCursor( cx, obj ); Convertor c(cx); *rval = c.toval((double) cursor->objsLeftInBatch() ); return JS_TRUE; } - JSBool internal_cursor_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool internal_cursor_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { DBClientCursor *cursor = getCursor( cx, obj ); - BSONObj n; - + BSONObj n; + try { - if ( ! cursor->more() ){ + if ( ! cursor->more() ) { JS_ReportError( cx , "cursor at the end" ); return JS_FALSE; } n = cursor->next(); } - catch ( std::exception& e ){ + catch ( std::exception& e ) { JS_ReportError( cx , e.what() ); return JS_FALSE; } @@ -149,15 +149,15 @@ namespace mongo { JSCLASS_NO_OPTIONAL_MEMBERS }; - + // ------ mongo stuff ------ - JSBool mongo_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool mongo_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { uassert( 10237 , "mongo_constructor not implemented yet" , 0 ); throw -1; } - - JSBool mongo_local_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + + JSBool mongo_local_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { Convertor c( cx ); shared_ptr< DBClientWithCommands > client( createDirectClient() ); @@ -169,29 +169,29 @@ namespace mongo { return JS_TRUE; } - JSBool mongo_external_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool mongo_external_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { Convertor c( cx ); - + smuassert( cx , "0 or 1 args to Mongo" , argc <= 1 ); - + string host = "127.0.0.1"; if ( argc > 0 ) host = c.toString( argv[0] ); - + string errmsg; ConnectionString cs = ConnectionString::parse( host , errmsg ); - if ( ! cs.isValid() ){ + if ( ! cs.isValid() ) { JS_ReportError( cx , errmsg.c_str() ); return JS_FALSE; } shared_ptr< DBClientWithCommands > conn( cs.connect( errmsg ) ); - if ( ! conn ){ + if ( ! conn ) { JS_ReportError( cx , errmsg.c_str() ); return JS_FALSE; } - + ScriptEngine::runConnectCallback( *conn ); assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( conn ) ) ) ); @@ -206,10 +206,10 @@ namespace mongo { uassert( 10239 , "no connection!" , connHolder && connHolder->get() ); return connHolder->get(); } - - void mongo_finalize( JSContext * cx , JSObject * obj ){ + + void mongo_finalize( JSContext * cx , JSObject * obj ) { shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); - if ( connHolder ){ + if ( connHolder ) { delete connHolder; assert( JS_SetPrivate( cx , obj , 0 ) ); } @@ -220,30 +220,31 @@ namespace mongo { JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, mongo_finalize, JSCLASS_NO_OPTIONAL_MEMBERS - }; + }; - JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ - smuassert( cx , "mongo_find needs 6 args" , argc == 6 ); + JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + smuassert( cx , "mongo_find needs 7 args" , argc == 7 ); shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); smuassert( cx , "no connection!" , connHolder && connHolder->get() ); DBClientWithCommands *conn = connHolder->get(); - + Convertor c( cx ); string ns = c.toString( argv[0] ); - + BSONObj q = c.toObject( argv[1] ); BSONObj f = c.toObject( argv[2] ); - + int nToReturn = (int) c.toNumber( argv[3] ); int nToSkip = (int) c.toNumber( argv[4] ); bool slaveOk = c.getBoolean( obj , "slaveOk" ); int batchSize = (int) c.toNumber( argv[5] ); + int options = (int)c.toNumber( argv[6] ); try { - auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , slaveOk ? QueryOption_SlaveOk : 0 , batchSize ); - if ( ! cursor.get() ){ + auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , options | ( slaveOk ? QueryOption_SlaveOk : 0 ) , batchSize ); + if ( ! cursor.get() ) { log() << "query failed : " << ns << " " << q << " to: " << conn->toString() << endl; JS_ReportError( cx , "error doing query: failed" ); return JS_FALSE; @@ -254,19 +255,19 @@ namespace mongo { *rval = OBJECT_TO_JSVAL( mycursor ); return JS_TRUE; } - catch ( ... ){ + catch ( ... ) { JS_ReportError( cx , "error doing query: unknown" ); return JS_FALSE; } } - JSBool mongo_update(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool mongo_update(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { smuassert( cx , "mongo_find needs at elast 3 args" , argc >= 3 ); smuassert( cx , "2nd param to update has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); smuassert( cx , "3rd param to update has to be an object" , JSVAL_IS_OBJECT( argv[2] ) ); Convertor c( cx ); - if ( c.getBoolean( obj , "readOnly" ) ){ + if ( c.getBoolean( obj , "readOnly" ) ) { JS_ReportError( cx , "js db in read only mode - mongo_update" ); return JS_FALSE; } @@ -283,76 +284,80 @@ namespace mongo { conn->update( ns , c.toObject( argv[1] ) , c.toObject( argv[2] ) , upsert , multi ); return JS_TRUE; } - catch ( ... ){ + catch ( ... ) { JS_ReportError( cx , "error doing update" ); return JS_FALSE; } } - JSBool mongo_insert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool mongo_insert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { smuassert( cx , "mongo_insert needs 2 args" , argc == 2 ); smuassert( cx , "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); Convertor c( cx ); - if ( c.getBoolean( obj , "readOnly" ) ){ + if ( c.getBoolean( obj , "readOnly" ) ) { JS_ReportError( cx , "js db in read only mode - mongo_insert" ); return JS_FALSE; } - + DBClientWithCommands * conn = getConnection( cx, obj ); uassert( 10248 , "no connection!" , conn ); - - + string ns = c.toString( argv[0] ); - BSONObj o = c.toObject( argv[1] ); - // TODO: add _id - try { + BSONObj o = c.toObject( argv[1] ); + // TODO: add _id + conn->insert( ns , o ); return JS_TRUE; } - catch ( std::exception& e ){ + catch ( std::exception& e ) { stringstream ss; ss << "error doing insert:" << e.what(); string s = ss.str(); JS_ReportError( cx , s.c_str() ); return JS_FALSE; } - catch ( ... ){ + catch ( ... ) { JS_ReportError( cx , "error doing insert" ); return JS_FALSE; } } - JSBool mongo_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool mongo_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { smuassert( cx , "mongo_remove needs 2 or 3 arguments" , argc == 2 || argc == 3 ); smuassert( cx , "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); Convertor c( cx ); - if ( c.getBoolean( obj , "readOnly" ) ){ + if ( c.getBoolean( obj , "readOnly" ) ) { JS_ReportError( cx , "js db in read only mode - mongo_remove" ); return JS_FALSE; } DBClientWithCommands * conn = getConnection( cx, obj ); uassert( 10251 , "no connection!" , conn ); - + string ns = c.toString( argv[0] ); BSONObj o = c.toObject( argv[1] ); bool justOne = false; if ( argc > 2 ) justOne = c.toBoolean( argv[2] ); - + try { conn->remove( ns , o , justOne ); return JS_TRUE; } - catch ( ... ){ - JS_ReportError( cx , "error doing remove" ); + catch ( std::exception& e ) { + JS_ReportError( cx , e.what() ); return JS_FALSE; } + catch ( ... ) { + JS_ReportError( cx , "error doing remove" ); + return JS_FALSE; + } + } JSFunctionSpec mongo_functions[] = { @@ -363,93 +368,93 @@ namespace mongo { { 0 } }; - // ------------- db_collection ------------- - - JSBool db_collection_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ - smuassert( cx , "db_collection_constructor wrong args" , argc == 4 ); - assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); - assert( JS_SetProperty( cx , obj , "_db" , &(argv[1]) ) ); - assert( JS_SetProperty( cx , obj , "_shortName" , &(argv[2]) ) ); - assert( JS_SetProperty( cx , obj , "_fullName" , &(argv[3]) ) ); - - Convertor c(cx); - if ( haveLocalShardingInfo( c.toString( argv[3] ) ) ){ - JS_ReportError( cx , "can't use sharded collection from db.eval" ); - return JS_FALSE; - } - - return JS_TRUE; - } - - JSBool db_collection_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ - if ( flags & JSRESOLVE_ASSIGNING ) - return JS_TRUE; - - Convertor c( cx ); - string collname = c.toString( id ); - - if ( isSpecialName( collname ) ) - return JS_TRUE; - - if ( obj == c.getGlobalPrototype( "DBCollection" ) ) - return JS_TRUE; - - JSObject * proto = JS_GetPrototype( cx , obj ); - if ( c.hasProperty( obj , collname.c_str() ) || ( proto && c.hasProperty( proto , collname.c_str() ) ) ) - return JS_TRUE; - - string name = c.toString( c.getProperty( obj , "_shortName" ) ); - name += "."; - name += collname; - - jsval db = c.getProperty( obj , "_db" ); - if ( ! JSVAL_IS_OBJECT( db ) ) - return JS_TRUE; - - JSObject * coll = doCreateCollection( cx , JSVAL_TO_OBJECT( db ) , name ); - if ( ! coll ) - return JS_FALSE; - c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); - *objp = obj; - return JS_TRUE; - } + // ------------- db_collection ------------- + + JSBool db_collection_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx , "db_collection_constructor wrong args" , argc == 4 ); + assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "_db" , &(argv[1]) ) ); + assert( JS_SetProperty( cx , obj , "_shortName" , &(argv[2]) ) ); + assert( JS_SetProperty( cx , obj , "_fullName" , &(argv[3]) ) ); + + Convertor c(cx); + if ( haveLocalShardingInfo( c.toString( argv[3] ) ) ) { + JS_ReportError( cx , "can't use sharded collection from db.eval" ); + return JS_FALSE; + } + + return JS_TRUE; + } + + JSBool db_collection_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + Convertor c( cx ); + string collname = c.toString( id ); + + if ( isSpecialName( collname ) ) + return JS_TRUE; + + if ( obj == c.getGlobalPrototype( "DBCollection" ) ) + return JS_TRUE; + + JSObject * proto = JS_GetPrototype( cx , obj ); + if ( c.hasProperty( obj , collname.c_str() ) || ( proto && c.hasProperty( proto , collname.c_str() ) ) ) + return JS_TRUE; + + string name = c.toString( c.getProperty( obj , "_shortName" ) ); + name += "."; + name += collname; + + jsval db = c.getProperty( obj , "_db" ); + if ( ! JSVAL_IS_OBJECT( db ) ) + return JS_TRUE; + + JSObject * coll = doCreateCollection( cx , JSVAL_TO_OBJECT( db ) , name ); + if ( ! coll ) + return JS_FALSE; + c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); + *objp = obj; + return JS_TRUE; + } JSClass db_collection_class = { - "DBCollection" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , - JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, - JS_EnumerateStub, (JSResolveOp)(&db_collection_resolve) , JS_ConvertStub, JS_FinalizeStub, - JSCLASS_NO_OPTIONAL_MEMBERS - }; - - - JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ){ - Convertor c(cx); - - assert( c.hasProperty( db , "_mongo" ) ); - assert( c.hasProperty( db , "_name" ) ); - - JSObject * coll = JS_NewObject( cx , &db_collection_class , 0 , 0 ); - CHECKNEWOBJECT( coll, cx, "doCreateCollection" ); - c.setProperty( coll , "_mongo" , c.getProperty( db , "_mongo" ) ); - c.setProperty( coll , "_db" , OBJECT_TO_JSVAL( db ) ); - c.setProperty( coll , "_shortName" , c.toval( shortName.c_str() ) ); - - string name = c.toString( c.getProperty( db , "_name" ) ); - name += "." + shortName; - c.setProperty( coll , "_fullName" , c.toval( name.c_str() ) ); - - if ( haveLocalShardingInfo( name ) ){ - JS_ReportError( cx , "can't use sharded collection from db.eval" ); - return 0; - } - - return coll; + "DBCollection" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&db_collection_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ) { + Convertor c(cx); + + assert( c.hasProperty( db , "_mongo" ) ); + assert( c.hasProperty( db , "_name" ) ); + + JSObject * coll = JS_NewObject( cx , &db_collection_class , 0 , 0 ); + CHECKNEWOBJECT( coll, cx, "doCreateCollection" ); + c.setProperty( coll , "_mongo" , c.getProperty( db , "_mongo" ) ); + c.setProperty( coll , "_db" , OBJECT_TO_JSVAL( db ) ); + c.setProperty( coll , "_shortName" , c.toval( shortName.c_str() ) ); + + string name = c.toString( c.getProperty( db , "_name" ) ); + name += "." + shortName; + c.setProperty( coll , "_fullName" , c.toval( name.c_str() ) ); + + if ( haveLocalShardingInfo( name ) ) { + JS_ReportError( cx , "can't use sharded collection from db.eval" ); + return 0; + } + + return coll; } - + // -------------- DB --------------- - - - JSBool db_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + + + JSBool db_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { smuassert( cx, "wrong number of arguments to DB" , argc == 2 ); assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); assert( JS_SetProperty( cx , obj , "_name" , &(argv[1]) ) ); @@ -457,7 +462,7 @@ namespace mongo { return JS_TRUE; } - JSBool db_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + JSBool db_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { if ( flags & JSRESOLVE_ASSIGNING ) return JS_TRUE; @@ -467,9 +472,9 @@ namespace mongo { return JS_TRUE; string collname = c.toString( id ); - + if ( isSpecialName( collname ) ) - return JS_TRUE; + return JS_TRUE; JSObject * proto = JS_GetPrototype( cx , obj ); if ( proto && c.hasProperty( proto , collname.c_str() ) ) @@ -479,26 +484,26 @@ namespace mongo { if ( ! coll ) return JS_FALSE; c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); - + *objp = obj; return JS_TRUE; } JSClass db_class = { - "DB" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + "DB" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, (JSResolveOp)(&db_resolve) , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; - + // -------------- object id ------------- - JSBool object_id_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool object_id_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { Convertor c( cx ); OID oid; - if ( argc == 0 ){ + if ( argc == 0 ) { oid.init(); } else { @@ -507,26 +512,27 @@ namespace mongo { try { Scope::validateObjectIdString( s ); - } catch ( const MsgAssertionException &m ) { + } + catch ( const MsgAssertionException &m ) { static string error = m.toString(); JS_ReportError( cx, error.c_str() ); return JS_FALSE; } oid.init( s ); } - - if ( ! JS_InstanceOf( cx , obj , &object_id_class , 0 ) ){ + + if ( ! JS_InstanceOf( cx , obj , &object_id_class , 0 ) ) { obj = JS_NewObject( cx , &object_id_class , 0 , 0 ); CHECKNEWOBJECT( obj, cx, "object_id_constructor" ); *rval = OBJECT_TO_JSVAL( obj ); } - + jsval v = c.toval( oid.str().c_str() ); assert( JS_SetProperty( cx , obj , "str" , &v ) ); return JS_TRUE; } - + JSClass object_id_class = { "ObjectId" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, @@ -534,7 +540,7 @@ namespace mongo { JSCLASS_NO_OPTIONAL_MEMBERS }; - JSBool object_id_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool object_id_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); return (JSBool) (*rval = c.getProperty( obj , "str" )); } @@ -546,26 +552,26 @@ namespace mongo { // dbpointer - JSBool dbpointer_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool dbpointer_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { Convertor c( cx ); - - if ( argc == 2 ){ - if ( ! JSVAL_IS_OID( argv[1] ) ){ + if ( argc == 2 ) { + + if ( ! JSVAL_IS_OID( argv[1] ) ) { JS_ReportError( cx , "2nd arg to DBPointer needs to be oid" ); - return JS_FALSE; + return JS_FALSE; } - + assert( JS_SetProperty( cx , obj , "ns" , &(argv[0]) ) ); assert( JS_SetProperty( cx , obj , "id" , &(argv[1]) ) ); return JS_TRUE; } else { JS_ReportError( cx , "DBPointer needs 2 arguments" ); - return JS_FALSE; + return JS_FALSE; } } - + JSClass dbpointer_class = { "DBPointer" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, @@ -578,10 +584,10 @@ namespace mongo { }; - JSBool dbref_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool dbref_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { Convertor c( cx ); - if ( argc == 2 ){ + if ( argc == 2 ) { JSObject * o = JS_NewObject( cx , NULL , NULL, NULL ); CHECKNEWOBJECT( o, cx, "dbref_constructor" ); assert( JS_SetProperty( cx, o , "$ref" , &argv[ 0 ] ) ); @@ -593,37 +599,37 @@ namespace mongo { else { JS_ReportError( cx , "DBRef needs 2 arguments" ); assert( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( BSONObj().getOwned() ) ) ) ); - return JS_FALSE; + return JS_FALSE; } } - + JSClass dbref_class = bson_class; // name will be fixed later // UUID ************************** - JSBool uuid_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool uuid_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { Convertor c( cx ); - - if( argc == 0 ) { + + if( argc == 0 ) { #if defined(HAVE_UUID) //uuids::uuid #else #endif JS_ReportError( cx , "UUID needs 1 argument -- UUID(hexstr)" ); - return JS_FALSE; + return JS_FALSE; } else if ( argc == 1 ) { string encoded = c.toString( argv[ 0 ] ); - if( encoded.size() != 32 ) { - JS_ReportError( cx, "expect 32 char hex string to UUID()" ); - return JS_FALSE; - } + if( encoded.size() != 32 ) { + JS_ReportError( cx, "expect 32 char hex string to UUID()" ); + return JS_FALSE; + } - char buf[16]; - for( int i = 0; i < 16; i++ ) { - buf[i] = fromHex(encoded.c_str() + i * 2); - } + char buf[16]; + for( int i = 0; i < 16; i++ ) { + buf[i] = fromHex(encoded.c_str() + i * 2); + } assert( JS_SetPrivate( cx, obj, new BinDataHolder( buf, 16 ) ) ); c.setProperty( obj, "len", c.toval( (double)16 ) ); @@ -633,11 +639,11 @@ namespace mongo { } else { JS_ReportError( cx , "UUID needs 1 argument -- UUID(hexstr)" ); - return JS_FALSE; + return JS_FALSE; } } - - JSBool uuid_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + + JSBool uuid_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); void *holder = JS_GetPrivate( cx, obj ); assert( holder ); @@ -649,15 +655,15 @@ namespace mongo { return *rval = c.toval( ret.c_str() ); } - void uuid_finalize( JSContext * cx , JSObject * obj ){ + void uuid_finalize( JSContext * cx , JSObject * obj ) { Convertor c(cx); void *holder = JS_GetPrivate( cx, obj ); - if ( holder ){ + if ( holder ) { delete ( BinDataHolder* )holder; assert( JS_SetPrivate( cx , obj , 0 ) ); } - } - + } + JSClass uuid_class = { "UUID" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, @@ -669,25 +675,25 @@ namespace mongo { { "toString" , uuid_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , { 0 } }; - + // BinData ************************** - JSBool bindata_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool bindata_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { Convertor c( cx ); - - if ( argc == 2 ){ + + if ( argc == 2 ) { int type = (int)c.toNumber( argv[ 0 ] ); - if( type < 0 || type > 255 ) { + if( type < 0 || type > 255 ) { JS_ReportError( cx , "invalid BinData subtype -- range is 0..255 see bsonspec.org" ); - return JS_FALSE; + return JS_FALSE; } string encoded = c.toString( argv[ 1 ] ); string decoded; try { decoded = base64::decode( encoded ); } - catch(...) { + catch(...) { JS_ReportError(cx, "BinData could not decode base64 parameter"); return JS_FALSE; } @@ -700,11 +706,11 @@ namespace mongo { } else { JS_ReportError( cx , "BinData needs 2 arguments -- BinData(subtype,data)" ); - return JS_FALSE; + return JS_FALSE; } } - - JSBool bindata_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + + JSBool bindata_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); int type = (int)c.getNumber( obj , "type" ); int len = (int)c.getNumber( obj, "len" ); @@ -719,7 +725,7 @@ namespace mongo { return *rval = c.toval( ret.c_str() ); } - JSBool bindataBase64(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool bindataBase64(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); int len = (int)c.getNumber( obj, "len" ); void *holder = JS_GetPrivate( cx, obj ); @@ -731,7 +737,7 @@ namespace mongo { return *rval = c.toval( ret.c_str() ); } - JSBool bindataAsHex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool bindataAsHex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); int len = (int)c.getNumber( obj, "len" ); void *holder = JS_GetPrivate( cx, obj ); @@ -747,27 +753,27 @@ namespace mongo { return *rval = c.toval( ret.c_str() ); } - JSBool bindataLength(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool bindataLength(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); int len = (int)c.getNumber( obj, "len" ); return *rval = c.toval((double) len); } - JSBool bindataSubtype(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool bindataSubtype(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); int t = (int)c.getNumber( obj, "type" ); return *rval = c.toval((double) t); } - void bindata_finalize( JSContext * cx , JSObject * obj ){ + void bindata_finalize( JSContext * cx , JSObject * obj ) { Convertor c(cx); void *holder = JS_GetPrivate( cx, obj ); - if ( holder ){ + if ( holder ) { delete ( BinDataHolder* )holder; assert( JS_SetPrivate( cx , obj , 0 ) ); } - } - + } + JSClass bindata_class = { "BinData" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, @@ -783,15 +789,15 @@ namespace mongo { { "subtype", bindataSubtype, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } , { 0 } }; - + // Map - bool specialMapString( const string& s ){ + bool specialMapString( const string& s ) { return s == "put" || s == "get" || s == "_get" || s == "values" || s == "_data" || s == "constructor" ; } - JSBool map_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ - if ( argc > 0 ){ + JSBool map_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + if ( argc > 0 ) { JS_ReportError( cx , "Map takes no arguments" ); return JS_FALSE; } @@ -804,28 +810,28 @@ namespace mongo { return JS_TRUE; } - - JSBool map_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp ){ + + JSBool map_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp ) { Convertor c(cx); if ( specialMapString( c.toString( idval ) ) ) return JS_TRUE; - + log() << "illegal prop access: " << c.toString( idval ) << endl; JS_ReportError( cx , "can't use array access with Map" ); return JS_FALSE; } - + JSClass map_class = { "Map" , JSCLASS_HAS_PRIVATE , map_prop, JS_PropertyStub, map_prop, map_prop, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; - + JSFunctionSpec map_functions[] = { { 0 } }; - + // ----- @@ -835,23 +841,54 @@ namespace mongo { JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; - + + JSBool timestamp_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx , "Timestamp needs 0 or 2 args" , argc == 0 || argc == 2 ); + + if ( ! JS_InstanceOf( cx , obj , ×tamp_class , 0 ) ) { + obj = JS_NewObject( cx , ×tamp_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "timestamp_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + Convertor c( cx ); + if ( argc == 0 ) { + c.setProperty( obj, "t", c.toval( 0.0 ) ); + c.setProperty( obj, "i", c.toval( 0.0 ) ); + } + else { + c.setProperty( obj, "t", argv[ 0 ] ); + c.setProperty( obj, "i", argv[ 1 ] ); + } + + return JS_TRUE; + } + + JSClass numberlong_class = { "NumberLong" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; - - JSBool numberlong_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + + JSBool numberlong_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { smuassert( cx , "NumberLong needs 0 or 1 args" , argc == 0 || argc == 1 ); - + + if ( ! JS_InstanceOf( cx , obj , &numberlong_class , 0 ) ) { + obj = JS_NewObject( cx , &numberlong_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "numberlong_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + Convertor c( cx ); if ( argc == 0 ) { c.setProperty( obj, "floatApprox", c.toval( 0.0 ) ); - } else if ( JSVAL_IS_NUMBER( argv[ 0 ] ) ) { + } + else if ( JSVAL_IS_NUMBER( argv[ 0 ] ) ) { c.setProperty( obj, "floatApprox", argv[ 0 ] ); - } else { + } + else { string num = c.toString( argv[ 0 ] ); //PRINT(num); const char *numStr = num.c_str(); @@ -859,25 +896,26 @@ namespace mongo { try { n = parseLL( numStr ); //PRINT(n); - } catch ( const AssertionException & ) { - smuassert( cx , "could not convert string to long long" , false ); + } + catch ( const AssertionException & ) { + smuassert( cx , "could not convert string to long long" , false ); } c.makeLongObj( n, obj ); } - + return JS_TRUE; } - - JSBool numberlong_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + + JSBool numberlong_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); - return *rval = c.toval( double( c.toNumberLongUnsafe( obj ) ) ); + return *rval = c.toval( double( c.toNumberLongUnsafe( obj ) ) ); } - - JSBool numberlong_tonumber(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + + JSBool numberlong_tonumber(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { return numberlong_valueof( cx, obj, argc, argv, rval ); } - JSBool numberlong_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + JSBool numberlong_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { Convertor c(cx); stringstream ss; long long val = c.toNumberLongUnsafe( obj ); @@ -893,12 +931,12 @@ namespace mongo { } JSFunctionSpec numberlong_functions[] = { - { "valueOf" , numberlong_valueof , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , - { "toNumber" , numberlong_tonumber , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , - { "toString" , numberlong_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , - { 0 } - }; - + { "valueOf" , numberlong_valueof , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toNumber" , numberlong_tonumber , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toString" , numberlong_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + JSClass minkey_class = { "MinKey" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, @@ -912,12 +950,12 @@ namespace mongo { JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; - + // dbquery - JSBool dbquery_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JSBool dbquery_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { smuassert( cx , "DDQuery needs at least 4 args" , argc >= 4 ); - + Convertor c(cx); c.setProperty( obj , "_mongo" , argv[0] ); c.setProperty( obj , "_db" , argv[1] ); @@ -931,28 +969,34 @@ namespace mongo { CHECKNEWOBJECT( temp, cx, "dbquery_constructor" ); c.setProperty( obj , "_query" , OBJECT_TO_JSVAL( temp ) ); } - + if ( argc > 5 && JSVAL_IS_OBJECT( argv[5] ) ) c.setProperty( obj , "_fields" , argv[5] ); else c.setProperty( obj , "_fields" , JSVAL_NULL ); - - + + if ( argc > 6 && JSVAL_IS_NUMBER( argv[6] ) ) c.setProperty( obj , "_limit" , argv[6] ); - else + else c.setProperty( obj , "_limit" , JSVAL_ZERO ); - + if ( argc > 7 && JSVAL_IS_NUMBER( argv[7] ) ) c.setProperty( obj , "_skip" , argv[7] ); - else + else c.setProperty( obj , "_skip" , JSVAL_ZERO ); if ( argc > 8 && JSVAL_IS_NUMBER( argv[8] ) ) c.setProperty( obj , "_batchSize" , argv[8] ); - else + else c.setProperty( obj , "_batchSize" , JSVAL_ZERO ); - + + if ( argc > 9 && JSVAL_IS_NUMBER( argv[9] ) ) + c.setProperty( obj , "_options" , argv[8] ); + else + c.setProperty( obj , "_options" , JSVAL_ZERO ); + + c.setProperty( obj , "_cursor" , JSVAL_NULL ); c.setProperty( obj , "_numReturned" , JSVAL_ZERO ); c.setProperty( obj , "_special" , JSVAL_FALSE ); @@ -960,7 +1004,7 @@ namespace mongo { return JS_TRUE; } - JSBool dbquery_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + JSBool dbquery_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { if ( flags & JSRESOLVE_ASSIGNING ) return JS_TRUE; @@ -981,13 +1025,13 @@ namespace mongo { JS_EnumerateStub, (JSResolveOp)(&dbquery_resolve) , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; - + // ---- other stuff ---- - - void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ){ + + void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ) { assert( JS_InitClass( cx , global , 0 , &mongo_class , local ? mongo_local_constructor : mongo_external_constructor , 0 , 0 , mongo_functions , 0 , 0 ) ); - + assert( JS_InitClass( cx , global , 0 , &object_id_class , object_id_constructor , 0 , 0 , object_id_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &db_class , db_constructor , 2 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &db_collection_class , db_collection_constructor , 4 , 0 , 0 , 0 , 0 ) ); @@ -997,84 +1041,84 @@ namespace mongo { assert( JS_InitClass( cx , global , 0 , &bindata_class , bindata_constructor , 0 , 0 , bindata_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &uuid_class , uuid_constructor , 0 , 0 , uuid_functions , 0 , 0 ) ); - assert( JS_InitClass( cx , global , 0 , ×tamp_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , ×tamp_class , timestamp_constructor , 0 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &numberlong_class , numberlong_constructor , 0 , 0 , numberlong_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &minkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &maxkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &map_class , map_constructor , 0 , 0 , map_functions , 0 , 0 ) ); - + assert( JS_InitClass( cx , global , 0 , &bson_ro_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &bson_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); - + static const char *dbrefName = "DBRef"; dbref_class.name = dbrefName; assert( JS_InitClass( cx , global , 0 , &dbref_class , dbref_constructor , 2 , 0 , bson_functions , 0 , 0 ) ); - - scope->exec( jsconcatcode ); + + scope->execCoreFiles(); } - bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ){ - - if ( JS_InstanceOf( c->_context , o , &object_id_class , 0 ) ){ + bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ) { + + if ( JS_InstanceOf( c->_context , o , &object_id_class , 0 ) ) { OID oid; oid.init( c->getString( o , "str" ) ); b.append( name , oid ); return true; } - if ( JS_InstanceOf( c->_context , o , &minkey_class , 0 ) ){ + if ( JS_InstanceOf( c->_context , o , &minkey_class , 0 ) ) { b.appendMinKey( name ); return true; } - if ( JS_InstanceOf( c->_context , o , &maxkey_class , 0 ) ){ + if ( JS_InstanceOf( c->_context , o , &maxkey_class , 0 ) ) { b.appendMaxKey( name ); return true; } - - if ( JS_InstanceOf( c->_context , o , ×tamp_class , 0 ) ){ + + if ( JS_InstanceOf( c->_context , o , ×tamp_class , 0 ) ) { b.appendTimestamp( name , (unsigned long long)c->getNumber( o , "t" ) , (unsigned int )c->getNumber( o , "i" ) ); return true; } - if ( JS_InstanceOf( c->_context , o , &numberlong_class , 0 ) ){ + if ( JS_InstanceOf( c->_context , o , &numberlong_class , 0 ) ) { b.append( name , c->toNumberLongUnsafe( o ) ); return true; } - - if ( JS_InstanceOf( c->_context , o , &dbpointer_class , 0 ) ){ - b.appendDBRef( name , c->getString( o , "ns" ).c_str() , c->toOID( c->getProperty( o , "id" ) ) ); + + if ( JS_InstanceOf( c->_context , o , &dbpointer_class , 0 ) ) { + b.appendDBRef( name , c->getString( o , "ns" ) , c->toOID( c->getProperty( o , "id" ) ) ); return true; } - - if ( JS_InstanceOf( c->_context , o , &bindata_class , 0 ) ){ + + if ( JS_InstanceOf( c->_context , o , &bindata_class , 0 ) ) { void *holder = JS_GetPrivate( c->_context , o ); const char *data = ( ( BinDataHolder * )( holder ) )->c_; - b.appendBinData( name , - (int)(c->getNumber( o , "len" )) , (BinDataType)((char)(c->getNumber( o , "type" ) ) ) , + b.appendBinData( name , + (int)(c->getNumber( o , "len" )) , (BinDataType)((char)(c->getNumber( o , "type" ) ) ) , data - ); + ); return true; } - + #if defined( SM16 ) || defined( MOZJS ) #warning dates do not work in your version of spider monkey { jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); - if ( d ){ + if ( d ) { b.appendDate( name , Date_t(d) ); return true; } } #elif defined( XULRUNNER ) - if ( JS_InstanceOf( c->_context , o, globalSMEngine->_dateClass , 0 ) ){ + if ( JS_InstanceOf( c->_context , o, globalSMEngine->_dateClass , 0 ) ) { jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); b.appendDate( name , Date_t(d) ); return true; } #else - if ( JS_InstanceOf( c->_context , o, &js_DateClass , 0 ) ){ + if ( JS_InstanceOf( c->_context , o, &js_DateClass , 0 ) ) { jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); //TODO: make signed b.appendDate( name , Date_t((unsigned long long)d) ); @@ -1082,35 +1126,35 @@ namespace mongo { } #endif - + if ( JS_InstanceOf( c->_context , o , &dbquery_class , 0 ) || - JS_InstanceOf( c->_context , o , &mongo_class , 0 ) || - JS_InstanceOf( c->_context , o , &db_collection_class , 0 ) ){ + JS_InstanceOf( c->_context , o , &mongo_class , 0 ) || + JS_InstanceOf( c->_context , o , &db_collection_class , 0 ) ) { b.append( name , c->toString( val ) ); return true; } -#if defined( XULRUNNER ) - if ( JS_InstanceOf( c->_context , o , globalSMEngine->_regexClass , 0 ) ){ +#if defined( XULRUNNER ) + if ( JS_InstanceOf( c->_context , o , globalSMEngine->_regexClass , 0 ) ) { c->appendRegex( b , name , c->toString( val ) ); return true; } -#elif defined( SM18 ) - if ( JS_InstanceOf( c->_context , o , &js_RegExpClass , 0 ) ){ +#elif defined( SM18 ) + if ( JS_InstanceOf( c->_context , o , &js_RegExpClass , 0 ) ) { c->appendRegex( b , name , c->toString( val ) ); return true; } #endif - + return false; } - bool isDate( JSContext * cx , JSObject * o ){ + bool isDate( JSContext * cx , JSObject * o ) { #if defined( SM16 ) || defined( MOZJS ) || defined( XULRUNNER ) return js_DateGetMsecSinceEpoch( cx , o ) != 0; #else return JS_InstanceOf( cx , o, &js_DateClass, 0 ); #endif } - + } diff --git a/scripting/utils.cpp b/scripting/utils.cpp index ee01bb2..97eea10 100644 --- a/scripting/utils.cpp +++ b/scripting/utils.cpp @@ -23,31 +23,40 @@ namespace mongo { - BSONObj jsmd5( const BSONObj &a ){ + void installBenchmarkSystem( Scope& scope ); + + BSONObj jsmd5( const BSONObj &a ) { uassert( 10261 , "js md5 needs a string" , a.firstElement().type() == String ); const char * s = a.firstElement().valuestrsafe(); - + md5digest d; md5_state_t st; md5_init(&st); md5_append( &st , (const md5_byte_t*)s , strlen( s ) ); md5_finish(&st, d); - + return BSON( "" << digestToString( d ) ); } - - BSONObj JSVersion( const BSONObj& args ){ + + BSONObj JSVersion( const BSONObj& args ) { cout << "version: " << versionString << endl; if ( strstr( versionString , "+" ) ) printGitVersion(); return BSONObj(); } - void installGlobalUtils( Scope& scope ){ + + // --------------------------------- + // ---- installer -------- + // --------------------------------- + + void installGlobalUtils( Scope& scope ) { scope.injectNative( "hex_md5" , jsmd5 ); scope.injectNative( "version" , JSVersion ); + + installBenchmarkSystem( scope ); } } - + diff --git a/scripting/v8_db.cpp b/scripting/v8_db.cpp index e178875..4d12454 100644 --- a/scripting/v8_db.cpp +++ b/scripting/v8_db.cpp @@ -18,10 +18,11 @@ #include "v8_wrapper.h" #include "v8_utils.h" #include "v8_db.h" -#include "engine.h" +#include "engine_v8.h" #include "util/base64.h" #include "util/text.h" #include "../client/syncclusterconnection.h" +#include "../s/d_logic.h" #include <iostream> using namespace std; @@ -31,99 +32,118 @@ namespace mongo { #define DDD(x) - v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( bool local ){ - v8::Local<v8::FunctionTemplate> mongo = FunctionTemplate::New( local ? mongoConsLocal : mongoConsExternal ); + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( bool local ) { + v8::Local<v8::FunctionTemplate> mongo; + if ( local ) { + mongo = newV8Function< mongoConsLocal >(); + } + else { + mongo = newV8Function< mongoConsExternal >(); + } mongo->InstanceTemplate()->SetInternalFieldCount( 1 ); - + v8::Local<v8::Template> proto = mongo->PrototypeTemplate(); - proto->Set( v8::String::New( "find" ) , FunctionTemplate::New( mongoFind ) ); - proto->Set( v8::String::New( "insert" ) , FunctionTemplate::New( mongoInsert ) ); - proto->Set( v8::String::New( "remove" ) , FunctionTemplate::New( mongoRemove ) ); - proto->Set( v8::String::New( "update" ) , FunctionTemplate::New( mongoUpdate ) ); + proto->Set( v8::String::New( "find" ) , newV8Function< mongoFind >() ); + proto->Set( v8::String::New( "insert" ) , newV8Function< mongoInsert >() ); + proto->Set( v8::String::New( "remove" ) , newV8Function< mongoRemove >() ); + proto->Set( v8::String::New( "update" ) , newV8Function< mongoUpdate >() ); - Local<FunctionTemplate> ic = FunctionTemplate::New( internalCursorCons ); + Local<FunctionTemplate> ic = newV8Function< internalCursorCons >(); ic->InstanceTemplate()->SetInternalFieldCount( 1 ); - ic->PrototypeTemplate()->Set( v8::String::New("next") , FunctionTemplate::New( internalCursorNext ) ); - ic->PrototypeTemplate()->Set( v8::String::New("hasNext") , FunctionTemplate::New( internalCursorHasNext ) ); - ic->PrototypeTemplate()->Set( v8::String::New("objsLeftInBatch") , FunctionTemplate::New( internalCursorObjsLeftInBatch ) ); + ic->PrototypeTemplate()->Set( v8::String::New("next") , newV8Function< internalCursorNext >() ); + ic->PrototypeTemplate()->Set( v8::String::New("hasNext") , newV8Function< internalCursorHasNext >() ); + ic->PrototypeTemplate()->Set( v8::String::New("objsLeftInBatch") , newV8Function< internalCursorObjsLeftInBatch >() ); proto->Set( v8::String::New( "internalCursor" ) , ic ); - + return mongo; } v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate() { - v8::Local<v8::FunctionTemplate> numberLong = FunctionTemplate::New( numberLongInit ); + v8::Local<v8::FunctionTemplate> numberLong = newV8Function< numberLongInit >(); v8::Local<v8::Template> proto = numberLong->PrototypeTemplate(); - - proto->Set( v8::String::New( "valueOf" ) , FunctionTemplate::New( numberLongValueOf ) ); - proto->Set( v8::String::New( "toNumber" ) , FunctionTemplate::New( numberLongToNumber ) ); - proto->Set( v8::String::New( "toString" ) , FunctionTemplate::New( numberLongToString ) ); - + + proto->Set( v8::String::New( "valueOf" ) , newV8Function< numberLongValueOf >() ); + proto->Set( v8::String::New( "toNumber" ) , newV8Function< numberLongToNumber >() ); + proto->Set( v8::String::New( "toString" ) , newV8Function< numberLongToString >() ); + return numberLong; } v8::Handle<v8::FunctionTemplate> getBinDataFunctionTemplate() { - v8::Local<v8::FunctionTemplate> binData = FunctionTemplate::New( binDataInit ); + v8::Local<v8::FunctionTemplate> binData = newV8Function< binDataInit >(); v8::Local<v8::Template> proto = binData->PrototypeTemplate(); - - proto->Set( v8::String::New( "toString" ) , FunctionTemplate::New( binDataToString ) ); - + + proto->Set( v8::String::New( "toString" ) , newV8Function< binDataToString >() ); + return binData; - } - - void installDBTypes( Handle<ObjectTemplate>& global ){ - v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); + } + + v8::Handle<v8::FunctionTemplate> getTimestampFunctionTemplate() { + v8::Local<v8::FunctionTemplate> ts = newV8Function< dbTimestampInit >(); + v8::Local<v8::Template> proto = ts->PrototypeTemplate(); + + ts->InstanceTemplate()->SetInternalFieldCount( 1 ); + + return ts; + } + + + void installDBTypes( Handle<ObjectTemplate>& global ) { + v8::Local<v8::FunctionTemplate> db = newV8Function< dbInit >(); db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); global->Set(v8::String::New("DB") , db ); - - v8::Local<v8::FunctionTemplate> dbCollection = FunctionTemplate::New( collectionInit ); + + v8::Local<v8::FunctionTemplate> dbCollection = newV8Function< collectionInit >(); dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); global->Set(v8::String::New("DBCollection") , dbCollection ); - v8::Local<v8::FunctionTemplate> dbQuery = FunctionTemplate::New( dbQueryInit ); + v8::Local<v8::FunctionTemplate> dbQuery = newV8Function< dbQueryInit >(); dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); global->Set(v8::String::New("DBQuery") , dbQuery ); - global->Set( v8::String::New("ObjectId") , FunctionTemplate::New( objectIdInit ) ); + global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >() ); - global->Set( v8::String::New("DBRef") , FunctionTemplate::New( dbRefInit ) ); + global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >() ); - global->Set( v8::String::New("DBPointer") , FunctionTemplate::New( dbPointerInit ) ); + global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >() ); global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate() ); global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate() ); + global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate() ); } - void installDBTypes( Handle<v8::Object>& global ){ - v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); + void installDBTypes( Handle<v8::Object>& global ) { + v8::Local<v8::FunctionTemplate> db = newV8Function< dbInit >(); db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); global->Set(v8::String::New("DB") , db->GetFunction() ); - - v8::Local<v8::FunctionTemplate> dbCollection = FunctionTemplate::New( collectionInit ); + + v8::Local<v8::FunctionTemplate> dbCollection = newV8Function< collectionInit >(); dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); global->Set(v8::String::New("DBCollection") , dbCollection->GetFunction() ); - v8::Local<v8::FunctionTemplate> dbQuery = FunctionTemplate::New( dbQueryInit ); + v8::Local<v8::FunctionTemplate> dbQuery = newV8Function< dbQueryInit >(); dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); global->Set(v8::String::New("DBQuery") , dbQuery->GetFunction() ); - global->Set( v8::String::New("ObjectId") , FunctionTemplate::New( objectIdInit )->GetFunction() ); + global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >()->GetFunction() ); - global->Set( v8::String::New("DBRef") , FunctionTemplate::New( dbRefInit )->GetFunction() ); - - global->Set( v8::String::New("DBPointer") , FunctionTemplate::New( dbPointerInit )->GetFunction() ); + global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >()->GetFunction() ); + + global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >()->GetFunction() ); global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate()->GetFunction() ); global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate()->GetFunction() ); + global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate()->GetFunction() ); + BSONObjBuilder b; b.appendMaxKey( "" ); b.appendMinKey( "" ); @@ -131,21 +151,21 @@ namespace mongo { BSONObjIterator i( o ); global->Set( v8::String::New("MaxKey"), mongoToV8Element( i.next() ) ); global->Set( v8::String::New("MinKey"), mongoToV8Element( i.next() ) ); - - global->Get( v8::String::New( "Object" ) )->ToObject()->Set( v8::String::New("bsonsize") , FunctionTemplate::New( bsonsize )->GetFunction() ); + + global->Get( v8::String::New( "Object" ) )->ToObject()->Set( v8::String::New("bsonsize") , newV8Function< bsonsize >()->GetFunction() ); } - void destroyConnection( Persistent<Value> self, void* parameter){ + void destroyConnection( Persistent<Value> self, void* parameter) { delete static_cast<DBClientBase*>(parameter); self.Dispose(); self.Clear(); } - Handle<Value> mongoConsExternal(const Arguments& args){ + Handle<Value> mongoConsExternal(const Arguments& args) { char host[255]; - - if ( args.Length() > 0 && args[0]->IsString() ){ + + if ( args.Length() > 0 && args[0]->IsString() ) { assert( args[0]->ToString()->Utf8Length() < 250 ); args[0]->ToString()->WriteAscii( host ); } @@ -157,30 +177,41 @@ namespace mongo { ConnectionString cs = ConnectionString::parse( host , errmsg ); if ( ! cs.isValid() ) return v8::ThrowException( v8::String::New( errmsg.c_str() ) ); - - - DBClientWithCommands * conn = cs.connect( errmsg ); + + + DBClientWithCommands * conn; + { + V8Unlock ul; + conn = cs.connect( errmsg ); + } if ( ! conn ) return v8::ThrowException( v8::String::New( errmsg.c_str() ) ); - + Persistent<v8::Object> self = Persistent<v8::Object>::New( args.Holder() ); self.MakeWeak( conn , destroyConnection ); - ScriptEngine::runConnectCallback( *conn ); + { + V8Unlock ul; + ScriptEngine::runConnectCallback( *conn ); + } args.This()->SetInternalField( 0 , External::New( conn ) ); args.This()->Set( v8::String::New( "slaveOk" ) , Boolean::New( false ) ); args.This()->Set( v8::String::New( "host" ) , v8::String::New( host ) ); - + return v8::Undefined(); } - Handle<Value> mongoConsLocal(const Arguments& args){ - + Handle<Value> mongoConsLocal(const Arguments& args) { + if ( args.Length() > 0 ) return v8::ThrowException( v8::String::New( "local Mongo constructor takes no args" ) ); - DBClientBase * conn = createDirectClient(); + DBClientBase * conn; + { + V8Unlock ul; + conn = createDirectClient(); + } Persistent<v8::Object> self = Persistent<v8::Object>::New( args.This() ); self.MakeWeak( conn , destroyConnection ); @@ -189,7 +220,7 @@ namespace mongo { args.This()->SetInternalField( 0 , External::New( conn ) ); args.This()->Set( v8::String::New( "slaveOk" ) , Boolean::New( false ) ); args.This()->Set( v8::String::New( "host" ) , v8::String::New( "EMBEDDED" ) ); - + return v8::Undefined(); } @@ -197,12 +228,12 @@ namespace mongo { // --- #ifdef _WIN32 -#define GETNS char * ns = new char[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); +#define GETNS char * ns = new char[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); #else -#define GETNS char ns[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); +#define GETNS char ns[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); #endif - DBClientBase * getConnection( const Arguments& args ){ + DBClientBase * getConnection( const Arguments& args ) { Local<External> c = External::Cast( *(args.This()->GetInternalField( 0 )) ); DBClientBase * conn = (DBClientBase*)(c->Value()); assert( conn ); @@ -211,7 +242,7 @@ namespace mongo { // ---- real methods - void destroyCursor( Persistent<Value> self, void* parameter){ + void destroyCursor( Persistent<Value> self, void* parameter) { delete static_cast<mongo::DBClientCursor*>(parameter); self.Dispose(); self.Clear(); @@ -224,60 +255,64 @@ namespace mongo { 3 - limit 4 - skip */ - Handle<Value> mongoFind(const Arguments& args){ + Handle<Value> mongoFind(const Arguments& args) { HandleScope handle_scope; - jsassert( args.Length() == 6 , "find needs 6 args" ); + jsassert( args.Length() == 7 , "find needs 7 args" ); jsassert( args[1]->IsObject() , "needs to be an object" ); DBClientBase * 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() ); - + Local<v8::Object> mongo = args.This(); Local<v8::Value> slaveOkVal = mongo->Get( v8::String::New( "slaveOk" ) ); jsassert( slaveOkVal->IsBoolean(), "slaveOk member invalid" ); bool slaveOk = slaveOkVal->BooleanValue(); - + try { auto_ptr<mongo::DBClientCursor> cursor; int nToReturn = (int)(args[3]->ToNumber()->Value()); int nToSkip = (int)(args[4]->ToNumber()->Value()); int batchSize = (int)(args[5]->ToNumber()->Value()); + int options = (int)(args[6]->ToNumber()->Value()); { - v8::Unlocker u; - cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, slaveOk ? QueryOption_SlaveOk : 0 , batchSize ); + V8Unlock u; + cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, options | ( slaveOk ? QueryOption_SlaveOk : 0 ) , batchSize ); } v8::Function * cons = (v8::Function*)( *( mongo->Get( v8::String::New( "internalCursor" ) ) ) ); assert( cons ); - + Persistent<v8::Object> c = Persistent<v8::Object>::New( cons->NewInstance() ); c.MakeWeak( cursor.get() , destroyCursor ); - + c->SetInternalField( 0 , External::New( cursor.release() ) ); return handle_scope.Close(c); } - catch ( ... ){ - return v8::ThrowException( v8::String::New( "socket error on query" ) ); + catch ( ... ) { + return v8::ThrowException( v8::String::New( "socket error on query" ) ); } } - v8::Handle<v8::Value> mongoInsert(const v8::Arguments& args){ + 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" ); - + + if ( args.This()->Get( v8::String::New( "readOnly" ) )->BooleanValue() ) + return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); + DBClientBase * conn = getConnection( args ); GETNS; - + v8::Handle<v8::Object> in = args[1]->ToObject(); - - if ( ! in->Has( v8::String::New( "_id" ) ) ){ + + if ( ! in->Has( v8::String::New( "_id" ) ) ) { v8::Handle<v8::Value> argv[1]; in->Set( v8::String::New( "_id" ) , getObjectIdCons()->NewInstance( 0 , argv ) ); } @@ -286,64 +321,70 @@ namespace mongo { DDD( "want to save : " << o.jsonString() ); try { - v8::Unlocker u; + V8Unlock u; conn->insert( ns , o ); } - catch ( ... ){ + catch ( ... ) { return v8::ThrowException( v8::String::New( "socket error on insert" ) ); } - + return v8::Undefined(); } - v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args){ + v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args) { jsassert( args.Length() == 2 || args.Length() == 3 , "remove needs 2 args" ); jsassert( args[1]->IsObject() , "have to remove an object template" ); + if ( args.This()->Get( v8::String::New( "readOnly" ) )->BooleanValue() ) + return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); + DBClientBase * conn = getConnection( args ); GETNS; - + v8::Handle<v8::Object> in = args[1]->ToObject(); BSONObj o = v8ToMongo( in ); - + bool justOne = false; - if ( args.Length() > 2 ){ + if ( args.Length() > 2 ) { justOne = args[2]->BooleanValue(); } DDD( "want to remove : " << o.jsonString() ); try { - v8::Unlocker u; + V8Unlock u; conn->remove( ns , o , justOne ); } - catch ( ... ){ + catch ( ... ) { return v8::ThrowException( v8::String::New( "socket error on remove" ) ); } return v8::Undefined(); } - v8::Handle<v8::Value> mongoUpdate(const v8::Arguments& args){ + 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" ); + + if ( args.This()->Get( v8::String::New( "readOnly" ) )->BooleanValue() ) + return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); DBClientBase * 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(); - bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); - + bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); + try { BSONObj q1 = v8ToMongo( q ); BSONObj o1 = v8ToMongo( o ); - v8::Unlocker u; + V8Unlock u; conn->update( ns , q1 , o1 , upsert, multi ); } - catch ( ... ){ + catch ( ... ) { return v8::ThrowException( v8::String::New( "socket error on remove" ) ); } @@ -355,48 +396,48 @@ namespace mongo { // --- cursor --- - mongo::DBClientCursor * getCursor( const Arguments& args ){ + mongo::DBClientCursor * getCursor( const Arguments& args ) { Local<External> c = External::Cast( *(args.This()->GetInternalField( 0 ) ) ); mongo::DBClientCursor * cursor = (mongo::DBClientCursor*)(c->Value()); return cursor; } - v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args){ + v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args) { return v8::Undefined(); } - v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args){ + v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return v8::Undefined(); BSONObj o; { - v8::Unlocker u; + V8Unlock u; o = cursor->next(); } return mongoToV8( o ); } - v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args){ + v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return Boolean::New( false ); bool ret; { - v8::Unlocker u; + V8Unlock u; ret = cursor->more(); } return Boolean::New( ret ); } - v8::Handle<v8::Value> internalCursorObjsLeftInBatch(const v8::Arguments& args){ + v8::Handle<v8::Value> internalCursorObjsLeftInBatch(const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return v8::Number::New( (double) 0 ); int ret; { - v8::Unlocker u; + V8Unlock u; ret = cursor->objsLeftInBatch(); } return v8::Number::New( (double) ret ); @@ -405,7 +446,7 @@ namespace mongo { // --- DB ---- - v8::Handle<v8::Value> dbInit(const v8::Arguments& args){ + v8::Handle<v8::Value> dbInit(const v8::Arguments& args) { assert( args.Length() == 2 ); args.This()->Set( v8::String::New( "_mongo" ) , args[0] ); @@ -417,26 +458,29 @@ namespace mongo { return v8::Undefined(); } - v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ){ + v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ) { assert( args.Length() == 4 ); args.This()->Set( v8::String::New( "_mongo" ) , args[0] ); args.This()->Set( v8::String::New( "_db" ) , args[1] ); args.This()->Set( v8::String::New( "_shortName" ) , args[2] ); args.This()->Set( v8::String::New( "_fullName" ) , args[3] ); - + + if ( haveLocalShardingInfo( toSTLString( args[3] ) ) ) + return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) ); + 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::Value> dbQueryInit( const v8::Arguments& args ) { + v8::Handle<v8::Object> t = args.This(); assert( args.Length() >= 4 ); - + t->Set( v8::String::New( "_mongo" ) , args[0] ); t->Set( v8::String::New( "_db" ) , args[1] ); t->Set( v8::String::New( "_collection" ) , args[2] ); @@ -444,46 +488,52 @@ namespace mongo { if ( args.Length() > 4 && args[4]->IsObject() ) t->Set( v8::String::New( "_query" ) , args[4] ); - else + else t->Set( v8::String::New( "_query" ) , v8::Object::New() ); - + if ( args.Length() > 5 && args[5]->IsObject() ) t->Set( v8::String::New( "_fields" ) , args[5] ); else t->Set( v8::String::New( "_fields" ) , v8::Null() ); - + if ( args.Length() > 6 && args[6]->IsNumber() ) t->Set( v8::String::New( "_limit" ) , args[6] ); - else + else t->Set( v8::String::New( "_limit" ) , Number::New( 0 ) ); if ( args.Length() > 7 && args[7]->IsNumber() ) t->Set( v8::String::New( "_skip" ) , args[7] ); - else + else t->Set( v8::String::New( "_skip" ) , Number::New( 0 ) ); if ( args.Length() > 8 && args[8]->IsNumber() ) - t->Set( v8::String::New( "_batchSize" ) , args[7] ); - else + t->Set( v8::String::New( "_batchSize" ) , args[8] ); + else t->Set( v8::String::New( "_batchSize" ) , Number::New( 0 ) ); - + + if ( args.Length() > 9 && args[9]->IsNumber() ) + t->Set( v8::String::New( "_options" ) , args[9] ); + else + t->Set( v8::String::New( "_options" ) , Number::New( 0 ) ); + + t->Set( v8::String::New( "_cursor" ) , v8::Null() ); t->Set( v8::String::New( "_numReturned" ) , v8::Number::New(0) ); t->Set( v8::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 ( sname[0] == '_' ) { if ( ! ( info.This()->HasRealNamedProperty( name ) ) ) return v8::Undefined(); return info.This()->GetRealNamedPropertyInPrototypeChain( name ); @@ -499,7 +549,7 @@ namespace mongo { return f->Call( info.This() , 1 , argv ); } - v8::Handle<v8::Value> dbQueryIndexAccess( unsigned int index , const v8::AccessorInfo& info ){ + v8::Handle<v8::Value> dbQueryIndexAccess( unsigned int index , const v8::AccessorInfo& info ) { v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "arrayAccess" ) ); assert( arrayAccess->IsFunction() ); @@ -507,35 +557,36 @@ namespace mongo { v8::Handle<v8::Value> argv[1]; argv[0] = v8::Number::New( index ); - return f->Call( info.This() , 1 , argv ); + return f->Call( info.This() , 1 , argv ); } - v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ){ + v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ) { v8::Handle<v8::Object> it = args.This(); - - if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function * f = getObjectIdCons(); it = f->NewInstance(); } - + OID oid; - - if ( args.Length() == 0 ){ + + if ( args.Length() == 0 ) { oid.init(); } else { string s = toSTLString( args[0] ); try { Scope::validateObjectIdString( s ); - } catch ( const MsgAssertionException &m ) { + } + catch ( const MsgAssertionException &m ) { string error = m.toString(); return v8::ThrowException( v8::String::New( error.c_str() ) ); - } + } oid.init( s ); - } + } it->Set( v8::String::New( "str" ) , v8::String::New( oid.str().c_str() ) ); - + return it; } @@ -547,7 +598,7 @@ namespace mongo { v8::Handle<v8::Object> it = args.This(); - if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = getNamedCons( "DBRef" ); it = f->NewInstance(); } @@ -561,74 +612,98 @@ namespace mongo { } v8::Handle<v8::Value> dbPointerInit( const v8::Arguments& args ) { - + if (args.Length() != 2) { return v8::ThrowException( v8::String::New( "DBPointer needs 2 arguments" ) ); } - + v8::Handle<v8::Object> it = args.This(); - - if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = getNamedCons( "DBPointer" ); it = f->NewInstance(); } - + it->Set( v8::String::New( "ns" ) , args[0] ); it->Set( v8::String::New( "id" ) , args[1] ); it->SetHiddenValue( v8::String::New( "__DBPointer" ), v8::Number::New( 1 ) ); - + + return it; + } + + v8::Handle<v8::Value> dbTimestampInit( const v8::Arguments& args ) { + + v8::Handle<v8::Object> it = args.This(); + + if ( args.Length() == 0 ) { + it->Set( v8::String::New( "t" ) , v8::Number::New( 0 ) ); + it->Set( v8::String::New( "i" ) , v8::Number::New( 0 ) ); + } + else if ( args.Length() == 2 ) { + it->Set( v8::String::New( "t" ) , args[0] ); + it->Set( v8::String::New( "i" ) , args[1] ); + } + else { + return v8::ThrowException( v8::String::New( "Timestamp needs 0 or 2 arguments" ) ); + } + + it->SetInternalField( 0, v8::Uint32::New( Timestamp ) ); + return it; } + v8::Handle<v8::Value> binDataInit( const v8::Arguments& args ) { v8::Handle<v8::Object> it = args.This(); - + // 3 args: len, type, data if (args.Length() == 3) { - - if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = getNamedCons( "BinData" ); it = f->NewInstance(); } - + it->Set( v8::String::New( "len" ) , args[0] ); it->Set( v8::String::New( "type" ) , args[1] ); it->Set( v8::String::New( "data" ), args[2] ); it->SetHiddenValue( v8::String::New( "__BinData" ), v8::Number::New( 1 ) ); - // 2 args: type, base64 string - } else if ( args.Length() == 2 ) { - - if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + // 2 args: type, base64 string + } + else if ( args.Length() == 2 ) { + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = getNamedCons( "BinData" ); it = f->NewInstance(); } - + v8::String::Utf8Value data( args[ 1 ] ); string decoded = base64::decode( *data ); it->Set( v8::String::New( "len" ) , v8::Number::New( decoded.length() ) ); it->Set( v8::String::New( "type" ) , args[ 0 ] ); it->Set( v8::String::New( "data" ), v8::String::New( decoded.data(), decoded.length() ) ); - it->SetHiddenValue( v8::String::New( "__BinData" ), v8::Number::New( 1 ) ); - - } else { + it->SetHiddenValue( v8::String::New( "__BinData" ), v8::Number::New( 1 ) ); + + } + else { return v8::ThrowException( v8::String::New( "BinData needs 3 arguments" ) ); } return it; } - + v8::Handle<v8::Value> binDataToString( const v8::Arguments& args ) { - + if (args.Length() != 0) { return v8::ThrowException( v8::String::New( "toString needs 0 arguments" ) ); } - + v8::Handle<v8::Object> it = args.This(); int len = it->Get( v8::String::New( "len" ) )->ToInt32()->Value(); int type = it->Get( v8::String::New( "type" ) )->ToInt32()->Value(); v8::String::Utf8Value data( it->Get( v8::String::New( "data" ) ) ); - + stringstream ss; ss << "BinData(" << type << ",\""; base64::encode( ss, *data, len ); @@ -638,49 +713,54 @@ namespace mongo { } v8::Handle<v8::Value> numberLongInit( const v8::Arguments& args ) { - + if (args.Length() != 0 && args.Length() != 1 && args.Length() != 3) { return v8::ThrowException( v8::String::New( "NumberLong needs 0, 1 or 3 arguments" ) ); } - + v8::Handle<v8::Object> it = args.This(); - - if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = getNamedCons( "NumberLong" ); it = f->NewInstance(); } if ( args.Length() == 0 ) { it->Set( v8::String::New( "floatApprox" ), v8::Number::New( 0 ) ); - } else if ( args.Length() == 1 ) { + } + else if ( args.Length() == 1 ) { if ( args[ 0 ]->IsNumber() ) { - it->Set( v8::String::New( "floatApprox" ), args[ 0 ] ); - } else { + it->Set( v8::String::New( "floatApprox" ), args[ 0 ] ); + } + else { v8::String::Utf8Value data( args[ 0 ] ); string num = *data; const char *numStr = num.c_str(); long long n; try { n = parseLL( numStr ); - } catch ( const AssertionException & ) { + } + catch ( const AssertionException & ) { return v8::ThrowException( v8::String::New( "could not convert string to long long" ) ); } unsigned long long val = n; if ( (long long)val == (long long)(double)(long long)(val) ) { it->Set( v8::String::New( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) ); - } else { + } + else { it->Set( v8::String::New( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) ); it->Set( v8::String::New( "top" ), v8::Integer::New( val >> 32 ) ); it->Set( v8::String::New( "bottom" ), v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ) ); - } + } } - } else { + } + else { it->Set( v8::String::New( "floatApprox" ) , args[0] ); it->Set( v8::String::New( "top" ) , args[1] ); it->Set( v8::String::New( "bottom" ) , args[2] ); } it->SetHiddenValue( v8::String::New( "__NumberLong" ), v8::Number::New( 1 ) ); - + return it; } @@ -688,21 +768,21 @@ namespace mongo { if ( !it->Has( v8::String::New( "top" ) ) ) return (long long)( it->Get( v8::String::New( "floatApprox" ) )->NumberValue() ); return - (long long) - ( (unsigned long long)( it->Get( v8::String::New( "top" ) )->ToInt32()->Value() ) << 32 ) + - (unsigned)( it->Get( v8::String::New( "bottom" ) )->ToInt32()->Value() ); + (long long) + ( (unsigned long long)( it->Get( v8::String::New( "top" ) )->ToInt32()->Value() ) << 32 ) + + (unsigned)( it->Get( v8::String::New( "bottom" ) )->ToInt32()->Value() ); } - + v8::Handle<v8::Value> numberLongValueOf( const v8::Arguments& args ) { - + if (args.Length() != 0) { return v8::ThrowException( v8::String::New( "toNumber needs 0 arguments" ) ); } - + v8::Handle<v8::Object> it = args.This(); - + long long val = numberLongVal( it ); - + return v8::Number::New( double( val ) ); } @@ -711,13 +791,13 @@ namespace mongo { } v8::Handle<v8::Value> numberLongToString( const v8::Arguments& args ) { - + if (args.Length() != 0) { return v8::ThrowException( v8::String::New( "toString needs 0 arguments" ) ); } - + v8::Handle<v8::Object> it = args.This(); - + stringstream ss; long long val = numberLongVal( it ); const long long limit = 2LL << 30; @@ -730,13 +810,65 @@ namespace mongo { string ret = ss.str(); return v8::String::New( ret.c_str() ); } - + v8::Handle<v8::Value> bsonsize( const v8::Arguments& args ) { - - if (args.Length() != 1 || !args[ 0 ]->IsObject()) { - return v8::ThrowException( v8::String::New( "bonsisze needs 1 object" ) ); - } + + if ( args.Length() != 1 ) + return v8::ThrowException( v8::String::New( "bonsisze needs 1 argument" ) ); + + if ( args[0]->IsNull() ) + return v8::Number::New(0); + + if ( ! args[ 0 ]->IsObject() ) + return v8::ThrowException( v8::String::New( "argument to bonsisze has to be an object" ) ); return v8::Number::New( v8ToMongo( args[ 0 ]->ToObject() ).objsize() ); } + + // to be called with v8 mutex + void enableV8Interrupt() { + if ( globalScriptEngine->haveGetInterruptSpecCallback() ) { + __interruptSpecToThreadId[ globalScriptEngine->getInterruptSpec() ] = v8::V8::GetCurrentThreadId(); + } + } + + // to be called with v8 mutex + void disableV8Interrupt() { + if ( globalScriptEngine->haveGetInterruptSpecCallback() ) { + __interruptSpecToThreadId.erase( globalScriptEngine->getInterruptSpec() ); + } + } + + namespace v8Locks { + boost::mutex& __v8Mutex = *( new boost::mutex ); + ThreadLocalValue< bool > __locked; + + RecursiveLock::RecursiveLock() : _unlock() { + if ( !__locked.get() ) { + __v8Mutex.lock(); + __locked.set( true ); + _unlock = true; + } + } + RecursiveLock::~RecursiveLock() { + if ( _unlock ) { + __v8Mutex.unlock(); + __locked.set( false ); + } + } + + RecursiveUnlock::RecursiveUnlock() : _lock() { + if ( __locked.get() ) { + __v8Mutex.unlock(); + __locked.set( false ); + _lock = true; + } + } + RecursiveUnlock::~RecursiveUnlock() { + if ( _lock ) { + __v8Mutex.lock(); + __locked.set( true ); + } + } + } // namespace v8Locks } diff --git a/scripting/v8_db.h b/scripting/v8_db.h index 4bebb32..7dbca92 100644 --- a/scripting/v8_db.h +++ b/scripting/v8_db.h @@ -22,43 +22,45 @@ #include <cstdio> #include <cstdlib> +#include "engine.h" #include "../client/dbclient.h" namespace mongo { // These functions may depend on the caller creating a handle scope and context scope. - + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( bool local ); void installDBTypes( v8::Handle<v8::ObjectTemplate>& global ); void installDBTypes( v8::Handle<v8::Object>& global ); - + // the actual globals - + mongo::DBClientBase * getConnection( const v8::Arguments& args ); // Mongo members v8::Handle<v8::Value> mongoConsLocal(const v8::Arguments& args); v8::Handle<v8::Value> mongoConsExternal(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); v8::Handle<v8::Value> internalCursorObjsLeftInBatch(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> dbRefInit( const v8::Arguments& args ); v8::Handle<v8::Value> dbPointerInit( const v8::Arguments& args ); + v8::Handle<v8::Value> dbTimestampInit( const v8::Arguments& args ); v8::Handle<v8::Value> binDataInit( const v8::Arguments& args ); v8::Handle<v8::Value> binDataToString( const v8::Arguments& args ); @@ -67,12 +69,82 @@ namespace mongo { v8::Handle<v8::Value> numberLongToNumber(const v8::Arguments& args); v8::Handle<v8::Value> numberLongValueOf(const v8::Arguments& args); v8::Handle<v8::Value> numberLongToString(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::Handle<v8::Value> bsonsize( const v8::Arguments& args ); + // call with v8 mutex: + void enableV8Interrupt(); + void disableV8Interrupt(); + + // The implementation below assumes that SERVER-1816 has been fixed - in + // particular, interrupted() must return true if an interrupt was ever + // sent; currently that is not the case if a new killop overwrites the data + // for an old one + template < v8::Handle< v8::Value > ( *f ) ( const v8::Arguments& ) > + v8::Handle< v8::Value > v8Callback( const v8::Arguments &args ) { + disableV8Interrupt(); // we don't want to have to audit all v8 calls for termination exceptions, so we don't allow these exceptions during the callback + if ( globalScriptEngine->interrupted() ) { + v8::V8::TerminateExecution(); // experimentally it seems that TerminateExecution() will override the return value + return v8::Undefined(); + } + v8::Handle< v8::Value > ret; + string exception; + try { + ret = f( args ); + } + catch( const std::exception &e ) { + exception = e.what(); + } + catch( ... ) { + exception = "unknown exception"; + } + enableV8Interrupt(); + if ( globalScriptEngine->interrupted() ) { + v8::V8::TerminateExecution(); + return v8::Undefined(); + } + if ( !exception.empty() ) { + // technically, ThrowException is supposed to be the last v8 call before returning + ret = v8::ThrowException( v8::String::New( exception.c_str() ) ); + } + return ret; + } + + template < v8::Handle< v8::Value > ( *f ) ( const v8::Arguments& ) > + v8::Local< v8::FunctionTemplate > newV8Function() { + return v8::FunctionTemplate::New( v8Callback< f > ); + } + + // Preemption is going to be allowed for the v8 mutex, and some of our v8 + // usage is not preemption safe. So we are using an additional mutex that + // will not be preempted. The V8Lock should be used in place of v8::Locker + // except in certain special cases involving interrupts. + namespace v8Locks { + // the implementations are quite simple - objects must be destroyed in + // reverse of the order created, and should not be shared between threads + struct RecursiveLock { + RecursiveLock(); + ~RecursiveLock(); + bool _unlock; + }; + struct RecursiveUnlock { + RecursiveUnlock(); + ~RecursiveUnlock(); + bool _lock; + }; + } // namespace v8Locks + class V8Lock { + v8Locks::RecursiveLock _noPreemptionLock; + v8::Locker _preemptionLock; + }; + struct V8Unlock { + v8::Unlocker _preemptionUnlock; + v8Locks::RecursiveUnlock _noPreemptionUnlock; + }; } + diff --git a/scripting/v8_utils.cpp b/scripting/v8_utils.cpp index 5a07a80..171ced5 100644 --- a/scripting/v8_utils.cpp +++ b/scripting/v8_utils.cpp @@ -16,6 +16,7 @@ */ #include "v8_utils.h" +#include "v8_db.h" #include <iostream> #include <map> #include <sstream> @@ -38,7 +39,8 @@ namespace mongo { HandleScope handle_scope; if (first) { first = false; - } else { + } + else { printf(" "); } v8::String::Utf8Value str(args[i]); @@ -48,66 +50,66 @@ namespace mongo { return v8::Undefined(); } - std::string toSTLString( const Handle<v8::Value> & o ){ - v8::String::Utf8Value str(o); + std::string toSTLString( const Handle<v8::Value> & o ) { + v8::String::Utf8Value str(o); const char * foo = *str; std::string s(foo); return s; } - std::string toSTLString( const v8::TryCatch * try_catch ){ - + std::string toSTLString( const v8::TryCatch * try_catch ) { + stringstream ss; - + //while ( try_catch ){ // disabled for v8 bleeding edge - - v8::String::Utf8Value exception(try_catch->Exception()); - Handle<v8::Message> message = try_catch->Message(); - - if (message.IsEmpty()) { - ss << *exception << endl; - } - else { - - v8::String::Utf8Value filename(message->GetScriptResourceName()); - int linenum = message->GetLineNumber(); - ss << *filename << ":" << linenum << " " << *exception << endl; - - v8::String::Utf8Value sourceline(message->GetSourceLine()); - ss << *sourceline << endl; - - int start = message->GetStartColumn(); - for (int i = 0; i < start; i++) - ss << " "; - - int end = message->GetEndColumn(); - for (int i = start; i < end; i++) - ss << "^"; - - ss << endl; - } - - //try_catch = try_catch->next_; + + v8::String::Utf8Value exception(try_catch->Exception()); + Handle<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + ss << *exception << endl; + } + else { + + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + ss << *filename << ":" << linenum << " " << *exception << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + ss << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + ss << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + ss << "^"; + + ss << endl; + } + + //try_catch = try_catch->next_; //} - + return ss.str(); } - std::ostream& operator<<( std::ostream &s, const Handle<v8::Value> & o ){ - v8::String::Utf8Value str(o); + std::ostream& operator<<( std::ostream &s, const Handle<v8::Value> & o ) { + v8::String::Utf8Value str(o); s << *str; return s; } - std::ostream& operator<<( std::ostream &s, const v8::TryCatch * try_catch ){ + std::ostream& operator<<( std::ostream &s, const v8::TryCatch * try_catch ) { HandleScope handle_scope; v8::String::Utf8Value exception(try_catch->Exception()); Handle<v8::Message> message = try_catch->Message(); - + if (message.IsEmpty()) { s << *exception << endl; - } + } else { v8::String::Utf8Value filename(message->GetScriptResourceName()); @@ -126,7 +128,7 @@ namespace mongo { cout << "^"; cout << endl; - } + } //if ( try_catch->next_ ) // disabled for v8 bleeding edge // s << try_catch->next_; @@ -143,9 +145,9 @@ namespace mongo { void ReportException(v8::TryCatch* try_catch) { cout << try_catch << endl; } - + Handle< Context > baseContext_; - + class JSThreadConfig { public: JSThreadConfig( const Arguments &args, bool newScope = false ) : started_(), done_(), newScope_( newScope ) { @@ -170,7 +172,7 @@ namespace mongo { } void join() { jsassert( started_ && !done_, "Thread not running" ); - Unlocker u; + V8Unlock u; thread_->join(); done_ = true; } @@ -184,7 +186,7 @@ namespace mongo { public: JSThread( JSThreadConfig &config ) : config_( config ) {} void operator()() { - Locker l; + V8Lock l; HandleScope handle_scope; Handle< Context > context; Handle< v8::Function > fun; @@ -198,7 +200,8 @@ namespace mongo { string fCode = toSTLString( config_.f_->ToString() ); Context::Scope context_scope( context ); fun = scope->__createFunction( fCode.c_str() ); - } else { + } + else { context = baseContext_; Context::Scope context_scope( context ); fun = config_.f_; @@ -220,7 +223,7 @@ namespace mongo { private: JSThreadConfig &config_; }; - + bool started_; bool done_; bool newScope_; @@ -229,7 +232,7 @@ namespace mongo { auto_ptr< boost::thread > thread_; Persistent< Value > returnData_; }; - + Handle< Value > ThreadInit( const Arguments &args ) { Handle<v8::Object> it = args.This(); // NOTE I believe the passed JSThreadConfig will never be freed. If this @@ -238,7 +241,7 @@ namespace mongo { it->SetHiddenValue( v8::String::New( "_JSThreadConfig" ), External::New( new JSThreadConfig( args ) ) ); return v8::Undefined(); } - + Handle< Value > ScopedThreadInit( const Arguments &args ) { Handle<v8::Object> it = args.This(); // NOTE I believe the passed JSThreadConfig will never be freed. If this @@ -253,17 +256,17 @@ namespace mongo { JSThreadConfig *config = (JSThreadConfig *)( c->Value() ); return config; } - + Handle< Value > ThreadStart( const Arguments &args ) { thisConfig( args )->start(); return v8::Undefined(); } - + Handle< Value > ThreadJoin( const Arguments &args ) { thisConfig( args )->join(); return v8::Undefined(); } - + Handle< Value > ThreadReturnData( const Arguments &args ) { HandleScope handle_scope; return handle_scope.Close( thisConfig( args )->returnData() ); @@ -272,39 +275,39 @@ namespace mongo { Handle< Value > ThreadInject( const Arguments &args ) { jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" ); jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" ); - + Local<v8::Object> o = args[0]->ToObject(); - - o->Set( v8::String::New( "init" ) , FunctionTemplate::New( ThreadInit )->GetFunction() ); - o->Set( v8::String::New( "start" ) , FunctionTemplate::New( ThreadStart )->GetFunction() ); - o->Set( v8::String::New( "join" ) , FunctionTemplate::New( ThreadJoin )->GetFunction() ); - o->Set( v8::String::New( "returnData" ) , FunctionTemplate::New( ThreadReturnData )->GetFunction() ); - - return v8::Undefined(); + + o->Set( v8::String::New( "init" ) , newV8Function< ThreadInit >()->GetFunction() ); + o->Set( v8::String::New( "start" ) , newV8Function< ThreadStart >()->GetFunction() ); + o->Set( v8::String::New( "join" ) , newV8Function< ThreadJoin >()->GetFunction() ); + o->Set( v8::String::New( "returnData" ) , newV8Function< ThreadReturnData >()->GetFunction() ); + + return v8::Undefined(); } Handle< Value > ScopedThreadInject( const Arguments &args ) { jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" ); jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" ); - + Local<v8::Object> o = args[0]->ToObject(); - - o->Set( v8::String::New( "init" ) , FunctionTemplate::New( ScopedThreadInit )->GetFunction() ); + + o->Set( v8::String::New( "init" ) , newV8Function< ScopedThreadInit >()->GetFunction() ); // inheritance takes care of other member functions - + return v8::Undefined(); } - + void installFork( v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ) { if ( baseContext_.IsEmpty() ) // if this is the shell, first call will be with shell context, otherwise don't expect to use fork() anyway baseContext_ = context; - global->Set( v8::String::New( "_threadInject" ), FunctionTemplate::New( ThreadInject )->GetFunction() ); - global->Set( v8::String::New( "_scopedThreadInject" ), FunctionTemplate::New( ScopedThreadInject )->GetFunction() ); + global->Set( v8::String::New( "_threadInject" ), newV8Function< ThreadInject >()->GetFunction() ); + global->Set( v8::String::New( "_scopedThreadInject" ), newV8Function< ScopedThreadInject >()->GetFunction() ); } Handle<v8::Value> GCV8(const Arguments& args) { - Locker l; - while( V8::IdleNotification() ); + V8Lock l; + while( !V8::IdleNotification() ); return v8::Undefined(); } diff --git a/scripting/v8_utils.h b/scripting/v8_utils.h index bc4b524..40662d2 100644 --- a/scripting/v8_utils.h +++ b/scripting/v8_utils.h @@ -32,9 +32,9 @@ namespace mongo { v8::Handle<v8::Value> GCV8(const v8::Arguments& args); void ReportException(v8::TryCatch* handler); - + #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 ); diff --git a/scripting/v8_wrapper.cpp b/scripting/v8_wrapper.cpp index 0e71c9a..ff67e8c 100644 --- a/scripting/v8_wrapper.cpp +++ b/scripting/v8_wrapper.cpp @@ -17,6 +17,7 @@ #include "v8_wrapper.h" #include "v8_utils.h" +#include "v8_db.h" #include <iostream> @@ -38,17 +39,17 @@ namespace mongo { cout << "cannot delete from read-only object" << endl; return Boolean::New( false ); } - + Handle<Value> IndexedReadOnlySet( uint32_t index, Local<Value> value, const AccessorInfo& info ) { cout << "cannot write to read-only array" << endl; return value; } - + Handle<Boolean> IndexedReadOnlyDelete( uint32_t index, const AccessorInfo& info ) { cout << "cannot delete from read-only array" << endl; return Boolean::New( false ); } - + Local< v8::Value > newFunction( const char *code ) { stringstream codeSS; codeSS << "____MontoToV8_newFunction_temp = " << code; @@ -57,15 +58,15 @@ namespace mongo { Local< Value > ret = compiled->Run(); return ret; } - + Local< v8::Value > newId( const OID &id ) { v8::Function * idCons = getObjectIdCons(); v8::Handle<v8::Value> argv[1]; argv[0] = v8::String::New( id.str().c_str() ); - return idCons->NewInstance( 1 , argv ); + return idCons->NewInstance( 1 , argv ); } - - Local<v8::Object> mongoToV8( const BSONObj& m , bool array, bool readOnly ){ + + Local<v8::Object> mongoToV8( const BSONObj& m , bool array, bool readOnly ) { Local<v8::Object> o; @@ -86,13 +87,16 @@ namespace mongo { if ( !o.IsEmpty() ) { readOnly = false; - } else if ( array ) { + } + else if ( array ) { // NOTE Looks like it's impossible to add interceptors to v8 arrays. readOnly = false; o = v8::Array::New(); - } else if ( !readOnly ) { + } + else if ( !readOnly ) { o = v8::Object::New(); - } else { + } + else { // NOTE Our readOnly implemention relies on undocumented ObjectTemplate // functionality that may be fragile, but it still seems like the best option // for now -- fwiw, the v8 docs are pretty sparse. I've determined experimentally @@ -115,15 +119,15 @@ namespace mongo { readOnlyObjects->SetIndexedPropertyHandler( 0 ); o = readOnlyObjects->NewInstance(); } - + mongo::BSONObj sub; for ( BSONObjIterator i(m); i.more(); ) { const BSONElement& f = i.next(); - + Local<Value> v; - - switch ( f.type() ){ + + switch ( f.type() ) { case mongo::Code: o->Set( v8::String::New( f.fieldName() ), newFunction( f.valuestr() ) ); @@ -134,31 +138,31 @@ namespace mongo { log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; o->Set( v8::String::New( f.fieldName() ), newFunction( f.codeWScopeCode() ) ); break; - - case mongo::String: + + 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 ) ); + 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, readOnly ) ); break; - + case mongo::Date: o->Set( v8::String::New( f.fieldName() ) , v8::Date::New( f.date() ) ); break; @@ -166,29 +170,29 @@ namespace mongo { case mongo::Bool: o->Set( v8::String::New( f.fieldName() ) , v8::Boolean::New( f.boolean() ) ); break; - + case mongo::jstNULL: case mongo::Undefined: // duplicate sm behavior 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; } - + case mongo::BinData: { Local<v8::Object> b = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); int len; const char *data = f.binData( len ); - + v8::Function* binData = getNamedCons( "BinData" ); v8::Handle<v8::Value> argv[3]; argv[0] = v8::Number::New( len ); @@ -197,36 +201,37 @@ namespace mongo { o->Set( v8::String::New( f.fieldName() ), binData->NewInstance(3, argv) ); break; } - + case mongo::Timestamp: { Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); - + sub->Set( v8::String::New( "t" ) , v8::Number::New( f.timestampTime() ) ); sub->Set( v8::String::New( "i" ) , v8::Number::New( f.timestampInc() ) ); sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - + o->Set( v8::String::New( f.fieldName() ) , sub ); break; } - + case mongo::NumberLong: { Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); unsigned long long val = f.numberLong(); v8::Function* numberLong = getNamedCons( "NumberLong" ); if ( (long long)val == (long long)(double)(long long)(val) ) { - v8::Handle<v8::Value> argv[1]; + v8::Handle<v8::Value> argv[1]; argv[0] = v8::Number::New( (double)(long long)( val ) ); o->Set( v8::String::New( f.fieldName() ), numberLong->NewInstance( 1, argv ) ); - } else { + } + else { v8::Handle<v8::Value> argv[3]; argv[0] = v8::Number::New( (double)(long long)(val) ); argv[1] = v8::Integer::New( val >> 32 ); argv[2] = v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ); o->Set( v8::String::New( f.fieldName() ), numberLong->NewInstance(3, argv) ); } - break; + break; } - + case mongo::MinKey: { Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); sub->Set( v8::String::New( "$MinKey" ), v8::Boolean::New( true ) ); @@ -234,7 +239,7 @@ namespace mongo { o->Set( v8::String::New( f.fieldName() ) , sub ); break; } - + case mongo::MaxKey: { Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); sub->Set( v8::String::New( "$MaxKey" ), v8::Boolean::New( true ) ); @@ -251,7 +256,7 @@ namespace mongo { o->Set( v8::String::New( f.fieldName() ), dbPointer->NewInstance(2, argv) ); break; } - + default: cout << "can't handle type: "; cout << f.type() << " "; @@ -259,14 +264,14 @@ namespace mongo { cout << endl; break; } - + } if ( readOnly ) { readOnlyObjects->SetNamedPropertyHandler( 0, NamedReadOnlySet, 0, NamedReadOnlyDelete ); - readOnlyObjects->SetIndexedPropertyHandler( 0, IndexedReadOnlySet, 0, IndexedReadOnlyDelete ); + readOnlyObjects->SetIndexedPropertyHandler( 0, IndexedReadOnlySet, 0, IndexedReadOnlyDelete ); } - + return o; } @@ -274,56 +279,56 @@ namespace mongo { Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); internalFieldObjects->SetInternalFieldCount( 1 ); - switch ( f.type() ){ + switch ( f.type() ) { case mongo::Code: return newFunction( f.valuestr() ); - + case CodeWScope: if ( f.codeWScopeObject().isEmpty() ) log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; return newFunction( f.codeWScopeCode() ); - - case mongo::String: + + case mongo::String: return v8::String::New( f.valuestr() ); - + case mongo::jstOID: return newId( f.__oid() ); - + case mongo::NumberDouble: case mongo::NumberInt: return v8::Number::New( f.number() ); - + case mongo::Array: case mongo::Object: return mongoToV8( f.embeddedObject() , f.type() == mongo::Array ); - + case mongo::Date: return v8::Date::New( f.date() ); - + case mongo::Bool: return v8::Boolean::New( f.boolean() ); - case mongo::EOO: + case mongo::EOO: case mongo::jstNULL: case mongo::Undefined: // duplicate sm behavior return v8::Null(); - + 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() ); - + return regex->NewInstance( 2 , argv ); break; } - + case mongo::BinData: { int len; const char *data = f.binData( len ); - + v8::Function* binData = getNamedCons( "BinData" ); v8::Handle<v8::Value> argv[3]; argv[0] = v8::Number::New( len ); @@ -331,26 +336,27 @@ namespace mongo { argv[2] = v8::String::New( data, len ); return binData->NewInstance( 3, argv ); }; - + case mongo::Timestamp: { Local<v8::Object> sub = internalFieldObjects->NewInstance(); - + sub->Set( v8::String::New( "t" ) , v8::Number::New( f.timestampTime() ) ); sub->Set( v8::String::New( "i" ) , v8::Number::New( f.timestampInc() ) ); sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); return sub; } - + case mongo::NumberLong: { Local<v8::Object> sub = internalFieldObjects->NewInstance(); unsigned long long val = f.numberLong(); v8::Function* numberLong = getNamedCons( "NumberLong" ); if ( (long long)val == (long long)(double)(long long)(val) ) { - v8::Handle<v8::Value> argv[1]; + v8::Handle<v8::Value> argv[1]; argv[0] = v8::Number::New( (double)(long long)( val ) ); return numberLong->NewInstance( 1, argv ); - } else { + } + else { v8::Handle<v8::Value> argv[3]; argv[0] = v8::Number::New( (double)(long long)( val ) ); argv[1] = v8::Integer::New( val >> 32 ); @@ -358,21 +364,21 @@ namespace mongo { return numberLong->NewInstance( 3, argv ); } } - + case mongo::MinKey: { Local<v8::Object> sub = internalFieldObjects->NewInstance(); sub->Set( v8::String::New( "$MinKey" ), v8::Boolean::New( true ) ); sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); return sub; } - + case mongo::MaxKey: { Local<v8::Object> sub = internalFieldObjects->NewInstance(); sub->Set( v8::String::New( "$MaxKey" ), v8::Boolean::New( true ) ); sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); return sub; } - + case mongo::DBRef: { v8::Function* dbPointer = getNamedCons( "DBPointer" ); v8::Handle<v8::Value> argv[2]; @@ -380,83 +386,83 @@ namespace mongo { argv[1] = newId( f.dbrefOID() ); return dbPointer->NewInstance(2, argv); } - + default: cout << "can't handle type: "; - cout << f.type() << " "; - cout << f.toString(); - cout << endl; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; break; - } - + } + return v8::Undefined(); } - void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value , int depth ){ - - if ( value->IsString() ){ + void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value , int depth ) { + + if ( value->IsString() ) { b.append( sname , toSTLString( value ).c_str() ); return; } - - if ( value->IsFunction() ){ - b.appendCode( sname , toSTLString( value ).c_str() ); + + if ( value->IsFunction() ) { + b.appendCode( sname , toSTLString( value ) ); return; } - - if ( value->IsNumber() ){ + + if ( value->IsNumber() ) { if ( value->IsInt32() ) b.append( sname, int( value->ToInt32()->Value() ) ); else b.append( sname , value->ToNumber()->Value() ); return; } - - if ( value->IsArray() ){ + + if ( value->IsArray() ) { BSONObj sub = v8ToMongo( value->ToObject() , depth ); b.appendArray( sname , sub ); return; } - - if ( value->IsDate() ){ + + if ( value->IsDate() ) { b.appendDate( sname , Date_t( (unsigned long long)(v8::Date::Cast( *value )->NumberValue())) ); return; } if ( value->IsExternal() ) return; - - if ( value->IsObject() ){ + + if ( value->IsObject() ) { // The user could potentially modify the fields of these special objects, // wreaking havoc when we attempt to reinterpret them. Not doing any validation // for now... Local< v8::Object > obj = value->ToObject(); if ( obj->InternalFieldCount() && obj->GetInternalField( 0 )->IsNumber() ) { switch( obj->GetInternalField( 0 )->ToInt32()->Value() ) { // NOTE Uint32's Value() gave me a linking error, so going with this instead - case Timestamp: - b.appendTimestamp( sname, - Date_t( (unsigned long long)(obj->Get( v8::String::New( "t" ) )->ToNumber()->Value() )), - obj->Get( v8::String::New( "i" ) )->ToInt32()->Value() ); - return; - case MinKey: - b.appendMinKey( sname ); - return; - case MaxKey: - b.appendMaxKey( sname ); - return; - default: - assert( "invalid internal field" == 0 ); + case Timestamp: + b.appendTimestamp( sname, + Date_t( (unsigned long long)(obj->Get( v8::String::New( "t" ) )->ToNumber()->Value() )), + obj->Get( v8::String::New( "i" ) )->ToInt32()->Value() ); + return; + case MinKey: + b.appendMinKey( sname ); + return; + case MaxKey: + b.appendMaxKey( sname ); + return; + default: + assert( "invalid internal field" == 0 ); } } string s = toSTLString( value ); - if ( s.size() && s[0] == '/' ){ + if ( s.size() && s[0] == '/' ) { s = s.substr( 1 ); string r = s.substr( 0 , s.rfind( "/" ) ); string o = s.substr( s.rfind( "/" ) + 1 ); - b.appendRegex( sname , r.c_str() , o.c_str() ); + b.appendRegex( sname , r , o ); } else if ( value->ToObject()->GetPrototype()->IsObject() && - value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( v8::String::New( "isObjectId" ) ) ){ + value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( v8::String::New( "isObjectId" ) ) ) { OID oid; oid.init( toSTLString( value ) ); b.appendOID( sname , &oid ); @@ -469,19 +475,20 @@ namespace mongo { long long val; if ( !it->Has( v8::String::New( "top" ) ) ) { val = (long long)( it->Get( v8::String::New( "floatApprox" ) )->NumberValue() ); - } else { + } + else { val = (long long) - ( (unsigned long long)( it->Get( v8::String::New( "top" ) )->ToInt32()->Value() ) << 32 ) + - (unsigned)( it->Get( v8::String::New( "bottom" ) )->ToInt32()->Value() ); + ( (unsigned long long)( it->Get( v8::String::New( "top" ) )->ToInt32()->Value() ) << 32 ) + + (unsigned)( it->Get( v8::String::New( "bottom" ) )->ToInt32()->Value() ); } - + b.append( sname, val ); } else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__DBPointer" ) ).IsEmpty() ) { OID oid; oid.init( toSTLString( value->ToObject()->Get( v8::String::New( "id" ) ) ) ); string ns = toSTLString( value->ToObject()->Get( v8::String::New( "ns" ) ) ); - b.appendDBRef( sname, ns.c_str(), oid ); + b.appendDBRef( sname, ns, oid ); } else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__BinData" ) ).IsEmpty() ) { int len = obj->Get( v8::String::New( "len" ) )->ToInt32()->Value(); @@ -489,27 +496,28 @@ namespace mongo { const char *dataArray = *data; assert( data.length() == len ); b.appendBinData( sname, - len, - mongo::BinDataType( obj->Get( v8::String::New( "type" ) )->ToInt32()->Value() ), - dataArray ); - } else { + len, + mongo::BinDataType( obj->Get( v8::String::New( "type" ) )->ToInt32()->Value() ), + dataArray ); + } + else { BSONObj sub = v8ToMongo( value->ToObject() , depth ); b.append( sname , sub ); } return; } - - if ( value->IsBoolean() ){ + + if ( value->IsBoolean() ) { b.appendBool( sname , value->ToBoolean()->Value() ); return; } - - else if ( value->IsUndefined() ){ + + else if ( value->IsUndefined() ) { b.appendUndefined( sname ); return; } - - else if ( value->IsNull() ){ + + else if ( value->IsNull() ) { b.appendNull( sname ); return; } @@ -517,26 +525,26 @@ namespace mongo { cout << "don't know how to convert to mongo field [" << name << "]\t" << value << endl; } - BSONObj v8ToMongo( v8::Handle<v8::Object> o , int depth ){ + BSONObj v8ToMongo( v8::Handle<v8::Object> o , int depth ) { BSONObjBuilder b; - - if ( depth == 0 ){ + + if ( depth == 0 ) { v8::Handle<v8::String> idName = v8::String::New( "_id" ); - if ( o->HasRealNamedProperty( idName ) ){ + 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++ ){ + 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 ) ) + o->GetPrototype()->ToObject()->HasRealNamedProperty( name ) ) continue; - + v8::Local<v8::Value> value = o->Get( name ); - + const string sname = toSTLString( name ); if ( depth == 0 && sname == "_id" ) continue; @@ -553,15 +561,15 @@ namespace mongo { WrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ) : _o(o), _readOnly( readOnly ), _iDelete( iDelete ) { } - - ~WrapperHolder(){ - if ( _o && _iDelete ){ + + ~WrapperHolder() { + if ( _o && _iDelete ) { delete _o; } _o = 0; } - v8::Handle<v8::Value> get( v8::Local<v8::String> name ){ + v8::Handle<v8::Value> get( v8::Local<v8::String> name ) { const string& s = toSTLString( name ); const BSONElement& e = _o->getField( s ); return mongoToV8Element(e); @@ -572,13 +580,13 @@ namespace mongo { bool _iDelete; }; - WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ){ + WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ) { return new WrapperHolder( o , readOnly , iDelete ); } #define WRAPPER_STRING (v8::String::New( "_wrapper" ) ) - WrapperHolder * getWrapper( v8::Handle<v8::Object> o ){ + WrapperHolder * getWrapper( v8::Handle<v8::Object> o ) { Handle<v8::Value> t = o->GetRealNamedProperty( WRAPPER_STRING ); assert( t->IsExternal() ); Local<External> c = External::Cast( *t ); @@ -588,32 +596,32 @@ namespace mongo { } - Handle<Value> wrapperCons(const Arguments& args){ + Handle<Value> wrapperCons(const Arguments& args) { if ( ! ( args.Length() == 1 && args[0]->IsExternal() ) ) return v8::ThrowException( v8::String::New( "wrapperCons needs 1 External arg" ) ); args.This()->Set( WRAPPER_STRING , args[0] ); - + return v8::Undefined(); } - v8::Handle<v8::Value> wrapperGetHandler( v8::Local<v8::String> name, const v8::AccessorInfo &info){ + v8::Handle<v8::Value> wrapperGetHandler( v8::Local<v8::String> name, const v8::AccessorInfo &info) { return getWrapper( info.This() )->get( name ); } - v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(){ - v8::Local<v8::FunctionTemplate> t = FunctionTemplate::New( wrapperCons ); + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate() { + v8::Local<v8::FunctionTemplate> t = newV8Function< wrapperCons >(); t->InstanceTemplate()->SetNamedPropertyHandler( wrapperGetHandler ); return t; } // --- random utils ---- - v8::Function * getNamedCons( const char * name ){ + v8::Function * getNamedCons( const char * name ) { return v8::Function::Cast( *(v8::Context::GetCurrent()->Global()->Get( v8::String::New( name ) ) ) ); } - v8::Function * getObjectIdCons(){ + v8::Function * getObjectIdCons() { return getNamedCons( "ObjectId" ); } diff --git a/scripting/v8_wrapper.h b/scripting/v8_wrapper.h index 838aaf4..e0b79e3 100644 --- a/scripting/v8_wrapper.h +++ b/scripting/v8_wrapper.h @@ -28,10 +28,10 @@ namespace mongo { v8::Local<v8::Object> mongoToV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false ); mongo::BSONObj v8ToMongo( v8::Handle<v8::Object> o , int depth = 0 ); - void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , + void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value , int depth = 0 ); v8::Handle<v8::Value> mongoToV8Element( const BSONElement &f ); - + v8::Function * getNamedCons( const char * name ); v8::Function * getObjectIdCons(); |