diff options
Diffstat (limited to 'scripting')
-rw-r--r-- | scripting/bench.cpp | 105 | ||||
-rw-r--r-- | scripting/engine.cpp | 45 | ||||
-rw-r--r-- | scripting/engine.h | 43 | ||||
-rw-r--r-- | scripting/engine_java.cpp | 10 | ||||
-rw-r--r-- | scripting/engine_spidermonkey.cpp | 129 | ||||
-rw-r--r-- | scripting/engine_spidermonkey.h | 34 | ||||
-rw-r--r-- | scripting/engine_v8.cpp | 1166 | ||||
-rw-r--r-- | scripting/engine_v8.h | 122 | ||||
-rw-r--r-- | scripting/sm_db.cpp | 120 | ||||
-rw-r--r-- | scripting/utils.cpp | 19 | ||||
-rw-r--r-- | scripting/v8_db.cpp | 605 | ||||
-rw-r--r-- | scripting/v8_db.h | 132 | ||||
-rw-r--r-- | scripting/v8_utils.cpp | 78 | ||||
-rw-r--r-- | scripting/v8_utils.h | 6 | ||||
-rw-r--r-- | scripting/v8_wrapper.cpp | 569 | ||||
-rw-r--r-- | scripting/v8_wrapper.h | 15 |
16 files changed, 2071 insertions, 1127 deletions
diff --git a/scripting/bench.cpp b/scripting/bench.cpp index 2723985..9ada7d6 100644 --- a/scripting/bench.cpp +++ b/scripting/bench.cpp @@ -30,13 +30,6 @@ namespace mongo { - /** - * benchQuery( "foo" , { _id : 1 } ) - */ - BSONObj benchQuery( const BSONObj& args ) { - return BSONObj(); - } - struct BenchRunConfig { BenchRunConfig() { host = "localhost"; @@ -54,7 +47,7 @@ namespace mongo { string db; unsigned parallel; - int seconds; + double seconds; BSONObj ops; @@ -64,6 +57,73 @@ namespace mongo { bool error; }; + static bool _hasSpecial( const BSONObj& obj ) { + BSONObjIterator i( obj ); + while ( i.more() ) { + BSONElement e = i.next(); + if ( e.fieldName()[0] == '#' ) + return true; + + if ( ! e.isABSONObj() ) + continue; + + if ( _hasSpecial( e.Obj() ) ) + return true; + } + return false; + } + + static void _fixField( BSONObjBuilder& b , const BSONElement& e ) { + assert( e.type() == Object ); + + BSONObj sub = e.Obj(); + assert( sub.nFields() == 1 ); + + BSONElement f = sub.firstElement(); + if ( str::equals( "#RAND_INT" , f.fieldName() ) ) { + BSONObjIterator i( f.Obj() ); + int min = i.next().numberInt(); + int max = i.next().numberInt(); + + int x = min + ( rand() % ( max - min ) ); + b.append( e.fieldName() , x ); + } + else { + uasserted( 14811 , str::stream() << "invalid bench dynamic piece: " << f.fieldName() ); + } + + } + + static void fixQuery( BSONObjBuilder& b , const BSONObj& obj ) { + BSONObjIterator i( obj ); + while ( i.more() ) { + BSONElement e = i.next(); + + if ( e.type() != Object ) { + b.append( e ); + continue; + } + + BSONObj sub = e.Obj(); + if ( sub.firstElement().fieldName()[0] != '#' ) { + b.append( e ); + continue; + } + + _fixField( b , e ); + } + } + + + static BSONObj fixQuery( const BSONObj& obj ) { + if ( ! _hasSpecial( obj ) ) + return obj; + + BSONObjBuilder b( obj.objsize() + 128 ); + fixQuery( b , obj ); + return b.obj(); + } + static void benchThread( BenchRunConfig * config ) { ScopedDbConnection conn( config->host ); config->threadsReady++; @@ -76,14 +136,20 @@ namespace mongo { string op = e["op"].String(); if ( op == "findOne" ) { - conn->findOne( ns , e["query"].Obj() ); + conn->findOne( ns , fixQuery( e["query"].Obj() ) ); + } + else if ( op == "remove" ) { + conn->remove( ns , fixQuery( e["query"].Obj() ) ); + } + else if ( op == "update" ) { + conn->update( ns , fixQuery( e["query"].Obj() ) , e["update"].Obj() , e["upsert"].trueValue() ); } else { log() << "don't understand op: " << op << endl; config->error = true; return; } - + } } @@ -93,7 +159,7 @@ namespace mongo { /** * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } */ - BSONObj benchRun( const BSONObj& argsFake ) { + BSONObj benchRun( const BSONObj& argsFake, void* data ) { assert( argsFake.firstElement().isABSONObj() ); BSONObj args = argsFake.firstElement().Obj(); @@ -109,7 +175,7 @@ namespace mongo { if ( args["parallel"].isNumber() ) config.parallel = args["parallel"].numberInt(); if ( args["seconds"].isNumber() ) - config.seconds = args["seconds"].numberInt(); + config.seconds = args["seconds"].number(); config.ops = args["ops"].Obj(); @@ -130,7 +196,7 @@ namespace mongo { BSONObj before; conn->simpleCommand( "admin" , &before , "serverStatus" ); - sleepsecs( config.seconds ); + sleepmillis( (int)(1000.0 * config.seconds) ); BSONObj after; conn->simpleCommand( "admin" , &after , "serverStatus" ); @@ -147,11 +213,14 @@ namespace mongo { // compute actual ops/sec - before = before["opcounters"].Obj(); - after = after["opcounters"].Obj(); + before = before["opcounters"].Obj().copy(); + after = after["opcounters"].Obj().copy(); + + bool totals = args["totals"].trueValue(); BSONObjBuilder buf; - buf.append( "note" , "values per second" ); + if ( ! totals ) + buf.append( "note" , "values per second" ); { BSONObjIterator i( after ); @@ -159,7 +228,9 @@ namespace mongo { BSONElement e = i.next(); double x = e.number(); x = x - before[e.fieldName()].number(); - buf.append( e.fieldName() , x / config.seconds ); + if ( ! totals ) + x = x / config.seconds; + buf.append( e.fieldName() , x ); } } BSONObj zoo = buf.obj(); diff --git a/scripting/engine.cpp b/scripting/engine.cpp index f9be639..1982940 100644 --- a/scripting/engine.cpp +++ b/scripting/engine.cpp @@ -85,10 +85,10 @@ namespace mongo { } - int Scope::invoke( const char* code , const BSONObj& args, int timeoutMs ) { + int Scope::invoke( const char* code , const BSONObj* args, const BSONObj* recv, int timeoutMs ) { ScriptingFunction func = createFunction( code ); uassert( 10207 , "compile failed" , func ); - return invoke( func , args, timeoutMs ); + return invoke( func , args, recv, timeoutMs ); } bool Scope::execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs ) { @@ -241,6 +241,27 @@ namespace mongo { return f; } + 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 utils; + extern const JSFile utils_sh; + } + + void Scope::execCoreFiles() { + // keeping same order as in SConstruct + execSetup(JSFiles::utils); + execSetup(JSFiles::utils_sh); + execSetup(JSFiles::db); + execSetup(JSFiles::mongo); + execSetup(JSFiles::mr); + execSetup(JSFiles::query); + execSetup(JSFiles::collection); + } + typedef map< string , list<Scope*> > PoolToScopes; class ScopeCache { @@ -373,8 +394,12 @@ namespace mongo { void setBoolean( const char *field , bool val ) { _real->setBoolean( field , val ); } - void setThis( const BSONObj * obj ) { - _real->setThis( obj ); +// void setThis( const BSONObj * obj ) { +// _real->setThis( obj ); +// } + + void setFunction( const char *field , const char * code ) { + _real->setFunction(field, code); } ScriptingFunction createFunction( const char * code ) { @@ -392,8 +417,8 @@ namespace mongo { /** * @return 0 on success */ - int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs , bool ignoreReturn ) { - return _real->invoke( func , args , timeoutMs , ignoreReturn ); + int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) { + return _real->invoke( func , args , recv, timeoutMs , ignoreReturn ); } string getError() { @@ -407,14 +432,18 @@ namespace mongo { return _real->execFile( filename , printResult , reportError , assertOnError , timeoutMs ); } - void injectNative( const char *field, NativeFunction func ) { - _real->injectNative( field , func ); + void injectNative( const char *field, NativeFunction func, void* data ) { + _real->injectNative( field , func, data ); } void gc() { _real->gc(); } + void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ) { + _real->append(builder, fieldName, scopeName); + } + private: string _pool; Scope * _real; diff --git a/scripting/engine.h b/scripting/engine.h index 62afd77..1f9f1f5 100644 --- a/scripting/engine.h +++ b/scripting/engine.h @@ -27,18 +27,8 @@ namespace mongo { 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 ); + typedef BSONObj (*NativeFunction) ( const BSONObj &args, void* data ); class Scope : boost::noncopyable { public: @@ -48,7 +38,7 @@ namespace mongo { virtual void reset() = 0; virtual void init( const BSONObj * data ) = 0; void init( const char * data ) { - BSONObj o( data , 0 ); + BSONObj o( data ); init( &o ); } @@ -79,14 +69,15 @@ namespace mongo { virtual int type( const char *field ) = 0; - void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ); + virtual void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ); virtual void setElement( const char *field , const BSONElement& e ) = 0; virtual void setNumber( const char *field , double val ) = 0; virtual void setString( const char *field , const char * val ) = 0; 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 void setFunction( const char *field , const char * code ) = 0; +// virtual void setThis( const BSONObj * obj ) = 0; virtual ScriptingFunction createFunction( const char * code ); @@ -94,18 +85,18 @@ namespace mongo { /** * @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 ) { - int res = invoke( func , args , timeoutMs ); + virtual int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool readOnlyArgs = false, bool readOnlyRecv = false ) = 0; + void invokeSafe( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0, bool readOnlyArgs = false, bool readOnlyRecv = false ) { + int res = invoke( func , args , recv, timeoutMs, readOnlyArgs, readOnlyRecv ); 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 ) { - if ( invoke( code , args , timeoutMs ) == 0 ) + int invoke( const char* code , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 ); + void invokeSafe( const char* code , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 ) { + if ( invoke( code , args , recv, timeoutMs ) == 0 ) return; throw UserException( 9005 , (string)"invoke failed: " + getError() ); } @@ -119,19 +110,11 @@ namespace mongo { 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); - } + void execCoreFiles(); 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 injectNative( const char *field, NativeFunction func, void* data = 0 ) = 0; virtual void gc() = 0; diff --git a/scripting/engine_java.cpp b/scripting/engine_java.cpp index fc8945f..5738816 100644 --- a/scripting/engine_java.cpp +++ b/scripting/engine_java.cpp @@ -43,7 +43,7 @@ namespace mongo { -#include "../util/message.h" +#include "../util/net/message.h" #include "../db/db.h" using namespace std; @@ -405,15 +405,17 @@ namespace mongo { if ( guess == 0 ) return BSONObj(); - char * buf = (char *) malloc(guess); - jobject bb = _getEnv()->NewDirectByteBuffer( (void*)buf , guess ); + BSONObj::Holder* holder = (BSONObj::Holder*) malloc(guess + sizeof(unsigned)); + holder->zero() + + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)holder->data , guess ); jassert( bb ); int len = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGetObject , id , _getEnv()->NewStringUTF( field ) , bb ); _getEnv()->DeleteLocalRef( bb ); jassert( len > 0 && len < guess ); - BSONObj obj(buf, true); + BSONObj obj(holder); assert( obj.objsize() <= guess ); return obj; } diff --git a/scripting/engine_spidermonkey.cpp b/scripting/engine_spidermonkey.cpp index aed7b13..64fe21c 100644 --- a/scripting/engine_spidermonkey.cpp +++ b/scripting/engine_spidermonkey.cpp @@ -242,6 +242,10 @@ namespace mongo { return val; } + int toNumberInt( JSObject *o ) { + return (boost::uint32_t)(boost::int32_t) getNumber( o, "floatApprox" ); + } + double toNumber( jsval v ) { double d; uassert( 10214 , "not a number" , JS_ValueToNumber( _context , v , &d ) ); @@ -492,7 +496,6 @@ namespace mongo { return func; } - jsval toval( double d ) { jsval val; assert( JS_NewNumberValue( _context, d , &val ) ); @@ -531,7 +534,7 @@ namespace mongo { JSObject * toJSObject( const BSONObj * obj , bool readOnly=false ) { static string ref = "$ref"; - if ( ref == obj->firstElement().fieldName() ) { + if ( ref == obj->firstElementFieldName() ) { JSObject * o = JS_NewObject( _context , &dbref_class , NULL, NULL); CHECKNEWOBJECT(o,_context,"toJSObject1"); assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); @@ -551,8 +554,9 @@ 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 ) ) ); - if ( (boost::int64_t)val != (boost::int64_t)(double)(boost::int64_t)( val ) ) { + double floatApprox = (double)(boost::int64_t)val; + setProperty( o , "floatApprox" , toval( floatApprox ) ); + if ( (boost::int64_t)val != (boost::int64_t)floatApprox ) { // using 2 doubles here instead of a single double because certain double // bit patterns represent undefined values and sm might trash them setProperty( o , "top" , toval( (double)(boost::uint32_t)( val >> 32 ) ) ); @@ -566,6 +570,19 @@ namespace mongo { return OBJECT_TO_JSVAL( o ); } + void makeIntObj( int n, JSObject * o ) { + boost::uint32_t val = (boost::uint32_t)n; + CHECKNEWOBJECT(o,_context,"NumberInt1"); + double floatApprox = (double)(boost::int32_t)val; + setProperty( o , "floatApprox" , toval( floatApprox ) ); + } + + jsval toval( int n ) { + JSObject * o = JS_NewObject( _context , &numberint_class , 0 , 0 ); + makeIntObj( n, o ); + return OBJECT_TO_JSVAL( o ); + } + jsval toval( const BSONElement& e ) { switch( e.type() ) { @@ -576,6 +593,8 @@ namespace mongo { case NumberDouble: case NumberInt: return toval( e.number() ); +// case NumberInt: +// return toval( e.numberInt() ); case Symbol: // TODO: should we make a special class for this case String: return toval( e.valuestr() ); @@ -651,7 +670,7 @@ namespace mongo { return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); } case Date: - return OBJECT_TO_JSVAL( js_NewDateObjectMsec( _context , (jsdouble) e.date().millis ) ); + return OBJECT_TO_JSVAL( js_NewDateObjectMsec( _context , (jsdouble) ((long long)e.date().millis) ) ); case MinKey: return OBJECT_TO_JSVAL( JS_NewObject( _context , &minkey_class , 0 , 0 ) ); @@ -903,6 +922,68 @@ namespace mongo { // --- global helpers --- + JSBool hexToBinData(JSContext * cx, jsval *rval, int subtype, string s) { + JSObject * o = JS_NewObject( cx , &bindata_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"Bindata_BinData1"); + int len = s.size() / 2; + char * data = new char[len]; + char *p = data; + const char *src = s.c_str(); + for( size_t i = 0; i+1 < s.size(); i += 2 ) { + *p++ = fromHex(src + i); + } + assert( JS_SetPrivate( cx , o , new BinDataHolder( data , len ) ) ); + Convertor c(cx); + c.setProperty( o, "len", c.toval((double)len) ); + c.setProperty( o, "type", c.toval((double)subtype) ); + *rval = OBJECT_TO_JSVAL( o ); + delete data; + return JS_TRUE; + } + + JSBool _HexData( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( argc != 2 ) { + JS_ReportError( cx , "HexData needs 2 arguments -- HexData(subtype,hexstring)" ); + return JS_FALSE; + } + int type = (int)c.toNumber( argv[ 0 ] ); + if ( type == 2 ) { + JS_ReportError( cx , "BinData subtype 2 is deprecated" ); + return JS_FALSE; + } + string s = c.toString(argv[1]); + return hexToBinData(cx, rval, type, s); + } + + JSBool _UUID( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( argc != 1 ) { + JS_ReportError( cx , "UUID needs argument -- UUID(hexstring)" ); + return JS_FALSE; + } + string s = c.toString(argv[0]); + if( s.size() != 32 ) { + JS_ReportError( cx , "bad UUID hex string len" ); + return JS_FALSE; + } + return hexToBinData(cx, rval, 3, s); + } + + JSBool _MD5( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( argc != 1 ) { + JS_ReportError( cx , "MD5 needs argument -- MD5(hexstring)" ); + return JS_FALSE; + } + string s = c.toString(argv[0]); + if( s.size() != 32 ) { + JS_ReportError( cx , "bad MD5 hex string len" ); + return JS_FALSE; + } + return hexToBinData(cx, rval, 5, s); + } + JSBool native_print( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { stringstream ss; Convertor c( cx ); @@ -920,6 +1001,7 @@ namespace mongo { Convertor c(cx); NativeFunction func = (NativeFunction)((long long)c.getNumber( obj , "x" ) ); + void* data = (void*)((long long)c.getNumber( obj , "y" ) ); assert( func ); BSONObj a; @@ -934,7 +1016,7 @@ namespace mongo { BSONObj out; try { - out = func( a ); + out = func( a, data ); } catch ( std::exception& e ) { JS_ReportError( cx , e.what() ); @@ -963,6 +1045,9 @@ namespace mongo { { "nativeHelper" , &native_helper , 1 , 0 , 0 } , { "load" , &native_load , 1 , 0 , 0 } , { "gc" , &native_gc , 1 , 0 , 0 } , + { "UUID", &_UUID, 0, 0, 0 } , + { "MD5", &_MD5, 0, 0, 0 } , + { "HexData", &_HexData, 0, 0, 0 } , { 0 , 0 , 0 , 0 , 0 } }; @@ -1050,6 +1135,7 @@ namespace mongo { if ( val != JSVAL_NULL && val != JSVAL_VOID && JSVAL_IS_OBJECT( val ) ) { // TODO: this is a hack to get around sub objects being modified + // basically right now whenever a sub object is read we mark whole obj as possibly modified JSObject * oo = JSVAL_TO_OBJECT( val ); if ( JS_InstanceOf( cx , oo , &bson_class , 0 ) || JS_IsArrayObject( cx , oo ) ) { @@ -1346,6 +1432,12 @@ namespace mongo { } } + void setFunction( const char *field , const char * code ) { + smlock; + jsval v = OBJECT_TO_JSVAL(JS_GetFunctionObject(_convertor->compileFunction(code))); + JS_SetProperty( _context , _global , field , &v ); + } + void rename( const char * from , const char * to ) { smlock; jsval v; @@ -1462,33 +1554,35 @@ namespace mongo { return worked; } - int invoke( JSFunction * func , const BSONObj& args, int timeoutMs , bool ignoreReturn ) { + int invoke( JSFunction * func , const BSONObj* args, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) { smlock; precall(); assert( JS_EnterLocalRootScope( _context ) ); - int nargs = args.nFields(); + int nargs = args ? args->nFields() : 0; scoped_array<jsval> smargsPtr( new jsval[nargs] ); if ( nargs ) { - BSONObjIterator it( args ); + BSONObjIterator it( *args ); for ( int i=0; i<nargs; i++ ) { smargsPtr[i] = _convertor->toval( it.next() ); } } - if ( args.isEmpty() ) { + if ( !args ) { _convertor->setProperty( _global , "args" , JSVAL_NULL ); } else { - setObject( "args" , args , true ); // this is for backwards compatability + setObject( "args" , *args , true ); // this is for backwards compatability } JS_LeaveLocalRootScope( _context ); installInterrupt( timeoutMs ); jsval rval; + setThis(recv); JSBool ret = JS_CallFunction( _context , _this ? _this : _global , func , nargs , smargsPtr.get() , &rval ); + setThis(0); uninstallInterrupt( timeoutMs ); if ( !ret ) { @@ -1502,8 +1596,8 @@ namespace mongo { return 0; } - int invoke( ScriptingFunction funcAddr , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = 0 ) { - return invoke( (JSFunction*)funcAddr , args , timeoutMs , ignoreReturn ); + int invoke( ScriptingFunction funcAddr , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = 0, bool readOnlyArgs = false, bool readOnlyRecv = false ) { + return invoke( (JSFunction*)funcAddr , args , recv, timeoutMs , ignoreReturn, readOnlyArgs, readOnlyRecv); } void gotError( string s ) { @@ -1514,13 +1608,18 @@ namespace mongo { return _error; } - void injectNative( const char *field, NativeFunction func ) { + void injectNative( const char *field, NativeFunction func, void* data ) { smlock; string name = field; _convertor->setProperty( _global , (name + "_").c_str() , _convertor->toval( (double)(long long)func ) ); stringstream code; - code << field << "_" << " = { x : " << field << "_ }; "; + if (data) { + _convertor->setProperty( _global , (name + "_data_").c_str() , _convertor->toval( (double)(long long)data ) ); + code << field << "_" << " = { x : " << field << "_ , y: " << field << "_data_ }; "; + } else { + code << field << "_" << " = { x : " << field << "_ }; "; + } code << field << " = function(){ return nativeHelper.apply( " << field << "_ , arguments ); }"; exec( code.str() ); } diff --git a/scripting/engine_spidermonkey.h b/scripting/engine_spidermonkey.h index 3ee7495..9fd430d 100644 --- a/scripting/engine_spidermonkey.h +++ b/scripting/engine_spidermonkey.h @@ -21,18 +21,6 @@ // START inc hacking -#if defined( MOZJS ) - -#define MOZILLA_1_8_BRANCH - -#include "mozjs/jsapi.h" -#include "mozjs/jsdate.h" -#include "mozjs/jsregexp.h" - -#warning if you are using an ubuntu version of spider monkey, we recommend installing spider monkey from source - -#elif defined( OLDJS ) - #ifdef WIN32 #include "jstypes.h" #undef JS_PUBLIC_API @@ -46,30 +34,11 @@ #include "jsdate.h" #include "jsregexp.h" -#else - -#include "js/jsapi.h" -#include "js/jsobj.h" -#include "js/jsdate.h" -#include "js/jsregexp.h" - -#endif - // END inc hacking // -- SM 1.6 hacks --- #ifndef JSCLASS_GLOBAL_FLAGS - -#warning old version of spider monkey ( probably 1.6 ) you should upgrade to at least 1.7 - -#define JSCLASS_GLOBAL_FLAGS 0 - -JSBool JS_CStringsAreUTF8() { - return false; -} - -#define SM16 - +#error old version of spider monkey ( probably 1.6 ) you should upgrade to at least 1.7 #endif // -- END SM 1.6 hacks --- @@ -95,6 +64,7 @@ namespace mongo { extern JSClass bindata_class; extern JSClass timestamp_class; extern JSClass numberlong_class; + extern JSClass numberint_class; extern JSClass minkey_class; extern JSClass maxkey_class; diff --git a/scripting/engine_v8.cpp b/scripting/engine_v8.cpp index cd186b4..fd69d66 100644 --- a/scripting/engine_v8.cpp +++ b/scripting/engine_v8.cpp @@ -15,6 +15,14 @@ * limitations under the License. */ +#if defined(_WIN32) +/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with + our usage of boost */ +#include "boost/cstdint.hpp" +using namespace boost; +#define V8STDINT_H_ +#endif + #include "engine_v8.h" #include "v8_wrapper.h" @@ -28,9 +36,239 @@ namespace mongo { // guarded by v8 mutex map< unsigned, int > __interruptSpecToThreadId; + /** + * Unwraps a BSONObj from the JS wrapper + */ + static BSONObj* unwrapBSONObj(const Handle<v8::Object>& obj) { + Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); + if (field.IsEmpty() || !field->IsExternal()) + return 0; + void* ptr = field->Value(); + return (BSONObj*)ptr; + } + + static void weakRefBSONCallback(v8::Persistent<v8::Value> p, void* scope) { + // should we lock here? no idea, and no doc from v8 of course + HandleScope handle_scope; + if (!p.IsNearDeath()) + return; + Handle<External> field = Handle<External>::Cast(p->ToObject()->GetInternalField(0)); + BSONObj* data = (BSONObj*) field->Value(); + delete data; + p.Dispose(); + } + + Persistent<v8::Object> V8Scope::wrapBSONObject(Local<v8::Object> obj, BSONObj* data) { + obj->SetInternalField(0, v8::External::New(data)); + Persistent<v8::Object> p = Persistent<v8::Object>::New(obj); + p.MakeWeak(this, weakRefBSONCallback); + return p; + } + + static void weakRefArrayCallback(v8::Persistent<v8::Value> p, void* scope) { + // should we lock here? no idea, and no doc from v8 of course + HandleScope handle_scope; + if (!p.IsNearDeath()) + return; + Handle<External> field = Handle<External>::Cast(p->ToObject()->GetInternalField(0)); + char* data = (char*) field->Value(); + delete [] data; + p.Dispose(); + } + + Persistent<v8::Object> V8Scope::wrapArrayObject(Local<v8::Object> obj, char* data) { + obj->SetInternalField(0, v8::External::New(data)); + Persistent<v8::Object> p = Persistent<v8::Object>::New(obj); + p.MakeWeak(this, weakRefArrayCallback); + return p; + } + + static Handle<v8::Value> namedGet(Local<v8::String> name, const v8::AccessorInfo &info) { + // all properties should be set, otherwise means builtin or deleted + if (!(info.This()->HasRealNamedProperty(name))) + return v8::Handle<v8::Value>(); + + Handle<v8::Value> val = info.This()->GetRealNamedProperty(name); + if (!val->IsUndefined()) { + // value already cached + return val; + } + + string key = toSTLString(name); + BSONObj *obj = unwrapBSONObj(info.Holder()); + BSONElement elmt = obj->getField(key.c_str()); + if (elmt.eoo()) + return Handle<Value>(); + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + val = scope->mongoToV8Element(elmt, false); + info.This()->ForceSet(name, val); + + if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) { + // if accessing a subobject, it may get modified and base obj would not know + // have to set base as modified, which means some optim is lost + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + } + return val; + } + + static Handle<v8::Value> namedGetRO(Local<v8::String> name, const v8::AccessorInfo &info) { + string key = toSTLString(name); + BSONObj *obj = unwrapBSONObj(info.Holder()); + BSONElement elmt = obj->getField(key.c_str()); + if (elmt.eoo()) + return Handle<Value>(); + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + Handle<v8::Value> val = scope->mongoToV8Element(elmt, true); + return val; + } + + static Handle<v8::Value> namedSet(Local<v8::String> name, Local<v8::Value> value_obj, const v8::AccessorInfo& info) { + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + return Handle<Value>(); + } + + static Handle<v8::Array> namedEnumerator(const AccessorInfo &info) { + BSONObj *obj = unwrapBSONObj(info.Holder()); + Handle<v8::Array> arr = Handle<v8::Array>(v8::Array::New(obj->nFields())); + int i = 0; + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + // note here that if keys are parseable number, v8 will access them using index + for ( BSONObjIterator it(*obj); it.more(); ++i) { + const BSONElement& f = it.next(); +// arr->Set(i, v8::String::NewExternal(new ExternalString(f.fieldName()))); + Handle<v8::String> name = scope->getV8Str(f.fieldName()); + arr->Set(i, name); + } + return arr; + } + + Handle<Boolean> namedDelete( Local<v8::String> property, const AccessorInfo& info ) { + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + return Handle<Boolean>(); + } + +// v8::Handle<v8::Integer> namedQuery(Local<v8::String> property, const AccessorInfo& info) { +// string key = ToString(property); +// return v8::Integer::New(None); +// } + + static Handle<v8::Value> indexedGet(uint32_t index, const v8::AccessorInfo &info) { + // all properties should be set, otherwise means builtin or deleted + if (!(info.This()->HasRealIndexedProperty(index))) + return v8::Handle<v8::Value>(); + + StringBuilder ss; + ss << index; + string key = ss.str(); + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + // cannot get v8 to properly cache the indexed val in the js object +// Handle<v8::String> name = scope->getV8Str(key); +// // v8 API really confusing here, must check existence on index, but then fetch with name +// if (info.This()->HasRealIndexedProperty(index)) { +// Handle<v8::Value> val = info.This()->GetRealNamedProperty(name); +// if (!val.IsEmpty() && !val->IsNull()) +// return val; +// } + BSONObj *obj = unwrapBSONObj(info.Holder()); + BSONElement elmt = obj->getField(key); + if (elmt.eoo()) + return Handle<Value>(); + Handle<Value> val = scope->mongoToV8Element(elmt, false); +// info.This()->ForceSet(name, val); + + if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) { + // if accessing a subobject, it may get modified and base obj would not know + // have to set base as modified, which means some optim is lost + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + } + return val; + } + + Handle<Boolean> indexedDelete( uint32_t index, const AccessorInfo& info ) { + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + return Handle<Boolean>(); + } + + static Handle<v8::Value> indexedGetRO(uint32_t index, const v8::AccessorInfo &info) { + StringBuilder ss; + ss << index; + string key = ss.str(); + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + // cannot get v8 to properly cache the indexed val in the js object +// Handle<v8::String> name = scope->getV8Str(key); +// // v8 API really confusing here, must check existence on index, but then fetch with name +// if (info.This()->HasRealIndexedProperty(index)) { +// Handle<v8::Value> val = info.This()->GetRealNamedProperty(name); +// if (!val.IsEmpty() && !val->IsNull()) +// return val; +// } + BSONObj *obj = unwrapBSONObj(info.Holder()); + BSONElement elmt = obj->getField(key); + if (elmt.eoo()) + return Handle<Value>(); + Handle<Value> val = scope->mongoToV8Element(elmt, true); +// info.This()->ForceSet(name, val); + return val; + } + + static Handle<v8::Value> indexedSet(uint32_t index, Local<v8::Value> value_obj, const v8::AccessorInfo& info) { + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + return Handle<Value>(); + } + +// static Handle<v8::Array> indexedEnumerator(const AccessorInfo &info) { +// BSONObj *obj = unwrapBSONObj(info.Holder()); +// Handle<v8::Array> arr = Handle<v8::Array>(v8::Array::New(obj->nFields())); +// Local< External > scp = External::Cast( *info.Data() ); +// V8Scope* scope = (V8Scope*)(scp->Value()); +// int i = 0; +// for ( BSONObjIterator it(*obj); it.more(); ++i) { +// const BSONElement& f = it.next(); +//// arr->Set(i, v8::String::NewExternal(new ExternalString(f.fieldName()))); +// arr->Set(i, scope->getV8Str(f.fieldName())); +// } +// return arr; +// } + + Handle<Value> NamedReadOnlySet( Local<v8::String> property, Local<Value> value, const AccessorInfo& info ) { + string key = toSTLString(property); + cout << "cannot write property " << key << " to read-only object" << endl; + return value; + } + + Handle<Boolean> NamedReadOnlyDelete( Local<v8::String> property, const AccessorInfo& info ) { + string key = toSTLString(property); + cout << "cannot delete property " << key << " from read-only object" << endl; + return Boolean::New( false ); + } + + Handle<Value> IndexedReadOnlySet( uint32_t index, Local<Value> value, const AccessorInfo& info ) { + cout << "cannot write property " << index << " to read-only array" << endl; + return value; + } + + Handle<Boolean> IndexedReadOnlyDelete( uint32_t index, const AccessorInfo& info ) { + cout << "cannot delete property " << index << " from read-only array" << endl; + return Boolean::New( false ); + } + // --- engine --- - V8ScriptEngine::V8ScriptEngine() {} + V8ScriptEngine::V8ScriptEngine() { + } V8ScriptEngine::~V8ScriptEngine() { } @@ -69,50 +307,105 @@ namespace mongo { _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"), newV8Function< Print >()->GetFunction() ); - _global->Set(v8::String::New("version"), newV8Function< Version >()->GetFunction() ); - - _global->Set(v8::String::New("load"), - v8::FunctionTemplate::New( v8Callback< loadCallback >, v8::External::New(this))->GetFunction() ); - - _wrapper = Persistent< v8::Function >::New( getObjectWrapperTemplate()->GetFunction() ); - - _global->Set(v8::String::New("gc"), newV8Function< GCV8 >()->GetFunction() ); - - - installDBTypes( _global ); + _emptyObj = Persistent< v8::Object >::New( v8::Object::New() ); + + // initialize lazy object template + lzObjectTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New()); + lzObjectTemplate->SetInternalFieldCount( 1 ); + lzObjectTemplate->SetNamedPropertyHandler(namedGet, namedSet, 0, namedDelete, 0, v8::External::New(this)); + lzObjectTemplate->SetIndexedPropertyHandler(indexedGet, indexedSet, 0, indexedDelete, 0, v8::External::New(this)); + + roObjectTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New()); + roObjectTemplate->SetInternalFieldCount( 1 ); + roObjectTemplate->SetNamedPropertyHandler(namedGetRO, NamedReadOnlySet, 0, NamedReadOnlyDelete, namedEnumerator, v8::External::New(this)); + roObjectTemplate->SetIndexedPropertyHandler(indexedGetRO, IndexedReadOnlySet, 0, IndexedReadOnlyDelete, 0, v8::External::New(this)); + + // initialize lazy array template + // unfortunately it is not possible to create true v8 array from a template + // this means we use an object template and copy methods over + // this it creates issues when calling certain methods that check array type + lzArrayTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New()); + lzArrayTemplate->SetInternalFieldCount( 1 ); + lzArrayTemplate->SetIndexedPropertyHandler(indexedGet, 0, 0, 0, 0, v8::External::New(this)); + + internalFieldObjects = Persistent<ObjectTemplate>::New(ObjectTemplate::New()); + internalFieldObjects->SetInternalFieldCount( 1 ); + + V8STR_CONN = getV8Str( "_conn" ); + V8STR_ID = getV8Str( "_id" ); + V8STR_LENGTH = getV8Str( "length" ); + V8STR_LEN = getV8Str( "len" ); + V8STR_TYPE = getV8Str( "type" ); + V8STR_ISOBJECTID = getV8Str( "isObjectId" ); + V8STR_RETURN = getV8Str( "return" ); + V8STR_ARGS = getV8Str( "args" ); + V8STR_T = getV8Str( "t" ); + V8STR_I = getV8Str( "i" ); + V8STR_EMPTY = getV8Str( "" ); + V8STR_MINKEY = getV8Str( "$MinKey" ); + V8STR_MAXKEY = getV8Str( "$MaxKey" ); + V8STR_NUMBERLONG = getV8Str( "__NumberLong" ); + V8STR_NUMBERINT = getV8Str( "__NumberInt" ); + V8STR_DBPTR = getV8Str( "__DBPointer" ); + V8STR_BINDATA = getV8Str( "__BinData" ); + V8STR_NATIVE_FUNC = getV8Str( "_native_function" ); + V8STR_NATIVE_DATA = getV8Str( "_native_data" ); + V8STR_V8_FUNC = getV8Str( "_v8_function" ); + V8STR_RO = getV8Str( "_ro" ); + V8STR_MODIFIED = getV8Str( "_mod" ); + + injectV8Function("print", Print); + injectV8Function("version", Version); + injectV8Function("load", load); + + _wrapper = Persistent< v8::Function >::New( getObjectWrapperTemplate(this)->GetFunction() ); + + injectV8Function("gc", GCV8); + + installDBTypes( this, _global ); } V8Scope::~V8Scope() { V8Lock l; Context::Scope context_scope( _context ); _wrapper.Dispose(); - _this.Dispose(); + _emptyObj.Dispose(); for( unsigned i = 0; i < _funcs.size(); ++i ) _funcs[ i ].Dispose(); _funcs.clear(); _global.Dispose(); _context.Dispose(); + std::map <string, v8::Persistent <v8::String> >::iterator it = _strCache.begin(); + std::map <string, v8::Persistent <v8::String> >::iterator end = _strCache.end(); + while (it != end) { + it->second.Dispose(); + ++it; + } + lzObjectTemplate.Dispose(); + lzArrayTemplate.Dispose(); + roObjectTemplate.Dispose(); + internalFieldObjects.Dispose(); } - Handle< Value > V8Scope::nativeCallback( const Arguments &args ) { + /** + * JS Callback that will call a c++ function with BSON arguments. + */ + Handle< Value > V8Scope::nativeCallback( V8Scope* scope, const Arguments &args ) { V8Lock l; HandleScope handle_scope; - Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_native_function" ) ) ); + Local< External > f = External::Cast( *args.Callee()->Get( scope->V8STR_NATIVE_FUNC ) ); NativeFunction function = (NativeFunction)(f->Value()); + Local< External > data = External::Cast( *args.Callee()->Get( scope->V8STR_NATIVE_DATA ) ); BSONObjBuilder b; for( int i = 0; i < args.Length(); ++i ) { stringstream ss; ss << i; - v8ToMongoElement( b, v8::String::New( "foo" ), ss.str(), args[ i ] ); + scope->v8ToMongoElement( b, scope->V8STR_EMPTY, ss.str(), args[ i ] ); } BSONObj nativeArgs = b.obj(); BSONObj ret; try { - ret = function( nativeArgs ); + ret = function( nativeArgs, data->Value() ); } catch( const std::exception &e ) { return v8::ThrowException(v8::String::New(e.what())); @@ -120,26 +413,63 @@ namespace mongo { catch( ... ) { return v8::ThrowException(v8::String::New("unknown exception")); } - return handle_scope.Close( mongoToV8Element( ret.firstElement() ) ); + return handle_scope.Close( scope->mongoToV8Element( ret.firstElement() ) ); } - Handle< Value > V8Scope::loadCallback( const Arguments &args ) { - V8Lock l; - HandleScope handle_scope; - Handle<External> field = Handle<External>::Cast(args.Data()); - void* ptr = field->Value(); - V8Scope* self = static_cast<V8Scope*>(ptr); - - Context::Scope context_scope(self->_context); + Handle< Value > V8Scope::load( V8Scope* scope, const Arguments &args ) { + Context::Scope context_scope(scope->_context); for (int i = 0; i < args.Length(); ++i) { std::string filename(toSTLString(args[i])); - if (!self->execFile(filename, false , true , false)) { + if (!scope->execFile(filename, false , true , false)) { return v8::ThrowException(v8::String::New((std::string("error loading file: ") + filename).c_str())); } } return v8::True(); } + /** + * JS Callback that will call a c++ function with the v8 scope and v8 arguments. + * Handles interrupts, exception handling, etc + * + * 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 + */ + v8::Handle< v8::Value > V8Scope::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(); + } + Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_v8_function" ) ) ); + v8Function function = (v8Function)(f->Value()); + Local< External > scp = External::Cast( *args.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + + v8::Handle< v8::Value > ret; + string exception; + try { + ret = function( scope, 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; + } + // ---- global stuff ---- void V8Scope::init( const BSONObj * data ) { @@ -156,29 +486,29 @@ namespace mongo { void V8Scope::setNumber( const char * field , double val ) { V8_SIMPLE_HEADER - _global->Set( v8::String::New( field ) , v8::Number::New( val ) ); + _global->Set( getV8Str( field ) , v8::Number::New( val ) ); } void V8Scope::setString( const char * field , const char * val ) { V8_SIMPLE_HEADER - _global->Set( v8::String::New( field ) , v8::String::New( val ) ); + _global->Set( getV8Str( field ) , v8::String::New( val ) ); } void V8Scope::setBoolean( const char * field , bool val ) { V8_SIMPLE_HEADER - _global->Set( v8::String::New( field ) , v8::Boolean::New( val ) ); + _global->Set( getV8Str( field ) , v8::Boolean::New( val ) ); } void V8Scope::setElement( const char *field , const BSONElement& e ) { V8_SIMPLE_HEADER - _global->Set( v8::String::New( field ) , mongoToV8Element( e ) ); + _global->Set( getV8Str( field ) , mongoToV8Element( e ) ); } 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) ); + _global->Set( getV8Str( field ) , mongoToLZV8( obj, false, readOnly) ); } int V8Scope::type( const char *field ) { @@ -196,8 +526,9 @@ namespace mongo { return Array; if ( v->IsBoolean() ) return Bool; - if ( v->IsInt32() ) - return NumberInt; + // needs to be explicit NumberInt to use integer +// if ( v->IsInt32() ) +// return NumberInt; if ( v->IsNumber() ) return NumberDouble; if ( v->IsExternal() ) { @@ -213,7 +544,7 @@ namespace mongo { } v8::Handle<v8::Value> V8Scope::get( const char * field ) { - return _global->Get( v8::String::New( field ) ); + return _global->Get( getV8Str( field ) ); } double V8Scope::getNumber( const char *field ) { @@ -314,45 +645,50 @@ namespace mongo { return num; } - void V8Scope::setThis( const BSONObj * obj ) { + void V8Scope::setFunction( const char *field , const char * code ) { V8_SIMPLE_HEADER - if ( ! obj ) { - _this = Persistent< v8::Object >::New( v8::Object::New() ); - return; - } - - //_this = mongoToV8( *obj ); - v8::Handle<v8::Value> argv[1]; - argv[0] = v8::External::New( createWrapperHolder( obj , true , false ) ); - _this = Persistent< v8::Object >::New( _wrapper->NewInstance( 1, argv ) ); + _global->Set( getV8Str( field ) , __createFunction(code) ); } +// void V8Scope::setThis( const BSONObj * obj ) { +// V8_SIMPLE_HEADER +// if ( ! obj ) { +// _this = Persistent< v8::Object >::New( v8::Object::New() ); +// return; +// } +// +// //_this = mongoToV8( *obj ); +// v8::Handle<v8::Value> argv[1]; +// argv[0] = v8::External::New( createWrapperHolder( this, obj , true , false ) ); +// _this = Persistent< v8::Object >::New( _wrapper->NewInstance( 1, argv ) ); +// } + 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 ); + Handle<v8::String> f = getV8Str( from ); + Handle<v8::String> t = getV8Str( to ); _global->Set( t , _global->Get( f ) ); _global->Set( f , v8::Undefined() ); } - int V8Scope::invoke( ScriptingFunction func , const BSONObj& argsObject, int timeoutMs , bool ignoreReturn ) { + int V8Scope::invoke( ScriptingFunction func , const BSONObj* argsObject, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) { V8_SIMPLE_HEADER Handle<Value> funcValue = _funcs[func-1]; TryCatch try_catch; - int nargs = argsObject.nFields(); + int nargs = argsObject ? argsObject->nFields() : 0; scoped_array< Handle<Value> > args; if ( nargs ) { args.reset( new Handle<Value>[nargs] ); - BSONObjIterator it( argsObject ); + BSONObjIterator it( *argsObject ); for ( int i=0; i<nargs; i++ ) { BSONElement next = it.next(); - args[i] = mongoToV8Element( next ); + args[i] = mongoToV8Element( next, readOnlyArgs ); } - setObject( "args", argsObject, true ); // for backwards compatibility + setObject( "args", *argsObject, readOnlyArgs); // for backwards compatibility } else { - _global->Set( v8::String::New( "args" ), v8::Undefined() ); + _global->Set( V8STR_ARGS, v8::Undefined() ); } if ( globalScriptEngine->interrupted() ) { stringstream ss; @@ -361,8 +697,14 @@ namespace mongo { log() << _error << endl; return 1; } + Handle<v8::Object> v8recv; + if (recv != 0) + v8recv = mongoToLZV8(*recv, false, readOnlyRecv); + else + v8recv = _emptyObj; + enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable - Local<Value> result = ((v8::Function*)(*funcValue))->Call( _this , nargs , args.get() ); + Local<Value> result = ((v8::Function*)(*funcValue))->Call( v8recv , nargs , nargs ? args.get() : 0 ); disableV8Interrupt(); if ( result.IsEmpty() ) { @@ -379,7 +721,7 @@ namespace mongo { } if ( ! ignoreReturn ) { - _global->Set( v8::String::New( "return" ) , result ); + _global->Set( V8STR_RETURN , result ); } return 0; @@ -438,7 +780,7 @@ namespace mongo { return false; } - _global->Set( v8::String::New( "__lastres__" ) , result ); + _global->Set( getV8Str( "__lastres__" ) , result ); if ( printResult && ! result->IsUndefined() ) { cout << toSTLString( result ) << endl; @@ -447,12 +789,43 @@ namespace mongo { return true; } - void V8Scope::injectNative( const char *field, NativeFunction func ) { + void V8Scope::injectNative( const char *field, NativeFunction func, void* data ) { + injectNative(field, func, _global, data); + } + + void V8Scope::injectNative( const char *field, NativeFunction func, Handle<v8::Object>& obj, void* data ) { + V8_SIMPLE_HEADER + + Handle< FunctionTemplate > ft = createV8Function(nativeCallback); + ft->Set( this->V8STR_NATIVE_FUNC, External::New( (void*)func ) ); + ft->Set( this->V8STR_NATIVE_DATA, External::New( data ) ); + obj->Set( getV8Str( field ), ft->GetFunction() ); + } + + void V8Scope::injectV8Function( const char *field, v8Function func ) { + injectV8Function(field, func, _global); + } + + void V8Scope::injectV8Function( const char *field, v8Function func, Handle<v8::Object>& obj ) { + V8_SIMPLE_HEADER + + Handle< FunctionTemplate > ft = createV8Function(func); + Handle<v8::Function> f = ft->GetFunction(); + obj->Set( getV8Str( field ), f ); + } + + void V8Scope::injectV8Function( const char *field, v8Function func, Handle<v8::Template>& t ) { V8_SIMPLE_HEADER - Handle< FunctionTemplate > f( newV8Function< nativeCallback >() ); - f->Set( v8::String::New( "_native_function" ), External::New( (void*)func ) ); - _global->Set( v8::String::New( field ), f->GetFunction() ); + Handle< FunctionTemplate > ft = createV8Function(func); + Handle<v8::Function> f = ft->GetFunction(); + t->Set( getV8Str( field ), f ); + } + + Handle<FunctionTemplate> V8Scope::createV8Function( v8Function func ) { + Handle< FunctionTemplate > ft = v8::FunctionTemplate::New(v8Callback, External::New( this )); + ft->Set( this->V8STR_V8_FUNC, External::New( (void*)func ) ); + return ft; } void V8Scope::gc() { @@ -479,7 +852,7 @@ namespace mongo { v8::Locker::StartPreemption( 50 ); //_global->Set( v8::String::New( "Mongo" ) , _engine->_externalTemplate->GetFunction() ); - _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( true )->GetFunction() ); + _global->Set( getV8Str( "Mongo" ) , getMongoFunctionTemplate( this, 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 ); @@ -496,8 +869,8 @@ namespace mongo { if ( _connectState == LOCAL ) throw UserException( 12512, "localConnect already called, can't call externalSetup" ); - installFork( _global, _context ); - _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( false )->GetFunction() ); + installFork( this, _global, _context ); + _global->Set( getV8Str( "Mongo" ) , getMongoFunctionTemplate( this, false )->GetFunction() ); execCoreFiles(); _connectState = EXTERNAL; } @@ -512,4 +885,663 @@ namespace mongo { _error = ""; } + Local< v8::Value > newFunction( const char *code ) { + stringstream codeSS; + codeSS << "____MontoToV8_newFunction_temp = " << code; + string codeStr = codeSS.str(); + Local< Script > compiled = Script::New( v8::String::New( codeStr.c_str() ) ); + Local< Value > ret = compiled->Run(); + return ret; + } + + Local< v8::Value > V8Scope::newId( const OID &id ) { + v8::Function * idCons = this->getObjectIdCons(); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::String::New( id.str().c_str() ); + return idCons->NewInstance( 1 , argv ); + } + + Local<v8::Object> V8Scope::mongoToV8( const BSONObj& m , bool array, bool readOnly ) { + + Local<v8::Object> o; + + // handle DBRef. needs to come first. isn't it? (metagoto) + static string ref = "$ref"; + if ( ref == m.firstElement().fieldName() ) { + const BSONElement& id = m["$id"]; + if (!id.eoo()) { // there's no check on $id exitence in sm implementation. risky ? + v8::Function* dbRef = getNamedCons( "DBRef" ); + o = dbRef->NewInstance(); + } + } + + Local< v8::ObjectTemplate > readOnlyObjects; + + if ( !o.IsEmpty() ) { + readOnly = false; + } + else if ( array ) { + // NOTE Looks like it's impossible to add interceptors to v8 arrays. + readOnly = false; + o = v8::Array::New(); + } + else if ( !readOnly ) { + o = v8::Object::New(); + } + 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 + // that when property handlers are set for an object template, they will attach + // to objects previously created by that template. To get this to work, though, + // it is necessary to initialize the template's property handlers before + // creating objects from the template (as I have in the following few lines + // of code). + // NOTE In my first attempt, I configured the permanent property handlers before + // constructiong the object and replaced the Set() calls below with ForceSet(). + // However, it turns out that ForceSet() only bypasses handlers for named + // properties and not for indexed properties. + readOnlyObjects = v8::ObjectTemplate::New(); + // NOTE This internal field will store type info for special db types. For + // regular objects the field is unnecessary - for simplicity I'm creating just + // one readOnlyObjects template for objects where the field is & isn't necessary, + // assuming that the overhead of an internal field is slight. + readOnlyObjects->SetInternalFieldCount( 1 ); + readOnlyObjects->SetNamedPropertyHandler( 0 ); + readOnlyObjects->SetIndexedPropertyHandler( 0 ); + o = readOnlyObjects->NewInstance(); + } + + mongo::BSONObj sub; + + for ( BSONObjIterator i(m); i.more(); ) { + const BSONElement& f = i.next(); + + Local<Value> v; + Handle<v8::String> name = getV8Str(f.fieldName()); + + switch ( f.type() ) { + + case mongo::Code: + o->Set( name, newFunction( f.valuestr() ) ); + break; + + case CodeWScope: + if ( f.codeWScopeObject().isEmpty() ) + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + o->Set( name, newFunction( f.codeWScopeCode() ) ); + break; + + case mongo::String: + o->Set( name , 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( name , + idCons->NewInstance( 1 , argv ) ); + break; + } + + case mongo::NumberDouble: + case mongo::NumberInt: + o->Set( name , v8::Number::New( f.number() ) ); + break; + +// case mongo::NumberInt: { +// Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); +// int val = f.numberInt(); +// v8::Function* numberInt = getNamedCons( "NumberInt" ); +// v8::Handle<v8::Value> argv[1]; +// argv[0] = v8::Int32::New( val ); +// o->Set( name, numberInt->NewInstance( 1, argv ) ); +// break; +// } + + case mongo::Array: + sub = f.embeddedObject(); + o->Set( name , mongoToV8( sub , true, readOnly ) ); + break; + case mongo::Object: + sub = f.embeddedObject(); + o->Set( name , mongoToLZV8( sub , false, readOnly ) ); + break; + + case mongo::Date: + o->Set( name , v8::Date::New( (double) ((long long)f.date().millis) )); + break; + + case mongo::Bool: + o->Set( name , v8::Boolean::New( f.boolean() ) ); + break; + + case mongo::jstNULL: + case mongo::Undefined: // duplicate sm behavior + o->Set( name , 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( name , 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 ); + argv[1] = v8::Number::New( f.binDataType() ); + argv[2] = v8::String::New( data, len ); + o->Set( name, binData->NewInstance(3, argv) ); + break; + } + + case mongo::Timestamp: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + + sub->Set( V8STR_T , v8::Number::New( f.timestampTime() ) ); + sub->Set( V8STR_I , v8::Number::New( f.timestampInc() ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + + o->Set( name , 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" ); + double floatApprox = (double)(long long)val; + if ( (long long)val == (long long)floatApprox ) { + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::Number::New( floatApprox ); + o->Set( name, numberLong->NewInstance( 1, argv ) ); + } + else { + v8::Handle<v8::Value> argv[3]; + argv[0] = v8::Number::New( floatApprox ); + argv[1] = v8::Integer::New( val >> 32 ); + argv[2] = v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ); + o->Set( name, numberLong->NewInstance(3, argv) ); + } + break; + } + + case mongo::MinKey: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + sub->Set( V8STR_MINKEY, v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + o->Set( name , sub ); + break; + } + + case mongo::MaxKey: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + sub->Set( V8STR_MAXKEY, v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + o->Set( name , sub ); + break; + } + + case mongo::DBRef: { + v8::Function* dbPointer = getNamedCons( "DBPointer" ); + v8::Handle<v8::Value> argv[2]; + argv[0] = getV8Str( f.dbrefNS() ); + argv[1] = newId( f.dbrefOID() ); + o->Set( name, dbPointer->NewInstance(2, argv) ); + break; + } + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + } + + if ( readOnly ) { + readOnlyObjects->SetNamedPropertyHandler( 0, NamedReadOnlySet, 0, NamedReadOnlyDelete ); + readOnlyObjects->SetIndexedPropertyHandler( 0, IndexedReadOnlySet, 0, IndexedReadOnlyDelete ); + } + + return o; + } + + /** + * converts a BSONObj to a Lazy V8 object + */ + Handle<v8::Object> V8Scope::mongoToLZV8( const BSONObj& m , bool array, bool readOnly ) { + Local<v8::Object> o; + + if (readOnly) { + o = roObjectTemplate->NewInstance(); + o->SetHiddenValue(V8STR_RO, v8::Boolean::New(true)); + } else { + if (array) { + o = lzArrayTemplate->NewInstance(); + o->SetPrototype(v8::Array::New(1)->GetPrototype()); + o->Set(V8STR_LENGTH, v8::Integer::New(m.nFields()), DontEnum); + // o->Set(ARRAY_STRING, v8::Boolean::New(true), DontEnum); + } else { + o = lzObjectTemplate->NewInstance(); + + static string ref = "$ref"; + if ( ref == m.firstElement().fieldName() ) { + const BSONElement& id = m["$id"]; + if (!id.eoo()) { + v8::Function* dbRef = getNamedCons( "DBRef" ); + o->SetPrototype(dbRef->NewInstance()->GetPrototype()); + } + } + } + + // need to set all keys with dummy values, so that order of keys is correct during enumeration + // otherwise v8 will list any newly set property in JS before the ones of underlying BSON obj. + for (BSONObjIterator it(m); it.more();) { + const BSONElement& f = it.next(); + o->ForceSet(getV8Str(f.fieldName()), v8::Undefined()); + } + } + + BSONObj* own = new BSONObj(m.getOwned()); +// BSONObj* own = new BSONObj(m); + Persistent<v8::Object> p = wrapBSONObject(o, own); + return p; + } + + Handle<v8::Value> V8Scope::mongoToV8Element( const BSONElement &f, bool readOnly ) { +// Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); +// internalFieldObjects->SetInternalFieldCount( 1 ); + + 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: +// return v8::String::NewExternal( new ExternalString( f.valuestr() )); + return v8::String::New( f.valuestr() ); +// return getV8Str( f.valuestr() ); + + case mongo::jstOID: + return newId( f.__oid() ); + + case mongo::NumberDouble: + case mongo::NumberInt: + return v8::Number::New( f.number() ); + + case mongo::Array: + // for arrays it's better to use non lazy object because: + // - the lazy array is not a true v8 array and requires some v8 src change for all methods to work + // - it made several tests about 1.5x slower + // - most times when an array is accessed, all its values will be used + return mongoToV8( f.embeddedObject() , true, readOnly ); + case mongo::Object: + return mongoToLZV8( f.embeddedObject() , false, readOnly); + + case mongo::Date: + return v8::Date::New( (double) ((long long)f.date().millis) ); + + case mongo::Bool: + return v8::Boolean::New( f.boolean() ); + + 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 ); + argv[1] = v8::Number::New( f.binDataType() ); + argv[2] = v8::String::New( data, len ); + return binData->NewInstance( 3, argv ); + }; + + case mongo::Timestamp: { + Local<v8::Object> sub = internalFieldObjects->NewInstance(); + + sub->Set( V8STR_T , v8::Number::New( f.timestampTime() ) ); + sub->Set( V8STR_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]; + argv[0] = v8::Number::New( (double)(long long)( val ) ); + return numberLong->NewInstance( 1, argv ); + } + 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) ); + return numberLong->NewInstance( 3, argv ); + } + } + +// case mongo::NumberInt: { +// Local<v8::Object> sub = internalFieldObjects->NewInstance(); +// int val = f.numberInt(); +// v8::Function* numberInt = getNamedCons( "NumberInt" ); +// v8::Handle<v8::Value> argv[1]; +// argv[0] = v8::Int32::New(val); +// return numberInt->NewInstance( 1, argv ); +// } + + case mongo::MinKey: { + Local<v8::Object> sub = internalFieldObjects->NewInstance(); + sub->Set( V8STR_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( V8STR_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]; + argv[0] = getV8Str( f.dbrefNS() ); + argv[1] = newId( f.dbrefOID() ); + return dbPointer->NewInstance(2, argv); + } + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + return v8::Undefined(); + } + + void V8Scope::append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ) { + V8_SIMPLE_HEADER + Handle<v8::String> v8name = getV8Str(scopeName); + Handle<Value> value = _global->Get( v8name ); + v8ToMongoElement(builder, v8name, fieldName, value); + } + + void V8Scope::v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value , int depth, BSONObj* originalParent ) { + + if ( value->IsString() ) { +// Handle<v8::String> str = Handle<v8::String>::Cast(value); +// ExternalString* es = (ExternalString*) (str->GetExternalAsciiStringResource()); +// b.append( sname , es->data() ); + b.append( sname , toSTLString( value ).c_str() ); + return; + } + + if ( value->IsFunction() ) { + b.appendCode( sname , toSTLString( value ) ); + return; + } + + if ( value->IsNumber() ) { + double val = value->ToNumber()->Value(); + // if previous type was integer, keep it + int intval = (int)val; + if (val == intval && originalParent) { + BSONElement elmt = originalParent->getField(sname); + if (elmt.type() == mongo::NumberInt) { + b.append( sname , intval ); + return; + } + } + + b.append( sname , val ); + return; + } + + if ( value->IsArray() ) { + BSONObj sub = v8ToMongo( value->ToObject() , depth ); + b.appendArray( sname , sub ); + return; + } + + if ( value->IsDate() ) { + long long dateval = (long long)(v8::Date::Cast( *value )->NumberValue()); + b.appendDate( sname , Date_t( (unsigned long long) dateval ) ); + return; + } + + if ( value->IsExternal() ) + return; + + 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( V8STR_T )->ToNumber()->Value() )), + obj->Get( V8STR_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] == '/' ) { + s = s.substr( 1 ); + string r = s.substr( 0 , s.rfind( "/" ) ); + string o = s.substr( s.rfind( "/" ) + 1 ); + b.appendRegex( sname , r , o ); + } + else if ( value->ToObject()->GetPrototype()->IsObject() && + value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( V8STR_ISOBJECTID ) ) { + OID oid; + oid.init( toSTLString( value ) ); + b.appendOID( sname , &oid ); + } + else if ( !value->ToObject()->GetHiddenValue( V8STR_NUMBERLONG ).IsEmpty() ) { + // TODO might be nice to potentially speed this up with an indexed internal + // field, but I don't yet know how to use an ObjectTemplate with a + // constructor. + v8::Handle< v8::Object > it = value->ToObject(); + long long val; + if ( !it->Has( getV8Str( "top" ) ) ) { + val = (long long)( it->Get( getV8Str( "floatApprox" ) )->NumberValue() ); + } + else { + val = (long long) + ( (unsigned long long)( it->Get( getV8Str( "top" ) )->ToInt32()->Value() ) << 32 ) + + (unsigned)( it->Get( getV8Str( "bottom" ) )->ToInt32()->Value() ); + } + + b.append( sname, val ); + } + else if ( !value->ToObject()->GetHiddenValue( V8STR_NUMBERINT ).IsEmpty() ) { + v8::Handle< v8::Object > it = value->ToObject(); + b.append(sname, it->GetHiddenValue(V8STR_NUMBERINT)->Int32Value()); + } + else if ( !value->ToObject()->GetHiddenValue( V8STR_DBPTR ).IsEmpty() ) { + OID oid; + oid.init( toSTLString( value->ToObject()->Get( getV8Str( "id" ) ) ) ); + string ns = toSTLString( value->ToObject()->Get( getV8Str( "ns" ) ) ); + b.appendDBRef( sname, ns, oid ); + } + else if ( !value->ToObject()->GetHiddenValue( V8STR_BINDATA ).IsEmpty() ) { + int len = obj->Get( getV8Str( "len" ) )->ToInt32()->Value(); + Local<External> c = External::Cast( *(obj->GetInternalField( 0 )) ); + const char* dataArray = (char*)(c->Value());; + b.appendBinData( sname, + len, + mongo::BinDataType( obj->Get( getV8Str( "type" ) )->ToInt32()->Value() ), + dataArray ); + } + else { + BSONObj sub = v8ToMongo( value->ToObject() , depth ); + b.append( sname , sub ); + } + return; + } + + if ( value->IsBoolean() ) { + b.appendBool( sname , value->ToBoolean()->Value() ); + return; + } + + else if ( value->IsUndefined() ) { + b.appendUndefined( sname ); + return; + } + + else if ( value->IsNull() ) { + b.appendNull( sname ); + return; + } + + cout << "don't know how to convert to mongo field [" << name << "]\t" << value << endl; + } + + BSONObj V8Scope::v8ToMongo( v8::Handle<v8::Object> o , int depth ) { + BSONObj* originalBSON = 0; + if (o->HasNamedLookupInterceptor()) { + originalBSON = unwrapBSONObj(o); + } + + if ( !o->GetHiddenValue( V8STR_RO ).IsEmpty() || + (o->HasNamedLookupInterceptor() && o->GetHiddenValue( V8STR_MODIFIED ).IsEmpty()) ) { + // object was readonly, use bson as is + if (originalBSON) + return *originalBSON; + } + + BSONObjBuilder b; + + if ( depth == 0 ) { + if ( o->HasRealNamedProperty( V8STR_ID ) ) { + v8ToMongoElement( b , V8STR_ID , "_id" , o->Get( V8STR_ID ), 0, originalBSON ); + } + } + + Local<v8::Array> names = o->GetPropertyNames(); + for ( unsigned int i=0; i<names->Length(); i++ ) { + v8::Local<v8::String> name = names->Get( i )->ToString(); + +// if ( o->GetPrototype()->IsObject() && +// o->GetPrototype()->ToObject()->HasRealNamedProperty( name ) ) +// continue; + + v8::Local<v8::Value> value = o->Get( name ); + + const string sname = toSTLString( name ); + if ( depth == 0 && sname == "_id" ) + continue; + + v8ToMongoElement( b , name , sname , value , depth + 1, originalBSON ); + } + return b.obj(); + } + + // --- random utils ---- + + v8::Function * V8Scope::getNamedCons( const char * name ) { + return v8::Function::Cast( *(v8::Context::GetCurrent()->Global()->Get( getV8Str( name ) ) ) ); + } + + v8::Function * V8Scope::getObjectIdCons() { + return getNamedCons( "ObjectId" ); + } + + Handle<v8::Value> V8Scope::Print(V8Scope* scope, const Arguments& args) { + bool first = true; + for (int i = 0; i < args.Length(); i++) { + HandleScope handle_scope; + if (first) { + first = false; + } + else { + printf(" "); + } + v8::String::Utf8Value str(args[i]); + printf("%s", *str); + } + printf("\n"); + return v8::Undefined(); + } + + Handle<v8::Value> V8Scope::Version(V8Scope* scope, const Arguments& args) { + HandleScope handle_scope; + return handle_scope.Close( v8::String::New(v8::V8::GetVersion()) ); + } + + Handle<v8::Value> V8Scope::GCV8(V8Scope* scope, const Arguments& args) { + V8Lock l; + while( !V8::IdleNotification() ); + return v8::Undefined(); + } + + /** + * Gets a V8 strings from the scope's cache, creating one if needed + */ + v8::Handle<v8::String> V8Scope::getV8Str(string str) { + Persistent<v8::String> ptr = _strCache[str]; + if (ptr.IsEmpty()) { + ptr = Persistent<v8::String>::New(v8::String::New(str.c_str())); + _strCache[str] = ptr; +// cout << "Adding str " + str << endl; + } +// cout << "Returning str " + str << endl; + return ptr; + } + } // namespace mongo diff --git a/scripting/engine_v8.h b/scripting/engine_v8.h index c770955..3f116c9 100644 --- a/scripting/engine_v8.h +++ b/scripting/engine_v8.h @@ -19,7 +19,6 @@ #include <vector> #include "engine.h" -#include "v8_db.h" #include <v8.h> using namespace v8; @@ -27,6 +26,36 @@ using namespace v8; namespace mongo { class V8ScriptEngine; + class V8Scope; + + typedef Handle< Value > (*v8Function) ( V8Scope* scope, const v8::Arguments& args ); + + // 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; + }; class V8Scope : public Scope { public: @@ -47,6 +76,7 @@ namespace mongo { virtual string getString( const char *field ); virtual bool getBoolean( const char *field ); virtual BSONObj getObject( const char *field ); + Handle<v8::Object> getGlobalObject() { return _global; }; virtual int type( const char *field ); @@ -55,28 +85,82 @@ namespace mongo { virtual void setBoolean( const char *field , bool val ); 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 setFunction( const char *field , const char * code ); +// 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 int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool readOnlyArgs = false, bool readOnlyRecv = false ); 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 ); + virtual void injectNative( const char *field, NativeFunction func, void* data = 0 ); + void injectNative( const char *field, NativeFunction func, Handle<v8::Object>& obj, void* data = 0 ); + void injectV8Function( const char *field, v8Function func ); + void injectV8Function( const char *field, v8Function func, Handle<v8::Object>& obj ); + void injectV8Function( const char *field, v8Function func, Handle<v8::Template>& t ); + Handle<v8::FunctionTemplate> createV8Function( v8Function func ); void gc(); Handle< Context > context() const { return _context; } + v8::Local<v8::Object> mongoToV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false ); + v8::Handle<v8::Object> mongoToLZV8( 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 , + const string sname , v8::Handle<v8::Value> value , int depth = 0, BSONObj* originalParent=0 ); + v8::Handle<v8::Value> mongoToV8Element( const BSONElement &f, bool readOnly = false ); + virtual void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ); + + v8::Function * getNamedCons( const char * name ); + v8::Function * getObjectIdCons(); + Local< v8::Value > newId( const OID &id ); + + Persistent<v8::Object> wrapBSONObject(Local<v8::Object> obj, BSONObj* data); + Persistent<v8::Object> wrapArrayObject(Local<v8::Object> obj, char* data); + + v8::Handle<v8::String> getV8Str(string str); +// inline v8::Handle<v8::String> getV8Str(string str) { return v8::String::New(str.c_str()); } + inline v8::Handle<v8::String> getLocalV8Str(string str) { return v8::String::New(str.c_str()); } + + Handle<v8::String> V8STR_CONN; + Handle<v8::String> V8STR_ID; + Handle<v8::String> V8STR_LENGTH; + Handle<v8::String> V8STR_LEN; + Handle<v8::String> V8STR_TYPE; + Handle<v8::String> V8STR_ISOBJECTID; + Handle<v8::String> V8STR_NATIVE_FUNC; + Handle<v8::String> V8STR_NATIVE_DATA; + Handle<v8::String> V8STR_V8_FUNC; + Handle<v8::String> V8STR_RETURN; + Handle<v8::String> V8STR_ARGS; + Handle<v8::String> V8STR_T; + Handle<v8::String> V8STR_I; + Handle<v8::String> V8STR_EMPTY; + Handle<v8::String> V8STR_MINKEY; + Handle<v8::String> V8STR_MAXKEY; + Handle<v8::String> V8STR_NUMBERLONG; + Handle<v8::String> V8STR_NUMBERINT; + Handle<v8::String> V8STR_DBPTR; + Handle<v8::String> V8STR_BINDATA; + Handle<v8::String> V8STR_WRAPPER; + Handle<v8::String> V8STR_RO; + Handle<v8::String> V8STR_MODIFIED; + private: void _startCall(); - static Handle< Value > nativeCallback( const Arguments &args ); + static Handle< Value > nativeCallback( V8Scope* scope, const Arguments &args ); + static v8::Handle< v8::Value > v8Callback( const v8::Arguments &args ); + static Handle< Value > load( V8Scope* scope, const Arguments &args ); + static Handle< Value > Print(V8Scope* scope, const v8::Arguments& args); + static Handle< Value > Version(V8Scope* scope, const v8::Arguments& args); + static Handle< Value > GCV8(V8Scope* scope, const v8::Arguments& args); - static Handle< Value > loadCallback( const Arguments &args ); V8ScriptEngine * _engine; @@ -85,12 +169,19 @@ namespace mongo { string _error; vector< Persistent<Value> > _funcs; - v8::Persistent<v8::Object> _this; + v8::Persistent<v8::Object> _emptyObj; v8::Persistent<v8::Function> _wrapper; enum ConnectState { NOT , LOCAL , EXTERNAL }; ConnectState _connectState; + + std::map <string, v8::Persistent <v8::String> > _strCache; + + Persistent<v8::ObjectTemplate> lzObjectTemplate; + Persistent<v8::ObjectTemplate> roObjectTemplate; + Persistent<v8::ObjectTemplate> lzArrayTemplate; + Persistent<v8::ObjectTemplate> internalFieldObjects; }; class V8ScriptEngine : public ScriptEngine { @@ -117,7 +208,24 @@ namespace mongo { friend class V8Scope; }; + class ExternalString : public v8::String::ExternalAsciiStringResource { + public: + ExternalString(std::string str) : _data(str) { + } + + ~ExternalString() { + } + + const char* data () const { return _data.c_str(); } + size_t length () const { return _data.length(); } + private: +// string _str; +// const char* _data; + std::string _data; +// size_t _len; + }; extern ScriptEngine * globalScriptEngine; extern map< unsigned, int > __interruptSpecToThreadId; + } diff --git a/scripting/sm_db.cpp b/scripting/sm_db.cpp index 4c9d541..2a9169b 100644 --- a/scripting/sm_db.cpp +++ b/scripting/sm_db.cpp @@ -192,7 +192,15 @@ namespace mongo { return JS_FALSE; } - ScriptEngine::runConnectCallback( *conn ); + try{ + ScriptEngine::runConnectCallback( *conn ); + } + catch( std::exception& e ){ + // Can happen if connection goes down while we're starting up here + // Catch so that we don't get a hard-to-trace segfault from SM + JS_ReportError( cx, ((string)( str::stream() << "Error during mongo startup." << causedBy( e ) )).c_str() ); + return JS_FALSE; + } assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( conn ) ) ) ); jsval host_val = c.toval( host.c_str() ); @@ -607,6 +615,7 @@ namespace mongo { // UUID ************************** +#if 0 JSBool uuid_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { Convertor c( cx ); @@ -631,6 +640,8 @@ namespace mongo { buf[i] = fromHex(encoded.c_str() + i * 2); } +zzz + assert( JS_SetPrivate( cx, obj, new BinDataHolder( buf, 16 ) ) ); c.setProperty( obj, "len", c.toval( (double)16 ) ); c.setProperty( obj, "type", c.toval( (double)3 ) ); @@ -676,6 +687,8 @@ namespace mongo { { 0 } }; +#endif + // BinData ************************** JSBool bindata_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { @@ -744,27 +757,17 @@ namespace mongo { assert( holder ); const char *data = ( ( BinDataHolder* )( holder ) )->c_; stringstream ss; - ss << hex; + ss.setf (ios_base::hex , ios_base::basefield); + ss.fill ('0'); + ss.setf (ios_base::right , ios_base::adjustfield); for( int i = 0; i < len; i++ ) { unsigned v = (unsigned char) data[i]; - ss << v; + ss << setw(2) << v; } string ret = ss.str(); return *rval = c.toval( ret.c_str() ); } - 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) { - Convertor c(cx); - int t = (int)c.getNumber( obj, "type" ); - return *rval = c.toval((double) t); - } - void bindata_finalize( JSContext * cx , JSObject * obj ) { Convertor c(cx); void *holder = JS_GetPrivate( cx, obj ); @@ -785,8 +788,6 @@ namespace mongo { { "toString" , bindata_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , { "hex", bindataAsHex, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } , { "base64", bindataBase64, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } , - { "length", bindataLength, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } , - { "subtype", bindataSubtype, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } , { 0 } }; @@ -937,6 +938,79 @@ namespace mongo { { 0 } }; + JSClass numberint_class = { + "NumberInt" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool numberint_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx , "NumberInt needs 0 or 1 args" , argc == 0 || argc == 1 ); + + if ( ! JS_InstanceOf( cx , obj , &numberint_class , 0 ) ) { + obj = JS_NewObject( cx , &numberint_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "numberint_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 ] ) ) { + c.setProperty( obj, "floatApprox", argv[ 0 ] ); + } + else { + string num = c.toString( argv[ 0 ] ); + //PRINT(num); + const char *numStr = num.c_str(); + int n; + try { + n = (int) parseLL( numStr ); + //PRINT(n); + } + catch ( const AssertionException & ) { + smuassert( cx , "could not convert string to integer" , false ); + } + c.makeIntObj( n, obj ); + } + + return JS_TRUE; + } + + JSBool numberint_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + return *rval = c.toval( double( c.toNumberInt( obj ) ) ); + } + + JSBool numberint_tonumber(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + return numberint_valueof( cx, obj, argc, argv, rval ); + } + + JSBool numberint_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + int val = c.toNumberInt( obj ); + string ret = str::stream() << "NumberInt(" << val << ")"; + return *rval = c.toval( ret.c_str() ); + } + + JSBool numberint_tojson(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + int val = c.toNumberInt( obj ); + string ret = str::stream() << val; + return *rval = c.toval( ret.c_str() ); + } + + + JSFunctionSpec numberint_functions[] = { + { "valueOf" , numberint_valueof , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toNumber" , numberint_tonumber , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toString" , numberint_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "tojson" , numberint_tojson , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + JSClass minkey_class = { "MinKey" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, @@ -1039,10 +1113,11 @@ namespace mongo { assert( JS_InitClass( cx , global , 0 , &dbquery_class , dbquery_constructor , 0 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &dbpointer_class , dbpointer_constructor , 0 , 0 , dbpointer_functions , 0 , 0 ) ); 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 , &uuid_class , uuid_constructor , 0 , 0 , uuid_functions , 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 , &numberint_class , numberint_constructor , 0 , 0 , numberint_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 ) ); @@ -1087,6 +1162,11 @@ namespace mongo { return true; } + if ( JS_InstanceOf( c->_context , o , &numberint_class , 0 ) ) { + b.append( name , c->toNumberInt( o ) ); + return true; + } + if ( JS_InstanceOf( c->_context , o , &dbpointer_class , 0 ) ) { b.appendDBRef( name , c->getString( o , "ns" ) , c->toOID( c->getProperty( o , "id" ) ) ); return true; @@ -1120,8 +1200,8 @@ namespace mongo { #else 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) ); + long long d2 = (long long)d; + b.appendDate( name , Date_t((unsigned long long)d2) ); return true; } #endif diff --git a/scripting/utils.cpp b/scripting/utils.cpp index 97eea10..612b173 100644 --- a/scripting/utils.cpp +++ b/scripting/utils.cpp @@ -25,7 +25,7 @@ namespace mongo { void installBenchmarkSystem( Scope& scope ); - BSONObj jsmd5( const BSONObj &a ) { + BSONObj jsmd5( const BSONObj &a, void* data ) { uassert( 10261 , "js md5 needs a string" , a.firstElement().type() == String ); const char * s = a.firstElement().valuestrsafe(); @@ -38,7 +38,7 @@ namespace mongo { return BSON( "" << digestToString( d ) ); } - BSONObj JSVersion( const BSONObj& args ) { + BSONObj JSVersion( const BSONObj& args, void* data ) { cout << "version: " << versionString << endl; if ( strstr( versionString , "+" ) ) printGitVersion(); @@ -46,6 +46,20 @@ namespace mongo { } + BSONObj JSSleep(const mongo::BSONObj &args, void* data) { + assert( args.nFields() == 1 ); + assert( args.firstElement().isNumber() ); + int ms = int( args.firstElement().number() ); + { + auto_ptr< ScriptEngine::Unlocker > u = globalScriptEngine->newThreadUnlocker(); + sleepmillis( ms ); + } + + BSONObjBuilder b; + b.appendUndefined( "" ); + return b.obj(); + } + // --------------------------------- // ---- installer -------- // --------------------------------- @@ -53,6 +67,7 @@ namespace mongo { void installGlobalUtils( Scope& scope ) { scope.injectNative( "hex_md5" , jsmd5 ); scope.injectNative( "version" , JSVersion ); + scope.injectNative( "sleep" , JSSleep ); installBenchmarkSystem( scope ); } diff --git a/scripting/v8_db.cpp b/scripting/v8_db.cpp index 4d12454..bda549c 100644 --- a/scripting/v8_db.cpp +++ b/scripting/v8_db.cpp @@ -15,10 +15,18 @@ * limitations under the License. */ +#if defined(_WIN32) +/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with + our usage of boost */ +#include "boost/cstdint.hpp" +using namespace boost; +#define V8STDINT_H_ +#endif + #include "v8_wrapper.h" #include "v8_utils.h" -#include "v8_db.h" #include "engine_v8.h" +#include "v8_db.h" #include "util/base64.h" #include "util/text.h" #include "../client/syncclusterconnection.h" @@ -32,127 +40,161 @@ namespace mongo { #define DDD(x) - v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( bool local ) { - v8::Local<v8::FunctionTemplate> mongo; + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( V8Scope* scope, bool local ) { + v8::Handle<v8::FunctionTemplate> mongo; if ( local ) { - mongo = newV8Function< mongoConsLocal >(); + mongo = scope->createV8Function(mongoConsLocal); } else { - mongo = newV8Function< mongoConsExternal >(); + mongo = scope->createV8Function(mongoConsExternal); } mongo->InstanceTemplate()->SetInternalFieldCount( 1 ); + v8::Handle<v8::Template> proto = mongo->PrototypeTemplate(); + scope->injectV8Function("find", mongoFind, proto); + scope->injectV8Function("insert", mongoInsert, proto); + scope->injectV8Function("remove", mongoRemove, proto); + scope->injectV8Function("update", mongoUpdate, proto); - v8::Local<v8::Template> proto = mongo->PrototypeTemplate(); - - 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 = newV8Function< internalCursorCons >(); + v8::Handle<FunctionTemplate> ic = scope->createV8Function(internalCursorCons); ic->InstanceTemplate()->SetInternalFieldCount( 1 ); - 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 ); - - + v8::Handle<v8::Template> icproto = ic->PrototypeTemplate(); + scope->injectV8Function("next", internalCursorNext, icproto); + scope->injectV8Function("hasNext", internalCursorHasNext, icproto); + scope->injectV8Function("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto); + proto->Set( scope->getV8Str( "internalCursor" ) , ic ); return mongo; } - v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate() { - v8::Local<v8::FunctionTemplate> numberLong = newV8Function< numberLongInit >(); + v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> numberLong = scope->createV8Function(numberLongInit); v8::Local<v8::Template> proto = numberLong->PrototypeTemplate(); - - proto->Set( v8::String::New( "valueOf" ) , newV8Function< numberLongValueOf >() ); - proto->Set( v8::String::New( "toNumber" ) , newV8Function< numberLongToNumber >() ); - proto->Set( v8::String::New( "toString" ) , newV8Function< numberLongToString >() ); + scope->injectV8Function("valueOf", numberLongValueOf, proto); + scope->injectV8Function("toNumber", numberLongToNumber, proto); + scope->injectV8Function("toString", numberLongToString, proto); return numberLong; } - v8::Handle<v8::FunctionTemplate> getBinDataFunctionTemplate() { - v8::Local<v8::FunctionTemplate> binData = newV8Function< binDataInit >(); - v8::Local<v8::Template> proto = binData->PrototypeTemplate(); + v8::Handle<v8::FunctionTemplate> getNumberIntFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> numberInt = scope->createV8Function(numberIntInit); + v8::Local<v8::Template> proto = numberInt->PrototypeTemplate(); + scope->injectV8Function("valueOf", numberIntValueOf, proto); + scope->injectV8Function("toNumber", numberIntToNumber, proto); + scope->injectV8Function("toString", numberIntToString, proto); - proto->Set( v8::String::New( "toString" ) , newV8Function< binDataToString >() ); + return numberInt; + } + v8::Handle<v8::FunctionTemplate> getBinDataFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> binData = scope->createV8Function(binDataInit); + binData->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local<v8::Template> proto = binData->PrototypeTemplate(); + scope->injectV8Function("toString", binDataToString, proto); + scope->injectV8Function("base64", binDataToBase64, proto); + scope->injectV8Function("hex", binDataToHex, proto); return binData; } - 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; + v8::Handle<v8::FunctionTemplate> getUUIDFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(uuidInit); + templ->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local<v8::Template> proto = templ->PrototypeTemplate(); + scope->injectV8Function("toString", binDataToString, proto); + scope->injectV8Function("base64", binDataToBase64, proto); + scope->injectV8Function("hex", binDataToHex, proto); + return templ; } + v8::Handle<v8::FunctionTemplate> getMD5FunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(md5Init); + templ->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local<v8::Template> proto = templ->PrototypeTemplate(); + scope->injectV8Function("toString", binDataToString, proto); + scope->injectV8Function("base64", binDataToBase64, proto); + scope->injectV8Function("hex", binDataToHex, proto); + return templ; + } - 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 = newV8Function< collectionInit >(); - dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); - global->Set(v8::String::New("DBCollection") , dbCollection ); - - - v8::Local<v8::FunctionTemplate> dbQuery = newV8Function< dbQueryInit >(); - dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); - global->Set(v8::String::New("DBQuery") , dbQuery ); - - global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >() ); - - global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >() ); - - global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >() ); - - global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate() ); + v8::Handle<v8::FunctionTemplate> getHexDataFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(hexDataInit); + templ->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local<v8::Template> proto = templ->PrototypeTemplate(); + scope->injectV8Function("toString", binDataToString, proto); + scope->injectV8Function("base64", binDataToBase64, proto); + scope->injectV8Function("hex", binDataToHex, proto); + return templ; + } - global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate() ); + v8::Handle<v8::FunctionTemplate> getTimestampFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> ts = scope->createV8Function(dbTimestampInit); + v8::Local<v8::Template> proto = ts->PrototypeTemplate(); + ts->InstanceTemplate()->SetInternalFieldCount( 1 ); - global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate() ); + return ts; } - void installDBTypes( Handle<v8::Object>& global ) { - v8::Local<v8::FunctionTemplate> db = newV8Function< dbInit >(); +// void installDBTypes( V8Scope* scope, Handle<ObjectTemplate>& global ) { +// v8::Handle<v8::FunctionTemplate> db = scope->createV8Function(dbInit); +// db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); +// global->Set(v8::String::New("DB") , db ); +// +// v8::Handle<v8::FunctionTemplate> dbCollection = scope->createV8Function(collectionInit); +// dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); +// global->Set(v8::String::New("DBCollection") , dbCollection ); +// +// +// v8::Handle<v8::FunctionTemplate> dbQuery = scope->createV8Function(dbQueryInit); +// dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); +// global->Set(v8::String::New("DBQuery") , dbQuery ); +// +// global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >(scope) ); +// +// global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >(scope) ); +// +// global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >(scope) ); +// +// global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate(scope) ); +// +// global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate(scope) ); +// +// global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate(scope) ); +// } + + void installDBTypes( V8Scope* scope, v8::Handle<v8::Object>& global ) { + v8::Handle<v8::FunctionTemplate> db = scope->createV8Function(dbInit); db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); - global->Set(v8::String::New("DB") , db->GetFunction() ); - - v8::Local<v8::FunctionTemplate> dbCollection = newV8Function< collectionInit >(); + global->Set(scope->getV8Str("DB") , db->GetFunction() ); + v8::Handle<v8::FunctionTemplate> dbCollection = scope->createV8Function(collectionInit); dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); - global->Set(v8::String::New("DBCollection") , dbCollection->GetFunction() ); + global->Set(scope->getV8Str("DBCollection") , dbCollection->GetFunction() ); - v8::Local<v8::FunctionTemplate> dbQuery = newV8Function< dbQueryInit >(); + v8::Handle<v8::FunctionTemplate> dbQuery = scope->createV8Function(dbQueryInit); dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); - global->Set(v8::String::New("DBQuery") , dbQuery->GetFunction() ); - - global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >()->GetFunction() ); - - global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >()->GetFunction() ); + global->Set(scope->getV8Str("DBQuery") , dbQuery->GetFunction() ); - global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >()->GetFunction() ); + scope->injectV8Function("ObjectId", objectIdInit, global); + scope->injectV8Function("DBRef", dbRefInit, global); + scope->injectV8Function("DBPointer", dbPointerInit, global); - global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate()->GetFunction() ); - - global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate()->GetFunction() ); - - global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate()->GetFunction() ); + global->Set( scope->getV8Str("BinData") , getBinDataFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("UUID") , getUUIDFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("MD5") , getMD5FunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("HexData") , getHexDataFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("NumberLong") , getNumberLongFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("NumberInt") , getNumberIntFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("Timestamp") , getTimestampFunctionTemplate(scope)->GetFunction() ); BSONObjBuilder b; b.appendMaxKey( "" ); b.appendMinKey( "" ); BSONObj o = b.obj(); BSONObjIterator i( o ); - global->Set( v8::String::New("MaxKey"), mongoToV8Element( i.next() ) ); - global->Set( v8::String::New("MinKey"), mongoToV8Element( i.next() ) ); + global->Set( scope->getV8Str("MaxKey"), scope->mongoToV8Element( i.next() ) ); + global->Set( scope->getV8Str("MinKey"), scope->mongoToV8Element( i.next() ) ); - global->Get( v8::String::New( "Object" ) )->ToObject()->Set( v8::String::New("bsonsize") , newV8Function< bsonsize >()->GetFunction() ); + global->Get( scope->getV8Str( "Object" ) )->ToObject()->Set( scope->getV8Str("bsonsize") , scope->createV8Function(bsonsize)->GetFunction() ); } void destroyConnection( Persistent<Value> self, void* parameter) { @@ -161,7 +203,7 @@ namespace mongo { self.Clear(); } - Handle<Value> mongoConsExternal(const Arguments& args) { + Handle<Value> mongoConsExternal(V8Scope* scope, const Arguments& args) { char host[255]; @@ -196,13 +238,13 @@ 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( host ) ); + args.This()->Set( scope->getV8Str( "slaveOk" ) , Boolean::New( false ) ); + args.This()->Set( scope->getV8Str( "host" ) , scope->getV8Str( host ) ); return v8::Undefined(); } - Handle<Value> mongoConsLocal(const Arguments& args) { + Handle<Value> mongoConsLocal(V8Scope* scope, const Arguments& args) { if ( args.Length() > 0 ) return v8::ThrowException( v8::String::New( "local Mongo constructor takes no args" ) ); @@ -218,8 +260,8 @@ namespace mongo { // NOTE I don't believe the conn object will ever be freed. 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" ) ); + args.This()->Set( scope->getV8Str( "slaveOk" ) , Boolean::New( false ) ); + args.This()->Set( scope->getV8Str( "host" ) , scope->getV8Str( "EMBEDDED" ) ); return v8::Undefined(); } @@ -255,7 +297,7 @@ namespace mongo { 3 - limit 4 - skip */ - Handle<Value> mongoFind(const Arguments& args) { + Handle<Value> mongoFind(V8Scope* scope, const Arguments& args) { HandleScope handle_scope; jsassert( args.Length() == 7 , "find needs 7 args" ); @@ -263,16 +305,16 @@ namespace mongo { DBClientBase * conn = getConnection( args ); GETNS; - BSONObj q = v8ToMongo( args[1]->ToObject() ); + BSONObj q = scope->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() ); + fields = scope->v8ToMongo( args[2]->ToObject() ); Local<v8::Object> mongo = args.This(); - Local<v8::Value> slaveOkVal = mongo->Get( v8::String::New( "slaveOk" ) ); + Local<v8::Value> slaveOkVal = mongo->Get( scope->getV8Str( "slaveOk" ) ); jsassert( slaveOkVal->IsBoolean(), "slaveOk member invalid" ); bool slaveOk = slaveOkVal->BooleanValue(); @@ -285,8 +327,10 @@ namespace mongo { { V8Unlock u; cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, options | ( slaveOk ? QueryOption_SlaveOk : 0 ) , batchSize ); + if ( ! cursor.get() ) + return v8::ThrowException( v8::String::New( "error doing query: failed" ) ); } - v8::Function * cons = (v8::Function*)( *( mongo->Get( v8::String::New( "internalCursor" ) ) ) ); + v8::Function * cons = (v8::Function*)( *( mongo->Get( scope->getV8Str( "internalCursor" ) ) ) ); assert( cons ); Persistent<v8::Object> c = Persistent<v8::Object>::New( cons->NewInstance() ); @@ -300,11 +344,11 @@ namespace mongo { } } - v8::Handle<v8::Value> mongoInsert(const v8::Arguments& args) { + v8::Handle<v8::Value> mongoInsert(V8Scope* scope, 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() ) + if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() ) return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); DBClientBase * conn = getConnection( args ); @@ -312,12 +356,12 @@ namespace mongo { v8::Handle<v8::Object> in = args[1]->ToObject(); - if ( ! in->Has( v8::String::New( "_id" ) ) ) { + if ( ! in->Has( scope->getV8Str( "_id" ) ) ) { v8::Handle<v8::Value> argv[1]; - in->Set( v8::String::New( "_id" ) , getObjectIdCons()->NewInstance( 0 , argv ) ); + in->Set( scope->getV8Str( "_id" ) , scope->getObjectIdCons()->NewInstance( 0 , argv ) ); } - BSONObj o = v8ToMongo( in ); + BSONObj o = scope->v8ToMongo( in ); DDD( "want to save : " << o.jsonString() ); try { @@ -331,18 +375,18 @@ namespace mongo { return v8::Undefined(); } - v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args) { + v8::Handle<v8::Value> mongoRemove(V8Scope* scope, 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() ) + if ( args.This()->Get( scope->getV8Str( "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 ); + BSONObj o = scope->v8ToMongo( in ); bool justOne = false; if ( args.Length() > 2 ) { @@ -361,12 +405,12 @@ namespace mongo { return v8::Undefined(); } - v8::Handle<v8::Value> mongoUpdate(const v8::Arguments& args) { + v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, 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() ) + if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() ) return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); DBClientBase * conn = getConnection( args ); @@ -379,8 +423,8 @@ namespace mongo { bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); try { - BSONObj q1 = v8ToMongo( q ); - BSONObj o1 = v8ToMongo( o ); + BSONObj q1 = scope->v8ToMongo( q ); + BSONObj o1 = scope->v8ToMongo( o ); V8Unlock u; conn->update( ns , q1 , o1 , upsert, multi ); } @@ -403,11 +447,11 @@ namespace mongo { return cursor; } - v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args) { + v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args) { return v8::Undefined(); } - v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args) { + v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return v8::Undefined(); @@ -416,10 +460,13 @@ namespace mongo { V8Unlock u; o = cursor->next(); } - return mongoToV8( o ); + bool ro = false; + if (args.This()->Has(scope->V8STR_RO)) + ro = args.This()->Get(scope->V8STR_RO)->BooleanValue(); + return scope->mongoToLZV8( o, false, ro ); } - v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args) { + v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return Boolean::New( false ); @@ -431,7 +478,7 @@ namespace mongo { return Boolean::New( ret ); } - v8::Handle<v8::Value> internalCursorObjsLeftInBatch(const v8::Arguments& args) { + v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return v8::Number::New( (double) 0 ); @@ -443,14 +490,19 @@ namespace mongo { return v8::Number::New( (double) ret ); } +// v8::Handle<v8::Value> internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args) { +// Local<v8::Object> cursor = args.This(); +// cursor->Set(scope->V8STR_RO, v8::Undefined()); +// return cursor; +// } // --- DB ---- - v8::Handle<v8::Value> dbInit(const v8::Arguments& args) { + v8::Handle<v8::Value> dbInit(V8Scope* scope, const v8::Arguments& args) { assert( args.Length() == 2 ); - args.This()->Set( v8::String::New( "_mongo" ) , args[0] ); - args.This()->Set( v8::String::New( "_name" ) , args[1] ); + args.This()->Set( scope->getV8Str( "_mongo" ) , args[0] ); + args.This()->Set( scope->getV8Str( "_name" ) , args[1] ); for ( int i=0; i<args.Length(); i++ ) assert( ! args[i]->IsUndefined() ); @@ -458,13 +510,13 @@ namespace mongo { return v8::Undefined(); } - v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ) { + v8::Handle<v8::Value> collectionInit( V8Scope* scope, 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] ); + args.This()->Set( scope->getV8Str( "_mongo" ) , args[0] ); + args.This()->Set( scope->getV8Str( "_db" ) , args[1] ); + args.This()->Set( scope->getV8Str( "_shortName" ) , args[2] ); + args.This()->Set( scope->getV8Str( "_fullName" ) , args[3] ); if ( haveLocalShardingInfo( toSTLString( args[3] ) ) ) return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) ); @@ -475,52 +527,52 @@ namespace mongo { return v8::Undefined(); } - v8::Handle<v8::Value> dbQueryInit( const v8::Arguments& args ) { + v8::Handle<v8::Value> dbQueryInit( V8Scope* scope, 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] ); - t->Set( v8::String::New( "_ns" ) , args[3] ); + t->Set( scope->getV8Str( "_mongo" ) , args[0] ); + t->Set( scope->getV8Str( "_db" ) , args[1] ); + t->Set( scope->getV8Str( "_collection" ) , args[2] ); + t->Set( scope->getV8Str( "_ns" ) , args[3] ); if ( args.Length() > 4 && args[4]->IsObject() ) - t->Set( v8::String::New( "_query" ) , args[4] ); + t->Set( scope->getV8Str( "_query" ) , args[4] ); else - t->Set( v8::String::New( "_query" ) , v8::Object::New() ); + t->Set( scope->getV8Str( "_query" ) , v8::Object::New() ); if ( args.Length() > 5 && args[5]->IsObject() ) - t->Set( v8::String::New( "_fields" ) , args[5] ); + t->Set( scope->getV8Str( "_fields" ) , args[5] ); else - t->Set( v8::String::New( "_fields" ) , v8::Null() ); + t->Set( scope->getV8Str( "_fields" ) , v8::Null() ); if ( args.Length() > 6 && args[6]->IsNumber() ) - t->Set( v8::String::New( "_limit" ) , args[6] ); + t->Set( scope->getV8Str( "_limit" ) , args[6] ); else - t->Set( v8::String::New( "_limit" ) , Number::New( 0 ) ); + t->Set( scope->getV8Str( "_limit" ) , Number::New( 0 ) ); if ( args.Length() > 7 && args[7]->IsNumber() ) - t->Set( v8::String::New( "_skip" ) , args[7] ); + t->Set( scope->getV8Str( "_skip" ) , args[7] ); else - t->Set( v8::String::New( "_skip" ) , Number::New( 0 ) ); + t->Set( scope->getV8Str( "_skip" ) , Number::New( 0 ) ); if ( args.Length() > 8 && args[8]->IsNumber() ) - t->Set( v8::String::New( "_batchSize" ) , args[8] ); + t->Set( scope->getV8Str( "_batchSize" ) , args[8] ); else - t->Set( v8::String::New( "_batchSize" ) , Number::New( 0 ) ); + t->Set( scope->getV8Str( "_batchSize" ) , Number::New( 0 ) ); if ( args.Length() > 9 && args[9]->IsNumber() ) - t->Set( v8::String::New( "_options" ) , args[9] ); + t->Set( scope->getV8Str( "_options" ) , args[9] ); else - t->Set( v8::String::New( "_options" ) , Number::New( 0 ) ); + t->Set( scope->getV8Str( "_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) ); + t->Set( scope->getV8Str( "_cursor" ) , v8::Null() ); + t->Set( scope->getV8Str( "_numReturned" ) , v8::Number::New(0) ); + t->Set( scope->getV8Str( "_special" ) , Boolean::New(false) ); return v8::Undefined(); } @@ -560,11 +612,11 @@ namespace mongo { return f->Call( info.This() , 1 , argv ); } - v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ) { + v8::Handle<v8::Value> objectIdInit( V8Scope* scope, const v8::Arguments& args ) { v8::Handle<v8::Object> it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { - v8::Function * f = getObjectIdCons(); + v8::Function * f = scope->getObjectIdCons(); it = f->NewInstance(); } @@ -585,12 +637,12 @@ namespace mongo { oid.init( s ); } - it->Set( v8::String::New( "str" ) , v8::String::New( oid.str().c_str() ) ); + it->Set( scope->getV8Str( "str" ) , v8::String::New( oid.str().c_str() ) ); return it; } - v8::Handle<v8::Value> dbRefInit( const v8::Arguments& args ) { + v8::Handle<v8::Value> dbRefInit( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 2 && args.Length() != 0) { return v8::ThrowException( v8::String::New( "DBRef needs 2 arguments" ) ); @@ -599,19 +651,19 @@ namespace mongo { v8::Handle<v8::Object> it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { - v8::Function* f = getNamedCons( "DBRef" ); + v8::Function* f = scope->getNamedCons( "DBRef" ); it = f->NewInstance(); } if ( args.Length() == 2 ) { - it->Set( v8::String::New( "$ref" ) , args[0] ); - it->Set( v8::String::New( "$id" ) , args[1] ); + it->Set( scope->getV8Str( "$ref" ) , args[0] ); + it->Set( scope->getV8Str( "$id" ) , args[1] ); } return it; } - v8::Handle<v8::Value> dbPointerInit( const v8::Arguments& args ) { + v8::Handle<v8::Value> dbPointerInit( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 2) { return v8::ThrowException( v8::String::New( "DBPointer needs 2 arguments" ) ); @@ -620,28 +672,28 @@ namespace mongo { v8::Handle<v8::Object> it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { - v8::Function* f = getNamedCons( "DBPointer" ); + v8::Function* f = scope->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 ) ); + it->Set( scope->getV8Str( "ns" ) , args[0] ); + it->Set( scope->getV8Str( "id" ) , args[1] ); + it->SetHiddenValue( scope->getV8Str( "__DBPointer" ), v8::Number::New( 1 ) ); return it; } - v8::Handle<v8::Value> dbTimestampInit( const v8::Arguments& args ) { + v8::Handle<v8::Value> dbTimestampInit( V8Scope* scope, 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 ) ); + it->Set( scope->getV8Str( "t" ) , v8::Number::New( 0 ) ); + it->Set( scope->getV8Str( "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] ); + it->Set( scope->getV8Str( "t" ) , args[0] ); + it->Set( scope->getV8Str( "i" ) , args[1] ); } else { return v8::ThrowException( v8::String::New( "Timestamp needs 0 or 2 arguments" ) ); @@ -653,66 +705,157 @@ namespace mongo { } - v8::Handle<v8::Value> binDataInit( const v8::Arguments& args ) { - v8::Handle<v8::Object> it = args.This(); + v8::Handle<v8::Value> binDataInit( V8Scope* scope, const v8::Arguments& args ) { + v8::Local<v8::Object> it = args.This(); - // 3 args: len, type, data + Handle<Value> type; + Handle<Value> len; + int rlen; + char* data; if (args.Length() == 3) { + // 3 args: len, type, data if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { - v8::Function* f = getNamedCons( "BinData" ); + v8::Function* f = scope->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 + len = args[0]; + rlen = len->IntegerValue(); + type = args[1]; + v8::String::Utf8Value utf( args[ 2 ] ); + char* tmp = *utf; + data = new char[rlen]; + memcpy(data, tmp, rlen); } else if ( args.Length() == 2 ) { + // 2 args: type, base64 string if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { - v8::Function* f = getNamedCons( "BinData" ); + v8::Function* f = scope->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 ) ); - + type = args[0]; + v8::String::Utf8Value utf( args[ 1 ] ); + string decoded = base64::decode( *utf ); + const char* tmp = decoded.data(); + rlen = decoded.length(); + data = new char[rlen]; + memcpy(data, tmp, rlen); + len = v8::Number::New(rlen); +// it->Set( scope->getV8Str( "data" ), v8::String::New( decoded.data(), decoded.length() ) ); } else { - return v8::ThrowException( v8::String::New( "BinData needs 3 arguments" ) ); + return v8::ThrowException( v8::String::New( "BinData needs 2 or 3 arguments" ) ); } - return it; + it->Set( scope->getV8Str( "len" ) , len ); + it->Set( scope->getV8Str( "type" ) , type ); + it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) ); + Persistent<v8::Object> res = scope->wrapArrayObject(it, data); + return res; } - 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::Value> binDataToString( V8Scope* scope, const v8::Arguments& args ) { 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" ) ) ); + int len = it->Get( scope->V8STR_LEN )->Int32Value(); + int type = it->Get( scope->V8STR_TYPE )->Int32Value(); + Local<External> c = External::Cast( *(it->GetInternalField( 0 )) ); + char* data = (char*)(c->Value()); stringstream ss; ss << "BinData(" << type << ",\""; - base64::encode( ss, *data, len ); + base64::encode( ss, data, len ); ss << "\")"; string ret = ss.str(); return v8::String::New( ret.c_str() ); } - v8::Handle<v8::Value> numberLongInit( const v8::Arguments& args ) { + v8::Handle<v8::Value> binDataToBase64( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + int len = Handle<v8::Number>::Cast(it->Get(scope->V8STR_LEN))->Int32Value(); + Local<External> c = External::Cast( *(it->GetInternalField( 0 )) ); + char* data = (char*)(c->Value()); + stringstream ss; + base64::encode( ss, (const char *)data, len ); + return v8::String::New(ss.str().c_str()); + } + + v8::Handle<v8::Value> binDataToHex( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + int len = Handle<v8::Number>::Cast(it->Get(scope->V8STR_LEN))->Int32Value(); + Local<External> c = External::Cast( *(it->GetInternalField( 0 )) ); + char* data = (char*)(c->Value()); + stringstream ss; + ss.setf (ios_base::hex , ios_base::basefield); + ss.fill ('0'); + ss.setf (ios_base::right , ios_base::adjustfield); + for( int i = 0; i < len; i++ ) { + unsigned v = (unsigned char) data[i]; + ss << setw(2) << v; + } + return v8::String::New(ss.str().c_str()); + } + + static v8::Handle<v8::Value> hexToBinData( V8Scope* scope, v8::Local<v8::Object> it, int type, string hexstr ) { + int len = hexstr.length() / 2; + char* data = new char[len]; + const char* src = hexstr.c_str(); + for( int i = 0; i < 16; i++ ) { + data[i] = fromHex(src + i * 2); + } + + it->Set( scope->V8STR_LEN , v8::Number::New(len) ); + it->Set( scope->V8STR_TYPE , v8::Number::New(type) ); + it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) ); + Persistent<v8::Object> res = scope->wrapArrayObject(it, data); + return res; + } + + v8::Handle<v8::Value> uuidInit( V8Scope* scope, const v8::Arguments& args ) { + if (args.Length() != 1) { + return v8::ThrowException( v8::String::New( "UUIS needs 1 argument" ) ); + } + v8::String::Utf8Value utf( args[ 0 ] ); + if( utf.length() != 32 ) { + return v8::ThrowException( v8::String::New( "UUIS string must have 32 characters" ) ); + } + + return hexToBinData(scope, args.This(), bdtUUID, *utf); + } + +// v8::Handle<v8::Value> uuidToString( V8Scope* scope, const v8::Arguments& args ) { +// v8::Handle<v8::Object> it = args.This(); +// Local<External> c = External::Cast( *(it->GetInternalField( 0 )) ); +// char* data = (char*)(c->Value()); +// +// stringstream ss; +// ss << "UUID(\"" << toHex(data, 16) << "\")"; +// return v8::String::New( ss.str().c_str() ); +// } + + v8::Handle<v8::Value> md5Init( V8Scope* scope, const v8::Arguments& args ) { + if (args.Length() != 1) { + return v8::ThrowException( v8::String::New( "MD5 needs 1 argument" ) ); + } + v8::String::Utf8Value utf( args[ 0 ] ); + if( utf.length() != 32 ) { + return v8::ThrowException( v8::String::New( "MD5 string must have 32 characters" ) ); + } + + return hexToBinData(scope, args.This(), MD5Type, *utf); + } + + v8::Handle<v8::Value> hexDataInit( V8Scope* scope, const v8::Arguments& args ) { + if (args.Length() != 2) { + return v8::ThrowException( v8::String::New( "HexData needs 2 arguments" ) ); + } + v8::String::Utf8Value utf( args[ 1 ] ); + return hexToBinData(scope, args.This(), args[0]->IntegerValue(), *utf); + } + + v8::Handle<v8::Value> numberLongInit( V8Scope* scope, 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" ) ); @@ -721,16 +864,16 @@ namespace mongo { v8::Handle<v8::Object> it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { - v8::Function* f = getNamedCons( "NumberLong" ); + v8::Function* f = scope->getNamedCons( "NumberLong" ); it = f->NewInstance(); } if ( args.Length() == 0 ) { - it->Set( v8::String::New( "floatApprox" ), v8::Number::New( 0 ) ); + it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( 0 ) ); } else if ( args.Length() == 1 ) { if ( args[ 0 ]->IsNumber() ) { - it->Set( v8::String::New( "floatApprox" ), args[ 0 ] ); + it->Set( scope->getV8Str( "floatApprox" ), args[ 0 ] ); } else { v8::String::Utf8Value data( args[ 0 ] ); @@ -745,21 +888,21 @@ namespace mongo { } 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 ) ) ); + it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) ); } 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) ) ); + it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) ); + it->Set( scope->getV8Str( "top" ), v8::Integer::New( val >> 32 ) ); + it->Set( scope->getV8Str( "bottom" ), v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ) ); } } } 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->Set( scope->getV8Str( "floatApprox" ) , args[0] ); + it->Set( scope->getV8Str( "top" ) , args[1] ); + it->Set( scope->getV8Str( "bottom" ) , args[2] ); } - it->SetHiddenValue( v8::String::New( "__NumberLong" ), v8::Number::New( 1 ) ); + it->SetHiddenValue( scope->V8STR_NUMBERLONG, v8::Number::New( 1 ) ); return it; } @@ -773,29 +916,17 @@ namespace mongo { (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::Value> numberLongValueOf( V8Scope* scope, const v8::Arguments& args ) { v8::Handle<v8::Object> it = args.This(); - long long val = numberLongVal( it ); - return v8::Number::New( double( val ) ); } - v8::Handle<v8::Value> numberLongToNumber( const v8::Arguments& args ) { - return numberLongValueOf( args ); + v8::Handle<v8::Value> numberLongToNumber( V8Scope* scope, const v8::Arguments& args ) { + return numberLongValueOf( scope, args ); } - 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::Value> numberLongToString( V8Scope* scope, const v8::Arguments& args ) { v8::Handle<v8::Object> it = args.This(); stringstream ss; @@ -811,18 +942,62 @@ namespace mongo { return v8::String::New( ret.c_str() ); } - v8::Handle<v8::Value> bsonsize( const v8::Arguments& args ) { + v8::Handle<v8::Value> numberIntInit( V8Scope* scope, const v8::Arguments& args ) { + + if (args.Length() != 0 && args.Length() != 1) { + return v8::ThrowException( v8::String::New( "NumberInt needs 0, 1 argument" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { + v8::Function* f = scope->getNamedCons( "NumberInt" ); + it = f->NewInstance(); + } + + if ( args.Length() == 0 ) { + it->SetHiddenValue( scope->V8STR_NUMBERINT, v8::Number::New( 0 ) ); + } + else if ( args.Length() == 1 ) { + it->SetHiddenValue( scope->V8STR_NUMBERINT, args[0]->ToInt32() ); + } + + return it; + } + + v8::Handle<v8::Value> numberIntValueOf( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value(); + return v8::Number::New( double( val ) ); + } + + v8::Handle<v8::Value> numberIntToNumber( V8Scope* scope, const v8::Arguments& args ) { + return numberIntValueOf( scope, args ); + } + + v8::Handle<v8::Value> numberIntToString( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + + stringstream ss; + int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value(); + ss << "NumberInt(" << val << ")"; + + string ret = ss.str(); + return v8::String::New( ret.c_str() ); + } + + v8::Handle<v8::Value> bsonsize( V8Scope* scope, const v8::Arguments& args ) { if ( args.Length() != 1 ) - return v8::ThrowException( v8::String::New( "bonsisze needs 1 argument" ) ); + return v8::ThrowException( v8::String::New( "bsonsize 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::ThrowException( v8::String::New( "argument to bsonsize has to be an object" ) ); - return v8::Number::New( v8ToMongo( args[ 0 ]->ToObject() ).objsize() ); + return v8::Number::New( scope->v8ToMongo( args[ 0 ]->ToObject() ).objsize() ); } // to be called with v8 mutex diff --git a/scripting/v8_db.h b/scripting/v8_db.h index 7dbca92..08d15d0 100644 --- a/scripting/v8_db.h +++ b/scripting/v8_db.h @@ -22,129 +22,75 @@ #include <cstdio> #include <cstdlib> -#include "engine.h" +#include "engine_v8.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 ); + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( V8Scope * scope, bool local ); +// void installDBTypes( V8Scope * scope, v8::Handle<v8::ObjectTemplate>& global ); + void installDBTypes( V8Scope * scope, 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> mongoConsLocal(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoConsExternal(V8Scope* scope, 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> mongoFind(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, 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); + v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, 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> dbInit(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> collectionInit(V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> objectIdInit( V8Scope* scope, 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> dbRefInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> dbPointerInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> dbTimestampInit( V8Scope* scope, const v8::Arguments& args ); - v8::Handle<v8::Value> binDataInit( const v8::Arguments& args ); - v8::Handle<v8::Value> binDataToString( const v8::Arguments& args ); + v8::Handle<v8::Value> binDataInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> binDataToString( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> binDataToBase64( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> binDataToHex( V8Scope* scope, const v8::Arguments& args ); - v8::Handle<v8::Value> numberLongInit( const v8::Arguments& args ); - 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> uuidInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> md5Init( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> hexDataInit( V8Scope* scope, const v8::Arguments& args ); - v8::Handle<v8::Value> dbQueryInit( const v8::Arguments& args ); + v8::Handle<v8::Value> numberLongInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> numberLongToNumber(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> numberLongValueOf(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> numberLongToString(V8Scope* scope, const v8::Arguments& args); + + v8::Handle<v8::Value> numberIntInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> numberIntToNumber(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> numberIntValueOf(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> numberIntToString(V8Scope* scope, const v8::Arguments& args); + + v8::Handle<v8::Value> dbQueryInit( V8Scope* scope, 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 ); + v8::Handle<v8::Value> bsonsize( V8Scope* scope, 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 171ced5..0f575cf 100644 --- a/scripting/v8_utils.cpp +++ b/scripting/v8_utils.cpp @@ -15,14 +15,20 @@ * limitations under the License. */ +#if defined(_WIN32) +/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with + our usage of boost */ +#include "boost/cstdint.hpp" +using namespace boost; +#define V8STDINT_H_ +#endif + #include "v8_utils.h" #include "v8_db.h" #include <iostream> #include <map> #include <sstream> #include <vector> -#include <sys/socket.h> -#include <netinet/in.h> #include <boost/smart_ptr.hpp> #include <boost/thread/thread.hpp> #include <boost/thread/xtime.hpp> @@ -33,23 +39,6 @@ using namespace v8; namespace mongo { - Handle<v8::Value> Print(const Arguments& args) { - bool first = true; - for (int i = 0; i < args.Length(); i++) { - HandleScope handle_scope; - if (first) { - first = false; - } - else { - printf(" "); - } - v8::String::Utf8Value str(args[i]); - printf("%s", *str); - } - printf("\n"); - return v8::Undefined(); - } - std::string toSTLString( const Handle<v8::Value> & o ) { v8::String::Utf8Value str(o); const char * foo = *str; @@ -136,12 +125,6 @@ namespace mongo { return s; } - - Handle<v8::Value> Version(const Arguments& args) { - HandleScope handle_scope; - return handle_scope.Close( v8::String::New(v8::V8::GetVersion()) ); - } - void ReportException(v8::TryCatch* try_catch) { cout << try_catch << endl; } @@ -233,7 +216,7 @@ namespace mongo { Persistent< Value > returnData_; }; - Handle< Value > ThreadInit( const Arguments &args ) { + Handle< Value > ThreadInit( V8Scope* scope, const Arguments &args ) { Handle<v8::Object> it = args.This(); // NOTE I believe the passed JSThreadConfig will never be freed. If this // policy is changed, JSThread may no longer be able to store JSThreadConfig @@ -242,7 +225,7 @@ namespace mongo { return v8::Undefined(); } - Handle< Value > ScopedThreadInit( const Arguments &args ) { + Handle< Value > ScopedThreadInit( V8Scope* scope, const Arguments &args ) { Handle<v8::Object> it = args.This(); // NOTE I believe the passed JSThreadConfig will never be freed. If this // policy is changed, JSThread may no longer be able to store JSThreadConfig @@ -251,65 +234,58 @@ namespace mongo { return v8::Undefined(); } - JSThreadConfig *thisConfig( const Arguments &args ) { + JSThreadConfig *thisConfig( V8Scope* scope, const Arguments &args ) { Local< External > c = External::Cast( *(args.This()->GetHiddenValue( v8::String::New( "_JSThreadConfig" ) ) ) ); JSThreadConfig *config = (JSThreadConfig *)( c->Value() ); return config; } - Handle< Value > ThreadStart( const Arguments &args ) { - thisConfig( args )->start(); + Handle< Value > ThreadStart( V8Scope* scope, const Arguments &args ) { + thisConfig( scope, args )->start(); return v8::Undefined(); } - Handle< Value > ThreadJoin( const Arguments &args ) { - thisConfig( args )->join(); + Handle< Value > ThreadJoin( V8Scope* scope, const Arguments &args ) { + thisConfig( scope, args )->join(); return v8::Undefined(); } - Handle< Value > ThreadReturnData( const Arguments &args ) { + Handle< Value > ThreadReturnData( V8Scope* scope, const Arguments &args ) { HandleScope handle_scope; - return handle_scope.Close( thisConfig( args )->returnData() ); + return handle_scope.Close( thisConfig( scope, args )->returnData() ); } - Handle< Value > ThreadInject( const Arguments &args ) { + Handle< Value > ThreadInject( V8Scope* scope, 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" ) , 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() ); + scope->injectV8Function("init", ThreadInit, o); + scope->injectV8Function("start", ThreadStart, o); + scope->injectV8Function("join", ThreadJoin, o); + scope->injectV8Function("returnData", ThreadReturnData, o); return v8::Undefined(); } - Handle< Value > ScopedThreadInject( const Arguments &args ) { + Handle< Value > ScopedThreadInject( V8Scope* scope, 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" ) , newV8Function< ScopedThreadInit >()->GetFunction() ); + scope->injectV8Function("init", ScopedThreadInit, o); // inheritance takes care of other member functions return v8::Undefined(); } - void installFork( v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ) { + void installFork( V8Scope* scope, 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" ), newV8Function< ThreadInject >()->GetFunction() ); - global->Set( v8::String::New( "_scopedThreadInject" ), newV8Function< ScopedThreadInject >()->GetFunction() ); + scope->injectV8Function("_threadInject", ThreadInject, global); + scope->injectV8Function("_scopedThreadInject", ScopedThreadInject, global); } - Handle<v8::Value> GCV8(const Arguments& args) { - V8Lock l; - while( !V8::IdleNotification() ); - return v8::Undefined(); - } - - } diff --git a/scripting/v8_utils.h b/scripting/v8_utils.h index 40662d2..ca5d317 100644 --- a/scripting/v8_utils.h +++ b/scripting/v8_utils.h @@ -27,10 +27,6 @@ namespace mongo { - v8::Handle<v8::Value> Print(const v8::Arguments& args); - v8::Handle<v8::Value> Version(const v8::Arguments& args); - v8::Handle<v8::Value> GCV8(const v8::Arguments& args); - void ReportException(v8::TryCatch* handler); #define jsassert(x,msg) assert(x) @@ -42,6 +38,6 @@ namespace mongo { std::string toSTLString( const v8::TryCatch * try_catch ); class V8Scope; - void installFork( v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ); + void installFork( V8Scope* scope, v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ); } diff --git a/scripting/v8_wrapper.cpp b/scripting/v8_wrapper.cpp index ff67e8c..7c28a39 100644 --- a/scripting/v8_wrapper.cpp +++ b/scripting/v8_wrapper.cpp @@ -15,9 +15,18 @@ * limitations under the License. */ +#if defined(_WIN32) +/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with + our usage of boost */ +#include "boost/cstdint.hpp" +using namespace boost; +#define V8STDINT_H_ +#endif + #include "v8_wrapper.h" #include "v8_utils.h" #include "v8_db.h" +#include "engine_v8.h" #include <iostream> @@ -26,540 +35,14 @@ using namespace v8; namespace mongo { -#define CONN_STRING (v8::String::New( "_conn" )) - #define DDD(x) - Handle<Value> NamedReadOnlySet( Local<v8::String> property, Local<Value> value, const AccessorInfo& info ) { - cout << "cannot write to read-only object" << endl; - return value; - } - - Handle<Boolean> NamedReadOnlyDelete( Local<v8::String> property, const AccessorInfo& info ) { - 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; - string codeStr = codeSS.str(); - Local< Script > compiled = Script::New( v8::String::New( codeStr.c_str() ) ); - 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 ); - } - - Local<v8::Object> mongoToV8( const BSONObj& m , bool array, bool readOnly ) { - - Local<v8::Object> o; - - // handle DBRef. needs to come first. isn't it? (metagoto) - static string ref = "$ref"; - if ( ref == m.firstElement().fieldName() ) { - const BSONElement& id = m["$id"]; - if (!id.eoo()) { // there's no check on $id exitence in sm implementation. risky ? - v8::Function* dbRef = getNamedCons( "DBRef" ); - o = dbRef->NewInstance(); - } - } - - Local< v8::ObjectTemplate > readOnlyObjects; - // Hoping template construction is fast... - Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); - internalFieldObjects->SetInternalFieldCount( 1 ); - - if ( !o.IsEmpty() ) { - readOnly = false; - } - else if ( array ) { - // NOTE Looks like it's impossible to add interceptors to v8 arrays. - readOnly = false; - o = v8::Array::New(); - } - else if ( !readOnly ) { - o = v8::Object::New(); - } - 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 - // that when property handlers are set for an object template, they will attach - // to objects previously created by that template. To get this to work, though, - // it is necessary to initialize the template's property handlers before - // creating objects from the template (as I have in the following few lines - // of code). - // NOTE In my first attempt, I configured the permanent property handlers before - // constructiong the object and replaced the Set() calls below with ForceSet(). - // However, it turns out that ForceSet() only bypasses handlers for named - // properties and not for indexed properties. - readOnlyObjects = v8::ObjectTemplate::New(); - // NOTE This internal field will store type info for special db types. For - // regular objects the field is unnecessary - for simplicity I'm creating just - // one readOnlyObjects template for objects where the field is & isn't necessary, - // assuming that the overhead of an internal field is slight. - readOnlyObjects->SetInternalFieldCount( 1 ); - readOnlyObjects->SetNamedPropertyHandler( 0 ); - 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() ) { - - case mongo::Code: - o->Set( v8::String::New( f.fieldName() ), newFunction( f.valuestr() ) ); - break; - - case CodeWScope: - if ( f.codeWScopeObject().isEmpty() ) - log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; - o->Set( v8::String::New( f.fieldName() ), newFunction( f.codeWScopeCode() ) ); - break; - - case mongo::String: - o->Set( v8::String::New( f.fieldName() ) , v8::String::New( f.valuestr() ) ); - break; - - case mongo::jstOID: { - v8::Function * idCons = getObjectIdCons(); - v8::Handle<v8::Value> argv[1]; - argv[0] = v8::String::New( f.__oid().str().c_str() ); - o->Set( v8::String::New( f.fieldName() ) , - idCons->NewInstance( 1 , argv ) ); - break; - } - - case mongo::NumberDouble: - case mongo::NumberInt: - o->Set( v8::String::New( f.fieldName() ) , v8::Number::New( f.number() ) ); - break; - - case mongo::Array: - case mongo::Object: - sub = f.embeddedObject(); - o->Set( v8::String::New( f.fieldName() ) , mongoToV8( sub , f.type() == mongo::Array, readOnly ) ); - break; - - case mongo::Date: - o->Set( v8::String::New( f.fieldName() ) , v8::Date::New( f.date() ) ); - break; - - case mongo::Bool: - o->Set( v8::String::New( f.fieldName() ) , v8::Boolean::New( f.boolean() ) ); - break; - - case mongo::jstNULL: - 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 ); - argv[1] = v8::Number::New( f.binDataType() ); - argv[2] = v8::String::New( data, len ); - 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]; - argv[0] = v8::Number::New( (double)(long long)( val ) ); - o->Set( v8::String::New( f.fieldName() ), numberLong->NewInstance( 1, argv ) ); - } - 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; - } - - case mongo::MinKey: { - Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); - sub->Set( v8::String::New( "$MinKey" ), v8::Boolean::New( true ) ); - sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - 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 ) ); - sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - o->Set( v8::String::New( f.fieldName() ) , sub ); - break; - } - - case mongo::DBRef: { - v8::Function* dbPointer = getNamedCons( "DBPointer" ); - v8::Handle<v8::Value> argv[2]; - argv[0] = v8::String::New( f.dbrefNS() ); - argv[1] = newId( f.dbrefOID() ); - o->Set( v8::String::New( f.fieldName() ), dbPointer->NewInstance(2, argv) ); - break; - } - - default: - cout << "can't handle type: "; - cout << f.type() << " "; - cout << f.toString(); - cout << endl; - break; - } - - } - - if ( readOnly ) { - readOnlyObjects->SetNamedPropertyHandler( 0, NamedReadOnlySet, 0, NamedReadOnlyDelete ); - readOnlyObjects->SetIndexedPropertyHandler( 0, IndexedReadOnlySet, 0, IndexedReadOnlyDelete ); - } - - return o; - } - - Handle<v8::Value> mongoToV8Element( const BSONElement &f ) { - Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); - internalFieldObjects->SetInternalFieldCount( 1 ); - - 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: - 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::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 ); - argv[1] = v8::Number::New( f.binDataType() ); - 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]; - argv[0] = v8::Number::New( (double)(long long)( val ) ); - return numberLong->NewInstance( 1, argv ); - } - 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) ); - 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]; - argv[0] = v8::String::New( f.dbrefNS() ); - argv[1] = newId( f.dbrefOID() ); - return dbPointer->NewInstance(2, argv); - } - - default: - cout << "can't handle type: "; - 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() ) { - b.append( sname , toSTLString( value ).c_str() ); - return; - } - - if ( value->IsFunction() ) { - b.appendCode( sname , toSTLString( value ) ); - return; - } - - if ( value->IsNumber() ) { - if ( value->IsInt32() ) - b.append( sname, int( value->ToInt32()->Value() ) ); - else - b.append( sname , value->ToNumber()->Value() ); - return; - } - - if ( value->IsArray() ) { - BSONObj sub = v8ToMongo( value->ToObject() , depth ); - b.appendArray( sname , sub ); - return; - } - - if ( value->IsDate() ) { - b.appendDate( sname , Date_t( (unsigned long long)(v8::Date::Cast( *value )->NumberValue())) ); - return; - } - - if ( value->IsExternal() ) - return; - - 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 ); - } - } - string s = toSTLString( value ); - 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 , o ); - } - else if ( value->ToObject()->GetPrototype()->IsObject() && - value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( v8::String::New( "isObjectId" ) ) ) { - OID oid; - oid.init( toSTLString( value ) ); - b.appendOID( sname , &oid ); - } - else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__NumberLong" ) ).IsEmpty() ) { - // TODO might be nice to potentially speed this up with an indexed internal - // field, but I don't yet know how to use an ObjectTemplate with a - // constructor. - v8::Handle< v8::Object > it = value->ToObject(); - long long val; - if ( !it->Has( v8::String::New( "top" ) ) ) { - val = (long long)( it->Get( v8::String::New( "floatApprox" ) )->NumberValue() ); - } - 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() ); - } - - 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, oid ); - } - else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__BinData" ) ).IsEmpty() ) { - int len = obj->Get( v8::String::New( "len" ) )->ToInt32()->Value(); - v8::String::Utf8Value data( obj->Get( v8::String::New( "data" ) ) ); - const char *dataArray = *data; - assert( data.length() == len ); - b.appendBinData( sname, - 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() ) { - b.appendBool( sname , value->ToBoolean()->Value() ); - return; - } - - else if ( value->IsUndefined() ) { - b.appendUndefined( sname ); - return; - } - - else if ( value->IsNull() ) { - b.appendNull( sname ); - return; - } - - cout << "don't know how to convert to mongo field [" << name << "]\t" << value << endl; - } - - BSONObj v8ToMongo( v8::Handle<v8::Object> o , int depth ) { - BSONObjBuilder b; - - if ( depth == 0 ) { - v8::Handle<v8::String> idName = v8::String::New( "_id" ); - if ( o->HasRealNamedProperty( idName ) ) { - v8ToMongoElement( b , idName , "_id" , o->Get( idName ) ); - } - } - - Local<v8::Array> names = o->GetPropertyNames(); - for ( unsigned int i=0; i<names->Length(); i++ ) { - v8::Local<v8::String> name = names->Get(v8::Integer::New(i) )->ToString(); - - if ( o->GetPrototype()->IsObject() && - o->GetPrototype()->ToObject()->HasRealNamedProperty( name ) ) - continue; - - v8::Local<v8::Value> value = o->Get( name ); - - const string sname = toSTLString( name ); - if ( depth == 0 && sname == "_id" ) - continue; - - v8ToMongoElement( b , name , sname , value , depth + 1 ); - } - return b.obj(); - } - // --- object wrapper --- class WrapperHolder { public: - WrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ) - : _o(o), _readOnly( readOnly ), _iDelete( iDelete ) { + WrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ) + : _scope(scope), _o(o), _readOnly( readOnly ), _iDelete( iDelete ) { } ~WrapperHolder() { @@ -572,22 +55,21 @@ namespace mongo { v8::Handle<v8::Value> get( v8::Local<v8::String> name ) { const string& s = toSTLString( name ); const BSONElement& e = _o->getField( s ); - return mongoToV8Element(e); + return _scope->mongoToV8Element(e); } + V8Scope* _scope; const BSONObj * _o; bool _readOnly; bool _iDelete; }; - WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ) { - return new WrapperHolder( o , readOnly , iDelete ); + WrapperHolder * createWrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ) { + return new WrapperHolder( scope, o , readOnly , iDelete ); } -#define WRAPPER_STRING (v8::String::New( "_wrapper" ) ) - WrapperHolder * getWrapper( v8::Handle<v8::Object> o ) { - Handle<v8::Value> t = o->GetRealNamedProperty( WRAPPER_STRING ); + Handle<v8::Value> t = o->GetRealNamedProperty( v8::String::New( "_wrapper" ) ); assert( t->IsExternal() ); Local<External> c = External::Cast( *t ); WrapperHolder * w = (WrapperHolder*)(c->Value()); @@ -596,11 +78,11 @@ namespace mongo { } - Handle<Value> wrapperCons(const Arguments& args) { + Handle<Value> wrapperCons(V8Scope* scope, 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] ); + args.This()->Set( v8::String::New( "_wrapper" ) , args[0] ); return v8::Undefined(); } @@ -609,20 +91,9 @@ namespace mongo { return getWrapper( info.This() )->get( name ); } - v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate() { - v8::Local<v8::FunctionTemplate> t = newV8Function< wrapperCons >(); + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> t = scope->createV8Function(wrapperCons); t->InstanceTemplate()->SetNamedPropertyHandler( wrapperGetHandler ); return t; } - - // --- random utils ---- - - v8::Function * getNamedCons( const char * name ) { - return v8::Function::Cast( *(v8::Context::GetCurrent()->Global()->Get( v8::String::New( name ) ) ) ); - } - - v8::Function * getObjectIdCons() { - return getNamedCons( "ObjectId" ); - } - } diff --git a/scripting/v8_wrapper.h b/scripting/v8_wrapper.h index e0b79e3..22f14e6 100644 --- a/scripting/v8_wrapper.h +++ b/scripting/v8_wrapper.h @@ -22,22 +22,13 @@ #include <cstdio> #include <cstdlib> #include "../db/jsobj.h" +#include "engine_v8.h" 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 , - 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(); - - v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(); + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(V8Scope* scope); class WrapperHolder; - WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ); + WrapperHolder * createWrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ); } |