summaryrefslogtreecommitdiff
path: root/src/mongo/scripting/v8_db.cpp
diff options
context:
space:
mode:
authorEliot Horowitz <eliot@10gen.com>2011-12-24 15:33:26 -0500
committerEliot Horowitz <eliot@10gen.com>2011-12-24 15:33:45 -0500
commitae1ecd9c786911f9f1f0242f0f7d702b3e5dfeba (patch)
tree92f8e1649e6f080b251ff5f1763679a72eb59b34 /src/mongo/scripting/v8_db.cpp
parentdfa4cd7e2cf109b072440155fabc08a93c8045a0 (diff)
downloadmongo-ae1ecd9c786911f9f1f0242f0f7d702b3e5dfeba.tar.gz
bulk move of code to src/ SERVER-4551
Diffstat (limited to 'src/mongo/scripting/v8_db.cpp')
-rw-r--r--src/mongo/scripting/v8_db.cpp1128
1 files changed, 1128 insertions, 0 deletions
diff --git a/src/mongo/scripting/v8_db.cpp b/src/mongo/scripting/v8_db.cpp
new file mode 100644
index 00000000000..de419b368d9
--- /dev/null
+++ b/src/mongo/scripting/v8_db.cpp
@@ -0,0 +1,1128 @@
+// 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 <iostream>
+
+using namespace std;
+using namespace v8;
+
+namespace mongo {
+
+#define DDD(x)
+
+ static v8::Handle<v8::Value> newInstance( v8::Function* f, const v8::Arguments& args ) {
+ // need to translate arguments into an array
+ int argc = args.Length();
+ scoped_array< Handle<Value> > argv( new Handle<Value>[argc] );
+ for (int i = 0; i < argc; ++i) {
+ argv[i] = args[i];
+ }
+ return f->NewInstance(argc, argv.get());
+ }
+
+ v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( V8Scope* scope, bool local ) {
+ v8::Handle<v8::FunctionTemplate> mongo;
+ if ( local ) {
+ mongo = scope->createV8Function(mongoConsLocal);
+ }
+ else {
+ mongo = scope->createV8Function(mongoConsExternal);
+ }
+ mongo->InstanceTemplate()->SetInternalFieldCount( 1 );
+ v8::Handle<v8::Template> proto = mongo->PrototypeTemplate();
+ scope->injectV8Function("find", mongoFind, proto);
+ scope->injectV8Function("insert", mongoInsert, proto);
+ scope->injectV8Function("remove", mongoRemove, proto);
+ scope->injectV8Function("update", mongoUpdate, proto);
+ scope->injectV8Function("auth", mongoAuth, proto);
+
+ v8::Handle<FunctionTemplate> ic = scope->createV8Function(internalCursorCons);
+ ic->InstanceTemplate()->SetInternalFieldCount( 1 );
+ v8::Handle<v8::Template> icproto = ic->PrototypeTemplate();
+ scope->injectV8Function("next", internalCursorNext, icproto);
+ scope->injectV8Function("hasNext", internalCursorHasNext, icproto);
+ scope->injectV8Function("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto);
+ scope->injectV8Function("readOnly", internalCursorReadOnly, icproto);
+ proto->Set( scope->getV8Str( "internalCursor" ) , ic );
+
+ return mongo;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> numberLong = scope->createV8Function(numberLongInit);
+ v8::Local<v8::Template> proto = numberLong->PrototypeTemplate();
+ scope->injectV8Function("valueOf", numberLongValueOf, proto);
+ scope->injectV8Function("toNumber", numberLongToNumber, proto);
+ scope->injectV8Function("toString", numberLongToString, proto);
+
+ return numberLong;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getNumberIntFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> numberInt = scope->createV8Function(numberIntInit);
+ v8::Local<v8::Template> proto = numberInt->PrototypeTemplate();
+ scope->injectV8Function("valueOf", numberIntValueOf, proto);
+ scope->injectV8Function("toNumber", numberIntToNumber, proto);
+ scope->injectV8Function("toString", numberIntToString, proto);
+
+ return numberInt;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getBinDataFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> binData = scope->createV8Function(binDataInit);
+ binData->InstanceTemplate()->SetInternalFieldCount(1);
+ v8::Local<v8::Template> proto = binData->PrototypeTemplate();
+ scope->injectV8Function("toString", binDataToString, proto);
+ scope->injectV8Function("base64", binDataToBase64, proto);
+ scope->injectV8Function("hex", binDataToHex, proto);
+ return binData;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getUUIDFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(uuidInit);
+ return templ;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getMD5FunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(md5Init);
+ return templ;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getHexDataFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(hexDataInit);
+ return templ;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getTimestampFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> ts = scope->createV8Function(dbTimestampInit);
+ ts->InstanceTemplate()->SetInternalFieldCount( 1 );
+ return ts;
+ }
+
+// void installDBTypes( V8Scope* scope, Handle<ObjectTemplate>& global ) {
+// v8::Handle<v8::FunctionTemplate> db = scope->createV8Function(dbInit);
+// db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback );
+// global->Set(v8::String::New("DB") , db );
+//
+// v8::Handle<v8::FunctionTemplate> dbCollection = scope->createV8Function(collectionInit);
+// dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback );
+// global->Set(v8::String::New("DBCollection") , dbCollection );
+//
+//
+// v8::Handle<v8::FunctionTemplate> dbQuery = scope->createV8Function(dbQueryInit);
+// dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess );
+// global->Set(v8::String::New("DBQuery") , dbQuery );
+//
+// global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >(scope) );
+//
+// global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >(scope) );
+//
+// global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >(scope) );
+//
+// global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate(scope) );
+//
+// global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate(scope) );
+//
+// global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate(scope) );
+// }
+
+ void installDBTypes( V8Scope* scope, v8::Handle<v8::Object>& global ) {
+ v8::Handle<v8::FunctionTemplate> db = scope->createV8Function(dbInit);
+ db->InstanceTemplate()->SetNamedPropertyHandler( collectionGetter, collectionSetter );
+ global->Set(scope->getV8Str("DB") , db->GetFunction() );
+ v8::Handle<v8::FunctionTemplate> dbCollection = scope->createV8Function(collectionInit);
+ dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionGetter, collectionSetter );
+ global->Set(scope->getV8Str("DBCollection") , dbCollection->GetFunction() );
+
+
+ v8::Handle<v8::FunctionTemplate> 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<Value> self, void* parameter) {
+ delete static_cast<DBClientBase*>(parameter);
+ self.Dispose();
+ self.Clear();
+ }
+
+ Handle<Value> 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<v8::Object> self = Persistent<v8::Object>::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<Value> 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<v8::Object> self = Persistent<v8::Object>::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<External> c = External::Cast( *(args.This()->GetInternalField( 0 )) );
+ DBClientBase * conn = (DBClientBase*)(c->Value());
+ assert( conn );
+ return conn;
+ }
+
+ // ---- real methods
+
+ void destroyCursor( Persistent<Value> self, void* parameter) {
+ delete static_cast<mongo::DBClientCursor*>(parameter);
+ self.Dispose();
+ self.Clear();
+ }
+
+ /**
+ 0 - namespace
+ 1 - query
+ 2 - fields
+ 3 - limit
+ 4 - skip
+ */
+ Handle<Value> 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<v8::Object> mongo = args.This();
+
+ try {
+ auto_ptr<mongo::DBClientCursor> cursor;
+ int nToReturn = (int)(args[3]->ToNumber()->Value());
+ int nToSkip = (int)(args[4]->ToNumber()->Value());
+ int batchSize = (int)(args[5]->ToNumber()->Value());
+ int options = (int)(args[6]->ToNumber()->Value());
+ {
+ V8Unlock u;
+ cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, options , batchSize );
+ if ( ! cursor.get() )
+ return v8::ThrowException( v8::String::New( "error doing query: failed" ) );
+ }
+ v8::Function * cons = (v8::Function*)( *( mongo->Get( scope->getV8Str( "internalCursor" ) ) ) );
+ if ( !cons ) {
+ // may get here in case of thread termination
+ return v8::ThrowException( v8::String::New( "Could not create a cursor" ) );
+ }
+
+ Persistent<v8::Object> c = Persistent<v8::Object>::New( cons->NewInstance() );
+ c.MakeWeak( cursor.get() , destroyCursor );
+ c->SetInternalField( 0 , External::New( cursor.release() ) );
+ return handle_scope.Close(c);
+ }
+ catch ( ... ) {
+ return v8::ThrowException( v8::String::New( "socket error on query" ) );
+ }
+ }
+
+ v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args) {
+ jsassert( args.Length() == 2 , "insert needs 2 args" );
+ jsassert( args[1]->IsObject() , "have to insert an object" );
+
+ if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() )
+ return v8::ThrowException( v8::String::New( "js db in read only mode" ) );
+
+ DBClientBase * conn = getConnection( args );
+ GETNS;
+
+ v8::Handle<v8::Object> in = args[1]->ToObject();
+
+ if( args[1]->IsArray() ){
+
+ v8::Local<v8::Array> arr = v8::Array::Cast( *args[1] );
+ vector<BSONObj> bos;
+ uint32_t len = arr->Length();
+
+ for( uint32_t i = 0; i < len; i++ ){
+
+ v8::Local<v8::Object> el = arr->CloneElementAt( i );
+
+ // Set ID on the element if necessary
+ if ( ! el->Has( scope->getV8Str( "_id" ) ) ) {
+ v8::Handle<v8::Value> argv[1];
+ el->Set( scope->getV8Str( "_id" ) , scope->getObjectIdCons()->NewInstance( 0 , argv ) );
+ }
+
+ bos.push_back( scope->v8ToMongo( arr->CloneElementAt( i ) ) );
+ }
+
+ DDD( "want to save batch : " << bos.length );
+ try {
+ V8Unlock u;
+ conn->insert( ns , bos );
+ }
+ catch ( ... ) {
+ return v8::ThrowException( v8::String::New( "socket error on bulk insert" ) );
+ }
+
+ }
+ else {
+
+ if ( ! in->Has( scope->getV8Str( "_id" ) ) ) {
+ v8::Handle<v8::Value> 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<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args) {
+ jsassert( args.Length() == 2 || args.Length() == 3 , "remove needs 2 args" );
+ jsassert( args[1]->IsObject() , "have to remove an object template" );
+
+ if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() )
+ return v8::ThrowException( v8::String::New( "js db in read only mode" ) );
+
+ DBClientBase * conn = getConnection( args );
+ GETNS;
+
+ v8::Handle<v8::Object> in = args[1]->ToObject();
+ BSONObj o = 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<v8::Value> mongoUpdate(V8Scope* scope, const v8::Arguments& args) {
+ jsassert( args.Length() >= 3 , "update needs at least 3 args" );
+ jsassert( args[1]->IsObject() , "1st param to update has to be an object" );
+ jsassert( args[2]->IsObject() , "2nd param to update has to be an object" );
+
+ if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() )
+ return v8::ThrowException( v8::String::New( "js db in read only mode" ) );
+
+ DBClientBase * conn = getConnection( args );
+ GETNS;
+
+ v8::Handle<v8::Object> q = args[1]->ToObject();
+ v8::Handle<v8::Object> o = args[2]->ToObject();
+
+ bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value();
+ bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value();
+
+ 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();
+ }
+
+ v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args) {
+ jsassert( args.Length() >= 3 , "update needs at least 3 args" );
+ DBClientBase * conn = getConnection( args );
+ string db = toSTLString(args[0]);
+ string username = toSTLString(args[1]);
+ string password = toSTLString(args[2]);
+ string errmsg = "";
+
+ try {
+ if (conn->auth(db, username, password, errmsg)) {
+ return v8::Boolean::New(true);
+ }
+ } catch ( ... ) {
+ }
+ return v8::ThrowException( v8::String::New( errmsg.c_str() ) );
+ }
+
+// + JSBool mongo_auth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+// + smuassert( cx , "mongo_auth needs 3 args" , argc == 3 );
+// + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj );
+// + smuassert( cx , "no connection!" , connHolder && connHolder->get() );
+// + DBClientWithCommands *conn = connHolder->get();
+// +
+// + Convertor c( cx );
+// +
+// + string db = c.toString( argv[0] );
+// + string username = c.toString( argv[1] );
+// + string password = c.toString( argv[2] );
+// + string errmsg = "";
+// +
+// + try {
+// + if (conn->auth(db, username, password, errmsg)) {
+// + return JS_TRUE;
+// + }
+// + JS_ReportError( cx, errmsg.c_str() );
+// + }
+// + catch ( ... ) {
+// + JS_ReportError( cx , "error doing query: unknown" );
+// + }
+// + return JS_FALSE;
+// + }
+
+
+ // --- cursor ---
+
+ mongo::DBClientCursor * getCursor( const Arguments& args ) {
+ Local<External> c = External::Cast( *(args.This()->GetInternalField( 0 ) ) );
+
+ mongo::DBClientCursor * cursor = (mongo::DBClientCursor*)(c->Value());
+ return cursor;
+ }
+
+ v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args) {
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> 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<v8::Value> 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<v8::Value> 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<v8::Value> internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args) {
+ Local<v8::Object> cursor = args.This();
+ cursor->Set(scope->V8STR_RO, v8::Boolean::New(true));
+ return cursor;
+ }
+
+ // --- DB ----
+
+ v8::Handle<v8::Value> 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; i<args.Length(); i++ )
+ assert( ! args[i]->IsUndefined() );
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> 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->V8STR_FULLNAME , args[3] );
+
+ if ( haveLocalShardingInfo( toSTLString( args[3] ) ) )
+ return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) );
+
+ for ( int i=0; i<args.Length(); i++ )
+ assert( ! args[i]->IsUndefined() );
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> dbQueryInit( V8Scope* scope, const v8::Arguments& args ) {
+
+ v8::Handle<v8::Object> 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();
+ }
+
+ Handle<Value> collectionSetter( Local<v8::String> name, Local<Value> value, const AccessorInfo& info ) {
+ // a collection name cannot be overwritten by a variable
+ string sname = toSTLString( name );
+ if ( sname.length() == 0 || sname[0] == '_' ) {
+ // if starts with '_' we allow overwrite
+ return Handle<Value>();
+ }
+ // dont set
+ return value;
+ }
+
+ v8::Handle<v8::Value> collectionGetter( v8::Local<v8::String> name, const v8::AccessorInfo &info) {
+ DDD( "collectionFallback [" << name << "]" );
+
+ // first look in prototype, may be a function
+ v8::Handle<v8::Value> real = info.This()->GetPrototype()->ToObject()->Get( name );
+ if ( !real->IsUndefined() )
+ return real;
+
+ // 2nd look into real values, may be cached collection object
+ string sname = toSTLString( name );
+ if (info.This()->HasRealNamedProperty(name)) {
+ v8::Local<v8::Value> prop = info.This()->GetRealNamedProperty( name );
+ if (prop->IsObject() && prop->ToObject()->HasRealNamedProperty(v8::String::New("_fullName"))) {
+ // need to check every time that the collection did not get sharded
+ if ( haveLocalShardingInfo( toSTLString( prop->ToObject()->GetRealNamedProperty(v8::String::New("_fullName")) ) ) )
+ return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) );
+ }
+ return prop;
+ } else if ( sname.length() == 0 || sname[0] == '_' ) {
+ // if starts with '_' we dont return collection, one must use getCollection()
+ return v8::Undefined();
+ }
+
+ // no hit, create new collection
+ v8::Handle<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "getCollection" ) );
+ assert( getCollection->IsFunction() );
+
+ TryCatch tryCatch;
+ v8::Function * f = (v8::Function*)(*getCollection);
+ v8::Handle<v8::Value> argv[1];
+ argv[0] = name;
+ v8::Local<v8::Value> coll = f->Call( info.This() , 1 , argv );
+ if (coll.IsEmpty()) {
+ if (tryCatch.HasCaught()) {
+ return v8::ThrowException( tryCatch.Exception() );
+ }
+ return Handle<Value>();
+ }
+
+ // cache collection for reuse, dont enumerate
+ info.This()->ForceSet(name, coll, v8::DontEnum);
+ return coll;
+ }
+
+ v8::Handle<v8::Value> dbQueryIndexAccess( unsigned int index , const v8::AccessorInfo& info ) {
+ v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "arrayAccess" ) );
+ assert( arrayAccess->IsFunction() );
+
+ v8::Function * f = (v8::Function*)(*arrayAccess);
+ v8::Handle<v8::Value> argv[1];
+ argv[0] = v8::Number::New( index );
+
+ return f->Call( info.This() , 1 , argv );
+ }
+
+ v8::Handle<v8::Value> objectIdInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getObjectIdCons();
+ return newInstance(f, args);
+ }
+
+ 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<v8::Value> dbRefInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "DBRef" );
+ return newInstance(f, args);
+ }
+
+ if (args.Length() != 2 && args.Length() != 0) {
+ return v8::ThrowException( v8::String::New( "DBRef needs 2 arguments" ) );
+ }
+
+ if ( args.Length() == 2 ) {
+ it->Set( scope->getV8Str( "$ref" ) , args[0] );
+ it->Set( scope->getV8Str( "$id" ) , args[1] );
+ }
+
+ return it;
+ }
+
+ v8::Handle<v8::Value> dbPointerInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "DBPointer" );
+ return newInstance(f, args);
+ }
+
+ if (args.Length() != 2) {
+ return v8::ThrowException( v8::String::New( "DBPointer needs 2 arguments" ) );
+ }
+
+ it->Set( scope->getV8Str( "ns" ) , args[0] );
+ it->Set( scope->getV8Str( "id" ) , args[1] );
+ it->SetHiddenValue( scope->getV8Str( "__DBPointer" ), v8::Number::New( 1 ) );
+
+ return it;
+ }
+
+ v8::Handle<v8::Value> dbTimestampInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "Timestamp" );
+ return newInstance(f, args);
+ }
+
+ 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<v8::Value> binDataInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Local<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function* f = scope->getNamedCons( "BinData" );
+ return newInstance(f, args);
+ }
+
+ Handle<Value> type;
+ Handle<Value> len;
+ int rlen;
+ char* data;
+ if (args.Length() == 3) {
+ // 3 args: len, type, data
+ 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
+ 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 if (args.Length() == 0) {
+ // this is called by subclasses that will fill properties
+ return it;
+ } 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<v8::Object> res = scope->wrapArrayObject(it, data);
+ return res;
+ }
+
+ v8::Handle<v8::Value> binDataToString( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ int len = it->Get( scope->V8STR_LEN )->Int32Value();
+ int type = it->Get( scope->V8STR_TYPE )->Int32Value();
+ Local<External> c = External::Cast( *(it->GetInternalField( 0 )) );
+ char* data = (char*)(c->Value());
+
+ stringstream ss;
+ ss << "BinData(" << type << ",\"";
+ base64::encode( ss, data, len );
+ ss << "\")";
+ string ret = ss.str();
+ return v8::String::New( ret.c_str() );
+ }
+
+ v8::Handle<v8::Value> binDataToBase64( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ int len = Handle<v8::Number>::Cast(it->Get(scope->V8STR_LEN))->Int32Value();
+ Local<External> c = External::Cast( *(it->GetInternalField( 0 )) );
+ char* data = (char*)(c->Value());
+ stringstream ss;
+ base64::encode( ss, (const char *)data, len );
+ return v8::String::New(ss.str().c_str());
+ }
+
+ v8::Handle<v8::Value> binDataToHex( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ int len = Handle<v8::Number>::Cast(it->Get(scope->V8STR_LEN))->Int32Value();
+ Local<External> c = External::Cast( *(it->GetInternalField( 0 )) );
+ char* data = (char*)(c->Value());
+ stringstream ss;
+ ss.setf (ios_base::hex , ios_base::basefield);
+ ss.fill ('0');
+ ss.setf (ios_base::right , ios_base::adjustfield);
+ for( int i = 0; i < len; i++ ) {
+ unsigned v = (unsigned char) data[i];
+ ss << setw(2) << v;
+ }
+ return v8::String::New(ss.str().c_str());
+ }
+
+ static v8::Handle<v8::Value> hexToBinData( V8Scope* scope, v8::Local<v8::Object> it, int type, string hexstr ) {
+ int len = hexstr.length() / 2;
+ char* data = new char[len];
+ const char* src = hexstr.c_str();
+ for( int i = 0; i < 16; i++ ) {
+ data[i] = fromHex(src + i * 2);
+ }
+
+ it->Set( scope->V8STR_LEN , v8::Number::New(len) );
+ it->Set( scope->V8STR_TYPE , v8::Number::New(type) );
+ it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) );
+ Persistent<v8::Object> res = scope->wrapArrayObject(it, data);
+ return res;
+ }
+
+ v8::Handle<v8::Value> uuidInit( V8Scope* scope, const v8::Arguments& args ) {
+ if (args.Length() != 1) {
+ return v8::ThrowException( v8::String::New( "UUIS needs 1 argument" ) );
+ }
+ v8::String::Utf8Value utf( args[ 0 ] );
+ if( utf.length() != 32 ) {
+ return v8::ThrowException( v8::String::New( "UUIS string must have 32 characters" ) );
+ }
+
+ v8::Function * f = scope->getNamedCons("BinData");
+ Local<v8::Object> it = f->NewInstance();
+ return hexToBinData(scope, it, bdtUUID, *utf);
+ }
+
+ v8::Handle<v8::Value> md5Init( V8Scope* scope, const v8::Arguments& args ) {
+ if (args.Length() != 1) {
+ return v8::ThrowException( v8::String::New( "MD5 needs 1 argument" ) );
+ }
+ v8::String::Utf8Value utf( args[ 0 ] );
+ if( utf.length() != 32 ) {
+ return v8::ThrowException( v8::String::New( "MD5 string must have 32 characters" ) );
+ }
+
+ v8::Function * f = scope->getNamedCons("BinData");
+ Local<v8::Object> it = f->NewInstance();
+ return hexToBinData(scope, it, MD5Type, *utf);
+ }
+
+ v8::Handle<v8::Value> hexDataInit( V8Scope* scope, const v8::Arguments& args ) {
+ if (args.Length() != 2) {
+ return v8::ThrowException( v8::String::New( "HexData needs 2 arguments" ) );
+ }
+ v8::String::Utf8Value utf( args[ 1 ] );
+ v8::Function * f = scope->getNamedCons("BinData");
+ Local<v8::Object> it = f->NewInstance();
+ return hexToBinData(scope, it, args[0]->IntegerValue(), *utf);
+ }
+
+ v8::Handle<v8::Value> numberLongInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "NumberLong" );
+ return newInstance(f, args);
+ }
+
+ if (args.Length() != 0 && args.Length() != 1 && args.Length() != 3) {
+ return v8::ThrowException( v8::String::New( "NumberLong needs 0, 1 or 3 arguments" ) );
+ }
+
+ 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;
+ // values above 2^53 are not accurately represented in JS
+ if ( (long long)val == (long long)(double)(long long)(val) && val < 9007199254740992ULL ) {
+ 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<v8::Value> numberLongValueOf( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ long long val = numberLongVal( it );
+ return v8::Number::New( double( val ) );
+ }
+
+ v8::Handle<v8::Value> numberLongToNumber( V8Scope* scope, const v8::Arguments& args ) {
+ return numberLongValueOf( scope, args );
+ }
+
+ v8::Handle<v8::Value> numberLongToString( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> 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<v8::Value> numberIntInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "NumberInt" );
+ return newInstance(f, args);
+ }
+
+ if (args.Length() != 0 && args.Length() != 1) {
+ return v8::ThrowException( v8::String::New( "NumberInt needs 0, 1 argument" ) );
+ }
+
+ if ( args.Length() == 0 ) {
+ it->SetHiddenValue( scope->V8STR_NUMBERINT, v8::Number::New( 0 ) );
+ }
+ else if ( args.Length() == 1 ) {
+ it->SetHiddenValue( scope->V8STR_NUMBERINT, args[0]->ToInt32() );
+ }
+
+ return it;
+ }
+
+ v8::Handle<v8::Value> numberIntValueOf( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value();
+ return v8::Number::New( double( val ) );
+ }
+
+ v8::Handle<v8::Value> numberIntToNumber( V8Scope* scope, const v8::Arguments& args ) {
+ return numberIntValueOf( scope, args );
+ }
+
+ v8::Handle<v8::Value> numberIntToString( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+
+ stringstream ss;
+ int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value();
+ ss << "NumberInt(" << val << ")";
+
+ string ret = ss.str();
+ return v8::String::New( ret.c_str() );
+ }
+
+ v8::Handle<v8::Value> bsonsize( V8Scope* scope, const v8::Arguments& args ) {
+
+ if ( args.Length() != 1 )
+ return v8::ThrowException( v8::String::New( "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() );
+ }
+
+ namespace v8Locks {
+ boost::mutex& __interruptMutex = *( new boost::mutex );
+
+ InterruptLock::InterruptLock() {
+ __interruptMutex.lock();
+ }
+
+ InterruptLock::~InterruptLock() {
+ __interruptMutex.unlock();
+ }
+
+ 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
+}