diff options
author | Eliot Horowitz <eliot@10gen.com> | 2009-10-10 01:30:00 -0400 |
---|---|---|
committer | Eliot Horowitz <eliot@10gen.com> | 2009-10-10 01:30:00 -0400 |
commit | 96d0418d96ccc5c5ff13330551b85410a30bd155 (patch) | |
tree | db93b622a39be4a37fcad3e373a96a477c3dde83 | |
parent | 8a6dfdc9e967dca275720391dfa297087d4337c0 (diff) | |
download | mongo-96d0418d96ccc5c5ff13330551b85410a30bd155.tar.gz |
checkpoint for playing with v8
-rw-r--r-- | SConstruct | 20 | ||||
-rw-r--r-- | dbtests/jstests.cpp | 9 | ||||
-rw-r--r-- | scripting/engine_v8.cpp | 49 | ||||
-rw-r--r-- | scripting/engine_v8.h | 76 | ||||
-rw-r--r-- | scripting/v8_utils.cpp | 136 | ||||
-rw-r--r-- | scripting/v8_utils.h | 34 | ||||
-rw-r--r-- | scripting/v8_wrapper.cpp | 692 | ||||
-rw-r--r-- | scripting/v8_wrapper.h | 55 |
8 files changed, 1035 insertions, 36 deletions
diff --git a/SConstruct b/SConstruct index 135ee402c58..086f45a14d3 100644 --- a/SConstruct +++ b/SConstruct @@ -101,6 +101,13 @@ AddOption('--usesm', action="store", help="use spider monkey for javascript" ) +AddOption('--usev8', + dest='usev8', + type="string", + nargs=0, + action="store", + help="use v8 for javascript" ) + AddOption('--usejvm', dest='usejvm', type="string", @@ -213,6 +220,7 @@ noshell = not GetOption( "noshell" ) is None nojni = not GetOption( "nojni" ) is None usesm = not GetOption( "usesm" ) is None +usev8 = not GetOption( "usev8" ) is None usejvm = not GetOption( "usejvm" ) is None env = Environment( MSVS_ARCH=msarch , tools = ["default", "gch"], toolpath = '.' ) @@ -244,7 +252,7 @@ if ( usesm and usejvm ): print( "can't say usesm and usejvm at the same time" ) Exit(1) -if ( not ( usesm or usejvm ) ): +if ( not ( usesm or usejvm or usev8 ) ): usesm = True if GetOption( "extrapath" ) is not None: @@ -284,6 +292,9 @@ serverOnlyFiles = Split( "db/query.cpp db/introspect.cpp db/btree.cpp db/clientc if usesm: commonFiles += [ "scripting/engine_spidermonkey.cpp" ] nojni = True +elif usev8: + commonFiles += [ Glob( "scripting/*v8*.cpp" ) ] + nojni = True elif not nojni: commonFiles += [ "scripting/engine_java.cpp" ] else: @@ -547,6 +558,10 @@ if nix: #Depends( "stdafx.o" , "stdafx.h.gch" ) #SideEffect( "dummyGCHSideEffect" , "stdafx.h.gch" ) +if usev8: + env.Append( CPPPATH=["../v8/include/"] ) + env.Append( LIBPATH=["../v8/"] ) + if "uname" in dir(os): hacks = buildscripts.findHacks( os.uname() ) @@ -722,6 +737,9 @@ def doConfigure( myenv , needJava=True , needPcre=True , shell=False ): print( "no spider monkey headers!" ) Exit(1) + if usev8: + myCheckLib( "v8" , True ) + if shell: haveReadLine = False if darwin: diff --git a/dbtests/jstests.cpp b/dbtests/jstests.cpp index 7e02e50e612..de2eb0c9451 100644 --- a/dbtests/jstests.cpp +++ b/dbtests/jstests.cpp @@ -46,11 +46,12 @@ namespace JSTests { class BasicScope { public: void run(){ - Scope * s = globalScriptEngine->createScope(); - + auto_ptr<Scope> s; + s.reset( globalScriptEngine->createScope() ); + s->setNumber( "x" , 5 ); ASSERT( 5 == s->getNumber( "x" ) ); - + s->setNumber( "x" , 1.67 ); ASSERT( 1.67 == s->getNumber( "x" ) ); @@ -64,8 +65,6 @@ namespace JSTests { s->setBoolean( "b" , false ); ASSERT( ! s->getBoolean( "b" ) ); } - - delete s; } }; diff --git a/scripting/engine_v8.cpp b/scripting/engine_v8.cpp index d7c14cb21fd..261fe8a9dfb 100644 --- a/scripting/engine_v8.cpp +++ b/scripting/engine_v8.cpp @@ -1,15 +1,36 @@ #include "engine_v8.h" -#include "../shell/MongoJS.h" +#include "v8_wrapper.h" +#include "v8_utils.h" namespace mongo { + V8ScriptEngine::V8ScriptEngine() + : _handleScope() , _globalTemplate( ObjectTemplate::New() ) { + + } + + V8ScriptEngine::~V8ScriptEngine(){ + } + void ScriptEngine::setup(){ if ( !globalScriptEngine ){ globalScriptEngine = new V8ScriptEngine(); } } - + + V8Scope::V8Scope( V8ScriptEngine * engine ) + : _handleScope(), + _context( Context::New( 0 , engine->_globalTemplate ) ) , + _scope( _context ) , + _global( _context->Global() ){ + + } + + V8Scope::~V8Scope(){ + + } + Handle< Value > V8Scope::nativeCallback( const Arguments &args ) { Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_native_function" ) ) ); NativeFunction function = ( NativeFunction )( f->Value() ); @@ -30,4 +51,28 @@ namespace mongo { return mongoToV8Element( ret.firstElement() ); } + void V8Scope::setNumber( const char * field , double val ){ + _global->Set( v8::String::New( field ) , v8::Number::New( val ) ); + } + + void V8Scope::setString( const char * field , const char * val ){ + _global->Set( v8::String::New( field ) , v8::String::New( val ) ); + } + + void V8Scope::setBoolean( const char * field , bool val ){ + _global->Set( v8::String::New( field ) , v8::Boolean::New( val ) ); + } + + double V8Scope::getNumber( const char *field ){ + return _global->Get( v8::String::New( field ) )->ToNumber()->Value(); + } + + string V8Scope::getString( const char *field ){ + return toSTLString( _global->Get( v8::String::New( field ) ) ); + } + + bool V8Scope::getBoolean( const char *field ){ + return _global->Get( v8::String::New( field ) )->ToBoolean()->Value(); + } + } // namespace mongo diff --git a/scripting/engine_v8.h b/scripting/engine_v8.h index db1827e967b..61b026b4719 100644 --- a/scripting/engine_v8.h +++ b/scripting/engine_v8.h @@ -6,54 +6,74 @@ using namespace v8; namespace mongo { + + class V8ScriptEngine; class V8Scope : public Scope { public: - virtual void reset() {} - virtual void init( BSONObj * data ) {} - - virtual void localConnect( const char * dbName ) {} - virtual double getNumber( const char *field ) { assert( false ); return 0; } - virtual string getString( const char *field ) { assert( false ); return ""; } - virtual bool getBoolean( const char *field ) { assert( false ); return false; } - virtual BSONObj getObject( const char *field ) { assert( false ); return BSONObj(); } + V8Scope( V8ScriptEngine * engine ); + ~V8Scope(); - virtual int type( const char *field ) { assert( false ); return 0; } + virtual void reset(){} + virtual void init( BSONObj * data ){ assert(0); } + + virtual void localConnect( const char * dbName ){ assert(0); } + virtual void externalSetup(){ assert(0); }; - virtual void setNumber( const char *field , double val ) { assert(0); } - virtual void setString( const char *field , const char * val ) { assert(0); } - virtual void setObject( const char *field , const BSONObj& obj , bool readOnly) { assert(0); } - virtual void setBoolean( const char *field , bool val ) { assert(0); } - virtual void setThis( const BSONObj * obj ) { assert(0); } + virtual double getNumber( const char *field ); + virtual string getString( const char *field ); + virtual bool getBoolean( const char *field ); + virtual BSONObj getObject( const char *field ){ assert( false ); return BSONObj(); } - virtual ScriptingFunction createFunction( const char * code ) { assert( false ); return 0; } - virtual int invoke( ScriptingFunction func , const BSONObj& args ) { assert( false ); return 0; } - virtual string getError() { assert( false ); return ""; } + virtual int type( const char *field ){ assert( false ); return 0; } + + virtual void setNumber( const char *field , double val ); + virtual void setString( const char *field , const char * val ); + virtual void setBoolean( const char *field , bool val ); + virtual void setElement( const char *field , const BSONElement& e ){ assert( 0 );} + virtual void setObject( const char *field , const BSONObj& obj , bool readOnly){ assert(0); } + virtual void setThis( const BSONObj * obj ){ assert(0); } - virtual void injectNative( const char *field, NativeFunction func ) { + virtual ScriptingFunction _createFunction( const char * code ){ assert( false ); return 0; } + virtual int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = false ){ assert(0); return 0;} + virtual bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ){ assert(0); return 0; } + virtual string getError(){ assert( false ); return ""; } + + virtual void injectNative( const char *field, NativeFunction func ){ Handle< FunctionTemplate > f( v8::FunctionTemplate::New( nativeCallback ) ); f->Set( v8::String::New( "_native_function" ), External::New( (void*)func ) ); - global_->Set( v8::String::New( field ), f ); + _global->Set( v8::String::New( field ), f->GetFunction() ); } - void setGlobal( const Handle< v8::ObjectTemplate > &global ) { - global_ = global; - } - + void gc(){ assert(0); } + private: + static Handle< Value > nativeCallback( const Arguments &args ); - Handle< v8::ObjectTemplate > global_; + + HandleScope _handleScope; + Handle<Context> _context; + Context::Scope _scope; + Handle<v8::Object> _global; }; class V8ScriptEngine : public ScriptEngine { public: - V8ScriptEngine() {} - virtual ~V8ScriptEngine() {} + V8ScriptEngine(); + virtual ~V8ScriptEngine(); - virtual Scope * createScope() { return new V8Scope(); } + virtual Scope * createScope(){ return new V8Scope( this ); } - virtual void runTest() {} + virtual void runTest(){} + + bool utf8Ok() const { return true; } + + private: + HandleScope _handleScope; + Handle<ObjectTemplate> _globalTemplate; + + friend class V8Scope; }; diff --git a/scripting/v8_utils.cpp b/scripting/v8_utils.cpp new file mode 100644 index 00000000000..0777b3c3220 --- /dev/null +++ b/scripting/v8_utils.cpp @@ -0,0 +1,136 @@ +// ShellUtils.cpp + +#include "v8_utils.h" +#include <iostream> +#include <map> +#include <sstream> +#include <vector> +#include <sys/socket.h> +#include <netinet/in.h> + +using namespace std; +using namespace v8; + +namespace mongo { + + Handle<v8::Value> Print(const Arguments& args) { + bool first = true; + for (int i = 0; i < args.Length(); i++) { + HandleScope handle_scope; + if (first) { + first = false; + } else { + printf(" "); + } + v8::String::Utf8Value str(args[i]); + printf("%s", *str); + } + printf("\n"); + return v8::Undefined(); + } + + std::string toSTLString( const Handle<v8::Value> & o ){ + v8::String::Utf8Value str(o); + const char * foo = *str; + std::string s(foo); + return s; + } + + std::ostream& operator<<( std::ostream &s, const Handle<v8::Value> & o ){ + v8::String::Utf8Value str(o); + s << *str; + return s; + } + + std::ostream& operator<<( std::ostream &s, const v8::TryCatch * try_catch ){ + HandleScope handle_scope; + v8::String::Utf8Value exception(try_catch->Exception()); + Handle<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + s << *exception << endl; + } + else { + + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + cout << *filename << ":" << linenum << " " << *exception << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + cout << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + cout << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + cout << "^"; + + cout << endl; + } + + if ( try_catch->next_ ) + s << try_catch->next_; + + return s; + } + + + Handle<v8::Value> Version(const Arguments& args) { + return v8::String::New(v8::V8::GetVersion()); + } + + bool ExecuteString(Handle<v8::String> source, Handle<v8::Value> name, + bool print_result, bool report_exceptions ){ + + HandleScope handle_scope; + v8::TryCatch try_catch; + + Handle<v8::Script> script = v8::Script::Compile(source, name); + if (script.IsEmpty()) { + if (report_exceptions) + ReportException(&try_catch); + return false; + } + + Handle<v8::Value> result = script->Run(); + if ( result.IsEmpty() ){ + if (report_exceptions) + ReportException(&try_catch); + return false; + } + + if ( print_result ){ + + Local<Context> current = Context::GetCurrent(); + Local<Object> global = current->Global(); + + Local<Value> shellPrint = global->Get( String::New( "shellPrint" ) ); + + if ( shellPrint->IsFunction() ){ + v8::Function * f = (v8::Function*)(*shellPrint); + Handle<v8::Value> argv[1]; + argv[0] = result; + f->Call( global , 1 , argv ); + } + else if ( ! result->IsUndefined() ){ + cout << result << endl; + } + } + + return true; + } + + void ReportException(v8::TryCatch* try_catch) { + cout << try_catch << endl; + } + + extern v8::Handle< v8::Context > baseContext_; + + void installShellUtils( Handle<v8::ObjectTemplate>& global ){ + global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)); + global->Set(v8::String::New("version"), v8::FunctionTemplate::New(Version)); + } + +} diff --git a/scripting/v8_utils.h b/scripting/v8_utils.h new file mode 100644 index 00000000000..bcbf54f6f2b --- /dev/null +++ b/scripting/v8_utils.h @@ -0,0 +1,34 @@ +// v8_utils.h + +#pragma once + +#include <v8.h> + +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include <assert.h> +#include <iostream> + +namespace mongo { + + // Executes a string within the current v8 context. + bool ExecuteString(v8::Handle<v8::String> source, + v8::Handle<v8::Value> name, + bool print_result, + bool report_exceptions); + + v8::Handle<v8::Value> Print(const v8::Arguments& args); + void ReportException(v8::TryCatch* handler); + + + void installShellUtils( v8::Handle<v8::ObjectTemplate>& global ); + +#define jsassert(x,msg) assert(x) + + std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::Value> & o ); + std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::TryCatch> * try_catch ); + std::string toSTLString( const v8::Handle<v8::Value> & o ); + +} + diff --git a/scripting/v8_wrapper.cpp b/scripting/v8_wrapper.cpp new file mode 100644 index 00000000000..e98d3316932 --- /dev/null +++ b/scripting/v8_wrapper.cpp @@ -0,0 +1,692 @@ +// v8_wrapper.cpp + +#include "v8_wrapper.h" +#include "v8_utils.h" + +#include <iostream> + +using namespace std; +using namespace v8; + +namespace mongo { + +#define CONN_STRING (v8::String::New( "_conn" )) + +#define DDD(x) + //#define DDD(x) ( cout << x << endl ); + + void installMongoGlobals( Handle<ObjectTemplate>& global ){ + global->Set(v8::String::New("mongoInject"), FunctionTemplate::New(mongoInject)); + + v8::Local<v8::FunctionTemplate> mongo = FunctionTemplate::New( mongoInit ); + global->Set(v8::String::New("Mongo") , mongo ); + + v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); + global->Set(v8::String::New("DB") , db ); + db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + + v8::Local<v8::FunctionTemplate> dbCollection = FunctionTemplate::New( collectionInit ); + global->Set(v8::String::New("DBCollection") , dbCollection ); + dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + + v8::Local<v8::FunctionTemplate> dbQuery = FunctionTemplate::New( dbQueryInit ); + global->Set(v8::String::New("DBQuery") , dbQuery ); + dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); + + v8::Local<v8::FunctionTemplate> objectId = FunctionTemplate::New( objectIdInit ); + global->Set(v8::String::New("ObjectId") , objectId ); + } + + Handle<Value> mongoInject(const Arguments& args){ + jsassert( args.Length() == 1 , "mongoInject takes exactly 1 argument" ); + jsassert( args[0]->IsObject() , "mongoInject needs to be passed a prototype" ); + + Local<v8::Object> o = args[0]->ToObject(); + + o->Set( v8::String::New( "init" ) , FunctionTemplate::New( mongoInit )->GetFunction() ); + o->Set( v8::String::New( "find" ) , FunctionTemplate::New( mongoFind )->GetFunction() ); + o->Set( v8::String::New( "insert" ) , FunctionTemplate::New( mongoInsert )->GetFunction() ); + o->Set( v8::String::New( "remove" ) , FunctionTemplate::New( mongoRemove )->GetFunction() ); + o->Set( v8::String::New( "update" ) , FunctionTemplate::New( mongoUpdate )->GetFunction() ); + + Local<FunctionTemplate> t = FunctionTemplate::New( internalCursorCons ); + t->PrototypeTemplate()->Set( v8::String::New("next") , FunctionTemplate::New( internalCursorNext ) ); + t->PrototypeTemplate()->Set( v8::String::New("hasNext") , FunctionTemplate::New( internalCursorHasNext ) ); + o->Set( v8::String::New( "internalCursor" ) , t->GetFunction() ); + + return v8::Undefined(); + } + + Handle<Value> mongoInit(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" ); + } + + DBClientConnection * conn = new DBClientConnection( true ); + + string errmsg; + if ( ! conn->connect( host , errmsg ) ){ + return v8::ThrowException( v8::String::New( "couldn't connect" ) ); + } + + // 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 ) ); + + return v8::Undefined(); + } + + + // --- + + Local<v8::Object> mongoToV8( const BSONObj& m , bool array ){ + Local<v8::Object> o; + if ( array ) + o = v8::Array::New(); + else + o = v8::Object::New(); + + mongo::BSONObj sub; + + for ( BSONObjIterator i(m); i.more(); ) { + BSONElement f = i.next(); + if ( f.eoo() ) + break; + + Local<Value> v; + + switch ( f.type() ){ + + case mongo::Code: + cout << "warning, code saved in database just turned into string right now" << endl; + case mongo::String: + o->Set( v8::String::New( f.fieldName() ) , v8::String::New( f.valuestr() ) ); + break; + + case mongo::jstOID: { + v8::Function * idCons = getObjectIdCons(); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::String::New( f.__oid().str().c_str() ); + o->Set( v8::String::New( f.fieldName() ) , + idCons->NewInstance( 1 , argv ) ); + break; + } + + case mongo::NumberDouble: + case mongo::NumberInt: + o->Set( v8::String::New( f.fieldName() ) , v8::Number::New( f.number() ) ); + break; + + case mongo::Array: + case mongo::Object: + sub = f.embeddedObject(); + o->Set( v8::String::New( f.fieldName() ) , mongoToV8( sub , f.type() == mongo::Array ) ); + break; + + case mongo::Date: + o->Set( v8::String::New( f.fieldName() ) , v8::Date::New( f.date() ) ); + break; + + case mongo::Bool: + o->Set( v8::String::New( f.fieldName() ) , v8::Boolean::New( f.boolean() ) ); + break; + + case mongo::jstNULL: + o->Set( v8::String::New( f.fieldName() ) , v8::Null() ); + break; + + case mongo::RegEx: { + v8::Function * regex = getNamedCons( "RegExp" ); + + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.regex() ); + argv[1] = v8::String::New( f.regexFlags() ); + + o->Set( v8::String::New( f.fieldName() ) , regex->NewInstance( 2 , argv ) ); + break; + } + + case mongo::BinData: { + Local<v8::Object> b = v8::Object::New(); + + int len; + f.binData( len ); + + b->Set( v8::String::New( "subtype" ) , v8::Number::New( f.binDataType() ) ); + b->Set( v8::String::New( "length" ) , v8::Number::New( len ) ); + + o->Set( v8::String::New( f.fieldName() ) , b ); + break; + }; + + case mongo::Timestamp: { + Local<v8::Object> sub = v8::Object::New(); + + sub->Set( v8::String::New( "time" ) , v8::Date::New( f.timestampTime() ) ); + sub->Set( v8::String::New( "i" ) , v8::Number::New( f.timestampInc() ) ); + + o->Set( v8::String::New( f.fieldName() ) , sub ); + break; + } + + case mongo::MinKey: + // TODO: make a special type + o->Set( v8::String::New( f.fieldName() ) , v8::String::New( "MinKey" ) ); + break; + + case mongo::MaxKey: + // TODO: make a special type + o->Set( v8::String::New( f.fieldName() ) , v8::String::New( "MaxKey" ) ); + break; + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + } + + return o; + } + + Handle<v8::Value> mongoToV8Element( const BSONElement &f ) { + assert( !f.eoo() ); + switch ( f.type() ){ + + case mongo::Code: + cout << "warning, code saved in database just turned into string right now" << endl; + case mongo::String: + return v8::String::New( f.valuestr() ); + + case mongo::jstOID: { + v8::Function * idCons = getObjectIdCons(); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::String::New( f.__oid().str().c_str() ); + return idCons->NewInstance( 1 , argv ); + } + + case mongo::NumberDouble: + case mongo::NumberInt: + return v8::Number::New( f.number() ); + + case mongo::Array: + case mongo::Object: + return mongoToV8( f.embeddedObject() , f.type() == mongo::Array ); + + case mongo::Date: + return v8::Date::New( f.date() ); + + case mongo::Bool: + return v8::Boolean::New( f.boolean() ); + + case mongo::jstNULL: + return v8::Null(); + + case mongo::RegEx: { + v8::Function * regex = getNamedCons( "RegExp" ); + + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.regex() ); + argv[1] = v8::String::New( f.regexFlags() ); + + return regex->NewInstance( 2 , argv ); + break; + } + + case mongo::BinData: { + Local<v8::Object> b = v8::Object::New(); + + int len; + f.binData( len ); + + b->Set( v8::String::New( "subtype" ) , v8::Number::New( f.binDataType() ) ); + b->Set( v8::String::New( "length" ) , v8::Number::New( len ) ); + + return b; + }; + + case mongo::Timestamp: { + Local<v8::Object> sub = v8::Object::New(); + + sub->Set( v8::String::New( "time" ) , v8::Date::New( f.timestampTime() ) ); + sub->Set( v8::String::New( "i" ) , v8::Number::New( f.timestampInc() ) ); + + return sub; + } + + case mongo::MinKey: + // TODO: make a special type + return v8::String::New( "MinKey" ); + + case mongo::MaxKey: + // TODO: make a special type + return v8::String::New( "MaxKey" ); + + case mongo::Undefined: + return v8::Undefined(); + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + return v8::Undefined(); + } + + void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value ){ + + if ( value->IsString() ){ + if ( sname == "$where" ) + b.appendCode( sname.c_str() , toSTLString( value ).c_str() ); + else + b.append( sname.c_str() , toSTLString( value ).c_str() ); + return; + } + + if ( value->IsFunction() ){ + b.appendCode( sname.c_str() , toSTLString( value ).c_str() ); + return; + } + + if ( value->IsNumber() ){ + b.append( sname.c_str() , value->ToNumber()->Value() ); + return; + } + + if ( value->IsArray() ){ + BSONObj sub = v8ToMongo( value->ToObject() ); + b.appendArray( sname.c_str() , sub ); + return; + } + + if ( value->IsDate() ){ + b.appendDate( sname.c_str() , (unsigned long long )(v8::Date::Cast( *value )->NumberValue()) ); + return; + } + + if ( value->IsObject() ){ + 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 ); + b.appendRegex( sname.c_str() , r.c_str() , o.c_str() ); + } + else if ( value->ToObject()->GetPrototype()->IsObject() && + value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( v8::String::New( "isObjectId" ) ) ){ + OID oid; + oid.init( toSTLString( value ) ); + b.appendOID( sname.c_str() , &oid ); + } + else { + BSONObj sub = v8ToMongo( value->ToObject() ); + b.append( sname.c_str() , sub ); + } + return; + } + + if ( value->IsBoolean() ){ + b.appendBool( sname.c_str() , value->ToBoolean()->Value() ); + return; + } + + else if ( value->IsUndefined() ){ + return; + } + + else if ( value->IsNull() ){ + b.appendNull( sname.c_str() ); + return; + } + + cout << "don't know how to covert to mongo field [" << name << "]\t" << value << endl; + } + + BSONObj v8ToMongo( v8::Handle<v8::Object> o ){ + BSONObjBuilder b; + + v8::Handle<v8::String> idName = v8::String::New( "_id" ); + if ( o->HasRealNamedProperty( idName ) ){ + v8ToMongoElement( b , idName , "_id" , o->Get( idName ) ); + } + + Local<v8::Array> names = o->GetPropertyNames(); + for ( unsigned int i=0; i<names->Length(); i++ ){ + v8::Local<v8::String> name = names->Get(v8::Integer::New(i) )->ToString(); + + if ( o->GetPrototype()->IsObject() && + o->GetPrototype()->ToObject()->HasRealNamedProperty( name ) ) + continue; + + v8::Local<v8::Value> value = o->Get( name ); + + const string sname = toSTLString( name ); + if ( sname == "_id" ) + continue; + + v8ToMongoElement( b , name , sname , value ); + } + return b.obj(); + } + +#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 + + DBClientConnection * getConnection( const Arguments& args ){ + Local<External> c = External::Cast( *(args.This()->Get( CONN_STRING )) ); + DBClientConnection * conn = (DBClientConnection*)(c->Value()); + assert( conn ); + return conn; + } + + // ---- real methods + + /** + 0 - namespace + 1 - query + 2 - fields + 3 - limit + 4 - skip + */ + Handle<Value> mongoFind(const Arguments& args){ + jsassert( args.Length() == 5 , "find needs 5 args" ); + jsassert( args[1]->IsObject() , "needs to be an object" ); + DBClientConnection * conn = getConnection( args ); + GETNS; + + BSONObj q = v8ToMongo( args[1]->ToObject() ); + DDD( "query:" << q ); + + BSONObj fields; + bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0; + if ( haveFields ) + fields = v8ToMongo( args[2]->ToObject() ); + + Local<v8::Object> mongo = args.This(); + 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()); + { + v8::Unlocker u; + cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, slaveOk ? Option_SlaveOk : 0 ); + } + + v8::Function * cons = (v8::Function*)( *( mongo->Get( v8::String::New( "internalCursor" ) ) ) ); + Local<v8::Object> c = cons->NewInstance(); + + // NOTE I don't believe the cursor object will ever be freed. + c->Set( v8::String::New( "cursor" ) , External::New( cursor.release() ) ); + return c; + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on query" ) ); + } + } + + v8::Handle<v8::Value> mongoInsert(const v8::Arguments& args){ + jsassert( args.Length() == 2 , "insert needs 2 args" ); + jsassert( args[1]->IsObject() , "have to insert an object" ); + + DBClientConnection * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + + if ( ! in->Has( v8::String::New( "_id" ) ) ){ + v8::Handle<v8::Value> argv[1]; + in->Set( v8::String::New( "_id" ) , getObjectIdCons()->NewInstance( 0 , argv ) ); + } + + BSONObj o = v8ToMongo( in ); + + DDD( "want to save : " << o.jsonString() ); + try { + conn->insert( ns , o ); + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on insert" ) ); + } + + return args[1]; + } + + v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args){ + jsassert( args.Length() == 2 , "remove needs 2 args" ); + jsassert( args[1]->IsObject() , "have to remove an object template" ); + + DBClientConnection * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + BSONObj o = v8ToMongo( in ); + + DDD( "want to remove : " << o.jsonString() ); + try { + conn->remove( ns , o ); + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on remove" ) ); + } + + return v8::Undefined(); + } + + v8::Handle<v8::Value> mongoUpdate(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" ); + + DBClientConnection * 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(); + + try { + conn->update( ns , v8ToMongo( q ) , v8ToMongo( o ) , upsert ); + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on remove" ) ); + } + + return v8::Undefined(); + } + + + + + // --- cursor --- + + mongo::DBClientCursor * getCursor( const Arguments& args ){ + Local<External> c = External::Cast( *(args.This()->Get( v8::String::New( "cursor" ) ) ) ); + mongo::DBClientCursor * cursor = (mongo::DBClientCursor*)(c->Value()); + return cursor; + } + + v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args){ + return v8::Undefined(); + } + + v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args){ + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return v8::Undefined(); + BSONObj o; + { + v8::Unlocker u; + o = cursor->next(); + } + return mongoToV8( o ); + } + + v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args){ + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return Boolean::New( false ); + bool more; + { + v8::Unlocker u; + more = cursor->more(); + } + return Boolean::New( more ); + } + + + // --- DB ---- + + v8::Handle<v8::Value> dbInit(const v8::Arguments& args){ + assert( args.Length() == 2 ); + + args.This()->Set( v8::String::New( "_mongo" ) , args[0] ); + args.This()->Set( v8::String::New( "_name" ) , args[1] ); + + for ( int i=0; i<args.Length(); i++ ) + assert( ! args[i]->IsUndefined() ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ){ + assert( args.Length() == 4 ); + + args.This()->Set( v8::String::New( "_mongo" ) , args[0] ); + args.This()->Set( v8::String::New( "_db" ) , args[1] ); + args.This()->Set( v8::String::New( "_shortName" ) , args[2] ); + args.This()->Set( v8::String::New( "_fullName" ) , args[3] ); + + for ( int i=0; i<args.Length(); i++ ) + assert( ! args[i]->IsUndefined() ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> dbQueryInit( const v8::Arguments& args ){ + + v8::Handle<v8::Object> t = args.This(); + + assert( args.Length() >= 4 ); + + t->Set( v8::String::New( "_mongo" ) , args[0] ); + t->Set( v8::String::New( "_db" ) , args[1] ); + t->Set( v8::String::New( "_collection" ) , args[2] ); + t->Set( v8::String::New( "_ns" ) , args[3] ); + + if ( args.Length() > 4 && args[4]->IsObject() ) + t->Set( v8::String::New( "_query" ) , args[4] ); + else + t->Set( v8::String::New( "_query" ) , v8::Object::New() ); + + if ( args.Length() > 5 && args[5]->IsObject() ) + t->Set( v8::String::New( "_fields" ) , args[5] ); + else + t->Set( v8::String::New( "_fields" ) , v8::Null() ); + + + if ( args.Length() > 6 && args[6]->IsNumber() ) + t->Set( v8::String::New( "_limit" ) , args[6] ); + else + t->Set( v8::String::New( "_limit" ) , Number::New( 0 ) ); + + if ( args.Length() > 7 && args[7]->IsNumber() ) + t->Set( v8::String::New( "_skip" ) , args[7] ); + else + t->Set( v8::String::New( "_skip" ) , Number::New( 0 ) ); + + t->Set( v8::String::New( "_cursor" ) , v8::Null() ); + t->Set( v8::String::New( "_numReturned" ) , v8::Number::New(0) ); + t->Set( v8::String::New( "_special" ) , Boolean::New(false) ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> collectionFallback( v8::Local<v8::String> name, const v8::AccessorInfo &info) { + DDD( "collectionFallback [" << name << "]" ); + + v8::Handle<v8::Value> 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<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "getCollection" ) ); + assert( getCollection->IsFunction() ); + + v8::Function * f = (v8::Function*)(*getCollection); + v8::Handle<v8::Value> argv[1]; + argv[0] = name; + + return f->Call( info.This() , 1 , argv ); + } + + 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::Function * getNamedCons( const char * name ){ + return v8::Function::Cast( *(v8::Context::GetCurrent()->Global()->Get( v8::String::New( name ) ) ) ); + } + + v8::Function * getObjectIdCons(){ + return getNamedCons( "ObjectId" ); + } + + v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ){ + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function * f = getObjectIdCons(); + it = f->NewInstance(); + } + + OID oid; + + if ( args.Length() == 0 ){ + oid.init(); + } + else { + string s = toSTLString( args[0] ); + oid.init( s ); + } + + it->Set( v8::String::New( "str" ) , v8::String::New( oid.str().c_str() ) ); + + return it; + } + + +} diff --git a/scripting/v8_wrapper.h b/scripting/v8_wrapper.h new file mode 100644 index 00000000000..2f41822ae41 --- /dev/null +++ b/scripting/v8_wrapper.h @@ -0,0 +1,55 @@ +// v8_wrapper.h + +#pragma once + +#include <v8.h> +#include <cstring> +#include <cstdio> +#include <cstdlib> + +#include "../client/dbclient.h" + +namespace mongo { + void installMongoGlobals( v8::Handle<v8::ObjectTemplate>& global ); + + // the actual globals + v8::Handle<v8::Value> mongoInject(const v8::Arguments& args); + + // utils + v8::Local<v8::Object> mongoToV8( const mongo::BSONObj & m , bool array = 0 ); + mongo::BSONObj v8ToMongo( v8::Handle<v8::Object> o ); + + void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value ); + v8::Handle<v8::Value> mongoToV8Element( const BSONElement &f ); + + mongo::DBClientConnection * getConnection( const v8::Arguments& args ); + + + + // Mongo members + v8::Handle<v8::Value> mongoInit(const v8::Arguments& args); + v8::Handle<v8::Value> mongoFind(const v8::Arguments& args); + v8::Handle<v8::Value> mongoInsert(const v8::Arguments& args); + v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args); + v8::Handle<v8::Value> mongoUpdate(const v8::Arguments& args); + + + v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args); + + // DB members + + v8::Handle<v8::Value> dbInit(const v8::Arguments& args); + v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ); + v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ); + + v8::Handle<v8::Value> dbQueryInit( const v8::Arguments& args ); + v8::Handle<v8::Value> dbQueryIndexAccess( uint32_t index , const v8::AccessorInfo& info ); + + v8::Handle<v8::Value> collectionFallback( v8::Local<v8::String> name, const v8::AccessorInfo &info); + + v8::Function * getNamedCons( const char * name ); + v8::Function * getObjectIdCons(); + +} |