diff options
Diffstat (limited to 'scripting')
-rw-r--r-- | scripting/engine.cpp | 34 | ||||
-rw-r--r-- | scripting/engine.h | 14 | ||||
-rw-r--r-- | scripting/engine_spidermonkey.cpp | 92 | ||||
-rw-r--r-- | scripting/engine_spidermonkey.h | 20 | ||||
-rw-r--r-- | scripting/sm_db.cpp | 181 | ||||
-rw-r--r-- | scripting/utils.cpp | 52 | ||||
-rw-r--r-- | scripting/v8_db.cpp | 210 | ||||
-rw-r--r-- | scripting/v8_db.h | 6 | ||||
-rw-r--r-- | scripting/v8_wrapper.cpp | 109 | ||||
-rw-r--r-- | scripting/v8_wrapper.h | 4 |
10 files changed, 588 insertions, 134 deletions
diff --git a/scripting/engine.cpp b/scripting/engine.cpp index dc088fb..cc245b6 100644 --- a/scripting/engine.cpp +++ b/scripting/engine.cpp @@ -164,9 +164,12 @@ namespace mongo { _loadedVersion = _lastVersion; string coll = _localDBName + ".system.js"; - + static DBClientBase * db = createDirectClient(); auto_ptr<DBClientCursor> c = db->query( coll , Query() ); + + set<string> thisTime; + while ( c->more() ){ BSONObj o = c->next(); @@ -177,6 +180,26 @@ namespace mongo { uassert( 10210 , "value has to be set" , v.type() != EOO ); setElement( n.valuestr() , v ); + + thisTime.insert( n.valuestr() ); + _storedNames.insert( n.valuestr() ); + + } + + // --- remove things from scope that were removed + + list<string> toremove; + + for ( set<string>::iterator i=_storedNames.begin(); i!=_storedNames.end(); i++ ){ + string n = *i; + if ( thisTime.count( n ) == 0 ) + toremove.push_back( n ); + } + + for ( list<string>::iterator i=toremove.begin(); i!=toremove.end(); i++ ){ + string n = *i; + _storedNames.erase( n ); + execSetup( (string)"delete " + n , "clean up scope" ); } } @@ -220,7 +243,7 @@ namespace mongo { } void done( const string& pool , Scope * s ){ - boostlock lk( _mutex ); + scoped_lock lk( _mutex ); list<Scope*> & l = _pools[pool]; if ( l.size() > 10 ){ delete s; @@ -232,7 +255,7 @@ namespace mongo { } Scope * get( const string& pool ){ - boostlock lk( _mutex ); + scoped_lock lk( _mutex ); list<Scope*> & l = _pools[pool]; if ( l.size() == 0 ) return 0; @@ -260,7 +283,7 @@ namespace mongo { private: PoolToScopes _pools; - boost::mutex _mutex; + mongo::mutex _mutex; int _magic; }; @@ -395,5 +418,8 @@ namespace mongo { } } + void ( *ScriptEngine::_connectCallback )( DBClientWithCommands & ) = 0; + ScriptEngine * globalScriptEngine; } +
\ No newline at end of file diff --git a/scripting/engine.h b/scripting/engine.h index 99c88cf..9907d31 100644 --- a/scripting/engine.h +++ b/scripting/engine.h @@ -26,7 +26,7 @@ namespace mongo { typedef unsigned long long ScriptingFunction; typedef BSONObj (*NativeFunction) ( const BSONObj &args ); - + class Scope : boost::noncopyable { public: Scope(); @@ -111,12 +111,17 @@ namespace mongo { string _localDBName; long long _loadedVersion; + set<string> _storedNames; static long long _lastVersion; map<string,ScriptingFunction> _cachedFunctions; static int _numScopes; }; + void installGlobalUtils( Scope& scope ); + + class DBClientWithCommands; + class ScriptEngine : boost::noncopyable { public: ScriptEngine(); @@ -126,6 +131,7 @@ namespace mongo { Scope *s = createScope(); if ( s && _scopeInitCallback ) _scopeInitCallback( *s ); + installGlobalUtils( *s ); return s; } @@ -142,12 +148,18 @@ namespace mongo { virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new Unlocker ); } void setScopeInitCallback( void ( *func )( Scope & ) ) { _scopeInitCallback = func; } + static void setConnectCallback( void ( *func )( DBClientWithCommands& ) ) { _connectCallback = func; } + static void runConnectCallback( DBClientWithCommands &c ) { + if ( _connectCallback ) + _connectCallback( c ); + } protected: virtual Scope * createScope() = 0; private: void ( *_scopeInitCallback )( Scope & ); + static void ( *_connectCallback )( DBClientWithCommands & ); }; extern ScriptEngine * globalScriptEngine; diff --git a/scripting/engine_spidermonkey.cpp b/scripting/engine_spidermonkey.cpp index d75a734..6609925 100644 --- a/scripting/engine_spidermonkey.cpp +++ b/scripting/engine_spidermonkey.cpp @@ -32,6 +32,11 @@ return JS_FALSE; \ } +#define CHECKNEWOBJECT(xx,ctx,w) \ + if ( ! xx ){ \ + massert(13072,(string)"JS_NewObject failed: " + w ,xx); \ + } + namespace mongo { string trim( string s ){ @@ -45,8 +50,8 @@ namespace mongo { } boost::thread_specific_ptr<SMScope> currentScope( dontDeleteScope ); - boost::recursive_mutex smmutex; -#define smlock recursive_boostlock ___lk( smmutex ); + boost::recursive_mutex &smmutex = *( new boost::recursive_mutex ); +#define smlock recursive_scoped_lock ___lk( smmutex ); #define GETHOLDER(x,o) ((BSONHolder*)JS_GetPrivate( x , o )) @@ -158,6 +163,19 @@ namespace mongo { return toString( JS_ValueToString( _context , v ) ); } + // NOTE No validation of passed in object + long long toNumberLongUnsafe( JSObject *o ) { + boost::uint64_t val; + if ( hasProperty( o, "top" ) ) { + val = + ( (boost::uint64_t)(boost::uint32_t)getNumber( o , "top" ) << 32 ) + + ( boost::uint32_t)( getNumber( o , "bottom" ) ); + } else { + val = (boost::uint64_t) getNumber( o, "floatApprox" ); + } + return val; + } + double toNumber( jsval v ){ double d; uassert( 10214 , "not a number" , JS_ValueToNumber( _context , v , &d ) ); @@ -180,7 +198,7 @@ namespace mongo { return oid; } - BSONObj toObject( JSObject * o ){ + BSONObj toObject( JSObject * o , int depth = 0){ if ( ! o ) return BSONObj(); @@ -204,9 +222,11 @@ namespace mongo { if ( ! appendSpecialDBObject( this , b , "value" , OBJECT_TO_JSVAL( o ) , o ) ){ - jsval theid = getProperty( o , "_id" ); - if ( ! JSVAL_IS_VOID( theid ) ){ - append( b , "_id" , theid ); + if ( depth == 0 ){ + jsval theid = getProperty( o , "_id" ); + if ( ! JSVAL_IS_VOID( theid ) ){ + append( b , "_id" , theid , EOO , depth + 1 ); + } } JSIdArray * properties = JS_Enumerate( _context , o ); @@ -217,10 +237,10 @@ namespace mongo { jsval nameval; assert( JS_IdToValue( _context ,id , &nameval ) ); string name = toString( nameval ); - if ( name == "_id" ) + if ( depth == 0 && name == "_id" ) continue; - append( b , name , getProperty( o , name.c_str() ) , orig[name].type() ); + append( b , name , getProperty( o , name.c_str() ) , orig[name].type() , depth + 1 ); } JS_DestroyIdArray( _context , properties ); @@ -254,7 +274,7 @@ namespace mongo { b.appendRegex( name.c_str() , s.substr( 0 , end ).c_str() , s.substr( end + 1 ).c_str() ); } - void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO ){ + void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO , int depth=0 ){ //cout << "name: " << name << "\t" << typeString( val ) << " oldType: " << oldType << endl; switch ( JS_TypeOfValue( _context , val ) ){ @@ -278,7 +298,7 @@ namespace mongo { b.appendNull( name.c_str() ); } else if ( ! appendSpecialDBObject( this , b , name , val , o ) ){ - BSONObj sub = toObject( o ); + BSONObj sub = toObject( o , depth ); if ( JS_IsArrayObject( _context , o ) ){ b.appendArray( name.c_str() , sub ); } @@ -389,12 +409,12 @@ namespace mongo { paramString = trim( paramString ); } - const char ** paramArray = new const char*[params.size()]; + boost::scoped_array<const char *> paramArray (new const char*[params.size()]); for ( size_t i=0; i<params.size(); i++ ) paramArray[i] = params[i].c_str(); - JSFunction * func = JS_CompileFunction( _context , assoc , fname.str().c_str() , params.size() , paramArray , code.c_str() , strlen( code.c_str() ) , "nofile_b" , 0 ); - delete paramArray; + JSFunction * func = JS_CompileFunction( _context , assoc , fname.str().c_str() , params.size() , paramArray.get() , code.c_str() , strlen( code.c_str() ) , "nofile_b" , 0 ); + if ( ! func ){ cout << "compile failed for: " << raw << endl; return 0; @@ -444,13 +464,12 @@ namespace mongo { static string ref = "$ref"; if ( ref == obj->firstElement().fieldName() ){ JSObject * o = JS_NewObject( _context , &dbref_class , NULL, NULL); - assert( o ); - setProperty( o , "$ref" , toval( obj->firstElement() ) ); - setProperty( o , "$id" , toval( (*obj)["$id"] ) ); + CHECKNEWOBJECT(o,_context,"toJSObject1"); + assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); return o; } JSObject * o = JS_NewObject( _context , readOnly ? &bson_ro_class : &bson_class , NULL, NULL); - assert( o ); + CHECKNEWOBJECT(o,_context,"toJSObject2"); assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); return o; } @@ -469,7 +488,6 @@ namespace mongo { return JSVAL_NULL; case NumberDouble: case NumberInt: - case NumberLong: return toval( e.number() ); case Symbol: // TODO: should we make a special class for this case String: @@ -505,6 +523,7 @@ namespace mongo { case jstOID:{ OID oid = e.__oid(); JSObject * o = JS_NewObject( _context , &object_id_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"jstOID"); setProperty( o , "str" , toval( oid.str().c_str() ) ); return OBJECT_TO_JSVAL( o ); } @@ -553,16 +572,31 @@ namespace mongo { case Timestamp: { JSObject * o = JS_NewObject( _context , ×tamp_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"Timestamp1"); setProperty( o , "t" , toval( (double)(e.timestampTime()) ) ); setProperty( o , "i" , toval( (double)(e.timestampInc()) ) ); return OBJECT_TO_JSVAL( o ); } - + case NumberLong: { + boost::uint64_t val = (boost::uint64_t)e.numberLong(); + JSObject * o = JS_NewObject( _context , &numberlong_class , 0 , 0 ); + 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 ) ) { + // 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 ) ) ); + setProperty( o , "bottom" , toval( (double)(boost::uint32_t)( val & 0x00000000ffffffff ) ) ); + } + return OBJECT_TO_JSVAL( o ); + } case DBRef: { JSObject * o = JS_NewObject( _context , &dbpointer_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"DBRef1"); setProperty( o , "ns" , toval( e.dbrefNS() ) ); JSObject * oid = JS_NewObject( _context , &object_id_class , 0 , 0 ); + CHECKNEWOBJECT(oid,_context,"DBRef2"); setProperty( oid , "str" , toval( e.dbrefOID().str().c_str() ) ); setProperty( o , "id" , OBJECT_TO_JSVAL( oid ) ); @@ -570,9 +604,10 @@ namespace mongo { } case BinData:{ JSObject * o = JS_NewObject( _context , &bindata_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"Bindata_BinData1"); int len; - void * data = (void*)e.binData( len ); - assert( JS_SetPrivate( _context , o , data ) ); + const char * data = e.binData( len ); + assert( JS_SetPrivate( _context , o , new BinDataHolder( data ) ) ); setProperty( o , "len" , toval( len ) ); setProperty( o , "type" , toval( (int)e.binDataType() ) ); @@ -753,6 +788,8 @@ namespace mongo { JSBool mark_modified( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ Convertor c(cx); BSONHolder * holder = GETHOLDER( cx , obj ); + if ( !holder ) // needed when we're messing with DBRef.prototype + return JS_TRUE; if ( holder->_inResolve ) return JS_TRUE; holder->_modified = true; @@ -812,7 +849,15 @@ namespace mongo { a = args.obj(); } - BSONObj out = func( a ); + + BSONObj out; + try { + out = func( a ); + } + catch ( std::exception& e ){ + JS_ReportError( cx , e.what() ); + return JS_FALSE; + } if ( out.isEmpty() ){ *rval = JSVAL_VOID; @@ -1346,6 +1391,7 @@ namespace mongo { } virtual void gc(){ + smlock; JS_GC( _context ); } @@ -1382,7 +1428,7 @@ namespace mongo { stringstream ss; ss << "JS Error: " << message; - if ( report ){ + if ( report && report->filename ){ ss << " " << report->filename << ":" << report->lineno; } diff --git a/scripting/engine_spidermonkey.h b/scripting/engine_spidermonkey.h index 8aeb56c..a39d8fb 100644 --- a/scripting/engine_spidermonkey.h +++ b/scripting/engine_spidermonkey.h @@ -93,6 +93,7 @@ namespace mongo { extern JSClass dbref_class; extern JSClass bindata_class; extern JSClass timestamp_class; + extern JSClass numberlong_class; extern JSClass minkey_class; extern JSClass maxkey_class; @@ -112,5 +113,22 @@ namespace mongo { #define JSVAL_IS_OID(v) ( JSVAL_IS_OBJECT( v ) && JS_InstanceOf( cx , JSVAL_TO_OBJECT( v ) , &object_id_class , 0 ) ) bool isDate( JSContext * cx , JSObject * o ); - + + // JS private data must be 2byte aligned, so we use a holder to refer to an unaligned pointer. + struct BinDataHolder { + BinDataHolder( const char *c, int copyLen = -1 ) : + c_( const_cast< char * >( c ) ), + iFree_( copyLen != -1 ) { + if ( copyLen != -1 ) { + c_ = (char*)malloc( copyLen ); + memcpy( c_, c, copyLen ); + } + } + ~BinDataHolder() { + if ( iFree_ ) + free( c_ ); + } + char *c_; + bool iFree_; + }; } diff --git a/scripting/sm_db.cpp b/scripting/sm_db.cpp index 72d8638..1c15170 100644 --- a/scripting/sm_db.cpp +++ b/scripting/sm_db.cpp @@ -18,6 +18,7 @@ // hacked in right now from engine_spidermonkey.cpp #include "../client/syncclusterconnection.h" +#include "../util/base64.h" namespace mongo { @@ -101,7 +102,6 @@ namespace mongo { *rval = c.toval( &n ); return JS_TRUE; } - JSFunctionSpec internal_cursor_functions[] = { { "hasNext" , internal_cursor_hasNext , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , @@ -144,42 +144,36 @@ namespace mongo { string host = "127.0.0.1"; if ( argc > 0 ) host = c.toString( argv[0] ); + + int numCommas = DBClientBase::countCommas( host ); shared_ptr< DBClientWithCommands > conn; string errmsg; - if ( host.find( "," ) == string::npos ){ + if ( numCommas == 0 ){ DBClientConnection * c = new DBClientConnection( true ); conn.reset( c ); if ( ! c->connect( host , errmsg ) ){ JS_ReportError( cx , ((string)"couldn't connect: " + errmsg).c_str() ); return JS_FALSE; } + ScriptEngine::runConnectCallback( *c ); } - else { // paired - int numCommas = 0; - for ( uint i=0; i<host.size(); i++ ) - if ( host[i] == ',' ) - numCommas++; - - assert( numCommas > 0 ); - - if ( numCommas == 1 ){ - DBClientPaired * c = new DBClientPaired(); - conn.reset( c ); - if ( ! c->connect( host ) ){ - JS_ReportError( cx , "couldn't connect to pair" ); + else if ( numCommas == 1 ){ // paired + DBClientPaired * c = new DBClientPaired(); + conn.reset( c ); + if ( ! c->connect( host ) ){ + JS_ReportError( cx , "couldn't connect to pair" ); return JS_FALSE; - } - } - else if ( numCommas == 2 ){ - conn.reset( new SyncCluterConnection( host ) ); - } - else { - JS_ReportError( cx , "1 (paired) or 2(quorum) commas are allowed" ); - return JS_FALSE; } } + else if ( numCommas == 2 ){ + conn.reset( new SyncClusterConnection( host ) ); + } + else { + JS_ReportError( cx , "1 (paired) or 2(quorum) commas are allowed" ); + return JS_FALSE; + } assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( conn ) ) ) ); @@ -211,7 +205,7 @@ namespace mongo { }; JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ - uassert( 10240 , "mongo_find neesd 5 args" , argc == 5 ); + uassert( 10240 , "mongo_find neesd 6 args" , argc == 6 ); shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); uassert( 10241 , "no connection!" , connHolder && connHolder->get() ); DBClientWithCommands *conn = connHolder->get(); @@ -226,15 +220,18 @@ namespace mongo { int nToReturn = (int) c.toNumber( argv[3] ); int nToSkip = (int) c.toNumber( argv[4] ); bool slaveOk = c.getBoolean( obj , "slaveOk" ); + int batchSize = (int) c.toNumber( argv[5] ); try { - auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , slaveOk ? QueryOption_SlaveOk : 0 ); + auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , slaveOk ? QueryOption_SlaveOk : 0 , batchSize ); if ( ! cursor.get() ){ + log() << "query failed : " << ns << " " << q << " to: " << conn->toString() << endl; JS_ReportError( cx , "error doing query: failed" ); return JS_FALSE; } JSObject * mycursor = JS_NewObject( cx , &internal_cursor_class , 0 , 0 ); + CHECKNEWOBJECT( mycursor, cx, "internal_cursor_class" ); assert( JS_SetPrivate( cx , mycursor , new CursorHolder( cursor, *connHolder ) ) ); *rval = OBJECT_TO_JSVAL( mycursor ); return JS_TRUE; @@ -412,6 +409,7 @@ namespace mongo { assert( c.hasProperty( db , "_name" ) ); JSObject * coll = JS_NewObject( cx , &db_collection_class , 0 , 0 ); + CHECKNEWOBJECT( coll, cx, "doCreateCollection" ); c.setProperty( coll , "_mongo" , c.getProperty( db , "_mongo" ) ); c.setProperty( coll , "_db" , OBJECT_TO_JSVAL( db ) ); c.setProperty( coll , "_shortName" , c.toval( shortName.c_str() ) ); @@ -499,7 +497,7 @@ namespace mongo { if ( ! JS_InstanceOf( cx , obj , &object_id_class , 0 ) ){ obj = JS_NewObject( cx , &object_id_class , 0 , 0 ); - assert( obj ); + CHECKNEWOBJECT( obj, cx, "object_id_constructor" ); *rval = OBJECT_TO_JSVAL( obj ); } @@ -526,6 +524,7 @@ namespace mongo { { 0 } }; + // dbpointer JSBool dbpointer_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ @@ -562,46 +561,82 @@ namespace mongo { JSBool dbref_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ Convertor c( cx ); - + if ( argc == 2 ){ - assert( JS_SetProperty( cx , obj , "$ref" , &(argv[0]) ) ); - assert( JS_SetProperty( cx , obj , "$id" , &(argv[1]) ) ); + JSObject * o = JS_NewObject( cx , NULL , NULL, NULL ); + CHECKNEWOBJECT( o, cx, "dbref_constructor" ); + assert( JS_SetProperty( cx, o , "$ref" , &argv[ 0 ] ) ); + assert( JS_SetProperty( cx, o , "$id" , &argv[ 1 ] ) ); + BSONObj bo = c.toObject( o ); + assert( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( bo.getOwned() ) ) ) ); return JS_TRUE; } else { JS_ReportError( cx , "DBRef needs 2 arguments" ); + assert( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( BSONObj().getOwned() ) ) ) ); return JS_FALSE; } } - JSClass dbref_class = { - "DBRef" , JSCLASS_HAS_PRIVATE , - JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, - JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, - JSCLASS_NO_OPTIONAL_MEMBERS - }; - - JSFunctionSpec dbref_functions[] = { - { 0 } - }; - + JSClass dbref_class = bson_class; // name will be fixed later // BinData JSBool bindata_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ - JS_ReportError( cx , "can't create a BinData yet" ); - return JS_FALSE; + Convertor c( cx ); + + if ( argc == 2 ){ + + int type = (int)c.toNumber( argv[ 0 ] ); + string encoded = c.toString( argv[ 1 ] ); + string decoded = base64::decode( encoded ); + + assert( JS_SetPrivate( cx, obj, new BinDataHolder( decoded.data(), decoded.length() ) ) ); + c.setProperty( obj, "len", c.toval( decoded.length() ) ); + c.setProperty( obj, "type", c.toval( type ) ); + + return JS_TRUE; + } + else { + JS_ReportError( cx , "BinData needs 2 arguments" ); + return JS_FALSE; + } } + JSBool bindata_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + Convertor c(cx); + int type = (int)c.getNumber( obj , "type" ); + int len = (int)c.getNumber( obj, "len" ); + void *holder = JS_GetPrivate( cx, obj ); + assert( holder ); + const char *data = ( ( BinDataHolder* )( holder ) )->c_; + stringstream ss; + ss << "BinData( type: " << type << ", base64: \""; + base64::encode( ss, (const char *)data, len ); + ss << "\" )"; + string ret = ss.str(); + return *rval = c.toval( ret.c_str() ); + } + + void bindata_finalize( JSContext * cx , JSObject * obj ){ + Convertor c(cx); + void *holder = JS_GetPrivate( cx, obj ); + if ( holder ){ + delete ( BinDataHolder* )holder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + JSClass bindata_class = { "BinData" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, - JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, bindata_finalize, JSCLASS_NO_OPTIONAL_MEMBERS }; JSFunctionSpec bindata_functions[] = { + { "toString" , bindata_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , { 0 } }; @@ -618,7 +653,7 @@ namespace mongo { } JSObject * array = JS_NewObject( cx , 0 , 0 , 0 ); - assert( array ); + CHECKNEWOBJECT( array, cx, "map_constructor" ); jsval a = OBJECT_TO_JSVAL( array ); JS_SetProperty( cx , obj , "_data" , &a ); @@ -656,7 +691,38 @@ namespace mongo { JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; + + JSClass numberlong_class = { + "NumberLong" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool numberlong_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + Convertor c(cx); + return *rval = c.toval( double( c.toNumberLongUnsafe( obj ) ) ); + } + + JSBool numberlong_tonumber(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + return numberlong_valueof( cx, obj, argc, argv, rval ); + } + + JSBool numberlong_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + Convertor c(cx); + stringstream ss; + ss << c.toNumberLongUnsafe( obj ); + string ret = ss.str(); + return *rval = c.toval( ret.c_str() ); + } + JSFunctionSpec numberlong_functions[] = { + { "valueOf" , numberlong_valueof , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toNumber" , numberlong_tonumber , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toString" , numberlong_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + JSClass minkey_class = { "MinKey" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, @@ -684,8 +750,11 @@ namespace mongo { if ( argc > 4 && JSVAL_IS_OBJECT( argv[4] ) ) c.setProperty( obj , "_query" , argv[4] ); - else - c.setProperty( obj , "_query" , OBJECT_TO_JSVAL( JS_NewObject( cx , 0 , 0 , 0 ) ) ); + else { + JSObject * temp = JS_NewObject( cx , 0 , 0 , 0 ); + CHECKNEWOBJECT( temp, cx, "dbquery_constructor" ); + c.setProperty( obj , "_query" , OBJECT_TO_JSVAL( temp ) ); + } if ( argc > 5 && JSVAL_IS_OBJECT( argv[5] ) ) c.setProperty( obj , "_fields" , argv[5] ); @@ -702,6 +771,11 @@ namespace mongo { c.setProperty( obj , "_skip" , argv[7] ); else c.setProperty( obj , "_skip" , JSVAL_ZERO ); + + if ( argc > 8 && JSVAL_IS_NUMBER( argv[8] ) ) + c.setProperty( obj , "_batchSize" , argv[8] ); + else + c.setProperty( obj , "_batchSize" , JSVAL_ZERO ); c.setProperty( obj , "_cursor" , JSVAL_NULL ); c.setProperty( obj , "_numReturned" , JSVAL_ZERO ); @@ -744,10 +818,10 @@ namespace mongo { assert( JS_InitClass( cx , global , 0 , &internal_cursor_class , internal_cursor_constructor , 0 , 0 , internal_cursor_functions , 0 , 0 ) ); 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 , &dbref_class , dbref_constructor , 0 , 0 , dbref_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 , ×tamp_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &numberlong_class , 0 , 0 , 0 , numberlong_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &minkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &maxkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); @@ -756,6 +830,10 @@ namespace mongo { assert( JS_InitClass( cx , global , 0 , &bson_ro_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &bson_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); + static const char *dbrefName = "DBRef"; + dbref_class.name = dbrefName; + assert( JS_InitClass( cx , global , 0 , &dbref_class , dbref_constructor , 2 , 0 , bson_functions , 0 , 0 ) ); + scope->exec( jsconcatcode ); } @@ -783,15 +861,22 @@ namespace mongo { return true; } + if ( JS_InstanceOf( c->_context , o , &numberlong_class , 0 ) ){ + b.append( name.c_str() , c->toNumberLongUnsafe( o ) ); + return true; + } + if ( JS_InstanceOf( c->_context , o , &dbpointer_class , 0 ) ){ b.appendDBRef( name.c_str() , c->getString( o , "ns" ).c_str() , c->toOID( c->getProperty( o , "id" ) ) ); return true; } if ( JS_InstanceOf( c->_context , o , &bindata_class , 0 ) ){ + void *holder = JS_GetPrivate( c->_context , o ); + const char *data = ( ( BinDataHolder * )( holder ) )->c_; b.appendBinData( name.c_str() , (int)(c->getNumber( o , "len" )) , (BinDataType)((char)(c->getNumber( o , "type" ) ) ) , - (char*)JS_GetPrivate( c->_context , o ) + 1 + data ); return true; } diff --git a/scripting/utils.cpp b/scripting/utils.cpp new file mode 100644 index 0000000..21089ac --- /dev/null +++ b/scripting/utils.cpp @@ -0,0 +1,52 @@ +// utils.cpp +/* + * Copyright (C) 2010 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "stdafx.h" +#include "engine.h" +#include "../util/md5.hpp" + +namespace mongo { + + BSONObj jsmd5( const BSONObj &a ){ + uassert( 10261 , "js md5 needs a string" , a.firstElement().type() == String ); + const char * s = a.firstElement().valuestrsafe(); + + md5digest d; + md5_state_t st; + md5_init(&st); + md5_append( &st , (const md5_byte_t*)s , strlen( s ) ); + md5_finish(&st, d); + + return BSON( "" << digestToString( d ) ); + } + + BSONObj JSVersion( const BSONObj& args ){ + cout << "version: " << versionString << endl; + if ( strstr( versionString , "+" ) ) + printGitVersion(); + return BSONObj(); + } + + void installGlobalUtils( Scope& scope ){ + scope.injectNative( "hex_md5" , jsmd5 ); + scope.injectNative( "version" , JSVersion ); + } + +} + + diff --git a/scripting/v8_db.cpp b/scripting/v8_db.cpp index 6d859d4..4d14a03 100644 --- a/scripting/v8_db.cpp +++ b/scripting/v8_db.cpp @@ -19,7 +19,8 @@ #include "v8_utils.h" #include "v8_db.h" #include "engine.h" - +#include "util/base64.h" +#include "../client/syncclusterconnection.h" #include <iostream> using namespace std; @@ -49,6 +50,26 @@ namespace mongo { return mongo; } + v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate() { + v8::Local<v8::FunctionTemplate> numberLong = FunctionTemplate::New( numberLongInit ); + v8::Local<v8::Template> proto = numberLong->PrototypeTemplate(); + + proto->Set( v8::String::New( "valueOf" ) , FunctionTemplate::New( numberLongValueOf ) ); + proto->Set( v8::String::New( "toNumber" ) , FunctionTemplate::New( numberLongToNumber ) ); + proto->Set( v8::String::New( "toString" ) , FunctionTemplate::New( numberLongToString ) ); + + return numberLong; + } + + v8::Handle<v8::FunctionTemplate> getBinDataFunctionTemplate() { + v8::Local<v8::FunctionTemplate> binData = FunctionTemplate::New( binDataInit ); + v8::Local<v8::Template> proto = binData->PrototypeTemplate(); + + proto->Set( v8::String::New( "toString" ) , FunctionTemplate::New( binDataToString ) ); + + return binData; + } + void installDBTypes( Handle<ObjectTemplate>& global ){ v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); @@ -69,7 +90,9 @@ namespace mongo { global->Set( v8::String::New("DBPointer") , FunctionTemplate::New( dbPointerInit ) ); - global->Set( v8::String::New("BinData") , FunctionTemplate::New( binDataInit ) ); + global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate() ); + + global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate() ); } @@ -93,7 +116,9 @@ namespace mongo { global->Set( v8::String::New("DBPointer") , FunctionTemplate::New( dbPointerInit )->GetFunction() ); - global->Set( v8::String::New("BinData") , FunctionTemplate::New( binDataInit )->GetFunction() ); + global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate()->GetFunction() ); + + global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate()->GetFunction() ); BSONObjBuilder b; b.appendMaxKey( "" ); @@ -107,7 +132,8 @@ namespace mongo { } void destroyConnection( Persistent<Value> object, void* parameter){ - cout << "Yo ho ho" << endl; + // TODO + cout << "warning: destroyConnection not implemented" << endl; } Handle<Value> mongoConsExternal(const Arguments& args){ @@ -122,16 +148,45 @@ namespace mongo { strcpy( host , "127.0.0.1" ); } - DBClientConnection * conn = new DBClientConnection( true ); - + DBClientWithCommands * conn = 0; + int commas = 0; + for ( int i=0; i<255; i++ ){ + if ( host[i] == ',' ) + commas++; + else if ( host[i] == 0 ) + break; + } + + if ( commas == 0 ){ + DBClientConnection * c = new DBClientConnection( true ); + string errmsg; + if ( ! c->connect( host , errmsg ) ){ + delete c; + string x = "couldn't connect: "; + x += errmsg; + return v8::ThrowException( v8::String::New( x.c_str() ) ); + } + conn = c; + } + else if ( commas == 1 ){ + DBClientPaired * c = new DBClientPaired(); + if ( ! c->connect( host ) ){ + delete c; + return v8::ThrowException( v8::String::New( "couldn't connect to pair" ) ); + } + conn = c; + } + else if ( commas == 2 ){ + conn = new SyncClusterConnection( host ); + } + else { + return v8::ThrowException( v8::String::New( "too many commas" ) ); + } + Persistent<v8::Object> self = Persistent<v8::Object>::New( args.This() ); self.MakeWeak( conn , destroyConnection ); - string errmsg; - if ( ! conn->connect( host , errmsg ) ){ - return v8::ThrowException( v8::String::New( "couldn't connect" ) ); - } - + ScriptEngine::runConnectCallback( *conn ); // NOTE I don't believe the conn object will ever be freed. args.This()->Set( CONN_STRING , External::New( conn ) ); args.This()->Set( v8::String::New( "slaveOk" ) , Boolean::New( false ) ); @@ -184,7 +239,7 @@ namespace mongo { 4 - skip */ Handle<Value> mongoFind(const Arguments& args){ - jsassert( args.Length() == 5 , "find needs 5 args" ); + jsassert( args.Length() == 6 , "find needs 6 args" ); jsassert( args[1]->IsObject() , "needs to be an object" ); DBClientBase * conn = getConnection( args ); GETNS; @@ -201,14 +256,15 @@ namespace mongo { Local<v8::Value> slaveOkVal = mongo->Get( v8::String::New( "slaveOk" ) ); jsassert( slaveOkVal->IsBoolean(), "slaveOk member invalid" ); bool slaveOk = slaveOkVal->BooleanValue(); - + try { auto_ptr<mongo::DBClientCursor> cursor; int nToReturn = (int)(args[3]->ToNumber()->Value()); int nToSkip = (int)(args[4]->ToNumber()->Value()); + int batchSize = (int)(args[5]->ToNumber()->Value()); { v8::Unlocker u; - cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, slaveOk ? QueryOption_SlaveOk : 0 ); + cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, slaveOk ? QueryOption_SlaveOk : 0 , batchSize ); } v8::Function * cons = (v8::Function*)( *( mongo->Get( v8::String::New( "internalCursor" ) ) ) ); assert( cons ); @@ -399,6 +455,11 @@ namespace mongo { t->Set( v8::String::New( "_skip" ) , args[7] ); else t->Set( v8::String::New( "_skip" ) , Number::New( 0 ) ); + + if ( args.Length() > 8 && args[8]->IsNumber() ) + t->Set( v8::String::New( "_batchSize" ) , args[7] ); + else + t->Set( v8::String::New( "_batchSize" ) , Number::New( 0 ) ); t->Set( v8::String::New( "_cursor" ) , v8::Null() ); t->Set( v8::String::New( "_numReturned" ) , v8::Number::New(0) ); @@ -473,7 +534,7 @@ namespace mongo { v8::Handle<v8::Value> dbRefInit( const v8::Arguments& args ) { - if (args.Length() != 2) { + if (args.Length() != 2 && args.Length() != 0) { return v8::ThrowException( v8::String::New( "DBRef needs 2 arguments" ) ); } @@ -484,8 +545,10 @@ namespace mongo { it = f->NewInstance(); } - it->Set( v8::String::New( "$ref" ) , args[0] ); - it->Set( v8::String::New( "$id" ) , args[1] ); + if ( args.Length() == 2 ) { + it->Set( v8::String::New( "$ref" ) , args[0] ); + it->Set( v8::String::New( "$id" ) , args[1] ); + } return it; } @@ -511,25 +574,126 @@ namespace mongo { } v8::Handle<v8::Value> binDataInit( const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + + // 3 args: len, type, data + if (args.Length() == 3) { - if (args.Length() != 3) { + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function* f = getNamedCons( "BinData" ); + it = f->NewInstance(); + } + + it->Set( v8::String::New( "len" ) , args[0] ); + it->Set( v8::String::New( "type" ) , args[1] ); + it->Set( v8::String::New( "data" ), args[2] ); + it->SetHiddenValue( v8::String::New( "__BinData" ), v8::Number::New( 1 ) ); + + // 2 args: type, base64 string + } else if ( args.Length() == 2 ) { + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function* f = getNamedCons( "BinData" ); + it = f->NewInstance(); + } + + v8::String::Utf8Value data( args[ 1 ] ); + string decoded = base64::decode( *data ); + it->Set( v8::String::New( "len" ) , v8::Number::New( decoded.length() ) ); + it->Set( v8::String::New( "type" ) , args[ 0 ] ); + it->Set( v8::String::New( "data" ), v8::String::New( decoded.data(), decoded.length() ) ); + it->SetHiddenValue( v8::String::New( "__BinData" ), v8::Number::New( 1 ) ); + + } else { return v8::ThrowException( v8::String::New( "BinData needs 3 arguments" ) ); } + + return it; + } + + v8::Handle<v8::Value> binDataToString( const v8::Arguments& args ) { + + if (args.Length() != 0) { + return v8::ThrowException( v8::String::New( "toString needs 0 arguments" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + int len = it->Get( v8::String::New( "len" ) )->ToInt32()->Value(); + int type = it->Get( v8::String::New( "type" ) )->ToInt32()->Value(); + v8::String::Utf8Value data( it->Get( v8::String::New( "data" ) ) ); + + stringstream ss; + ss << "BinData( type: " << type << ", base64: \""; + 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 ) { + + if (args.Length() != 1 && args.Length() != 3) { + return v8::ThrowException( v8::String::New( "NumberLong needs 1 or 3 arguments" ) ); + } v8::Handle<v8::Object> it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ - v8::Function* f = getNamedCons( "BinData" ); + v8::Function* f = getNamedCons( "NumberLong" ); 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 ) ); + it->Set( v8::String::New( "floatApprox" ) , args[0] ); + if ( args.Length() == 3 ) { + it->Set( v8::String::New( "top" ) , args[1] ); + it->Set( v8::String::New( "bottom" ) , args[2] ); + } + it->SetHiddenValue( v8::String::New( "__NumberLong" ), v8::Number::New( 1 ) ); return it; } + + long long numberLongVal( const v8::Handle< v8::Object > &it ) { + if ( !it->Has( v8::String::New( "top" ) ) ) + return (long long)( it->Get( v8::String::New( "floatApprox" ) )->NumberValue() ); + return + (long long) + ( (unsigned long long)( it->Get( v8::String::New( "top" ) )->ToInt32()->Value() ) << 32 ) + + (unsigned)( it->Get( v8::String::New( "bottom" ) )->ToInt32()->Value() ); + } + + v8::Handle<v8::Value> numberLongValueOf( const v8::Arguments& args ) { + + if (args.Length() != 0) { + return v8::ThrowException( v8::String::New( "toNumber needs 0 arguments" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + + long long val = numberLongVal( it ); + + return v8::Number::New( double( val ) ); + } + + v8::Handle<v8::Value> numberLongToNumber( const v8::Arguments& args ) { + return numberLongValueOf( 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::Object> it = args.This(); + + long long val = numberLongVal( it ); + + stringstream ss; + ss << val; + string ret = ss.str(); + return v8::String::New( ret.c_str() ); + } v8::Handle<v8::Value> bsonsize( const v8::Arguments& args ) { diff --git a/scripting/v8_db.h b/scripting/v8_db.h index c3f2ef1..92e2ae2 100644 --- a/scripting/v8_db.h +++ b/scripting/v8_db.h @@ -60,6 +60,12 @@ namespace mongo { v8::Handle<v8::Value> dbPointerInit( 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> 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> dbQueryInit( const v8::Arguments& args ); v8::Handle<v8::Value> dbQueryIndexAccess( uint32_t index , const v8::AccessorInfo& info ); diff --git a/scripting/v8_wrapper.cpp b/scripting/v8_wrapper.cpp index 29a70ba..c4e6b7d 100644 --- a/scripting/v8_wrapper.cpp +++ b/scripting/v8_wrapper.cpp @@ -67,16 +67,15 @@ namespace mongo { 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" ); - v8::Handle<v8::Value> argv[2]; - argv[0] = mongoToV8Element(m.firstElement()); - argv[1] = mongoToV8Element(m["$id"]); - return dbRef->NewInstance(2, argv); + o = dbRef->NewInstance(); } } @@ -85,9 +84,11 @@ namespace mongo { Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); internalFieldObjects->SetInternalFieldCount( 1 ); - Local<v8::Object> o; - if ( array ) { - // NOTE Looks like it's impossible to add interceptors to non array objects in v8. + 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(); @@ -149,7 +150,6 @@ namespace mongo { case mongo::NumberDouble: case mongo::NumberInt: - case mongo::NumberLong: // may lose information here - just copying sm engine behavior o->Set( v8::String::New( f.fieldName() ) , v8::Number::New( f.number() ) ); break; @@ -168,6 +168,7 @@ namespace mongo { break; case mongo::jstNULL: + case mongo::Undefined: // duplicate sm behavior o->Set( v8::String::New( f.fieldName() ) , v8::Null() ); break; @@ -200,7 +201,7 @@ namespace mongo { case mongo::Timestamp: { Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); - sub->Set( v8::String::New( "time" ) , v8::Date::New( f.timestampTime() ) ); + 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() ) ); @@ -208,6 +209,24 @@ namespace mongo { 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 ) ); @@ -224,10 +243,6 @@ namespace mongo { break; } - case mongo::Undefined: - o->Set( v8::String::New( f.fieldName() ), v8::Undefined() ); - break; - case mongo::DBRef: { v8::Function* dbPointer = getNamedCons( "DBPointer" ); v8::Handle<v8::Value> argv[2]; @@ -247,7 +262,7 @@ namespace mongo { } - if ( !array && readOnly ) { + if ( readOnly ) { readOnlyObjects->SetNamedPropertyHandler( 0, NamedReadOnlySet, 0, NamedReadOnlyDelete ); readOnlyObjects->SetIndexedPropertyHandler( 0, IndexedReadOnlySet, 0, IndexedReadOnlyDelete ); } @@ -291,6 +306,7 @@ namespace mongo { case mongo::EOO: case mongo::jstNULL: + case mongo::Undefined: // duplicate sm behavior return v8::Null(); case mongo::RegEx: { @@ -319,12 +335,29 @@ namespace mongo { case mongo::Timestamp: { Local<v8::Object> sub = internalFieldObjects->NewInstance(); - sub->Set( v8::String::New( "time" ) , v8::Date::New( f.timestampTime() ) ); + 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(); @@ -340,9 +373,6 @@ namespace mongo { return sub; } - case mongo::Undefined: - return v8::Undefined(); - case mongo::DBRef: { v8::Function* dbPointer = getNamedCons( "DBPointer" ); v8::Handle<v8::Value> argv[2]; @@ -362,7 +392,7 @@ namespace mongo { return v8::Undefined(); } - void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value ){ + 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.c_str() , toSTLString( value ).c_str() ); @@ -383,7 +413,7 @@ namespace mongo { } if ( value->IsArray() ){ - BSONObj sub = v8ToMongo( value->ToObject() ); + BSONObj sub = v8ToMongo( value->ToObject() , depth ); b.appendArray( sname.c_str() , sub ); return; } @@ -405,7 +435,7 @@ namespace mongo { 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.c_str(), - Date_t( v8::Date::Cast( *obj->Get( v8::String::New( "time" ) ) )->NumberValue() ), + Date_t( obj->Get( v8::String::New( "t" ) )->ToNumber()->Value() ), obj->Get( v8::String::New( "i" ) )->ToInt32()->Value() ); return; case MinKey: @@ -421,8 +451,8 @@ namespace mongo { string s = toSTLString( value ); if ( s.size() && s[0] == '/' ){ s = s.substr( 1 ); - string r = s.substr( 0 , s.find( "/" ) ); - string o = s.substr( s.find( "/" ) + 1 ); + string r = s.substr( 0 , s.rfind( "/" ) ); + string o = s.substr( s.rfind( "/" ) + 1 ); b.appendRegex( sname.c_str() , r.c_str() , o.c_str() ); } else if ( value->ToObject()->GetPrototype()->IsObject() && @@ -431,10 +461,23 @@ namespace mongo { oid.init( toSTLString( value ) ); b.appendOID( sname.c_str() , &oid ); } - else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__DBPointer" ) ).IsEmpty() ) { + 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.c_str(), 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" ) ) ); @@ -450,7 +493,7 @@ namespace mongo { mongo::BinDataType( obj->Get( v8::String::New( "type" ) )->ToInt32()->Value() ), dataArray ); } else { - BSONObj sub = v8ToMongo( value->ToObject() ); + BSONObj sub = v8ToMongo( value->ToObject() , depth ); b.append( sname.c_str() , sub ); } return; @@ -474,12 +517,14 @@ namespace mongo { cout << "don't know how to convert to mongo field [" << name << "]\t" << value << endl; } - BSONObj v8ToMongo( v8::Handle<v8::Object> o ){ + BSONObj v8ToMongo( v8::Handle<v8::Object> o , int depth ){ BSONObjBuilder b; - - v8::Handle<v8::String> idName = v8::String::New( "_id" ); - if ( o->HasRealNamedProperty( idName ) ){ - v8ToMongoElement( b , idName , "_id" , o->Get( idName ) ); + + 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(); @@ -493,10 +538,10 @@ namespace mongo { v8::Local<v8::Value> value = o->Get( name ); const string sname = toSTLString( name ); - if ( sname == "_id" ) + if ( depth == 0 && sname == "_id" ) continue; - v8ToMongoElement( b , name , sname , value ); + v8ToMongoElement( b , name , sname , value , depth + 1 ); } return b.obj(); } diff --git a/scripting/v8_wrapper.h b/scripting/v8_wrapper.h index 1d67cf1..838aaf4 100644 --- a/scripting/v8_wrapper.h +++ b/scripting/v8_wrapper.h @@ -26,10 +26,10 @@ namespace mongo { v8::Local<v8::Object> mongoToV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false ); - mongo::BSONObj v8ToMongo( v8::Handle<v8::Object> o ); + 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 ); + 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 ); |