diff options
Diffstat (limited to 'scripting/v8_wrapper.cpp')
-rw-r--r-- | scripting/v8_wrapper.cpp | 569 |
1 files changed, 20 insertions, 549 deletions
diff --git a/scripting/v8_wrapper.cpp b/scripting/v8_wrapper.cpp index ff67e8c..7c28a39 100644 --- a/scripting/v8_wrapper.cpp +++ b/scripting/v8_wrapper.cpp @@ -15,9 +15,18 @@ * 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 "v8_db.h" +#include "engine_v8.h" #include <iostream> @@ -26,540 +35,14 @@ using namespace v8; namespace mongo { -#define CONN_STRING (v8::String::New( "_conn" )) - #define DDD(x) - Handle<Value> NamedReadOnlySet( Local<v8::String> property, Local<Value> value, const AccessorInfo& info ) { - cout << "cannot write to read-only object" << endl; - return value; - } - - Handle<Boolean> NamedReadOnlyDelete( Local<v8::String> property, const AccessorInfo& info ) { - cout << "cannot delete from read-only object" << endl; - return Boolean::New( false ); - } - - Handle<Value> IndexedReadOnlySet( uint32_t index, Local<Value> value, const AccessorInfo& info ) { - cout << "cannot write to read-only array" << endl; - return value; - } - - Handle<Boolean> IndexedReadOnlyDelete( uint32_t index, const AccessorInfo& info ) { - cout << "cannot delete from read-only array" << endl; - return Boolean::New( false ); - } - - Local< v8::Value > newFunction( const char *code ) { - stringstream codeSS; - codeSS << "____MontoToV8_newFunction_temp = " << code; - string codeStr = codeSS.str(); - Local< Script > compiled = Script::New( v8::String::New( codeStr.c_str() ) ); - Local< Value > ret = compiled->Run(); - return ret; - } - - Local< v8::Value > newId( const OID &id ) { - v8::Function * idCons = getObjectIdCons(); - v8::Handle<v8::Value> argv[1]; - argv[0] = v8::String::New( id.str().c_str() ); - return idCons->NewInstance( 1 , argv ); - } - - Local<v8::Object> mongoToV8( const BSONObj& m , bool array, bool readOnly ) { - - Local<v8::Object> o; - - // handle DBRef. needs to come first. isn't it? (metagoto) - static string ref = "$ref"; - if ( ref == m.firstElement().fieldName() ) { - const BSONElement& id = m["$id"]; - if (!id.eoo()) { // there's no check on $id exitence in sm implementation. risky ? - v8::Function* dbRef = getNamedCons( "DBRef" ); - o = dbRef->NewInstance(); - } - } - - Local< v8::ObjectTemplate > readOnlyObjects; - // Hoping template construction is fast... - Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); - internalFieldObjects->SetInternalFieldCount( 1 ); - - if ( !o.IsEmpty() ) { - readOnly = false; - } - else if ( array ) { - // NOTE Looks like it's impossible to add interceptors to v8 arrays. - readOnly = false; - o = v8::Array::New(); - } - else if ( !readOnly ) { - o = v8::Object::New(); - } - else { - // NOTE Our readOnly implemention relies on undocumented ObjectTemplate - // functionality that may be fragile, but it still seems like the best option - // for now -- fwiw, the v8 docs are pretty sparse. I've determined experimentally - // that when property handlers are set for an object template, they will attach - // to objects previously created by that template. To get this to work, though, - // it is necessary to initialize the template's property handlers before - // creating objects from the template (as I have in the following few lines - // of code). - // NOTE In my first attempt, I configured the permanent property handlers before - // constructiong the object and replaced the Set() calls below with ForceSet(). - // However, it turns out that ForceSet() only bypasses handlers for named - // properties and not for indexed properties. - readOnlyObjects = v8::ObjectTemplate::New(); - // NOTE This internal field will store type info for special db types. For - // regular objects the field is unnecessary - for simplicity I'm creating just - // one readOnlyObjects template for objects where the field is & isn't necessary, - // assuming that the overhead of an internal field is slight. - readOnlyObjects->SetInternalFieldCount( 1 ); - readOnlyObjects->SetNamedPropertyHandler( 0 ); - readOnlyObjects->SetIndexedPropertyHandler( 0 ); - o = readOnlyObjects->NewInstance(); - } - - mongo::BSONObj sub; - - for ( BSONObjIterator i(m); i.more(); ) { - const BSONElement& f = i.next(); - - Local<Value> v; - - switch ( f.type() ) { - - case mongo::Code: - o->Set( v8::String::New( f.fieldName() ), newFunction( f.valuestr() ) ); - break; - - case CodeWScope: - if ( f.codeWScopeObject().isEmpty() ) - log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; - o->Set( v8::String::New( f.fieldName() ), newFunction( f.codeWScopeCode() ) ); - break; - - 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, readOnly ) ); - 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: - case mongo::Undefined: // duplicate sm behavior - 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 = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); - - int len; - const char *data = f.binData( len ); - - v8::Function* binData = getNamedCons( "BinData" ); - v8::Handle<v8::Value> argv[3]; - argv[0] = v8::Number::New( len ); - argv[1] = v8::Number::New( f.binDataType() ); - argv[2] = v8::String::New( data, len ); - o->Set( v8::String::New( f.fieldName() ), binData->NewInstance(3, argv) ); - break; - } - - case mongo::Timestamp: { - Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); - - sub->Set( v8::String::New( "t" ) , v8::Number::New( f.timestampTime() ) ); - sub->Set( v8::String::New( "i" ) , v8::Number::New( f.timestampInc() ) ); - sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - - o->Set( v8::String::New( f.fieldName() ) , sub ); - break; - } - - case mongo::NumberLong: { - Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); - unsigned long long val = f.numberLong(); - v8::Function* numberLong = getNamedCons( "NumberLong" ); - if ( (long long)val == (long long)(double)(long long)(val) ) { - v8::Handle<v8::Value> argv[1]; - argv[0] = v8::Number::New( (double)(long long)( val ) ); - o->Set( v8::String::New( f.fieldName() ), numberLong->NewInstance( 1, argv ) ); - } - else { - v8::Handle<v8::Value> argv[3]; - argv[0] = v8::Number::New( (double)(long long)(val) ); - argv[1] = v8::Integer::New( val >> 32 ); - argv[2] = v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ); - o->Set( v8::String::New( f.fieldName() ), numberLong->NewInstance(3, argv) ); - } - break; - } - - case mongo::MinKey: { - Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); - sub->Set( v8::String::New( "$MinKey" ), v8::Boolean::New( true ) ); - sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - o->Set( v8::String::New( f.fieldName() ) , sub ); - break; - } - - case mongo::MaxKey: { - Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); - sub->Set( v8::String::New( "$MaxKey" ), v8::Boolean::New( true ) ); - sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - o->Set( v8::String::New( f.fieldName() ) , sub ); - break; - } - - case mongo::DBRef: { - v8::Function* dbPointer = getNamedCons( "DBPointer" ); - v8::Handle<v8::Value> argv[2]; - argv[0] = v8::String::New( f.dbrefNS() ); - argv[1] = newId( f.dbrefOID() ); - o->Set( v8::String::New( f.fieldName() ), dbPointer->NewInstance(2, argv) ); - break; - } - - default: - cout << "can't handle type: "; - cout << f.type() << " "; - cout << f.toString(); - cout << endl; - break; - } - - } - - if ( readOnly ) { - readOnlyObjects->SetNamedPropertyHandler( 0, NamedReadOnlySet, 0, NamedReadOnlyDelete ); - readOnlyObjects->SetIndexedPropertyHandler( 0, IndexedReadOnlySet, 0, IndexedReadOnlyDelete ); - } - - return o; - } - - Handle<v8::Value> mongoToV8Element( const BSONElement &f ) { - Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); - internalFieldObjects->SetInternalFieldCount( 1 ); - - switch ( f.type() ) { - - case mongo::Code: - return newFunction( f.valuestr() ); - - case CodeWScope: - if ( f.codeWScopeObject().isEmpty() ) - log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; - return newFunction( f.codeWScopeCode() ); - - case mongo::String: - return v8::String::New( f.valuestr() ); - - case mongo::jstOID: - return newId( f.__oid() ); - - 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::EOO: - case mongo::jstNULL: - case mongo::Undefined: // duplicate sm behavior - 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: { - int len; - const char *data = f.binData( len ); - - v8::Function* binData = getNamedCons( "BinData" ); - v8::Handle<v8::Value> argv[3]; - argv[0] = v8::Number::New( len ); - argv[1] = v8::Number::New( f.binDataType() ); - argv[2] = v8::String::New( data, len ); - return binData->NewInstance( 3, argv ); - }; - - case mongo::Timestamp: { - Local<v8::Object> sub = internalFieldObjects->NewInstance(); - - sub->Set( v8::String::New( "t" ) , v8::Number::New( f.timestampTime() ) ); - sub->Set( v8::String::New( "i" ) , v8::Number::New( f.timestampInc() ) ); - sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - - return sub; - } - - case mongo::NumberLong: { - Local<v8::Object> sub = internalFieldObjects->NewInstance(); - unsigned long long val = f.numberLong(); - v8::Function* numberLong = getNamedCons( "NumberLong" ); - if ( (long long)val == (long long)(double)(long long)(val) ) { - v8::Handle<v8::Value> argv[1]; - argv[0] = v8::Number::New( (double)(long long)( val ) ); - return numberLong->NewInstance( 1, argv ); - } - else { - v8::Handle<v8::Value> argv[3]; - argv[0] = v8::Number::New( (double)(long long)( val ) ); - argv[1] = v8::Integer::New( val >> 32 ); - argv[2] = v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ); - return numberLong->NewInstance( 3, argv ); - } - } - - case mongo::MinKey: { - Local<v8::Object> sub = internalFieldObjects->NewInstance(); - sub->Set( v8::String::New( "$MinKey" ), v8::Boolean::New( true ) ); - sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - return sub; - } - - case mongo::MaxKey: { - Local<v8::Object> sub = internalFieldObjects->NewInstance(); - sub->Set( v8::String::New( "$MaxKey" ), v8::Boolean::New( true ) ); - sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); - return sub; - } - - case mongo::DBRef: { - v8::Function* dbPointer = getNamedCons( "DBPointer" ); - v8::Handle<v8::Value> argv[2]; - argv[0] = v8::String::New( f.dbrefNS() ); - argv[1] = newId( f.dbrefOID() ); - return dbPointer->NewInstance(2, argv); - } - - 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 , int depth ) { - - if ( value->IsString() ) { - b.append( sname , toSTLString( value ).c_str() ); - return; - } - - if ( value->IsFunction() ) { - b.appendCode( sname , toSTLString( value ) ); - return; - } - - if ( value->IsNumber() ) { - if ( value->IsInt32() ) - b.append( sname, int( value->ToInt32()->Value() ) ); - else - b.append( sname , value->ToNumber()->Value() ); - return; - } - - if ( value->IsArray() ) { - BSONObj sub = v8ToMongo( value->ToObject() , depth ); - b.appendArray( sname , sub ); - return; - } - - if ( value->IsDate() ) { - b.appendDate( sname , Date_t( (unsigned long long)(v8::Date::Cast( *value )->NumberValue())) ); - return; - } - - if ( value->IsExternal() ) - return; - - if ( value->IsObject() ) { - // The user could potentially modify the fields of these special objects, - // wreaking havoc when we attempt to reinterpret them. Not doing any validation - // for now... - Local< v8::Object > obj = value->ToObject(); - if ( obj->InternalFieldCount() && obj->GetInternalField( 0 )->IsNumber() ) { - switch( obj->GetInternalField( 0 )->ToInt32()->Value() ) { // NOTE Uint32's Value() gave me a linking error, so going with this instead - case Timestamp: - b.appendTimestamp( sname, - Date_t( (unsigned long long)(obj->Get( v8::String::New( "t" ) )->ToNumber()->Value() )), - obj->Get( v8::String::New( "i" ) )->ToInt32()->Value() ); - return; - case MinKey: - b.appendMinKey( sname ); - return; - case MaxKey: - b.appendMaxKey( sname ); - return; - default: - assert( "invalid internal field" == 0 ); - } - } - string s = toSTLString( value ); - if ( s.size() && s[0] == '/' ) { - s = s.substr( 1 ); - string r = s.substr( 0 , s.rfind( "/" ) ); - string o = s.substr( s.rfind( "/" ) + 1 ); - b.appendRegex( sname , r , o ); - } - else if ( value->ToObject()->GetPrototype()->IsObject() && - value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( v8::String::New( "isObjectId" ) ) ) { - OID oid; - oid.init( toSTLString( value ) ); - b.appendOID( sname , &oid ); - } - else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__NumberLong" ) ).IsEmpty() ) { - // TODO might be nice to potentially speed this up with an indexed internal - // field, but I don't yet know how to use an ObjectTemplate with a - // constructor. - v8::Handle< v8::Object > it = value->ToObject(); - long long val; - if ( !it->Has( v8::String::New( "top" ) ) ) { - val = (long long)( it->Get( v8::String::New( "floatApprox" ) )->NumberValue() ); - } - else { - val = (long long) - ( (unsigned long long)( it->Get( v8::String::New( "top" ) )->ToInt32()->Value() ) << 32 ) + - (unsigned)( it->Get( v8::String::New( "bottom" ) )->ToInt32()->Value() ); - } - - b.append( sname, val ); - } - else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__DBPointer" ) ).IsEmpty() ) { - OID oid; - oid.init( toSTLString( value->ToObject()->Get( v8::String::New( "id" ) ) ) ); - string ns = toSTLString( value->ToObject()->Get( v8::String::New( "ns" ) ) ); - b.appendDBRef( sname, ns, oid ); - } - else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__BinData" ) ).IsEmpty() ) { - int len = obj->Get( v8::String::New( "len" ) )->ToInt32()->Value(); - v8::String::Utf8Value data( obj->Get( v8::String::New( "data" ) ) ); - const char *dataArray = *data; - assert( data.length() == len ); - b.appendBinData( sname, - len, - mongo::BinDataType( obj->Get( v8::String::New( "type" ) )->ToInt32()->Value() ), - dataArray ); - } - else { - BSONObj sub = v8ToMongo( value->ToObject() , depth ); - b.append( sname , sub ); - } - return; - } - - if ( value->IsBoolean() ) { - b.appendBool( sname , value->ToBoolean()->Value() ); - return; - } - - else if ( value->IsUndefined() ) { - b.appendUndefined( sname ); - return; - } - - else if ( value->IsNull() ) { - b.appendNull( sname ); - return; - } - - cout << "don't know how to convert to mongo field [" << name << "]\t" << value << endl; - } - - BSONObj v8ToMongo( v8::Handle<v8::Object> o , int depth ) { - BSONObjBuilder b; - - if ( depth == 0 ) { - v8::Handle<v8::String> idName = v8::String::New( "_id" ); - if ( o->HasRealNamedProperty( idName ) ) { - v8ToMongoElement( b , idName , "_id" , o->Get( idName ) ); - } - } - - Local<v8::Array> names = o->GetPropertyNames(); - 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 ( depth == 0 && sname == "_id" ) - continue; - - v8ToMongoElement( b , name , sname , value , depth + 1 ); - } - return b.obj(); - } - // --- object wrapper --- class WrapperHolder { public: - WrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ) - : _o(o), _readOnly( readOnly ), _iDelete( iDelete ) { + WrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ) + : _scope(scope), _o(o), _readOnly( readOnly ), _iDelete( iDelete ) { } ~WrapperHolder() { @@ -572,22 +55,21 @@ namespace mongo { v8::Handle<v8::Value> get( v8::Local<v8::String> name ) { const string& s = toSTLString( name ); const BSONElement& e = _o->getField( s ); - return mongoToV8Element(e); + return _scope->mongoToV8Element(e); } + V8Scope* _scope; const BSONObj * _o; bool _readOnly; bool _iDelete; }; - WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ) { - return new WrapperHolder( o , readOnly , iDelete ); + WrapperHolder * createWrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ) { + return new WrapperHolder( scope, o , readOnly , iDelete ); } -#define WRAPPER_STRING (v8::String::New( "_wrapper" ) ) - WrapperHolder * getWrapper( v8::Handle<v8::Object> o ) { - Handle<v8::Value> t = o->GetRealNamedProperty( WRAPPER_STRING ); + Handle<v8::Value> t = o->GetRealNamedProperty( v8::String::New( "_wrapper" ) ); assert( t->IsExternal() ); Local<External> c = External::Cast( *t ); WrapperHolder * w = (WrapperHolder*)(c->Value()); @@ -596,11 +78,11 @@ namespace mongo { } - Handle<Value> wrapperCons(const Arguments& args) { + Handle<Value> wrapperCons(V8Scope* scope, const Arguments& args) { if ( ! ( args.Length() == 1 && args[0]->IsExternal() ) ) return v8::ThrowException( v8::String::New( "wrapperCons needs 1 External arg" ) ); - args.This()->Set( WRAPPER_STRING , args[0] ); + args.This()->Set( v8::String::New( "_wrapper" ) , args[0] ); return v8::Undefined(); } @@ -609,20 +91,9 @@ namespace mongo { return getWrapper( info.This() )->get( name ); } - v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate() { - v8::Local<v8::FunctionTemplate> t = newV8Function< wrapperCons >(); + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> t = scope->createV8Function(wrapperCons); t->InstanceTemplate()->SetNamedPropertyHandler( wrapperGetHandler ); return t; } - - // --- random utils ---- - - 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" ); - } - } |