summaryrefslogtreecommitdiff
path: root/scripting
diff options
context:
space:
mode:
Diffstat (limited to 'scripting')
-rw-r--r--scripting/engine.cpp34
-rw-r--r--scripting/engine.h14
-rw-r--r--scripting/engine_spidermonkey.cpp92
-rw-r--r--scripting/engine_spidermonkey.h20
-rw-r--r--scripting/sm_db.cpp181
-rw-r--r--scripting/utils.cpp52
-rw-r--r--scripting/v8_db.cpp210
-rw-r--r--scripting/v8_db.h6
-rw-r--r--scripting/v8_wrapper.cpp109
-rw-r--r--scripting/v8_wrapper.h4
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 , &timestamp_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 , &timestamp_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 );