// v8_db.cpp /* Copyright 2009 10gen Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #if defined(_WIN32) /** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with our usage of boost */ #include "boost/cstdint.hpp" using namespace boost; #define V8STDINT_H_ #endif #include "v8_wrapper.h" #include "v8_utils.h" #include "engine_v8.h" #include "v8_db.h" #include "util/base64.h" #include "util/text.h" #include "../client/syncclusterconnection.h" #include "../s/d_logic.h" #include using namespace std; using namespace v8; namespace mongo { #define DDD(x) v8::Handle getMongoFunctionTemplate( V8Scope* scope, bool local ) { v8::Handle mongo; if ( local ) { mongo = scope->createV8Function(mongoConsLocal); } else { mongo = scope->createV8Function(mongoConsExternal); } mongo->InstanceTemplate()->SetInternalFieldCount( 1 ); v8::Handle proto = mongo->PrototypeTemplate(); scope->injectV8Function("find", mongoFind, proto); scope->injectV8Function("insert", mongoInsert, proto); scope->injectV8Function("remove", mongoRemove, proto); scope->injectV8Function("update", mongoUpdate, proto); v8::Handle ic = scope->createV8Function(internalCursorCons); ic->InstanceTemplate()->SetInternalFieldCount( 1 ); v8::Handle icproto = ic->PrototypeTemplate(); scope->injectV8Function("next", internalCursorNext, icproto); scope->injectV8Function("hasNext", internalCursorHasNext, icproto); scope->injectV8Function("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto); proto->Set( scope->getV8Str( "internalCursor" ) , ic ); return mongo; } v8::Handle getNumberLongFunctionTemplate(V8Scope* scope) { v8::Handle numberLong = scope->createV8Function(numberLongInit); v8::Local proto = numberLong->PrototypeTemplate(); scope->injectV8Function("valueOf", numberLongValueOf, proto); scope->injectV8Function("toNumber", numberLongToNumber, proto); scope->injectV8Function("toString", numberLongToString, proto); return numberLong; } v8::Handle getNumberIntFunctionTemplate(V8Scope* scope) { v8::Handle numberInt = scope->createV8Function(numberIntInit); v8::Local proto = numberInt->PrototypeTemplate(); scope->injectV8Function("valueOf", numberIntValueOf, proto); scope->injectV8Function("toNumber", numberIntToNumber, proto); scope->injectV8Function("toString", numberIntToString, proto); return numberInt; } v8::Handle getBinDataFunctionTemplate(V8Scope* scope) { v8::Handle binData = scope->createV8Function(binDataInit); binData->InstanceTemplate()->SetInternalFieldCount(1); v8::Local proto = binData->PrototypeTemplate(); scope->injectV8Function("toString", binDataToString, proto); scope->injectV8Function("base64", binDataToBase64, proto); scope->injectV8Function("hex", binDataToHex, proto); return binData; } v8::Handle getUUIDFunctionTemplate(V8Scope* scope) { v8::Handle templ = scope->createV8Function(uuidInit); templ->InstanceTemplate()->SetInternalFieldCount(1); v8::Local proto = templ->PrototypeTemplate(); scope->injectV8Function("toString", binDataToString, proto); scope->injectV8Function("base64", binDataToBase64, proto); scope->injectV8Function("hex", binDataToHex, proto); return templ; } v8::Handle getMD5FunctionTemplate(V8Scope* scope) { v8::Handle templ = scope->createV8Function(md5Init); templ->InstanceTemplate()->SetInternalFieldCount(1); v8::Local proto = templ->PrototypeTemplate(); scope->injectV8Function("toString", binDataToString, proto); scope->injectV8Function("base64", binDataToBase64, proto); scope->injectV8Function("hex", binDataToHex, proto); return templ; } v8::Handle getHexDataFunctionTemplate(V8Scope* scope) { v8::Handle templ = scope->createV8Function(hexDataInit); templ->InstanceTemplate()->SetInternalFieldCount(1); v8::Local proto = templ->PrototypeTemplate(); scope->injectV8Function("toString", binDataToString, proto); scope->injectV8Function("base64", binDataToBase64, proto); scope->injectV8Function("hex", binDataToHex, proto); return templ; } v8::Handle getTimestampFunctionTemplate(V8Scope* scope) { v8::Handle ts = scope->createV8Function(dbTimestampInit); /* v8::Local proto = */ ts->PrototypeTemplate(); ts->InstanceTemplate()->SetInternalFieldCount( 1 ); return ts; } // void installDBTypes( V8Scope* scope, Handle& global ) { // v8::Handle db = scope->createV8Function(dbInit); // db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); // global->Set(v8::String::New("DB") , db ); // // v8::Handle dbCollection = scope->createV8Function(collectionInit); // dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); // global->Set(v8::String::New("DBCollection") , dbCollection ); // // // v8::Handle dbQuery = scope->createV8Function(dbQueryInit); // dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); // global->Set(v8::String::New("DBQuery") , dbQuery ); // // global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >(scope) ); // // global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >(scope) ); // // global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >(scope) ); // // global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate(scope) ); // // global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate(scope) ); // // global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate(scope) ); // } void installDBTypes( V8Scope* scope, v8::Handle& global ) { v8::Handle db = scope->createV8Function(dbInit); db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); global->Set(scope->getV8Str("DB") , db->GetFunction() ); v8::Handle dbCollection = scope->createV8Function(collectionInit); dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); global->Set(scope->getV8Str("DBCollection") , dbCollection->GetFunction() ); v8::Handle dbQuery = scope->createV8Function(dbQueryInit); dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); global->Set(scope->getV8Str("DBQuery") , dbQuery->GetFunction() ); scope->injectV8Function("ObjectId", objectIdInit, global); scope->injectV8Function("DBRef", dbRefInit, global); scope->injectV8Function("DBPointer", dbPointerInit, global); global->Set( scope->getV8Str("BinData") , getBinDataFunctionTemplate(scope)->GetFunction() ); global->Set( scope->getV8Str("UUID") , getUUIDFunctionTemplate(scope)->GetFunction() ); global->Set( scope->getV8Str("MD5") , getMD5FunctionTemplate(scope)->GetFunction() ); global->Set( scope->getV8Str("HexData") , getHexDataFunctionTemplate(scope)->GetFunction() ); global->Set( scope->getV8Str("NumberLong") , getNumberLongFunctionTemplate(scope)->GetFunction() ); global->Set( scope->getV8Str("NumberInt") , getNumberIntFunctionTemplate(scope)->GetFunction() ); global->Set( scope->getV8Str("Timestamp") , getTimestampFunctionTemplate(scope)->GetFunction() ); BSONObjBuilder b; b.appendMaxKey( "" ); b.appendMinKey( "" ); BSONObj o = b.obj(); BSONObjIterator i( o ); global->Set( scope->getV8Str("MaxKey"), scope->mongoToV8Element( i.next() ) ); global->Set( scope->getV8Str("MinKey"), scope->mongoToV8Element( i.next() ) ); global->Get( scope->getV8Str( "Object" ) )->ToObject()->Set( scope->getV8Str("bsonsize") , scope->createV8Function(bsonsize)->GetFunction() ); } void destroyConnection( Persistent self, void* parameter) { delete static_cast(parameter); self.Dispose(); self.Clear(); } Handle mongoConsExternal(V8Scope* scope, const Arguments& args) { char host[255]; if ( args.Length() > 0 && args[0]->IsString() ) { assert( args[0]->ToString()->Utf8Length() < 250 ); args[0]->ToString()->WriteAscii( host ); } else { strcpy( host , "127.0.0.1" ); } string errmsg; ConnectionString cs = ConnectionString::parse( host , errmsg ); if ( ! cs.isValid() ) return v8::ThrowException( v8::String::New( errmsg.c_str() ) ); DBClientWithCommands * conn; { V8Unlock ul; conn = cs.connect( errmsg ); } if ( ! conn ) return v8::ThrowException( v8::String::New( errmsg.c_str() ) ); Persistent self = Persistent::New( args.Holder() ); self.MakeWeak( conn , destroyConnection ); { V8Unlock ul; ScriptEngine::runConnectCallback( *conn ); } args.This()->SetInternalField( 0 , External::New( conn ) ); args.This()->Set( scope->getV8Str( "slaveOk" ) , Boolean::New( false ) ); args.This()->Set( scope->getV8Str( "host" ) , scope->getV8Str( host ) ); return v8::Undefined(); } Handle mongoConsLocal(V8Scope* scope, const Arguments& args) { if ( args.Length() > 0 ) return v8::ThrowException( v8::String::New( "local Mongo constructor takes no args" ) ); DBClientBase * conn; { V8Unlock ul; conn = createDirectClient(); } Persistent self = Persistent::New( args.This() ); self.MakeWeak( conn , destroyConnection ); // NOTE I don't believe the conn object will ever be freed. args.This()->SetInternalField( 0 , External::New( conn ) ); args.This()->Set( scope->getV8Str( "slaveOk" ) , Boolean::New( false ) ); args.This()->Set( scope->getV8Str( "host" ) , scope->getV8Str( "EMBEDDED" ) ); return v8::Undefined(); } // --- #ifdef _WIN32 #define GETNS char * ns = new char[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); #else #define GETNS char ns[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); #endif DBClientBase * getConnection( const Arguments& args ) { Local c = External::Cast( *(args.This()->GetInternalField( 0 )) ); DBClientBase * conn = (DBClientBase*)(c->Value()); assert( conn ); return conn; } // ---- real methods void destroyCursor( Persistent self, void* parameter) { delete static_cast(parameter); self.Dispose(); self.Clear(); } /** 0 - namespace 1 - query 2 - fields 3 - limit 4 - skip */ Handle mongoFind(V8Scope* scope, const Arguments& args) { HandleScope handle_scope; jsassert( args.Length() == 7 , "find needs 7 args" ); jsassert( args[1]->IsObject() , "needs to be an object" ); DBClientBase * conn = getConnection( args ); GETNS; BSONObj q = scope->v8ToMongo( args[1]->ToObject() ); DDD( "query:" << q ); BSONObj fields; bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0; if ( haveFields ) fields = scope->v8ToMongo( args[2]->ToObject() ); Local mongo = args.This(); Local slaveOkVal = mongo->Get( scope->getV8Str( "slaveOk" ) ); jsassert( slaveOkVal->IsBoolean(), "slaveOk member invalid" ); bool slaveOk = slaveOkVal->BooleanValue(); try { auto_ptr cursor; int nToReturn = (int)(args[3]->ToNumber()->Value()); int nToSkip = (int)(args[4]->ToNumber()->Value()); int batchSize = (int)(args[5]->ToNumber()->Value()); int options = (int)(args[6]->ToNumber()->Value()); { V8Unlock u; cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, options | ( slaveOk ? QueryOption_SlaveOk : 0 ) , batchSize ); if ( ! cursor.get() ) return v8::ThrowException( v8::String::New( "error doing query: failed" ) ); } v8::Function * cons = (v8::Function*)( *( mongo->Get( scope->getV8Str( "internalCursor" ) ) ) ); assert( cons ); Persistent c = Persistent::New( cons->NewInstance() ); c.MakeWeak( cursor.get() , destroyCursor ); c->SetInternalField( 0 , External::New( cursor.release() ) ); return handle_scope.Close(c); } catch ( ... ) { return v8::ThrowException( v8::String::New( "socket error on query" ) ); } } v8::Handle mongoInsert(V8Scope* scope, const v8::Arguments& args) { jsassert( args.Length() == 2 , "insert needs 2 args" ); jsassert( args[1]->IsObject() , "have to insert an object" ); if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() ) return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); DBClientBase * conn = getConnection( args ); GETNS; v8::Handle in = args[1]->ToObject(); if ( ! in->Has( scope->getV8Str( "_id" ) ) ) { v8::Handle argv[1]; in->Set( scope->getV8Str( "_id" ) , scope->getObjectIdCons()->NewInstance( 0 , argv ) ); } BSONObj o = scope->v8ToMongo( in ); DDD( "want to save : " << o.jsonString() ); try { V8Unlock u; conn->insert( ns , o ); } catch ( ... ) { return v8::ThrowException( v8::String::New( "socket error on insert" ) ); } return v8::Undefined(); } v8::Handle mongoRemove(V8Scope* scope, const v8::Arguments& args) { jsassert( args.Length() == 2 || args.Length() == 3 , "remove needs 2 args" ); jsassert( args[1]->IsObject() , "have to remove an object template" ); if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() ) return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); DBClientBase * conn = getConnection( args ); GETNS; v8::Handle in = args[1]->ToObject(); BSONObj o = scope->v8ToMongo( in ); bool justOne = false; if ( args.Length() > 2 ) { justOne = args[2]->BooleanValue(); } DDD( "want to remove : " << o.jsonString() ); try { V8Unlock u; conn->remove( ns , o , justOne ); } catch ( ... ) { return v8::ThrowException( v8::String::New( "socket error on remove" ) ); } return v8::Undefined(); } v8::Handle mongoUpdate(V8Scope* scope, const v8::Arguments& args) { jsassert( args.Length() >= 3 , "update needs at least 3 args" ); jsassert( args[1]->IsObject() , "1st param to update has to be an object" ); jsassert( args[2]->IsObject() , "2nd param to update has to be an object" ); if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() ) return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); DBClientBase * conn = getConnection( args ); GETNS; v8::Handle q = args[1]->ToObject(); v8::Handle o = args[2]->ToObject(); bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value(); bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); try { BSONObj q1 = scope->v8ToMongo( q ); BSONObj o1 = scope->v8ToMongo( o ); V8Unlock u; conn->update( ns , q1 , o1 , upsert, multi ); } catch ( ... ) { return v8::ThrowException( v8::String::New( "socket error on remove" ) ); } return v8::Undefined(); } // --- cursor --- mongo::DBClientCursor * getCursor( const Arguments& args ) { Local c = External::Cast( *(args.This()->GetInternalField( 0 ) ) ); mongo::DBClientCursor * cursor = (mongo::DBClientCursor*)(c->Value()); return cursor; } v8::Handle internalCursorCons(V8Scope* scope, const v8::Arguments& args) { return v8::Undefined(); } v8::Handle internalCursorNext(V8Scope* scope, const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return v8::Undefined(); BSONObj o; { V8Unlock u; o = cursor->next(); } bool ro = false; if (args.This()->Has(scope->V8STR_RO)) ro = args.This()->Get(scope->V8STR_RO)->BooleanValue(); return scope->mongoToLZV8( o, false, ro ); } v8::Handle internalCursorHasNext(V8Scope* scope, const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return Boolean::New( false ); bool ret; { V8Unlock u; ret = cursor->more(); } return Boolean::New( ret ); } v8::Handle internalCursorObjsLeftInBatch(V8Scope* scope, const v8::Arguments& args) { mongo::DBClientCursor * cursor = getCursor( args ); if ( ! cursor ) return v8::Number::New( (double) 0 ); int ret; { V8Unlock u; ret = cursor->objsLeftInBatch(); } return v8::Number::New( (double) ret ); } // v8::Handle internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args) { // Local cursor = args.This(); // cursor->Set(scope->V8STR_RO, v8::Undefined()); // return cursor; // } // --- DB ---- v8::Handle dbInit(V8Scope* scope, const v8::Arguments& args) { assert( args.Length() == 2 ); args.This()->Set( scope->getV8Str( "_mongo" ) , args[0] ); args.This()->Set( scope->getV8Str( "_name" ) , args[1] ); for ( int i=0; iIsUndefined() ); return v8::Undefined(); } v8::Handle collectionInit( V8Scope* scope, const v8::Arguments& args ) { assert( args.Length() == 4 ); args.This()->Set( scope->getV8Str( "_mongo" ) , args[0] ); args.This()->Set( scope->getV8Str( "_db" ) , args[1] ); args.This()->Set( scope->getV8Str( "_shortName" ) , args[2] ); args.This()->Set( scope->getV8Str( "_fullName" ) , args[3] ); if ( haveLocalShardingInfo( toSTLString( args[3] ) ) ) return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) ); for ( int i=0; iIsUndefined() ); return v8::Undefined(); } v8::Handle dbQueryInit( V8Scope* scope, const v8::Arguments& args ) { v8::Handle t = args.This(); assert( args.Length() >= 4 ); t->Set( scope->getV8Str( "_mongo" ) , args[0] ); t->Set( scope->getV8Str( "_db" ) , args[1] ); t->Set( scope->getV8Str( "_collection" ) , args[2] ); t->Set( scope->getV8Str( "_ns" ) , args[3] ); if ( args.Length() > 4 && args[4]->IsObject() ) t->Set( scope->getV8Str( "_query" ) , args[4] ); else t->Set( scope->getV8Str( "_query" ) , v8::Object::New() ); if ( args.Length() > 5 && args[5]->IsObject() ) t->Set( scope->getV8Str( "_fields" ) , args[5] ); else t->Set( scope->getV8Str( "_fields" ) , v8::Null() ); if ( args.Length() > 6 && args[6]->IsNumber() ) t->Set( scope->getV8Str( "_limit" ) , args[6] ); else t->Set( scope->getV8Str( "_limit" ) , Number::New( 0 ) ); if ( args.Length() > 7 && args[7]->IsNumber() ) t->Set( scope->getV8Str( "_skip" ) , args[7] ); else t->Set( scope->getV8Str( "_skip" ) , Number::New( 0 ) ); if ( args.Length() > 8 && args[8]->IsNumber() ) t->Set( scope->getV8Str( "_batchSize" ) , args[8] ); else t->Set( scope->getV8Str( "_batchSize" ) , Number::New( 0 ) ); if ( args.Length() > 9 && args[9]->IsNumber() ) t->Set( scope->getV8Str( "_options" ) , args[9] ); else t->Set( scope->getV8Str( "_options" ) , Number::New( 0 ) ); t->Set( scope->getV8Str( "_cursor" ) , v8::Null() ); t->Set( scope->getV8Str( "_numReturned" ) , v8::Number::New(0) ); t->Set( scope->getV8Str( "_special" ) , Boolean::New(false) ); return v8::Undefined(); } v8::Handle collectionFallback( v8::Local name, const v8::AccessorInfo &info) { DDD( "collectionFallback [" << name << "]" ); v8::Handle real = info.This()->GetPrototype()->ToObject()->Get( name ); if ( ! real->IsUndefined() ) return real; string sname = toSTLString( name ); if ( sname[0] == '_' ) { if ( ! ( info.This()->HasRealNamedProperty( name ) ) ) return v8::Undefined(); return info.This()->GetRealNamedPropertyInPrototypeChain( name ); } v8::Handle getCollection = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "getCollection" ) ); assert( getCollection->IsFunction() ); v8::Function * f = (v8::Function*)(*getCollection); v8::Handle argv[1]; argv[0] = name; return f->Call( info.This() , 1 , argv ); } v8::Handle dbQueryIndexAccess( unsigned int index , const v8::AccessorInfo& info ) { v8::Handle arrayAccess = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "arrayAccess" ) ); assert( arrayAccess->IsFunction() ); v8::Function * f = (v8::Function*)(*arrayAccess); v8::Handle argv[1]; argv[0] = v8::Number::New( index ); return f->Call( info.This() , 1 , argv ); } v8::Handle objectIdInit( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function * f = scope->getObjectIdCons(); it = f->NewInstance(); } OID oid; if ( args.Length() == 0 ) { oid.init(); } else { string s = toSTLString( args[0] ); try { Scope::validateObjectIdString( s ); } catch ( const MsgAssertionException &m ) { string error = m.toString(); return v8::ThrowException( v8::String::New( error.c_str() ) ); } oid.init( s ); } it->Set( scope->getV8Str( "str" ) , v8::String::New( oid.str().c_str() ) ); return it; } v8::Handle dbRefInit( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 2 && args.Length() != 0) { return v8::ThrowException( v8::String::New( "DBRef needs 2 arguments" ) ); } v8::Handle it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = scope->getNamedCons( "DBRef" ); it = f->NewInstance(); } if ( args.Length() == 2 ) { it->Set( scope->getV8Str( "$ref" ) , args[0] ); it->Set( scope->getV8Str( "$id" ) , args[1] ); } return it; } v8::Handle dbPointerInit( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 2) { return v8::ThrowException( v8::String::New( "DBPointer needs 2 arguments" ) ); } v8::Handle it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = scope->getNamedCons( "DBPointer" ); it = f->NewInstance(); } it->Set( scope->getV8Str( "ns" ) , args[0] ); it->Set( scope->getV8Str( "id" ) , args[1] ); it->SetHiddenValue( scope->getV8Str( "__DBPointer" ), v8::Number::New( 1 ) ); return it; } v8::Handle dbTimestampInit( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); if ( args.Length() == 0 ) { it->Set( scope->getV8Str( "t" ) , v8::Number::New( 0 ) ); it->Set( scope->getV8Str( "i" ) , v8::Number::New( 0 ) ); } else if ( args.Length() == 2 ) { it->Set( scope->getV8Str( "t" ) , args[0] ); it->Set( scope->getV8Str( "i" ) , args[1] ); } else { return v8::ThrowException( v8::String::New( "Timestamp needs 0 or 2 arguments" ) ); } it->SetInternalField( 0, v8::Uint32::New( Timestamp ) ); return it; } v8::Handle binDataInit( V8Scope* scope, const v8::Arguments& args ) { v8::Local it = args.This(); Handle type; Handle len; int rlen; char* data; if (args.Length() == 3) { // 3 args: len, type, data if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = scope->getNamedCons( "BinData" ); it = f->NewInstance(); } len = args[0]; rlen = len->IntegerValue(); type = args[1]; v8::String::Utf8Value utf( args[ 2 ] ); char* tmp = *utf; data = new char[rlen]; memcpy(data, tmp, rlen); } else if ( args.Length() == 2 ) { // 2 args: type, base64 string if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = scope->getNamedCons( "BinData" ); it = f->NewInstance(); } type = args[0]; v8::String::Utf8Value utf( args[ 1 ] ); string decoded = base64::decode( *utf ); const char* tmp = decoded.data(); rlen = decoded.length(); data = new char[rlen]; memcpy(data, tmp, rlen); len = v8::Number::New(rlen); // it->Set( scope->getV8Str( "data" ), v8::String::New( decoded.data(), decoded.length() ) ); } else { return v8::ThrowException( v8::String::New( "BinData needs 2 or 3 arguments" ) ); } it->Set( scope->getV8Str( "len" ) , len ); it->Set( scope->getV8Str( "type" ) , type ); it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) ); Persistent res = scope->wrapArrayObject(it, data); return res; } v8::Handle binDataToString( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); int len = it->Get( scope->V8STR_LEN )->Int32Value(); int type = it->Get( scope->V8STR_TYPE )->Int32Value(); Local c = External::Cast( *(it->GetInternalField( 0 )) ); char* data = (char*)(c->Value()); stringstream ss; ss << "BinData(" << type << ",\""; base64::encode( ss, data, len ); ss << "\")"; string ret = ss.str(); return v8::String::New( ret.c_str() ); } v8::Handle binDataToBase64( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); int len = Handle::Cast(it->Get(scope->V8STR_LEN))->Int32Value(); Local c = External::Cast( *(it->GetInternalField( 0 )) ); char* data = (char*)(c->Value()); stringstream ss; base64::encode( ss, (const char *)data, len ); return v8::String::New(ss.str().c_str()); } v8::Handle binDataToHex( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); int len = Handle::Cast(it->Get(scope->V8STR_LEN))->Int32Value(); Local c = External::Cast( *(it->GetInternalField( 0 )) ); char* data = (char*)(c->Value()); stringstream ss; ss.setf (ios_base::hex , ios_base::basefield); ss.fill ('0'); ss.setf (ios_base::right , ios_base::adjustfield); for( int i = 0; i < len; i++ ) { unsigned v = (unsigned char) data[i]; ss << setw(2) << v; } return v8::String::New(ss.str().c_str()); } static v8::Handle hexToBinData( V8Scope* scope, v8::Local it, int type, string hexstr ) { int len = hexstr.length() / 2; char* data = new char[len]; const char* src = hexstr.c_str(); for( int i = 0; i < 16; i++ ) { data[i] = fromHex(src + i * 2); } it->Set( scope->V8STR_LEN , v8::Number::New(len) ); it->Set( scope->V8STR_TYPE , v8::Number::New(type) ); it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) ); Persistent res = scope->wrapArrayObject(it, data); return res; } v8::Handle uuidInit( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 1) { return v8::ThrowException( v8::String::New( "UUIS needs 1 argument" ) ); } v8::String::Utf8Value utf( args[ 0 ] ); if( utf.length() != 32 ) { return v8::ThrowException( v8::String::New( "UUIS string must have 32 characters" ) ); } return hexToBinData(scope, args.This(), bdtUUID, *utf); } // v8::Handle uuidToString( V8Scope* scope, const v8::Arguments& args ) { // v8::Handle it = args.This(); // Local c = External::Cast( *(it->GetInternalField( 0 )) ); // char* data = (char*)(c->Value()); // // stringstream ss; // ss << "UUID(\"" << toHex(data, 16) << "\")"; // return v8::String::New( ss.str().c_str() ); // } v8::Handle md5Init( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 1) { return v8::ThrowException( v8::String::New( "MD5 needs 1 argument" ) ); } v8::String::Utf8Value utf( args[ 0 ] ); if( utf.length() != 32 ) { return v8::ThrowException( v8::String::New( "MD5 string must have 32 characters" ) ); } return hexToBinData(scope, args.This(), MD5Type, *utf); } v8::Handle hexDataInit( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 2) { return v8::ThrowException( v8::String::New( "HexData needs 2 arguments" ) ); } v8::String::Utf8Value utf( args[ 1 ] ); return hexToBinData(scope, args.This(), args[0]->IntegerValue(), *utf); } v8::Handle numberLongInit( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 0 && args.Length() != 1 && args.Length() != 3) { return v8::ThrowException( v8::String::New( "NumberLong needs 0, 1 or 3 arguments" ) ); } v8::Handle it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = scope->getNamedCons( "NumberLong" ); it = f->NewInstance(); } if ( args.Length() == 0 ) { it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( 0 ) ); } else if ( args.Length() == 1 ) { if ( args[ 0 ]->IsNumber() ) { it->Set( scope->getV8Str( "floatApprox" ), args[ 0 ] ); } else { v8::String::Utf8Value data( args[ 0 ] ); string num = *data; const char *numStr = num.c_str(); long long n; try { n = parseLL( numStr ); } catch ( const AssertionException & ) { return v8::ThrowException( v8::String::New( "could not convert string to long long" ) ); } unsigned long long val = n; if ( (long long)val == (long long)(double)(long long)(val) ) { it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) ); } else { it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) ); it->Set( scope->getV8Str( "top" ), v8::Integer::New( val >> 32 ) ); it->Set( scope->getV8Str( "bottom" ), v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ) ); } } } else { it->Set( scope->getV8Str( "floatApprox" ) , args[0] ); it->Set( scope->getV8Str( "top" ) , args[1] ); it->Set( scope->getV8Str( "bottom" ) , args[2] ); } it->SetHiddenValue( scope->V8STR_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 numberLongValueOf( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); long long val = numberLongVal( it ); return v8::Number::New( double( val ) ); } v8::Handle numberLongToNumber( V8Scope* scope, const v8::Arguments& args ) { return numberLongValueOf( scope, args ); } v8::Handle numberLongToString( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); stringstream ss; long long val = numberLongVal( it ); const long long limit = 2LL << 30; if ( val <= -limit || limit <= val ) ss << "NumberLong(\"" << val << "\")"; else ss << "NumberLong(" << val << ")"; string ret = ss.str(); return v8::String::New( ret.c_str() ); } v8::Handle numberIntInit( V8Scope* scope, const v8::Arguments& args ) { if (args.Length() != 0 && args.Length() != 1) { return v8::ThrowException( v8::String::New( "NumberInt needs 0, 1 argument" ) ); } v8::Handle it = args.This(); if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { v8::Function* f = scope->getNamedCons( "NumberInt" ); it = f->NewInstance(); } if ( args.Length() == 0 ) { it->SetHiddenValue( scope->V8STR_NUMBERINT, v8::Number::New( 0 ) ); } else if ( args.Length() == 1 ) { it->SetHiddenValue( scope->V8STR_NUMBERINT, args[0]->ToInt32() ); } return it; } v8::Handle numberIntValueOf( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value(); return v8::Number::New( double( val ) ); } v8::Handle numberIntToNumber( V8Scope* scope, const v8::Arguments& args ) { return numberIntValueOf( scope, args ); } v8::Handle numberIntToString( V8Scope* scope, const v8::Arguments& args ) { v8::Handle it = args.This(); stringstream ss; int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value(); ss << "NumberInt(" << val << ")"; string ret = ss.str(); return v8::String::New( ret.c_str() ); } v8::Handle bsonsize( V8Scope* scope, const v8::Arguments& args ) { if ( args.Length() != 1 ) return v8::ThrowException( v8::String::New( "bsonsize needs 1 argument" ) ); if ( args[0]->IsNull() ) return v8::Number::New(0); if ( ! args[ 0 ]->IsObject() ) return v8::ThrowException( v8::String::New( "argument to bsonsize has to be an object" ) ); return v8::Number::New( scope->v8ToMongo( args[ 0 ]->ToObject() ).objsize() ); } // to be called with v8 mutex void enableV8Interrupt() { if ( globalScriptEngine->haveGetInterruptSpecCallback() ) { __interruptSpecToThreadId[ globalScriptEngine->getInterruptSpec() ] = v8::V8::GetCurrentThreadId(); } } // to be called with v8 mutex void disableV8Interrupt() { if ( globalScriptEngine->haveGetInterruptSpecCallback() ) { __interruptSpecToThreadId.erase( globalScriptEngine->getInterruptSpec() ); } } namespace v8Locks { boost::mutex& __v8Mutex = *( new boost::mutex ); ThreadLocalValue< bool > __locked; RecursiveLock::RecursiveLock() : _unlock() { if ( !__locked.get() ) { __v8Mutex.lock(); __locked.set( true ); _unlock = true; } } RecursiveLock::~RecursiveLock() { if ( _unlock ) { __v8Mutex.unlock(); __locked.set( false ); } } RecursiveUnlock::RecursiveUnlock() : _lock() { if ( __locked.get() ) { __v8Mutex.unlock(); __locked.set( false ); _lock = true; } } RecursiveUnlock::~RecursiveUnlock() { if ( _lock ) { __v8Mutex.lock(); __locked.set( true ); } } } // namespace v8Locks }