diff options
author | Antonin Kral <a.kral@bobek.cz> | 2010-01-31 08:32:52 +0100 |
---|---|---|
committer | Antonin Kral <a.kral@bobek.cz> | 2010-01-31 08:32:52 +0100 |
commit | 4eefaf421bfeddf040d96a3dafb12e09673423d7 (patch) | |
tree | cb2e5ccc7f98158894f977ff131949da36673591 /scripting | |
download | mongodb-4eefaf421bfeddf040d96a3dafb12e09673423d7.tar.gz |
Imported Upstream version 1.3.1
Diffstat (limited to 'scripting')
-rw-r--r-- | scripting/engine.cpp | 399 | ||||
-rw-r--r-- | scripting/engine.h | 154 | ||||
-rw-r--r-- | scripting/engine_java.cpp | 763 | ||||
-rw-r--r-- | scripting/engine_java.h | 224 | ||||
-rw-r--r-- | scripting/engine_none.cpp | 24 | ||||
-rw-r--r-- | scripting/engine_spidermonkey.cpp | 1464 | ||||
-rw-r--r-- | scripting/engine_spidermonkey.h | 116 | ||||
-rw-r--r-- | scripting/engine_v8.cpp | 436 | ||||
-rw-r--r-- | scripting/engine_v8.h | 116 | ||||
-rw-r--r-- | scripting/sm_db.cpp | 854 | ||||
-rw-r--r-- | scripting/v8_db.cpp | 542 | ||||
-rw-r--r-- | scripting/v8_db.h | 71 | ||||
-rw-r--r-- | scripting/v8_utils.cpp | 305 | ||||
-rw-r--r-- | scripting/v8_utils.h | 46 | ||||
-rw-r--r-- | scripting/v8_wrapper.cpp | 575 | ||||
-rw-r--r-- | scripting/v8_wrapper.h | 43 |
16 files changed, 6132 insertions, 0 deletions
diff --git a/scripting/engine.cpp b/scripting/engine.cpp new file mode 100644 index 0000000..dc088fb --- /dev/null +++ b/scripting/engine.cpp @@ -0,0 +1,399 @@ +// engine.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. + */ + +#include "stdafx.h" +#include "engine.h" +#include "../util/file.h" +#include "../client/dbclient.h" + +namespace mongo { + + long long Scope::_lastVersion = 1; + + int Scope::_numScopes = 0; + + Scope::Scope() : _localDBName("") , _loadedVersion(0){ + _numScopes++; + } + + Scope::~Scope(){ + _numScopes--; + } + + ScriptEngine::ScriptEngine() : _scopeInitCallback() { + } + + ScriptEngine::~ScriptEngine(){ + } + + void Scope::append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ){ + int t = type( scopeName ); + + switch ( t ){ + case Object: + builder.append( fieldName , getObject( scopeName ) ); + break; + case Array: + builder.appendArray( fieldName , getObject( scopeName ) ); + break; + case NumberDouble: + builder.append( fieldName , getNumber( scopeName ) ); + break; + case NumberInt: + builder.append( fieldName , getNumberInt( scopeName ) ); + break; + case NumberLong: + builder.append( fieldName , getNumberLongLong( scopeName ) ); + break; + case String: + builder.append( fieldName , getString( scopeName ).c_str() ); + break; + case Bool: + builder.appendBool( fieldName , getBoolean( scopeName ) ); + break; + case jstNULL: + case Undefined: + builder.appendNull( fieldName ); + break; + case Date: + // TODO: make signed + builder.appendDate( fieldName , Date_t((unsigned long long)getNumber( scopeName )) ); + break; + default: + stringstream temp; + temp << "can't append type from:"; + temp << t; + uassert( 10206 , temp.str() , 0 ); + } + + } + + int Scope::invoke( const char* code , const BSONObj& args, int timeoutMs ){ + ScriptingFunction func = createFunction( code ); + uassert( 10207 , "compile failed" , func ); + return invoke( func , args, timeoutMs ); + } + + bool Scope::execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs ){ + + path p( filename ); + + if ( ! exists( p ) ){ + cout << "file [" << filename << "] doesn't exist" << endl; + if ( assertOnError ) + assert( 0 ); + return false; + } + + // iterate directories and recurse using all *.js files in the directory + if ( is_directory( p ) ){ + directory_iterator end; + bool empty = true; + for (directory_iterator it (p); it != end; it++){ + empty = false; + path sub (*it); + if (!endsWith(sub.string().c_str(), ".js")) + continue; + if (!execFile(sub.string().c_str(), printResult, reportError, assertOnError, timeoutMs)) + return false; + } + + if (empty){ + cout << "directory [" << filename << "] doesn't have any *.js files" << endl; + if ( assertOnError ) + assert( 0 ); + return false; + } + + return true; + } + + File f; + f.open( filename.c_str() , true ); + + fileofs L = f.len(); + assert( L <= 0x7ffffffe ); + char * data = (char*)malloc( (size_t) L+1 ); + data[L] = 0; + f.read( 0 , data , (size_t) L ); + + return exec( data , filename , printResult , reportError , assertOnError, timeoutMs ); + } + + void Scope::storedFuncMod(){ + _lastVersion++; + } + + void Scope::validateObjectIdString( const string &str ) { + massert( 10448 , "invalid object id: length", str.size() == 24 ); + + for ( string::size_type i=0; i<str.size(); i++ ){ + char c = str[i]; + if ( ( c >= '0' && c <= '9' ) || + ( c >= 'a' && c <= 'f' ) || + ( c >= 'A' && c <= 'F' ) ){ + continue; + } + massert( 10430 , "invalid object id: not hex", false ); + } + } + + void Scope::loadStored( bool ignoreNotConnected ){ + if ( _localDBName.size() == 0 ){ + if ( ignoreNotConnected ) + return; + uassert( 10208 , "need to have locallyConnected already" , _localDBName.size() ); + } + if ( _loadedVersion == _lastVersion ) + return; + + _loadedVersion = _lastVersion; + + string coll = _localDBName + ".system.js"; + + static DBClientBase * db = createDirectClient(); + auto_ptr<DBClientCursor> c = db->query( coll , Query() ); + while ( c->more() ){ + BSONObj o = c->next(); + + BSONElement n = o["_id"]; + BSONElement v = o["value"]; + + uassert( 10209 , "name has to be a string" , n.type() == String ); + uassert( 10210 , "value has to be set" , v.type() != EOO ); + + setElement( n.valuestr() , v ); + } + + } + + ScriptingFunction Scope::createFunction( const char * code ){ + if ( code[0] == '/' && code [1] == '*' ){ + code += 2; + while ( code[0] && code[1] ){ + if ( code[0] == '*' && code[1] == '/' ){ + code += 2; + break; + } + code++; + } + } + map<string,ScriptingFunction>::iterator i = _cachedFunctions.find( code ); + if ( i != _cachedFunctions.end() ) + return i->second; + ScriptingFunction f = _createFunction( code ); + _cachedFunctions[code] = f; + return f; + } + + typedef map< string , list<Scope*> > PoolToScopes; + + class ScopeCache { + public: + + ScopeCache(){ + _magic = 17; + } + + ~ScopeCache(){ + assert( _magic == 17 ); + _magic = 1; + + if ( inShutdown() ) + return; + + clear(); + } + + void done( const string& pool , Scope * s ){ + boostlock lk( _mutex ); + list<Scope*> & l = _pools[pool]; + if ( l.size() > 10 ){ + delete s; + } + else { + l.push_back( s ); + s->reset(); + } + } + + Scope * get( const string& pool ){ + boostlock lk( _mutex ); + list<Scope*> & l = _pools[pool]; + if ( l.size() == 0 ) + return 0; + + Scope * s = l.back(); + l.pop_back(); + s->reset(); + return s; + } + + void clear(){ + set<Scope*> seen; + + for ( PoolToScopes::iterator i=_pools.begin() ; i != _pools.end(); i++ ){ + for ( list<Scope*>::iterator j=i->second.begin(); j != i->second.end(); j++ ){ + Scope * s = *j; + assert( ! seen.count( s ) ); + delete s; + seen.insert( s ); + } + } + + _pools.clear(); + } + + private: + PoolToScopes _pools; + boost::mutex _mutex; + int _magic; + }; + + thread_specific_ptr<ScopeCache> scopeCache; + + class PooledScope : public Scope { + public: + PooledScope( const string pool , Scope * real ) : _pool( pool ) , _real( real ){ + _real->loadStored( true ); + }; + virtual ~PooledScope(){ + ScopeCache * sc = scopeCache.get(); + if ( sc ){ + sc->done( _pool , _real ); + _real = 0; + } + else { + log() << "warning: scopeCache is empty!" << endl; + delete _real; + _real = 0; + } + } + + void reset(){ + _real->reset(); + } + void init( BSONObj * data ){ + _real->init( data ); + } + + void localConnect( const char * dbName ){ + _real->localConnect( dbName ); + } + void externalSetup(){ + _real->externalSetup(); + } + + double getNumber( const char *field ){ + return _real->getNumber( field ); + } + string getString( const char *field ){ + return _real->getString( field ); + } + bool getBoolean( const char *field ){ + return _real->getBoolean( field ); + } + BSONObj getObject( const char *field ){ + return _real->getObject( field ); + } + + int type( const char *field ){ + return _real->type( field ); + } + + void setElement( const char *field , const BSONElement& val ){ + _real->setElement( field , val ); + } + void setNumber( const char *field , double val ){ + _real->setNumber( field , val ); + } + void setString( const char *field , const char * val ){ + _real->setString( field , val ); + } + void setObject( const char *field , const BSONObj& obj , bool readOnly=true ){ + _real->setObject( field , obj , readOnly ); + } + void setBoolean( const char *field , bool val ){ + _real->setBoolean( field , val ); + } + void setThis( const BSONObj * obj ){ + _real->setThis( obj ); + } + + ScriptingFunction createFunction( const char * code ){ + return _real->createFunction( code ); + } + + ScriptingFunction _createFunction( const char * code ){ + return _real->createFunction( code ); + } + + /** + * @return 0 on success + */ + int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs , bool ignoreReturn ){ + return _real->invoke( func , args , timeoutMs , ignoreReturn ); + } + + string getError(){ + return _real->getError(); + } + + bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ){ + return _real->exec( code , name , printResult , reportError , assertOnError , timeoutMs ); + } + bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ){ + return _real->execFile( filename , printResult , reportError , assertOnError , timeoutMs ); + } + + void injectNative( const char *field, NativeFunction func ){ + _real->injectNative( field , func ); + } + + void gc(){ + _real->gc(); + } + + private: + string _pool; + Scope * _real; + }; + + auto_ptr<Scope> ScriptEngine::getPooledScope( const string& pool ){ + if ( ! scopeCache.get() ){ + scopeCache.reset( new ScopeCache() ); + } + + Scope * s = scopeCache->get( pool ); + if ( ! s ){ + s = newScope(); + } + + auto_ptr<Scope> p; + p.reset( new PooledScope( pool , s ) ); + return p; + } + + void ScriptEngine::threadDone(){ + ScopeCache * sc = scopeCache.get(); + if ( sc ){ + sc->clear(); + } + } + + ScriptEngine * globalScriptEngine; +} diff --git a/scripting/engine.h b/scripting/engine.h new file mode 100644 index 0000000..99c88cf --- /dev/null +++ b/scripting/engine.h @@ -0,0 +1,154 @@ +// engine.h + +/* 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. + */ + +#pragma once + +#include "../stdafx.h" +#include "../db/jsobj.h" + +extern const char * jsconcatcode; // TODO: change name to mongoJSCode + +namespace mongo { + + typedef unsigned long long ScriptingFunction; + typedef BSONObj (*NativeFunction) ( const BSONObj &args ); + + class Scope : boost::noncopyable { + public: + Scope(); + virtual ~Scope(); + + virtual void reset() = 0; + virtual void init( BSONObj * data ) = 0; + void init( const char * data ){ + BSONObj o( data , 0 ); + init( &o ); + } + + virtual void localConnect( const char * dbName ) = 0; + virtual void externalSetup() = 0; + + virtual double getNumber( const char *field ) = 0; + virtual int getNumberInt( const char *field ){ return (int)getNumber( field ); } + virtual long long getNumberLongLong( const char *field ){ return (long long)getNumber( field ); } + virtual string getString( const char *field ) = 0; + virtual bool getBoolean( const char *field ) = 0; + virtual BSONObj getObject( const char *field ) = 0; + + virtual int type( const char *field ) = 0; + + void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ); + + virtual void setElement( const char *field , const BSONElement& e ) = 0; + virtual void setNumber( const char *field , double val ) = 0; + virtual void setString( const char *field , const char * val ) = 0; + virtual void setObject( const char *field , const BSONObj& obj , bool readOnly=true ) = 0; + virtual void setBoolean( const char *field , bool val ) = 0; + virtual void setThis( const BSONObj * obj ) = 0; + + virtual ScriptingFunction createFunction( const char * code ); + + /** + * @return 0 on success + */ + virtual int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = false ) = 0; + void invokeSafe( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 ){ + int res = invoke( func , args , timeoutMs ); + if ( res == 0 ) + return; + throw UserException( 9004 , (string)"invoke failed: " + getError() ); + } + virtual string getError() = 0; + + int invoke( const char* code , const BSONObj& args, int timeoutMs = 0 ); + void invokeSafe( const char* code , const BSONObj& args, int timeoutMs = 0 ){ + if ( invoke( code , args , timeoutMs ) == 0 ) + return; + throw UserException( 9005 , (string)"invoke failed: " + getError() ); + } + + virtual bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) = 0; + virtual void execSetup( const string& code , const string& name = "setup" ){ + exec( code , name , false , true , true , 0 ); + } + virtual bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ); + + virtual void injectNative( const char *field, NativeFunction func ) = 0; + + virtual void gc() = 0; + + void loadStored( bool ignoreNotConnected = false ); + + /** + if any changes are made to .system.js, call this + right now its just global - slightly inefficient, but a lot simpler + */ + static void storedFuncMod(); + + static int getNumScopes(){ + return _numScopes; + } + + static void validateObjectIdString( const string &str ); + + protected: + + virtual ScriptingFunction _createFunction( const char * code ) = 0; + + string _localDBName; + long long _loadedVersion; + static long long _lastVersion; + map<string,ScriptingFunction> _cachedFunctions; + + static int _numScopes; + }; + + class ScriptEngine : boost::noncopyable { + public: + ScriptEngine(); + virtual ~ScriptEngine(); + + virtual Scope * newScope() { + Scope *s = createScope(); + if ( s && _scopeInitCallback ) + _scopeInitCallback( *s ); + return s; + } + + virtual void runTest() = 0; + + virtual bool utf8Ok() const = 0; + + static void setup(); + + auto_ptr<Scope> getPooledScope( const string& pool ); + void threadDone(); + + struct Unlocker { virtual ~Unlocker() {} }; + virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new Unlocker ); } + + void setScopeInitCallback( void ( *func )( Scope & ) ) { _scopeInitCallback = func; } + + protected: + virtual Scope * createScope() = 0; + + private: + void ( *_scopeInitCallback )( Scope & ); + }; + + extern ScriptEngine * globalScriptEngine; +} diff --git a/scripting/engine_java.cpp b/scripting/engine_java.cpp new file mode 100644 index 0000000..0ed6f1d --- /dev/null +++ b/scripting/engine_java.cpp @@ -0,0 +1,763 @@ +// java.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. + */ + + +#include "stdafx.h" +#include "engine_java.h" +#include <iostream> +#include <map> +#include <list> + +#include "../db/jsobj.h" +#include "../db/db.h" + +using namespace boost::filesystem; + +namespace mongo { + +//#define JNI_DEBUG 1 + +#ifdef JNI_DEBUG +#undef JNI_DEBUG +#define JNI_DEBUG(x) cout << x << endl +#else +#undef JNI_DEBUG +#define JNI_DEBUG(x) +#endif + +} // namespace mongo + + + +#include "../util/message.h" +#include "../db/db.h" + +using namespace std; + +namespace mongo { + +#if defined(_WIN32) + /* [dm] this being undefined without us adding it here means there is + no tss cleanup on windows for boost lib? + we don't care for now esp on windows only + + the boost source says: + + This function's sole purpose is to cause a link error in cases where + automatic tss cleanup is not implemented by Boost.Threads as a + reminder that user code is responsible for calling the necessary + functions at the appropriate times (and for implementing an a + tss_cleanup_implemented() function to eliminate the linker's + missing symbol error). + + If Boost.Threads later implements automatic tss cleanup in cases + where it currently doesn't (which is the plan), the duplicate + symbol error will warn the user that their custom solution is no + longer needed and can be removed. + */ + extern "C" void tss_cleanup_implemented(void) { + //out() << "tss_cleanup_implemented called" << endl; + } +#endif + + JavaJSImpl * JavaJS = 0; + extern string dbExecCommand; + +#if !defined(NOJNI) + + void myJNIClean( JNIEnv * env ) { + JavaJS->detach( env ); + } + +#if defined(_WIN32) + const char SYSTEM_COLON = ';'; +#else + const char SYSTEM_COLON = ':'; +#endif + + + void _addClassPath( const char * ed , stringstream & ss , const char * subdir ) { + path includeDir(ed); + includeDir /= subdir; + directory_iterator end; + try { + directory_iterator i(includeDir); + while ( i != end ) { + path p = *i; + ss << SYSTEM_COLON << p.string(); + i++; + } + } + catch (...) { + problem() << "exception looking for ed class path includeDir: " << includeDir.string() << endl; + sleepsecs(3); + dbexit( EXIT_JAVA ); + } + } + + + JavaJSImpl::JavaJSImpl(const char *appserverPath) { + _jvm = 0; + _mainEnv = 0; + _dbhook = 0; + + stringstream ss; + string edTemp; + + const char * ed = 0; + ss << "-Djava.class.path=."; + + if ( appserverPath ) { + ed = findEd(appserverPath); + assert( ed ); + + ss << SYSTEM_COLON << ed << "/build/"; + + _addClassPath( ed , ss , "include" ); + _addClassPath( ed , ss , "include/jython/" ); + _addClassPath( ed , ss , "include/jython/javalib" ); + } + else { + const string jars = findJars(); + _addClassPath( jars.c_str() , ss , "jars" ); + + edTemp += (string)jars + "/jars/mongojs-js.jar"; + ed = edTemp.c_str(); + } + + + +#if defined(_WIN32) + ss << SYSTEM_COLON << "C:\\Program Files\\Java\\jdk\\lib\\tools.jar"; +#else + ss << SYSTEM_COLON << "/opt/java/lib/tools.jar"; +#endif + + if ( getenv( "CLASSPATH" ) ) + ss << SYSTEM_COLON << getenv( "CLASSPATH" ); + + string s = ss.str(); + char * p = (char *)malloc( s.size() * 4 ); + strcpy( p , s.c_str() ); + char *q = p; +#if defined(_WIN32) + while ( *p ) { + if ( *p == '/' ) *p = '\\'; + p++; + } +#endif + + log(1) << "classpath: " << q << endl; + + JavaVMOption * options = new JavaVMOption[4]; + options[0].optionString = q; + options[1].optionString = (char*)"-Djava.awt.headless=true"; + options[2].optionString = (char*)"-Xmx300m"; + + // Prevent JVM from using async signals internally, since on linux the pre-installed handlers for these + // signals don't seem to be respected by JNI. + options[3].optionString = (char*)"-Xrs"; + // -Xcheck:jni + + _vmArgs = new JavaVMInitArgs(); + _vmArgs->version = JNI_VERSION_1_4; + _vmArgs->options = options; + _vmArgs->nOptions = 4; + _vmArgs->ignoreUnrecognized = JNI_FALSE; + + log(1) << "loading JVM" << endl; + jint res = JNI_CreateJavaVM( &_jvm, (void**)&_mainEnv, _vmArgs ); + + if ( res ) { + log() << "using classpath: " << q << endl; + log() + << " res : " << (unsigned) res << " " + << "_jvm : " << _jvm << " " + << "_env : " << _mainEnv << " " + << endl; + problem() << "Couldn't create JVM res:" << (int) res << " terminating" << endl; + log() << "(try --nojni if you do not require that functionality)" << endl; + exit(22); + } + jassert( res == 0 ); + jassert( _jvm > 0 ); + jassert( _mainEnv > 0 ); + + _envs = new boost::thread_specific_ptr<JNIEnv>( myJNIClean ); + assert( ! _envs->get() ); + _envs->reset( _mainEnv ); + + _dbhook = findClass( "ed/db/JSHook" ); + if ( _dbhook == 0 ) { + log() << "using classpath: " << q << endl; + printException(); + } + jassert( _dbhook ); + + if ( ed ) { + jmethodID init = _mainEnv->GetStaticMethodID( _dbhook , "init" , "(Ljava/lang/String;)V" ); + jassert( init ); + _mainEnv->CallStaticVoidMethod( _dbhook , init , _getEnv()->NewStringUTF( ed ) ); + } + + _dbjni = findClass( "ed/db/DBJni" ); + jassert( _dbjni ); + + _scopeCreate = _mainEnv->GetStaticMethodID( _dbhook , "scopeCreate" , "()J" ); + _scopeInit = _mainEnv->GetStaticMethodID( _dbhook , "scopeInit" , "(JLjava/nio/ByteBuffer;)Z" ); + _scopeSetThis = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetThis" , "(JLjava/nio/ByteBuffer;)Z" ); + _scopeReset = _mainEnv->GetStaticMethodID( _dbhook , "scopeReset" , "(J)Z" ); + _scopeFree = _mainEnv->GetStaticMethodID( _dbhook , "scopeFree" , "(J)V" ); + + _scopeGetNumber = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetNumber" , "(JLjava/lang/String;)D" ); + _scopeGetString = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetString" , "(JLjava/lang/String;)Ljava/lang/String;" ); + _scopeGetBoolean = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetBoolean" , "(JLjava/lang/String;)Z" ); + _scopeGetType = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetType" , "(JLjava/lang/String;)B" ); + _scopeGetObject = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetObject" , "(JLjava/lang/String;Ljava/nio/ByteBuffer;)I" ); + _scopeGuessObjectSize = _mainEnv->GetStaticMethodID( _dbhook , "scopeGuessObjectSize" , "(JLjava/lang/String;)J" ); + + _scopeSetNumber = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetNumber" , "(JLjava/lang/String;D)Z" ); + _scopeSetBoolean = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetBoolean" , "(JLjava/lang/String;Z)Z" ); + _scopeSetString = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetString" , "(JLjava/lang/String;Ljava/lang/String;)Z" ); + _scopeSetObject = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetObject" , "(JLjava/lang/String;Ljava/nio/ByteBuffer;)Z" ); + + _functionCreate = _mainEnv->GetStaticMethodID( _dbhook , "functionCreate" , "(Ljava/lang/String;)J" ); + _invoke = _mainEnv->GetStaticMethodID( _dbhook , "invoke" , "(JJ)I" ); + + jassert( _scopeCreate ); + jassert( _scopeInit ); + jassert( _scopeSetThis ); + jassert( _scopeReset ); + jassert( _scopeFree ); + + jassert( _scopeGetNumber ); + jassert( _scopeGetString ); + jassert( _scopeGetObject ); + jassert( _scopeGetBoolean ); + jassert( _scopeGetType ); + jassert( _scopeGuessObjectSize ); + + jassert( _scopeSetNumber ); + jassert( _scopeSetBoolean ); + jassert( _scopeSetString ); + jassert( _scopeSetObject ); + + jassert( _functionCreate ); + jassert( _invoke ); + + JNINativeMethod * nativeSay = new JNINativeMethod(); + nativeSay->name = (char*)"native_say"; + nativeSay->signature = (char*)"(Ljava/nio/ByteBuffer;)V"; + nativeSay->fnPtr = (void*)java_native_say; + _mainEnv->RegisterNatives( _dbjni , nativeSay , 1 ); + + + JNINativeMethod * nativeCall = new JNINativeMethod(); + nativeCall->name = (char*)"native_call"; + nativeCall->signature = (char*)"(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I"; + nativeCall->fnPtr = (void*)java_native_call; + _mainEnv->RegisterNatives( _dbjni , nativeCall , 1 ); + + } + + JavaJSImpl::~JavaJSImpl() { + if ( _jvm ) { + _jvm->DestroyJavaVM(); + cout << "Destroying JVM" << endl; + } + } + +// scope + + jlong JavaJSImpl::scopeCreate() { + return _getEnv()->CallStaticLongMethod( _dbhook , _scopeCreate ); + } + + jboolean JavaJSImpl::scopeReset( jlong id ) { + return _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeReset ); + } + + void JavaJSImpl::scopeFree( jlong id ) { + _getEnv()->CallStaticVoidMethod( _dbhook , _scopeFree , id ); + } + +// scope setters + + int JavaJSImpl::scopeSetBoolean( jlong id , const char * field , jboolean val ) { + jstring fieldString = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetNumber , id , fieldString , val ); + _getEnv()->DeleteLocalRef( fieldString ); + return res; + } + + int JavaJSImpl::scopeSetNumber( jlong id , const char * field , double val ) { + jstring fieldString = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetNumber , id , fieldString , val ); + _getEnv()->DeleteLocalRef( fieldString ); + return res; + } + + int JavaJSImpl::scopeSetString( jlong id , const char * field , const char * val ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jstring s2 = _getEnv()->NewStringUTF( val ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetString , id , s1 , s2 ); + _getEnv()->DeleteLocalRef( s1 ); + _getEnv()->DeleteLocalRef( s2 ); + return res; + } + + int JavaJSImpl::scopeSetObject( jlong id , const char * field , const BSONObj * obj ) { + jobject bb = 0; + if ( obj ) { + bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + } + + jstring s1 = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetObject , id , s1 , bb ); + _getEnv()->DeleteLocalRef( s1 ); + if ( bb ) + _getEnv()->DeleteLocalRef( bb ); + + return res; + } + + int JavaJSImpl::scopeInit( jlong id , const BSONObj * obj ) { + if ( ! obj ) + return 0; + + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeInit , id , bb ); + _getEnv()->DeleteLocalRef( bb ); + return res; + } + + int JavaJSImpl::scopeSetThis( jlong id , const BSONObj * obj ) { + if ( ! obj ) + return 0; + + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetThis , id , bb ); + _getEnv()->DeleteLocalRef( bb ); + return res; + } + +// scope getters + + char JavaJSImpl::scopeGetType( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + int res =_getEnv()->CallStaticByteMethod( _dbhook , _scopeGetType , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + double JavaJSImpl::scopeGetNumber( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + double res = _getEnv()->CallStaticDoubleMethod( _dbhook , _scopeGetNumber , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + jboolean JavaJSImpl::scopeGetBoolean( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jboolean res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeGetBoolean , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + string JavaJSImpl::scopeGetString( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jstring s = (jstring)_getEnv()->CallStaticObjectMethod( _dbhook , _scopeGetString , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + + if ( ! s ) + return ""; + + const char * c = _getEnv()->GetStringUTFChars( s , 0 ); + string retStr(c); + _getEnv()->ReleaseStringUTFChars( s , c ); + return retStr; + } + + BSONObj JavaJSImpl::scopeGetObject( jlong id , const char * field ) + { + jstring s1 = _getEnv()->NewStringUTF( field ); + int guess = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGuessObjectSize , id , _getEnv()->NewStringUTF( field ) ); + _getEnv()->DeleteLocalRef( s1 ); + + if ( guess == 0 ) + return BSONObj(); + + char * buf = (char *) malloc(guess); + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)buf , guess ); + jassert( bb ); + + int len = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGetObject , id , _getEnv()->NewStringUTF( field ) , bb ); + _getEnv()->DeleteLocalRef( bb ); + jassert( len > 0 && len < guess ); + + BSONObj obj(buf, true); + assert( obj.objsize() <= guess ); + return obj; + } + +// other + + jlong JavaJSImpl::functionCreate( const char * code ) { + jstring s = _getEnv()->NewStringUTF( code ); + jassert( s ); + jlong id = _getEnv()->CallStaticLongMethod( _dbhook , _functionCreate , s ); + _getEnv()->DeleteLocalRef( s ); + return id; + } + + int JavaJSImpl::invoke( jlong scope , jlong function ) { + return _getEnv()->CallStaticIntMethod( _dbhook , _invoke , scope , function ); + } + +// --- fun run method + + void JavaJSImpl::run( const char * js ) { + jclass c = findClass( "ed/js/JS" ); + jassert( c ); + + jmethodID m = _getEnv()->GetStaticMethodID( c , "eval" , "(Ljava/lang/String;)Ljava/lang/Object;" ); + jassert( m ); + + jstring s = _getEnv()->NewStringUTF( js ); + log() << _getEnv()->CallStaticObjectMethod( c , m , s ) << endl; + _getEnv()->DeleteLocalRef( s ); + } + + void JavaJSImpl::printException() { + jthrowable exc = _getEnv()->ExceptionOccurred(); + if ( exc ) { + _getEnv()->ExceptionDescribe(); + _getEnv()->ExceptionClear(); + } + + } + + JNIEnv * JavaJSImpl::_getEnv() { + JNIEnv * env = _envs->get(); + if ( env ) + return env; + + int res = _jvm->AttachCurrentThread( (void**)&env , (void*)&_vmArgs ); + if ( res ) { + out() << "ERROR javajs attachcurrentthread fails res:" << res << '\n'; + assert(false); + } + + _envs->reset( env ); + return env; + } + + Scope * JavaJSImpl::createScope(){ + return new JavaScope(); + } + + void ScriptEngine::setup(){ + if ( ! JavaJS ){ + JavaJS = new JavaJSImpl(); + globalScriptEngine = JavaJS; + } + } + + void jasserted(const char *msg, const char *file, unsigned line) { + log() << "jassert failed " << msg << " " << file << " " << line << endl; + if ( JavaJS ) JavaJS->printException(); + throw AssertionException(); + } + + + const char* findEd(const char *path) { + +#if defined(_WIN32) + + if (!path) { + path = findEd(); + } + + // @TODO check validity + + return path; +#else + + if (!path) { + return findEd(); + } + + log() << "Appserver location specified : " << path << endl; + + if (!path) { + log() << " invalid appserver location : " << path << " : terminating - prepare for bus error" << endl; + return 0; + } + + DIR *testDir = opendir(path); + + if (testDir) { + log(1) << " found directory for appserver : " << path << endl; + closedir(testDir); + return path; + } + else { + log() << " ERROR : not a directory for specified appserver location : " << path << " - prepare for bus error" << endl; + return null; + } +#endif + } + + const char * findEd() { + +#if defined(_WIN32) + log() << "Appserver location will be WIN32 default : c:/l/ed/" << endl; + return "c:/l/ed"; +#else + + static list<const char*> possibleEdDirs; + if ( ! possibleEdDirs.size() ) { + possibleEdDirs.push_back( "../../ed/ed/" ); // this one for dwight dev box + possibleEdDirs.push_back( "../ed/" ); + possibleEdDirs.push_back( "../../ed/" ); + possibleEdDirs.push_back( "../babble/" ); + possibleEdDirs.push_back( "../../babble/" ); + } + + for ( list<const char*>::iterator i = possibleEdDirs.begin() ; i != possibleEdDirs.end(); i++ ) { + const char * temp = *i; + DIR * test = opendir( temp ); + if ( ! test ) + continue; + + closedir( test ); + log(1) << "found directory for appserver : " << temp << endl; + return temp; + } + + return 0; +#endif + }; + + const string findJars() { + + static list<string> possible; + if ( ! possible.size() ) { + possible.push_back( "./" ); + possible.push_back( "../" ); + + log(2) << "dbExecCommand: " << dbExecCommand << endl; + + string dbDir = dbExecCommand; +#ifdef WIN32 + if ( dbDir.find( "\\" ) != string::npos ){ + dbDir = dbDir.substr( 0 , dbDir.find_last_of( "\\" ) ); + } + else { + dbDir = "."; + } +#else + if ( dbDir.find( "/" ) != string::npos ){ + dbDir = dbDir.substr( 0 , dbDir.find_last_of( "/" ) ); + } + else { + bool found = false; + + if ( getenv( "PATH" ) ){ + string s = getenv( "PATH" ); + s += ":"; + pcrecpp::StringPiece input( s ); + string dir; + pcrecpp::RE re("(.*?):"); + while ( re.Consume( &input, &dir ) ){ + string test = dir + "/" + dbExecCommand; + if ( boost::filesystem::exists( test ) ){ + while ( boost::filesystem::symbolic_link_exists( test ) ){ + char tmp[2048]; + int len = readlink( test.c_str() , tmp , 2048 ); + tmp[len] = 0; + log(5) << " symlink " << test << " -->> " << tmp << endl; + test = tmp; + + dir = test.substr( 0 , test.rfind( "/" ) ); + } + dbDir = dir; + found = true; + break; + } + } + } + + if ( ! found ) + dbDir = "."; + } +#endif + + log(2) << "dbDir [" << dbDir << "]" << endl; + possible.push_back( ( dbDir + "/../lib/mongo/" )); + possible.push_back( ( dbDir + "/../lib64/mongo/" )); + possible.push_back( ( dbDir + "/../lib32/mongo/" )); + possible.push_back( ( dbDir + "/" )); + possible.push_back( ( dbDir + "/lib64/mongo/" )); + possible.push_back( ( dbDir + "/lib32/mongo/" )); + } + + for ( list<string>::iterator i = possible.begin() ; i != possible.end(); i++ ) { + const string temp = *i; + const string jarDir = ((string)temp) + "jars/"; + + log(5) << "possible jarDir [" << jarDir << "]" << endl; + + path p(jarDir ); + if ( ! boost::filesystem::exists( p) ) + continue; + + log(1) << "found directory for jars : " << jarDir << endl; + return temp; + } + + problem() << "ERROR : can't find directory for jars - terminating" << endl; + exit(44); + return 0; + + }; + + +// --- + + JNIEXPORT void JNICALL java_native_say(JNIEnv * env , jclass, jobject outBuffer ) { + JNI_DEBUG( "native say called!" ); + + Message out( env->GetDirectBufferAddress( outBuffer ) , false ); + Message in; + + jniCallback( out , in ); + assert( ! out.doIFreeIt() ); + curNs = 0; + } + + JNIEXPORT jint JNICALL java_native_call(JNIEnv * env , jclass, jobject outBuffer , jobject inBuffer ) { + JNI_DEBUG( "native call called!" ); + + Message out( env->GetDirectBufferAddress( outBuffer ) , false ); + Message in; + + jniCallback( out , in ); + curNs = 0; + + JNI_DEBUG( "in.data : " << in.data ); + if ( in.data && in.data->len > 0 ) { + JNI_DEBUG( "copying data of len :" << in.data->len ); + assert( env->GetDirectBufferCapacity( inBuffer ) >= in.data->len ); + memcpy( env->GetDirectBufferAddress( inBuffer ) , in.data , in.data->len ); + + assert( ! out.doIFreeIt() ); + assert( in.doIFreeIt() ); + return in.data->len; + } + + return 0; + } + +// ---- + + void JavaJSImpl::runTest() { + + const int debug = 0; + + JavaJSImpl& JavaJS = *mongo::JavaJS; + + jlong scope = JavaJS.scopeCreate(); + jassert( scope ); + if ( debug ) out() << "got scope" << endl; + + + jlong func1 = JavaJS.functionCreate( "foo = 5.6; bar = \"eliot\"; abc = { foo : 517 }; " ); + jassert( ! JavaJS.invoke( scope , func1 ) ); + + + if ( debug ) out() << "func3 start" << endl; + jlong func3 = JavaJS.functionCreate( "function(){ z = true; } " ); + jassert( func3 ); + jassert( ! JavaJS.invoke( scope , func3 ) ); + jassert( JavaJS.scopeGetBoolean( scope , "z" ) ); + if ( debug ) out() << "func3 done" << endl; + + if ( debug ) out() << "going to get object" << endl; + BSONObj obj = JavaJS.scopeGetObject( scope , "abc" ); + if ( debug ) out() << "done getting object" << endl; + + if ( debug ) { + out() << "obj : " << obj.toString() << endl; + } + + { + time_t start = time(0); + for ( int i=0; i<5000; i++ ) { + JavaJS.scopeSetObject( scope , "obj" , &obj ); + } + time_t end = time(0); + + if ( debug ) + out() << "time : " << (unsigned) ( end - start ) << endl; + } + + if ( debug ) out() << "func4 start" << endl; + JavaJS.scopeSetObject( scope , "obj" , &obj ); + if ( debug ) out() << "\t here 1" << endl; + jlong func4 = JavaJS.functionCreate( "tojson( obj );" ); + if ( debug ) out() << "\t here 2" << endl; + jassert( ! JavaJS.invoke( scope , func4 ) ); + if ( debug ) out() << "func4 end" << endl; + + if ( debug ) out() << "func5 start" << endl; + jassert( JavaJS.scopeSetObject( scope , "c" , &obj ) ); + jlong func5 = JavaJS.functionCreate( "assert.eq( 517 , c.foo );" ); + jassert( func5 ); + jassert( ! JavaJS.invoke( scope , func5 ) ); + if ( debug ) out() << "func5 done" << endl; + + if ( debug ) out() << "func6 start" << endl; + for ( int i=0; i<100; i++ ) { + double val = i + 5; + JavaJS.scopeSetNumber( scope , "zzz" , val ); + jlong func6 = JavaJS.functionCreate( " xxx = zzz; " ); + jassert( ! JavaJS.invoke( scope , func6 ) ); + double n = JavaJS.scopeGetNumber( scope , "xxx" ); + jassert( val == n ); + } + if ( debug ) out() << "func6 done" << endl; + + jlong func7 = JavaJS.functionCreate( "return 11;" ); + jassert( ! JavaJS.invoke( scope , func7 ) ); + assert( 11 == JavaJS.scopeGetNumber( scope , "return" ) ); + + scope = JavaJS.scopeCreate(); + jlong func8 = JavaJS.functionCreate( "function(){ return 12; }" ); + jassert( ! JavaJS.invoke( scope , func8 ) ); + assert( 12 == JavaJS.scopeGetNumber( scope , "return" ) ); + + } + +#endif + +} // namespace mongo diff --git a/scripting/engine_java.h b/scripting/engine_java.h new file mode 100644 index 0000000..ae11cc1 --- /dev/null +++ b/scripting/engine_java.h @@ -0,0 +1,224 @@ +// engine_java.h + +/* 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. + */ + +/* this file contains code to call into java (into the 10gen sandbox) from inside the database */ + +#pragma once + +#include "../stdafx.h" + +#include <jni.h> +#include <boost/thread/tss.hpp> +#include <errno.h> +#include <sys/types.h> + +#if !defined(_WIN32) +#include <dirent.h> +#endif + +#include "../db/jsobj.h" + +#include "engine.h" + +namespace mongo { + + void jasserted(const char *msg, const char *file, unsigned line); +#define jassert(_Expression) if ( ! ( _Expression ) ){ jasserted(#_Expression, __FILE__, __LINE__); } + + const char * findEd(); + const char * findEd(const char *); + const string findJars(); + + class BSONObj; + + class JavaJSImpl : public ScriptEngine { + public: + JavaJSImpl(const char * = 0); + ~JavaJSImpl(); + + jlong scopeCreate(); + int scopeInit( jlong id , const BSONObj * obj ); + int scopeSetThis( jlong id , const BSONObj * obj ); + jboolean scopeReset( jlong id ); + void scopeFree( jlong id ); + + double scopeGetNumber( jlong id , const char * field ); + string scopeGetString( jlong id , const char * field ); + jboolean scopeGetBoolean( jlong id , const char * field ); + BSONObj scopeGetObject( jlong id , const char * field ); + char scopeGetType( jlong id , const char * field ); + + int scopeSetNumber( jlong id , const char * field , double val ); + int scopeSetString( jlong id , const char * field , const char * val ); + int scopeSetObject( jlong id , const char * field , const BSONObj * obj ); + int scopeSetBoolean( jlong id , const char * field , jboolean val ); + + jlong functionCreate( const char * code ); + + /* return values: + public static final int NO_SCOPE = -1; + public static final int NO_FUNCTION = -2; + public static final int INVOKE_ERROR = -3; + public static final int INVOKE_SUCCESS = 0; + */ + int invoke( jlong scope , jlong function ); + + void printException(); + + void run( const char * js ); + + void detach( JNIEnv * env ) { + _jvm->DetachCurrentThread(); + } + + Scope * createScope(); + + void runTest(); + private: + + jobject create( const char * name ) { + jclass c = findClass( name ); + if ( ! c ) + return 0; + + jmethodID cons = _getEnv()->GetMethodID( c , "<init>" , "()V" ); + if ( ! cons ) + return 0; + + return _getEnv()->NewObject( c , cons ); + } + + jclass findClass( const char * name ) { + return _getEnv()->FindClass( name ); + } + + + private: + + JNIEnv * _getEnv(); + + JavaVM * _jvm; + JNIEnv * _mainEnv; + JavaVMInitArgs * _vmArgs; + + boost::thread_specific_ptr<JNIEnv> * _envs; + + jclass _dbhook; + jclass _dbjni; + + jmethodID _scopeCreate; + jmethodID _scopeInit; + jmethodID _scopeSetThis; + jmethodID _scopeReset; + jmethodID _scopeFree; + + jmethodID _scopeGetNumber; + jmethodID _scopeGetString; + jmethodID _scopeGetObject; + jmethodID _scopeGetBoolean; + jmethodID _scopeGuessObjectSize; + jmethodID _scopeGetType; + + jmethodID _scopeSetNumber; + jmethodID _scopeSetString; + jmethodID _scopeSetObject; + jmethodID _scopeSetBoolean; + + jmethodID _functionCreate; + + jmethodID _invoke; + + }; + + extern JavaJSImpl *JavaJS; + +// a javascript "scope" + class JavaScope : public Scope { + public: + JavaScope() { + s = JavaJS->scopeCreate(); + } + virtual ~JavaScope() { + JavaJS->scopeFree(s); + s = 0; + } + void reset() { + JavaJS->scopeReset(s); + } + + void init( BSONObj * o ) { + JavaJS->scopeInit( s , o ); + } + + void localConnect( const char * dbName ){ + setString("$client", dbName ); + } + + double getNumber(const char *field) { + return JavaJS->scopeGetNumber(s,field); + } + string getString(const char *field) { + return JavaJS->scopeGetString(s,field); + } + bool getBoolean(const char *field) { + return JavaJS->scopeGetBoolean(s,field); + } + BSONObj getObject(const char *field ) { + return JavaJS->scopeGetObject(s,field); + } + int type(const char *field ) { + return JavaJS->scopeGetType(s,field); + } + + void setThis( const BSONObj * obj ){ + JavaJS->scopeSetThis( s , obj ); + } + + void setNumber(const char *field, double val ) { + JavaJS->scopeSetNumber(s,field,val); + } + void setString(const char *field, const char * val ) { + JavaJS->scopeSetString(s,field,val); + } + void setObject(const char *field, const BSONObj& obj , bool readOnly ) { + uassert( 10211 , "only readOnly setObject supported in java" , readOnly ); + JavaJS->scopeSetObject(s,field,&obj); + } + void setBoolean(const char *field, bool val ) { + JavaJS->scopeSetBoolean(s,field,val); + } + + ScriptingFunction createFunction( const char * code ){ + return JavaJS->functionCreate( code ); + } + + int invoke( ScriptingFunction function , const BSONObj& args ){ + setObject( "args" , args , true ); + return JavaJS->invoke(s,function); + } + + string getError(){ + return getString( "error" ); + } + + jlong s; + }; + + JNIEXPORT void JNICALL java_native_say(JNIEnv *, jclass, jobject outBuffer ); + JNIEXPORT jint JNICALL java_native_call(JNIEnv *, jclass, jobject outBuffer , jobject inBuffer ); + +} // namespace mongo diff --git a/scripting/engine_none.cpp b/scripting/engine_none.cpp new file mode 100644 index 0000000..2320d0e --- /dev/null +++ b/scripting/engine_none.cpp @@ -0,0 +1,24 @@ +// engine_none.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. + */ + +#include "engine.h" + +namespace mongo { + void ScriptEngine::setup(){ + // noop + } +} diff --git a/scripting/engine_spidermonkey.cpp b/scripting/engine_spidermonkey.cpp new file mode 100644 index 0000000..d75a734 --- /dev/null +++ b/scripting/engine_spidermonkey.cpp @@ -0,0 +1,1464 @@ +// engine_spidermonkey.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. + */ + +#include "stdafx.h" +#include "engine_spidermonkey.h" + +#include "../client/dbclient.h" + +#ifndef _WIN32 +#include <boost/date_time/posix_time/posix_time.hpp> +#undef assert +#define assert xassert +#endif + +#define smuassert( cx , msg , val ) \ + if ( ! ( val ) ){ \ + JS_ReportError( cx , msg ); \ + return JS_FALSE; \ + } + +namespace mongo { + + string trim( string s ){ + while ( s.size() && isspace( s[0] ) ) + s = s.substr( 1 ); + + while ( s.size() && isspace( s[s.size()-1] ) ) + s = s.substr( 0 , s.size() - 1 ); + + return s; + } + + boost::thread_specific_ptr<SMScope> currentScope( dontDeleteScope ); + boost::recursive_mutex smmutex; +#define smlock recursive_boostlock ___lk( smmutex ); + +#define GETHOLDER(x,o) ((BSONHolder*)JS_GetPrivate( x , o )) + + class BSONFieldIterator; + + class BSONHolder { + public: + + BSONHolder( BSONObj obj ){ + _obj = obj.getOwned(); + _inResolve = false; + _modified = false; + _magic = 17; + } + + ~BSONHolder(){ + _magic = 18; + } + + void check(){ + uassert( 10212 , "holder magic value is wrong" , _magic == 17 && _obj.isValid() ); + } + + BSONFieldIterator * it(); + + BSONObj _obj; + bool _inResolve; + char _magic; + list<string> _extra; + set<string> _removed; + bool _modified; + }; + + class BSONFieldIterator { + public: + + BSONFieldIterator( BSONHolder * holder ){ + + set<string> added; + + BSONObjIterator it( holder->_obj ); + while ( it.more() ){ + BSONElement e = it.next(); + if ( holder->_removed.count( e.fieldName() ) ) + continue; + _names.push_back( e.fieldName() ); + added.insert( e.fieldName() ); + } + + for ( list<string>::iterator i = holder->_extra.begin(); i != holder->_extra.end(); i++ ){ + if ( ! added.count( *i ) ) + _names.push_back( *i ); + } + + _it = _names.begin(); + } + + bool more(){ + return _it != _names.end(); + } + + string next(){ + string s = *_it; + _it++; + return s; + } + + private: + list<string> _names; + list<string>::iterator _it; + }; + + BSONFieldIterator * BSONHolder::it(){ + return new BSONFieldIterator( this ); + } + + + class Convertor : boost::noncopyable { + public: + Convertor( JSContext * cx ){ + _context = cx; + } + + string toString( JSString * so ){ + jschar * s = JS_GetStringChars( so ); + size_t srclen = JS_GetStringLength( so ); + if( srclen == 0 ) + return ""; + + size_t len = srclen * 6; // we only need *3, but see note on len below + char * dst = (char*)malloc( len ); + + len /= 2; + // doc re weird JS_EncodeCharacters api claims len expected in 16bit + // units, but experiments suggest 8bit units expected. We allocate + // enough memory that either will work. + + assert( JS_EncodeCharacters( _context , s , srclen , dst , &len) ); + + string ss( dst , len ); + free( dst ); + if ( !JS_CStringsAreUTF8() ) + for( string::const_iterator i = ss.begin(); i != ss.end(); ++i ) + uassert( 10213 , "non ascii character detected", (unsigned char)(*i) <= 127 ); + return ss; + } + + string toString( jsval v ){ + return toString( JS_ValueToString( _context , v ) ); + } + + double toNumber( jsval v ){ + double d; + uassert( 10214 , "not a number" , JS_ValueToNumber( _context , v , &d ) ); + return d; + } + + bool toBoolean( jsval v ){ + JSBool b; + assert( JS_ValueToBoolean( _context, v , &b ) ); + return b; + } + + OID toOID( jsval v ){ + JSContext * cx = _context; + assert( JSVAL_IS_OID( v ) ); + + JSObject * o = JSVAL_TO_OBJECT( v ); + OID oid; + oid.init( getString( o , "str" ) ); + return oid; + } + + BSONObj toObject( JSObject * o ){ + if ( ! o ) + return BSONObj(); + + if ( JS_InstanceOf( _context , o , &bson_ro_class , 0 ) ){ + BSONHolder * holder = GETHOLDER( _context , o ); + assert( holder ); + return holder->_obj.getOwned(); + } + + BSONObj orig; + if ( JS_InstanceOf( _context , o , &bson_class , 0 ) ){ + BSONHolder * holder = GETHOLDER(_context,o); + assert( holder ); + if ( ! holder->_modified ){ + return holder->_obj; + } + orig = holder->_obj; + } + + BSONObjBuilder b; + + if ( ! appendSpecialDBObject( this , b , "value" , OBJECT_TO_JSVAL( o ) , o ) ){ + + jsval theid = getProperty( o , "_id" ); + if ( ! JSVAL_IS_VOID( theid ) ){ + append( b , "_id" , theid ); + } + + JSIdArray * properties = JS_Enumerate( _context , o ); + assert( properties ); + + for ( jsint i=0; i<properties->length; i++ ){ + jsid id = properties->vector[i]; + jsval nameval; + assert( JS_IdToValue( _context ,id , &nameval ) ); + string name = toString( nameval ); + if ( name == "_id" ) + continue; + + append( b , name , getProperty( o , name.c_str() ) , orig[name].type() ); + } + + JS_DestroyIdArray( _context , properties ); + } + + return b.obj(); + } + + BSONObj toObject( jsval v ){ + if ( JSVAL_IS_NULL( v ) || + JSVAL_IS_VOID( v ) ) + return BSONObj(); + + uassert( 10215 , "not an object" , JSVAL_IS_OBJECT( v ) ); + return toObject( JSVAL_TO_OBJECT( v ) ); + } + + string getFunctionCode( JSFunction * func ){ + return toString( JS_DecompileFunction( _context , func , 0 ) ); + } + + string getFunctionCode( jsval v ){ + uassert( 10216 , "not a function" , JS_TypeOfValue( _context , v ) == JSTYPE_FUNCTION ); + return getFunctionCode( JS_ValueToFunction( _context , v ) ); + } + + void appendRegex( BSONObjBuilder& b , const string& name , string s ){ + assert( s[0] == '/' ); + s = s.substr(1); + string::size_type end = s.rfind( '/' ); + b.appendRegex( name.c_str() , s.substr( 0 , end ).c_str() , s.substr( end + 1 ).c_str() ); + } + + void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO ){ + //cout << "name: " << name << "\t" << typeString( val ) << " oldType: " << oldType << endl; + switch ( JS_TypeOfValue( _context , val ) ){ + + case JSTYPE_VOID: b.appendUndefined( name.c_str() ); break; + case JSTYPE_NULL: b.appendNull( name.c_str() ); break; + + case JSTYPE_NUMBER: { + double d = toNumber( val ); + if ( oldType == NumberInt && ((int)d) == d ) + b.append( name.c_str() , (int)d ); + else + b.append( name.c_str() , d ); + break; + } + case JSTYPE_STRING: b.append( name.c_str() , toString( val ) ); break; + case JSTYPE_BOOLEAN: b.appendBool( name.c_str() , toBoolean( val ) ); break; + + case JSTYPE_OBJECT: { + JSObject * o = JSVAL_TO_OBJECT( val ); + if ( ! o || o == JSVAL_NULL ){ + b.appendNull( name.c_str() ); + } + else if ( ! appendSpecialDBObject( this , b , name , val , o ) ){ + BSONObj sub = toObject( o ); + if ( JS_IsArrayObject( _context , o ) ){ + b.appendArray( name.c_str() , sub ); + } + else { + b.append( name.c_str() , sub ); + } + } + break; + } + + case JSTYPE_FUNCTION: { + string s = toString(val); + if ( s[0] == '/' ){ + appendRegex( b , name , s ); + } + else { + b.appendCode( name.c_str() , getFunctionCode( val ).c_str() ); + } + break; + } + + default: uassert( 10217 , (string)"can't append field. name:" + name + " type: " + typeString( val ) , 0 ); + } + } + + // ---------- to spider monkey --------- + + bool hasFunctionIdentifier( const string& code ){ + if ( code.size() < 9 || code.find( "function" ) != 0 ) + return false; + + return code[8] == ' ' || code[8] == '('; + } + + bool isSimpleStatement( const string& code ){ + if ( code.find( "return" ) != string::npos ) + return false; + + if ( code.find( ";" ) != string::npos && + code.find( ";" ) != code.rfind( ";" ) ) + return false; + + if ( code.find( "for(" ) != string::npos || + code.find( "for (" ) != string::npos || + code.find( "while (" ) != string::npos || + code.find( "while(" ) != string::npos ) + return false; + + return true; + } + + void addRoot( JSFunction * f , const char * name ); + + JSFunction * compileFunction( const char * code, JSObject * assoc = 0 ){ + const char * gcName = "unknown"; + JSFunction * f = _compileFunction( code , assoc , gcName ); + //addRoot( f , gcName ); + return f; + } + + JSFunction * _compileFunction( const char * raw , JSObject * assoc , const char *& gcName ){ + if ( ! assoc ) + assoc = JS_GetGlobalObject( _context ); + + while (isspace(*raw)) { + raw++; + } + + stringstream fname; + fname << "cf_"; + static int fnum = 1; + fname << "_" << fnum++ << "_"; + + + if ( ! hasFunctionIdentifier( raw ) ){ + string s = raw; + if ( isSimpleStatement( s ) ){ + s = "return " + s; + } + gcName = "cf anon"; + fname << "anon"; + return JS_CompileFunction( _context , assoc , fname.str().c_str() , 0 , 0 , s.c_str() , strlen( s.c_str() ) , "nofile_a" , 0 ); + } + + string code = raw; + + size_t start = code.find( '(' ); + assert( start != string::npos ); + + fname << "_f_" << trim( code.substr( 9 , start - 9 ) ); + + code = code.substr( start + 1 ); + size_t end = code.find( ')' ); + assert( end != string::npos ); + + string paramString = trim( code.substr( 0 , end ) ); + code = code.substr( end + 1 ); + + vector<string> params; + while ( paramString.size() ){ + size_t c = paramString.find( ',' ); + if ( c == string::npos ){ + params.push_back( paramString ); + break; + } + params.push_back( trim( paramString.substr( 0 , c ) ) ); + paramString = trim( paramString.substr( c + 1 ) ); + paramString = trim( paramString ); + } + + const char ** paramArray = new const char*[params.size()]; + for ( size_t i=0; i<params.size(); i++ ) + paramArray[i] = params[i].c_str(); + + JSFunction * func = JS_CompileFunction( _context , assoc , fname.str().c_str() , params.size() , paramArray , code.c_str() , strlen( code.c_str() ) , "nofile_b" , 0 ); + delete paramArray; + if ( ! func ){ + cout << "compile failed for: " << raw << endl; + return 0; + } + gcName = "cf normal"; + return func; + } + + + jsval toval( double d ){ + jsval val; + assert( JS_NewNumberValue( _context, d , &val ) ); + return val; + } + + jsval toval( const char * c ){ + JSString * s = JS_NewStringCopyZ( _context , c ); + if ( s ) + return STRING_TO_JSVAL( s ); + + // possibly unicode, try manual + + size_t len = strlen( c ); + size_t dstlen = len * 4; + jschar * dst = (jschar*)malloc( dstlen ); + + JSBool res = JS_DecodeBytes( _context , c , len , dst, &dstlen ); + if ( res ){ + s = JS_NewUCStringCopyN( _context , dst , dstlen ); + } + + free( dst ); + + if ( ! res ){ + cout << "decode failed. probably invalid utf-8 string [" << c << "]" << endl; + jsval v; + if ( JS_GetPendingException( _context , &v ) ) + cout << "\t why: " << toString( v ) << endl; + throw UserException( 9006 , "invalid utf8" ); + } + + assert( s ); + return STRING_TO_JSVAL( s ); + } + + JSObject * toJSObject( const BSONObj * obj , bool readOnly=false ){ + static string ref = "$ref"; + if ( ref == obj->firstElement().fieldName() ){ + JSObject * o = JS_NewObject( _context , &dbref_class , NULL, NULL); + assert( o ); + setProperty( o , "$ref" , toval( obj->firstElement() ) ); + setProperty( o , "$id" , toval( (*obj)["$id"] ) ); + return o; + } + JSObject * o = JS_NewObject( _context , readOnly ? &bson_ro_class : &bson_class , NULL, NULL); + assert( o ); + assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); + return o; + } + + jsval toval( const BSONObj* obj , bool readOnly=false ){ + JSObject * o = toJSObject( obj , readOnly ); + return OBJECT_TO_JSVAL( o ); + } + + jsval toval( const BSONElement& e ){ + + switch( e.type() ){ + case EOO: + case jstNULL: + case Undefined: + return JSVAL_NULL; + case NumberDouble: + case NumberInt: + case NumberLong: + return toval( e.number() ); + case Symbol: // TODO: should we make a special class for this + case String: + return toval( e.valuestr() ); + case Bool: + return e.boolean() ? JSVAL_TRUE : JSVAL_FALSE; + case Object:{ + BSONObj embed = e.embeddedObject().getOwned(); + return toval( &embed ); + } + case Array:{ + + BSONObj embed = e.embeddedObject().getOwned(); + + if ( embed.isEmpty() ){ + return OBJECT_TO_JSVAL( JS_NewArrayObject( _context , 0 , 0 ) ); + } + + int n = embed.nFields(); + + JSObject * array = JS_NewArrayObject( _context , n , 0 ); + assert( array ); + + jsval myarray = OBJECT_TO_JSVAL( array ); + + for ( int i=0; i<n; i++ ){ + jsval v = toval( embed[i] ); + assert( JS_SetElement( _context , array , i , &v ) ); + } + + return myarray; + } + case jstOID:{ + OID oid = e.__oid(); + JSObject * o = JS_NewObject( _context , &object_id_class , 0 , 0 ); + setProperty( o , "str" , toval( oid.str().c_str() ) ); + return OBJECT_TO_JSVAL( o ); + } + case RegEx:{ + const char * flags = e.regexFlags(); + uintN flagNumber = 0; + while ( *flags ){ + switch ( *flags ){ + case 'g': flagNumber |= JSREG_GLOB; break; + case 'i': flagNumber |= JSREG_FOLD; break; + case 'm': flagNumber |= JSREG_MULTILINE; break; + //case 'y': flagNumber |= JSREG_STICKY; break; + + default: + log() << "warning: unknown regex flag:" << *flags << endl; + } + flags++; + } + + JSObject * r = JS_NewRegExpObject( _context , (char*)e.regex() , strlen( e.regex() ) , flagNumber ); + assert( r ); + return OBJECT_TO_JSVAL( r ); + } + case Code:{ + JSFunction * func = compileFunction( e.valuestr() ); + return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); + } + case CodeWScope:{ + JSFunction * func = compileFunction( e.codeWScopeCode() ); + + BSONObj extraScope = e.codeWScopeObject(); + if ( ! extraScope.isEmpty() ){ + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + } + + return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); + } + case Date: + return OBJECT_TO_JSVAL( js_NewDateObjectMsec( _context , (jsdouble) e.date().millis ) ); + + case MinKey: + return OBJECT_TO_JSVAL( JS_NewObject( _context , &minkey_class , 0 , 0 ) ); + + case MaxKey: + return OBJECT_TO_JSVAL( JS_NewObject( _context , &maxkey_class , 0 , 0 ) ); + + case Timestamp: { + JSObject * o = JS_NewObject( _context , ×tamp_class , 0 , 0 ); + setProperty( o , "t" , toval( (double)(e.timestampTime()) ) ); + setProperty( o , "i" , toval( (double)(e.timestampInc()) ) ); + return OBJECT_TO_JSVAL( o ); + } + + case DBRef: { + JSObject * o = JS_NewObject( _context , &dbpointer_class , 0 , 0 ); + setProperty( o , "ns" , toval( e.dbrefNS() ) ); + + JSObject * oid = JS_NewObject( _context , &object_id_class , 0 , 0 ); + setProperty( oid , "str" , toval( e.dbrefOID().str().c_str() ) ); + + setProperty( o , "id" , OBJECT_TO_JSVAL( oid ) ); + return OBJECT_TO_JSVAL( o ); + } + case BinData:{ + JSObject * o = JS_NewObject( _context , &bindata_class , 0 , 0 ); + int len; + void * data = (void*)e.binData( len ); + assert( JS_SetPrivate( _context , o , data ) ); + + setProperty( o , "len" , toval( len ) ); + setProperty( o , "type" , toval( (int)e.binDataType() ) ); + return OBJECT_TO_JSVAL( o ); + } + } + + cout << "toval: unknown type: " << e.type() << endl; + uassert( 10218 , "not done: toval" , 0 ); + return 0; + } + + // ------- object helpers ------ + + JSObject * getJSObject( JSObject * o , const char * name ){ + jsval v; + assert( JS_GetProperty( _context , o , name , &v ) ); + return JSVAL_TO_OBJECT( v ); + } + + JSObject * getGlobalObject( const char * name ){ + return getJSObject( JS_GetGlobalObject( _context ) , name ); + } + + JSObject * getGlobalPrototype( const char * name ){ + return getJSObject( getGlobalObject( name ) , "prototype" ); + } + + bool hasProperty( JSObject * o , const char * name ){ + JSBool res; + assert( JS_HasProperty( _context , o , name , & res ) ); + return res; + } + + jsval getProperty( JSObject * o , const char * field ){ + uassert( 10219 , "object passed to getPropery is null" , o ); + jsval v; + assert( JS_GetProperty( _context , o , field , &v ) ); + return v; + } + + void setProperty( JSObject * o , const char * field , jsval v ){ + assert( JS_SetProperty( _context , o , field , &v ) ); + } + + string typeString( jsval v ){ + JSType t = JS_TypeOfValue( _context , v ); + return JS_GetTypeName( _context , t ); + } + + bool getBoolean( JSObject * o , const char * field ){ + return toBoolean( getProperty( o , field ) ); + } + + double getNumber( JSObject * o , const char * field ){ + return toNumber( getProperty( o , field ) ); + } + + string getString( JSObject * o , const char * field ){ + return toString( getProperty( o , field ) ); + } + + JSClass * getClass( JSObject * o , const char * field ){ + jsval v; + assert( JS_GetProperty( _context , o , field , &v ) ); + if ( ! JSVAL_IS_OBJECT( v ) ) + return 0; + return JS_GET_CLASS( _context , JSVAL_TO_OBJECT( v ) ); + } + + JSContext * _context; + + + }; + + + void bson_finalize( JSContext * cx , JSObject * obj ){ + BSONHolder * o = GETHOLDER( cx , obj ); + if ( o ){ + delete o; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSBool bson_enumerate( JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp ){ + + BSONHolder * o = GETHOLDER( cx , obj ); + + if ( enum_op == JSENUMERATE_INIT ){ + if ( o ){ + BSONFieldIterator * it = o->it(); + *statep = PRIVATE_TO_JSVAL( it ); + } + else { + *statep = 0; + } + if ( idp ) + *idp = JSVAL_ZERO; + return JS_TRUE; + } + + BSONFieldIterator * it = (BSONFieldIterator*)JSVAL_TO_PRIVATE( *statep ); + if ( ! it ){ + *statep = 0; + return JS_TRUE; + } + + if ( enum_op == JSENUMERATE_NEXT ){ + if ( it->more() ){ + string name = it->next(); + Convertor c(cx); + assert( JS_ValueToId( cx , c.toval( name.c_str() ) , idp ) ); + } + else { + delete it; + *statep = 0; + } + return JS_TRUE; + } + + if ( enum_op == JSENUMERATE_DESTROY ){ + if ( it ) + delete it; + return JS_TRUE; + } + + uassert( 10220 , "don't know what to do with this op" , 0 ); + return JS_FALSE; + } + + JSBool noaccess( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ){ + // in init code still + return JS_TRUE; + } + if ( holder->_inResolve ) + return JS_TRUE; + JS_ReportError( cx , "doing write op on read only operation" ); + return JS_FALSE; + } + + JSClass bson_ro_class = { + "bson_ro_object" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE , + noaccess, noaccess, JS_PropertyStub, noaccess, + (JSEnumerateOp)bson_enumerate, (JSResolveOp)(&resolveBSONField) , JS_ConvertStub, bson_finalize , + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool bson_cons( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + cerr << "bson_cons : shouldn't be here!" << endl; + JS_ReportError( cx , "can't construct bson object" ); + return JS_FALSE; + } + + JSFunctionSpec bson_functions[] = { + { 0 } + }; + + JSBool bson_add_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ){ + // static init + return JS_TRUE; + } + if ( ! holder->_inResolve ){ + Convertor c(cx); + string name = c.toString( idval ); + if ( holder->_obj[name].eoo() ){ + holder->_extra.push_back( name ); + } + holder->_modified = true; + } + return JS_TRUE; + } + + + JSBool mark_modified( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + Convertor c(cx); + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( holder->_inResolve ) + return JS_TRUE; + holder->_modified = true; + holder->_removed.erase( c.toString( idval ) ); + return JS_TRUE; + } + + JSBool mark_modified_remove( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + Convertor c(cx); + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( holder->_inResolve ) + return JS_TRUE; + holder->_modified = true; + holder->_removed.insert( c.toString( idval ) ); + return JS_TRUE; + } + + JSClass bson_class = { + "bson_object" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE , + bson_add_prop, mark_modified_remove, JS_PropertyStub, mark_modified, + (JSEnumerateOp)bson_enumerate, (JSResolveOp)(&resolveBSONField) , JS_ConvertStub, bson_finalize , + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + static JSClass global_class = { + "global", JSCLASS_GLOBAL_FLAGS, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // --- global helpers --- + + JSBool native_print( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + for ( uintN i=0; i<argc; i++ ){ + if ( i > 0 ) + cout << " "; + cout << c.toString( argv[i] ); + } + cout << endl; + return JS_TRUE; + } + + JSBool native_helper( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + Convertor c(cx); + + NativeFunction func = (NativeFunction)((long long)c.getNumber( obj , "x" ) ); + assert( func ); + + BSONObj a; + if ( argc > 0 ){ + BSONObjBuilder args; + for ( uintN i=0; i<argc; i++ ){ + c.append( args , args.numStr( i ) , argv[i] ); + } + + a = args.obj(); + } + BSONObj out = func( a ); + + if ( out.isEmpty() ){ + *rval = JSVAL_VOID; + } + else { + *rval = c.toval( out.firstElement() ); + } + + return JS_TRUE; + } + + JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ); + + JSBool native_gc( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + JS_GC( cx ); + return JS_TRUE; + } + + JSFunctionSpec globalHelpers[] = { + { "print" , &native_print , 0 , 0 , 0 } , + { "nativeHelper" , &native_helper , 1 , 0 , 0 } , + { "load" , &native_load , 1 , 0 , 0 } , + { "gc" , &native_gc , 1 , 0 , 0 } , + { 0 , 0 , 0 , 0 , 0 } + }; + + // ----END global helpers ---- + + // Object helpers + + JSBool bson_get_size(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + if ( argc != 1 || !JSVAL_IS_OBJECT( argv[ 0 ] ) ) { + JS_ReportError( cx , "bsonsize requires one valid object" ); + return JS_FALSE; + } + + BSONHolder * o = GETHOLDER( cx , JSVAL_TO_OBJECT( argv[ 0 ] ) ); + double size = 0; + if ( o ){ + size = o->_obj.objsize(); + } + Convertor c(cx); + *rval = c.toval( size ); + return JS_TRUE; + } + + JSFunctionSpec objectHelpers[] = { + { "bsonsize" , &bson_get_size , 1 , 0 , 0 } , + { 0 , 0 , 0 , 0 , 0 } + }; + + // end Object helpers + + JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + assert( JS_EnterLocalRootScope( cx ) ); + Convertor c( cx ); + + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ){ + // static init + *objp = 0; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + holder->check(); + + string s = c.toString( id ); + + BSONElement e = holder->_obj[ s.c_str() ]; + + if ( e.type() == EOO || holder->_removed.count( s ) ){ + *objp = 0; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + + jsval val = c.toval( e ); + + assert( ! holder->_inResolve ); + holder->_inResolve = true; + assert( JS_SetProperty( cx , obj , s.c_str() , &val ) ); + holder->_inResolve = false; + + if ( val != JSVAL_NULL && val != JSVAL_VOID && JSVAL_IS_OBJECT( val ) ){ + // TODO: this is a hack to get around sub objects being modified + JSObject * oo = JSVAL_TO_OBJECT( val ); + if ( JS_InstanceOf( cx , oo , &bson_class , 0 ) || + JS_IsArrayObject( cx , oo ) ){ + holder->_modified = true; + } + } + + *objp = obj; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + + + class SMScope; + + class SMEngine : public ScriptEngine { + public: + + SMEngine(){ +#ifdef SM18 + JS_SetCStringsAreUTF8(); +#endif + + _runtime = JS_NewRuntime(8L * 1024L * 1024L); + uassert( 10221 , "JS_NewRuntime failed" , _runtime ); + + if ( ! utf8Ok() ){ + log() << "*** warning: spider monkey build without utf8 support. consider rebuilding with utf8 support" << endl; + } + + int x = 0; + assert( x = 1 ); + uassert( 10222 , "assert not being executed" , x == 1 ); + } + + ~SMEngine(){ + JS_DestroyRuntime( _runtime ); + JS_ShutDown(); + } + + Scope * createScope(); + + void runTest(); + + virtual bool utf8Ok() const { return JS_CStringsAreUTF8(); } + +#ifdef XULRUNNER + JSClass * _dateClass; + JSClass * _regexClass; +#endif + + + private: + JSRuntime * _runtime; + friend class SMScope; + }; + + SMEngine * globalSMEngine; + + + void ScriptEngine::setup(){ + globalSMEngine = new SMEngine(); + globalScriptEngine = globalSMEngine; + } + + + // ------ scope ------ + + + JSBool no_gc(JSContext *cx, JSGCStatus status){ + return JS_FALSE; + } + + JSBool yes_gc(JSContext *cx, JSGCStatus status){ + return JS_TRUE; + } + + class SMScope : public Scope { + public: + SMScope() : _this( 0 ) , _externalSetup( false ) , _localConnect( false ) { + smlock; + _context = JS_NewContext( globalSMEngine->_runtime , 8192 ); + _convertor = new Convertor( _context ); + massert( 10431 , "JS_NewContext failed" , _context ); + + JS_SetOptions( _context , JSOPTION_VAROBJFIX); + //JS_SetVersion( _context , JSVERSION_LATEST); TODO + JS_SetErrorReporter( _context , errorReporter ); + + _global = JS_NewObject( _context , &global_class, NULL, NULL); + massert( 10432 , "JS_NewObject failed for global" , _global ); + JS_SetGlobalObject( _context , _global ); + massert( 10433 , "js init failed" , JS_InitStandardClasses( _context , _global ) ); + + JS_SetOptions( _context , JS_GetOptions( _context ) | JSOPTION_VAROBJFIX ); + + JS_DefineFunctions( _context , _global , globalHelpers ); + + JS_DefineFunctions( _context , _convertor->getGlobalObject( "Object" ), objectHelpers ); + + //JS_SetGCCallback( _context , no_gc ); // this is useful for seeing if something is a gc problem + + _postCreateHacks(); + } + + ~SMScope(){ + smlock; + uassert( 10223 , "deleted SMScope twice?" , _convertor ); + + for ( list<void*>::iterator i=_roots.begin(); i != _roots.end(); i++ ){ + JS_RemoveRoot( _context , *i ); + } + _roots.clear(); + + if ( _this ){ + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + + if ( _convertor ){ + delete _convertor; + _convertor = 0; + } + + if ( _context ){ + JS_DestroyContext( _context ); + _context = 0; + } + + } + + void reset(){ + smlock; + assert( _convertor ); + return; + if ( _this ){ + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + currentScope.reset( this ); + _error = ""; + } + + void addRoot( void * root , const char * name ){ + JS_AddNamedRoot( _context , root , name ); + _roots.push_back( root ); + } + + void init( BSONObj * data ){ + smlock; + if ( ! data ) + return; + + BSONObjIterator i( *data ); + while ( i.more() ){ + BSONElement e = i.next(); + _convertor->setProperty( _global , e.fieldName() , _convertor->toval( e ) ); + _initFieldNames.insert( e.fieldName() ); + } + + } + + void externalSetup(){ + smlock; + uassert( 10224 , "already local connected" , ! _localConnect ); + if ( _externalSetup ) + return; + initMongoJS( this , _context , _global , false ); + _externalSetup = true; + } + + void localConnect( const char * dbName ){ + smlock; + uassert( 10225 , "already setup for external db" , ! _externalSetup ); + if ( _localConnect ){ + uassert( 10226 , "connected to different db" , _localDBName == dbName ); + return; + } + + initMongoJS( this , _context , _global , true ); + + exec( "_mongo = new Mongo();" ); + exec( ((string)"db = _mongo.getDB( \"" + dbName + "\" ); ").c_str() ); + + _localConnect = true; + _localDBName = dbName; + loadStored(); + } + + // ----- getters ------ + double getNumber( const char *field ){ + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + return _convertor->toNumber( val ); + } + + string getString( const char *field ){ + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + JSString * s = JS_ValueToString( _context , val ); + return _convertor->toString( s ); + } + + bool getBoolean( const char *field ){ + smlock; + return _convertor->getBoolean( _global , field ); + } + + BSONObj getObject( const char *field ){ + smlock; + return _convertor->toObject( _convertor->getProperty( _global , field ) ); + } + + JSObject * getJSObject( const char * field ){ + smlock; + return _convertor->getJSObject( _global , field ); + } + + int type( const char *field ){ + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + + switch ( JS_TypeOfValue( _context , val ) ){ + case JSTYPE_VOID: return Undefined; + case JSTYPE_NULL: return jstNULL; + case JSTYPE_OBJECT: { + if ( val == JSVAL_NULL ) + return jstNULL; + JSObject * o = JSVAL_TO_OBJECT( val ); + if ( JS_IsArrayObject( _context , o ) ) + return Array; + if ( isDate( _context , o ) ) + return Date; + return Object; + } + case JSTYPE_FUNCTION: return Code; + case JSTYPE_STRING: return String; + case JSTYPE_NUMBER: return NumberDouble; + case JSTYPE_BOOLEAN: return Bool; + default: + uassert( 10227 , "unknown type" , 0 ); + } + return 0; + } + + // ----- setters ------ + + void setElement( const char *field , const BSONElement& val ){ + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setNumber( const char *field , double val ){ + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setString( const char *field , const char * val ){ + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setObject( const char *field , const BSONObj& obj , bool readOnly ){ + smlock; + jsval v = _convertor->toval( &obj , readOnly ); + JS_SetProperty( _context , _global , field , &v ); + } + + void setBoolean( const char *field , bool val ){ + smlock; + jsval v = BOOLEAN_TO_JSVAL( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setThis( const BSONObj * obj ){ + smlock; + if ( _this ){ + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + + if ( obj ){ + _this = _convertor->toJSObject( obj ); + JS_AddNamedRoot( _context , &_this , "scope this" ); + } + } + + // ---- functions ----- + + ScriptingFunction _createFunction( const char * code ){ + smlock; + precall(); + return (ScriptingFunction)_convertor->compileFunction( code ); + } + + struct TimeoutSpec { + boost::posix_time::ptime start; + boost::posix_time::time_duration timeout; + int count; + }; + + static JSBool _checkTimeout( JSContext *cx ){ + TimeoutSpec &spec = *(TimeoutSpec *)( JS_GetContextPrivate( cx ) ); + if ( ++spec.count % 1000 != 0 ) + return JS_TRUE; + boost::posix_time::time_duration elapsed = ( boost::posix_time::microsec_clock::local_time() - spec.start ); + if ( elapsed < spec.timeout ) { + return JS_TRUE; + } + JS_ReportError( cx, "Timeout exceeded" ); + return JS_FALSE; + + } + static JSBool checkTimeout( JSContext *cx, JSScript *script ){ + return _checkTimeout( cx ); + } + + + void installCheckTimeout( int timeoutMs ) { + if ( timeoutMs > 0 ) { + TimeoutSpec *spec = new TimeoutSpec; + spec->timeout = boost::posix_time::millisec( timeoutMs ); + spec->start = boost::posix_time::microsec_clock::local_time(); + spec->count = 0; + JS_SetContextPrivate( _context, (void*)spec ); +#if defined(SM181) && !defined(XULRUNNER190) + JS_SetOperationCallback( _context, _checkTimeout ); +#else + JS_SetBranchCallback( _context, checkTimeout ); +#endif + } + } + + void uninstallCheckTimeout( int timeoutMs ) { + if ( timeoutMs > 0 ) { +#if defined(SM181) && !defined(XULRUNNER190) + JS_SetOperationCallback( _context , 0 ); +#else + JS_SetBranchCallback( _context, 0 ); +#endif + delete (TimeoutSpec *)JS_GetContextPrivate( _context ); + JS_SetContextPrivate( _context, 0 ); + } + } + + void precall(){ + _error = ""; + currentScope.reset( this ); + } + + bool exec( const string& code , const string& name = "(anon)" , bool printResult = false , bool reportError = true , bool assertOnError = true, int timeoutMs = 0 ){ + smlock; + precall(); + + jsval ret = JSVAL_VOID; + + installCheckTimeout( timeoutMs ); + JSBool worked = JS_EvaluateScript( _context , _global , code.c_str() , strlen( code.c_str() ) , name.c_str() , 0 , &ret ); + uninstallCheckTimeout( timeoutMs ); + + if ( assertOnError ) + uassert( 10228 , name + " exec failed" , worked ); + + if ( reportError && ! _error.empty() ){ + // cout << "exec error: " << _error << endl; + // already printed in reportError, so... TODO + } + + if ( worked ) + _convertor->setProperty( _global , "__lastres__" , ret ); + + if ( worked && printResult && ! JSVAL_IS_VOID( ret ) ) + cout << _convertor->toString( ret ) << endl; + + return worked; + } + + int invoke( JSFunction * func , const BSONObj& args, int timeoutMs , bool ignoreReturn ){ + smlock; + precall(); + + assert( JS_EnterLocalRootScope( _context ) ); + + int nargs = args.nFields(); + scoped_array<jsval> smargsPtr( new jsval[nargs] ); + if ( nargs ){ + BSONObjIterator it( args ); + for ( int i=0; i<nargs; i++ ){ + smargsPtr[i] = _convertor->toval( it.next() ); + } + } + + if ( args.isEmpty() ){ + _convertor->setProperty( _global , "args" , JSVAL_NULL ); + } + else { + setObject( "args" , args , true ); // this is for backwards compatability + } + + JS_LeaveLocalRootScope( _context ); + + installCheckTimeout( timeoutMs ); + jsval rval; + JSBool ret = JS_CallFunction( _context , _this ? _this : _global , func , nargs , smargsPtr.get() , &rval ); + uninstallCheckTimeout( timeoutMs ); + + if ( !ret ) { + return -3; + } + + if ( ! ignoreReturn ){ + assert( JS_SetProperty( _context , _global , "return" , &rval ) ); + } + + return 0; + } + + int invoke( ScriptingFunction funcAddr , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = 0 ){ + return invoke( (JSFunction*)funcAddr , args , timeoutMs , ignoreReturn ); + } + + void gotError( string s ){ + _error = s; + } + + string getError(){ + return _error; + } + + void injectNative( const char *field, NativeFunction func ){ + smlock; + string name = field; + _convertor->setProperty( _global , (name + "_").c_str() , _convertor->toval( (double)(long long)func ) ); + + stringstream code; + code << field << "_" << " = { x : " << field << "_ }; "; + code << field << " = function(){ return nativeHelper.apply( " << field << "_ , arguments ); }"; + exec( code.str().c_str() ); + + } + + virtual void gc(){ + JS_GC( _context ); + } + + JSContext *SavedContext() const { return _context; } + + private: + + void _postCreateHacks(){ +#ifdef XULRUNNER + exec( "__x__ = new Date(1);" ); + globalSMEngine->_dateClass = _convertor->getClass( _global , "__x__" ); + exec( "__x__ = /abc/i" ); + globalSMEngine->_regexClass = _convertor->getClass( _global , "__x__" ); +#endif + } + + JSContext * _context; + Convertor * _convertor; + + JSObject * _global; + JSObject * _this; + + string _error; + list<void*> _roots; + + bool _externalSetup; + bool _localConnect; + + set<string> _initFieldNames; + + }; + + void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ){ + stringstream ss; + ss << "JS Error: " << message; + + if ( report ){ + ss << " " << report->filename << ":" << report->lineno; + } + + log() << ss.str() << endl; + + if ( currentScope.get() ){ + currentScope->gotError( ss.str() ); + } + } + + JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + Convertor c(cx); + + Scope * s = currentScope.get(); + + for ( uintN i=0; i<argc; i++ ){ + string filename = c.toString( argv[i] ); + cout << "should load [" << filename << "]" << endl; + + if ( ! s->execFile( filename , false , true , false ) ){ + JS_ReportError( cx , ((string)"error loading file: " + filename ).c_str() ); + return JS_FALSE; + } + } + + return JS_TRUE; + } + + + + void SMEngine::runTest(){ + SMScope s; + + s.localConnect( "foo" ); + + s.exec( "assert( db.getMongo() )" ); + s.exec( "assert( db.bar , 'collection getting does not work' ); " ); + s.exec( "assert.eq( db._name , 'foo' );" ); + s.exec( "assert( _mongo == db.getMongo() ); " ); + s.exec( "assert( _mongo == db._mongo ); " ); + s.exec( "assert( typeof DB.bar == 'undefined' ); " ); + s.exec( "assert( typeof DB.prototype.bar == 'undefined' , 'resolution is happening on prototype, not object' ); " ); + + s.exec( "assert( db.bar ); " ); + s.exec( "assert( typeof db.addUser == 'function' )" ); + s.exec( "assert( db.addUser == DB.prototype.addUser )" ); + s.exec( "assert.eq( 'foo.bar' , db.bar._fullName ); " ); + s.exec( "db.bar.verify();" ); + + s.exec( "db.bar.silly.verify();" ); + s.exec( "assert.eq( 'foo.bar.silly' , db.bar.silly._fullName )" ); + s.exec( "assert.eq( 'function' , typeof _mongo.find , 'mongo.find is not a function' )" ); + + assert( (string)"abc" == trim( "abc" ) ); + assert( (string)"abc" == trim( " abc" ) ); + assert( (string)"abc" == trim( "abc " ) ); + assert( (string)"abc" == trim( " abc " ) ); + + } + + Scope * SMEngine::createScope(){ + return new SMScope(); + } + + void Convertor::addRoot( JSFunction * f , const char * name ){ + if ( ! f ) + return; + + SMScope * scope = currentScope.get(); + uassert( 10229 , "need a scope" , scope ); + + JSObject * o = JS_GetFunctionObject( f ); + assert( o ); + scope->addRoot( &o , name ); + } + +} + +#include "sm_db.cpp" diff --git a/scripting/engine_spidermonkey.h b/scripting/engine_spidermonkey.h new file mode 100644 index 0000000..8aeb56c --- /dev/null +++ b/scripting/engine_spidermonkey.h @@ -0,0 +1,116 @@ +// engine_spidermonkey.h + +/* 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. + */ + +#pragma once + +#include "engine.h" + +// START inc hacking + +#if defined( MOZJS ) + +#define MOZILLA_1_8_BRANCH + +#include "mozjs/jsapi.h" +#include "mozjs/jsdate.h" +#include "mozjs/jsregexp.h" + +#warning if you are using an ubuntu version of spider monkey, we recommend installing spider monkey from source + +#elif defined( OLDJS ) + +#ifdef WIN32 +#include "jstypes.h" +#undef JS_PUBLIC_API +#undef JS_PUBLIC_DATA +#define JS_PUBLIC_API(t) t +#define JS_PUBLIC_DATA(t) t +#endif + +#include "jsapi.h" +#include "jsdate.h" +#include "jsregexp.h" + +#else + +#include "js/jsapi.h" +#include "js/jsobj.h" +#include "js/jsdate.h" +#include "js/jsregexp.h" + +#endif + +// END inc hacking + +// -- SM 1.6 hacks --- +#ifndef JSCLASS_GLOBAL_FLAGS + +#warning old version of spider monkey ( probably 1.6 ) you should upgrade to at least 1.7 + +#define JSCLASS_GLOBAL_FLAGS 0 + +JSBool JS_CStringsAreUTF8(){ + return false; +} + +#define SM16 + +#endif +// -- END SM 1.6 hacks --- + +#ifdef JSVAL_IS_TRACEABLE +#define SM18 +#endif + +#ifdef XULRUNNER +#define SM181 +#endif + +namespace mongo { + + class SMScope; + class Convertor; + + extern JSClass bson_class; + extern JSClass bson_ro_class; + + extern JSClass object_id_class; + extern JSClass dbpointer_class; + extern JSClass dbref_class; + extern JSClass bindata_class; + extern JSClass timestamp_class; + extern JSClass minkey_class; + extern JSClass maxkey_class; + + // internal things + void dontDeleteScope( SMScope * s ){} + void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ); + extern boost::thread_specific_ptr<SMScope> currentScope; + + // bson + JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ); + + + // mongo + void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ); + bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ); + +#define JSVAL_IS_OID(v) ( JSVAL_IS_OBJECT( v ) && JS_InstanceOf( cx , JSVAL_TO_OBJECT( v ) , &object_id_class , 0 ) ) + + bool isDate( JSContext * cx , JSObject * o ); + +} diff --git a/scripting/engine_v8.cpp b/scripting/engine_v8.cpp new file mode 100644 index 0000000..35f2eb8 --- /dev/null +++ b/scripting/engine_v8.cpp @@ -0,0 +1,436 @@ +//engine_v8.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. + */ + +#include "engine_v8.h" + +#include "v8_wrapper.h" +#include "v8_utils.h" +#include "v8_db.h" + +#define V8_SIMPLE_HEADER Locker l; HandleScope handle_scope; Context::Scope context_scope( _context ); + +namespace mongo { + + // --- engine --- + + V8ScriptEngine::V8ScriptEngine() {} + + V8ScriptEngine::~V8ScriptEngine(){ + } + + void ScriptEngine::setup(){ + if ( !globalScriptEngine ){ + globalScriptEngine = new V8ScriptEngine(); + } + } + + // --- scope --- + + V8Scope::V8Scope( V8ScriptEngine * engine ) + : _engine( engine ) , + _connectState( NOT ){ + + Locker l; + HandleScope handleScope; + _context = Context::New(); + Context::Scope context_scope( _context ); + _global = Persistent< v8::Object >::New( _context->Global() ); + + _this = Persistent< v8::Object >::New( v8::Object::New() ); + + _global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)->GetFunction() ); + _global->Set(v8::String::New("version"), v8::FunctionTemplate::New(Version)->GetFunction() ); + + _global->Set(v8::String::New("load"), + v8::FunctionTemplate::New(loadCallback, v8::External::New(this))->GetFunction() ); + + _wrapper = Persistent< v8::Function >::New( getObjectWrapperTemplate()->GetFunction() ); + + installDBTypes( _global ); + } + + V8Scope::~V8Scope(){ + Locker l; + Context::Scope context_scope( _context ); + _wrapper.Dispose(); + _this.Dispose(); + for( unsigned i = 0; i < _funcs.size(); ++i ) + _funcs[ i ].Dispose(); + _funcs.clear(); + _global.Dispose(); + _context.Dispose(); + } + + Handle< Value > V8Scope::nativeCallback( const Arguments &args ) { + Locker l; + HandleScope handle_scope; + Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_native_function" ) ) ); + NativeFunction function = (NativeFunction)(f->Value()); + BSONObjBuilder b; + for( int i = 0; i < args.Length(); ++i ) { + stringstream ss; + ss << i; + v8ToMongoElement( b, v8::String::New( "foo" ), ss.str(), args[ i ] ); + } + BSONObj nativeArgs = b.obj(); + BSONObj ret; + try { + ret = function( nativeArgs ); + } catch( const std::exception &e ) { + return v8::ThrowException(v8::String::New(e.what())); + } catch( ... ) { + return v8::ThrowException(v8::String::New("unknown exception")); + } + return handle_scope.Close( mongoToV8Element( ret.firstElement() ) ); + } + + Handle< Value > V8Scope::loadCallback( const Arguments &args ) { + Locker l; + HandleScope handle_scope; + Handle<External> field = Handle<External>::Cast(args.Data()); + void* ptr = field->Value(); + V8Scope* self = static_cast<V8Scope*>(ptr); + + Context::Scope context_scope(self->_context); + for (int i = 0; i < args.Length(); ++i) { + std::string filename(toSTLString(args[i])); + if (!self->execFile(filename, false , true , false)) { + return v8::ThrowException(v8::String::New((std::string("error loading file: ") + filename).c_str())); + } + } + return v8::True(); + } + + // ---- global stuff ---- + + void V8Scope::init( BSONObj * data ){ + Locker l; + if ( ! data ) + return; + + BSONObjIterator i( *data ); + while ( i.more() ){ + BSONElement e = i.next(); + setElement( e.fieldName() , e ); + } + } + + void V8Scope::setNumber( const char * field , double val ){ + V8_SIMPLE_HEADER + _global->Set( v8::String::New( field ) , v8::Number::New( val ) ); + } + + void V8Scope::setString( const char * field , const char * val ){ + V8_SIMPLE_HEADER + _global->Set( v8::String::New( field ) , v8::String::New( val ) ); + } + + void V8Scope::setBoolean( const char * field , bool val ){ + V8_SIMPLE_HEADER + _global->Set( v8::String::New( field ) , v8::Boolean::New( val ) ); + } + + void V8Scope::setElement( const char *field , const BSONElement& e ){ + V8_SIMPLE_HEADER + _global->Set( v8::String::New( field ) , mongoToV8Element( e ) ); + } + + void V8Scope::setObject( const char *field , const BSONObj& obj , bool readOnly){ + V8_SIMPLE_HEADER + // Set() accepts a ReadOnly parameter, but this just prevents the field itself + // from being overwritten and doesn't protect the object stored in 'field'. + _global->Set( v8::String::New( field ) , mongoToV8( obj, false, readOnly) ); + } + + int V8Scope::type( const char *field ){ + V8_SIMPLE_HEADER + Handle<Value> v = get( field ); + if ( v->IsNull() ) + return jstNULL; + if ( v->IsUndefined() ) + return Undefined; + if ( v->IsString() ) + return String; + if ( v->IsFunction() ) + return Code; + if ( v->IsArray() ) + return Array; + if ( v->IsBoolean() ) + return Bool; + if ( v->IsInt32() ) + return NumberInt; + if ( v->IsNumber() ) + return NumberDouble; + if ( v->IsExternal() ){ + uassert( 10230 , "can't handle external yet" , 0 ); + return -1; + } + if ( v->IsDate() ) + return Date; + if ( v->IsObject() ) + return Object; + + throw UserException( 12509, (string)"don't know what this is: " + field ); + } + + v8::Handle<v8::Value> V8Scope::get( const char * field ){ + return _global->Get( v8::String::New( field ) ); + } + + double V8Scope::getNumber( const char *field ){ + V8_SIMPLE_HEADER + return get( field )->ToNumber()->Value(); + } + + int V8Scope::getNumberInt( const char *field ){ + V8_SIMPLE_HEADER + return get( field )->ToInt32()->Value(); + } + + long long V8Scope::getNumberLongLong( const char *field ){ + V8_SIMPLE_HEADER + return get( field )->ToInteger()->Value(); + } + + string V8Scope::getString( const char *field ){ + V8_SIMPLE_HEADER + return toSTLString( get( field ) ); + } + + bool V8Scope::getBoolean( const char *field ){ + V8_SIMPLE_HEADER + return get( field )->ToBoolean()->Value(); + } + + BSONObj V8Scope::getObject( const char * field ){ + V8_SIMPLE_HEADER + Handle<Value> v = get( field ); + if ( v->IsNull() || v->IsUndefined() ) + return BSONObj(); + uassert( 10231 , "not an object" , v->IsObject() ); + return v8ToMongo( v->ToObject() ); + } + + // --- functions ----- + + Local< v8::Function > V8Scope::__createFunction( const char * raw ){ + for(; isspace( *raw ); ++raw ); // skip whitespace + string code = raw; + if ( code.find( "function" ) == string::npos ){ + if ( code.find( "\n" ) == string::npos && + code.find( "return" ) == string::npos && + ( code.find( ";" ) == string::npos || code.find( ";" ) == code.size() - 1 ) ){ + code = "return " + code; + } + code = "function(){ " + code + "}"; + } + + int num = _funcs.size() + 1; + + string fn; + { + stringstream ss; + ss << "_funcs" << num; + fn = ss.str(); + } + + code = fn + " = " + code; + + TryCatch try_catch; + Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) , + v8::String::New( fn.c_str() ) ); + if ( script.IsEmpty() ){ + _error = (string)"compile error: " + toSTLString( &try_catch ); + log() << _error << endl; + return Local< v8::Function >(); + } + + Local<Value> result = script->Run(); + if ( result.IsEmpty() ){ + _error = (string)"compile error: " + toSTLString( &try_catch ); + log() << _error << endl; + return Local< v8::Function >(); + } + + return v8::Function::Cast( *_global->Get( v8::String::New( fn.c_str() ) ) ); + } + + ScriptingFunction V8Scope::_createFunction( const char * raw ){ + V8_SIMPLE_HEADER + Local< Value > ret = __createFunction( raw ); + if ( ret.IsEmpty() ) + return 0; + Persistent<Value> f = Persistent< Value >::New( ret ); + uassert( 10232, "not a func" , f->IsFunction() ); + int num = _funcs.size() + 1; + _funcs.push_back( f ); + return num; + } + + void V8Scope::setThis( const BSONObj * obj ){ + V8_SIMPLE_HEADER + if ( ! obj ){ + _this = Persistent< v8::Object >::New( v8::Object::New() ); + return; + } + + //_this = mongoToV8( *obj ); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::External::New( createWrapperHolder( obj , true , false ) ); + _this = Persistent< v8::Object >::New( _wrapper->NewInstance( 1, argv ) ); + } + + int V8Scope::invoke( ScriptingFunction func , const BSONObj& argsObject, int timeoutMs , bool ignoreReturn ){ + V8_SIMPLE_HEADER + Handle<Value> funcValue = _funcs[func-1]; + + TryCatch try_catch; + int nargs = argsObject.nFields(); + scoped_array< Handle<Value> > args; + if ( nargs ){ + args.reset( new Handle<Value>[nargs] ); + BSONObjIterator it( argsObject ); + for ( int i=0; i<nargs; i++ ){ + BSONElement next = it.next(); + args[i] = mongoToV8Element( next ); + } + setObject( "args", argsObject, true ); // for backwards compatibility + } else { + _global->Set( v8::String::New( "args" ), v8::Undefined() ); + } + Local<Value> result = ((v8::Function*)(*funcValue))->Call( _this , nargs , args.get() ); + + if ( result.IsEmpty() ){ + stringstream ss; + ss << "error in invoke: " << toSTLString( &try_catch ); + _error = ss.str(); + log() << _error << endl; + return 1; + } + + if ( ! ignoreReturn ){ + _global->Set( v8::String::New( "return" ) , result ); + } + + return 0; + } + + bool V8Scope::exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ){ + if ( timeoutMs ){ + static bool t = 1; + if ( t ){ + log() << "timeoutMs not support for v8 yet" << endl; + t = 0; + } + } + + V8_SIMPLE_HEADER + + TryCatch try_catch; + + Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) , + v8::String::New( name.c_str() ) ); + if (script.IsEmpty()) { + stringstream ss; + ss << "compile error: " << toSTLString( &try_catch ); + _error = ss.str(); + if (reportError) + log() << _error << endl; + if ( assertOnError ) + uassert( 10233 , _error , 0 ); + return false; + } + + Handle<v8::Value> result = script->Run(); + if ( result.IsEmpty() ){ + _error = (string)"exec error: " + toSTLString( &try_catch ); + if ( reportError ) + log() << _error << endl; + if ( assertOnError ) + uassert( 10234 , _error , 0 ); + return false; + } + + _global->Set( v8::String::New( "__lastres__" ) , result ); + + if ( printResult && ! result->IsUndefined() ){ + cout << toSTLString( result ) << endl; + } + + return true; + } + + void V8Scope::injectNative( const char *field, NativeFunction func ){ + V8_SIMPLE_HEADER + + 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->GetFunction() ); + } + + void V8Scope::gc() { + Locker l; + while( V8::IdleNotification() ); + } + + // ----- db access ----- + + void V8Scope::localConnect( const char * dbName ){ + V8_SIMPLE_HEADER + + if ( _connectState == EXTERNAL ) + throw UserException( 12510, "externalSetup already called, can't call externalSetup" ); + if ( _connectState == LOCAL ){ + if ( _localDBName == dbName ) + return; + throw UserException( 12511, "localConnect called with a different name previously" ); + } + + //_global->Set( v8::String::New( "Mongo" ) , _engine->_externalTemplate->GetFunction() ); + _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( true )->GetFunction() ); + exec( jsconcatcode , "localConnect 1" , false , true , true , 0 ); + exec( "_mongo = new Mongo();" , "local connect 2" , false , true , true , 0 ); + exec( (string)"db = _mongo.getDB(\"" + dbName + "\");" , "local connect 3" , false , true , true , 0 ); + _connectState = LOCAL; + _localDBName = dbName; + loadStored(); + } + + void V8Scope::externalSetup(){ + V8_SIMPLE_HEADER + if ( _connectState == EXTERNAL ) + return; + if ( _connectState == LOCAL ) + throw UserException( 12512, "localConnect already called, can't call externalSetup" ); + + installFork( _global, _context ); + _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( false )->GetFunction() ); + exec( jsconcatcode , "shell setup" , false , true , true , 0 ); + _connectState = EXTERNAL; + } + + // ----- internal ----- + + void V8Scope::reset(){ + _startCall(); + } + + void V8Scope::_startCall(){ + _error = ""; + } + +} // namespace mongo diff --git a/scripting/engine_v8.h b/scripting/engine_v8.h new file mode 100644 index 0000000..9d86d92 --- /dev/null +++ b/scripting/engine_v8.h @@ -0,0 +1,116 @@ +//engine_v8.h + +/* 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. + */ + +#pragma once + +#include <vector> +#include "engine.h" +#include <v8.h> + +using namespace v8; + +namespace mongo { + + class V8ScriptEngine; + + class V8Scope : public Scope { + public: + + V8Scope( V8ScriptEngine * engine ); + ~V8Scope(); + + virtual void reset(); + virtual void init( BSONObj * data ); + + virtual void localConnect( const char * dbName ); + virtual void externalSetup(); + + v8::Handle<v8::Value> get( const char * field ); // caller must create context and handle scopes + virtual double getNumber( const char *field ); + virtual int getNumberInt( const char *field ); + virtual long long getNumberLongLong( const char *field ); + virtual string getString( const char *field ); + virtual bool getBoolean( const char *field ); + virtual BSONObj getObject( const char *field ); + + virtual int type( const char *field ); + + 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 ); + virtual void setObject( const char *field , const BSONObj& obj , bool readOnly); + virtual void setThis( const BSONObj * obj ); + + virtual ScriptingFunction _createFunction( const char * code ); + Local< v8::Function > __createFunction( const char * code ); + virtual int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = false ); + virtual bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ); + virtual string getError(){ return _error; } + + virtual void injectNative( const char *field, NativeFunction func ); + + void gc(); + + Handle< Context > context() const { return _context; } + + private: + void _startCall(); + + static Handle< Value > nativeCallback( const Arguments &args ); + + static Handle< Value > loadCallback( const Arguments &args ); + + V8ScriptEngine * _engine; + + Persistent<Context> _context; + Persistent<v8::Object> _global; + + string _error; + vector< Persistent<Value> > _funcs; + v8::Persistent<v8::Object> _this; + + v8::Persistent<v8::Function> _wrapper; + + enum ConnectState { NOT , LOCAL , EXTERNAL }; + ConnectState _connectState; + }; + + class V8ScriptEngine : public ScriptEngine { + public: + V8ScriptEngine(); + virtual ~V8ScriptEngine(); + + virtual Scope * createScope(){ return new V8Scope( this ); } + + virtual void runTest(){} + + bool utf8Ok() const { return true; } + + class V8Unlocker : public Unlocker { + v8::Unlocker u_; + }; + + virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new V8Unlocker ); } + + private: + friend class V8Scope; + }; + + + extern ScriptEngine * globalScriptEngine; +} diff --git a/scripting/sm_db.cpp b/scripting/sm_db.cpp new file mode 100644 index 0000000..72d8638 --- /dev/null +++ b/scripting/sm_db.cpp @@ -0,0 +1,854 @@ +// sm_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. + */ + +// hacked in right now from engine_spidermonkey.cpp + +#include "../client/syncclusterconnection.h" + +namespace mongo { + + bool haveLocalShardingInfo( const string& ns ); + + // ------------ some defs needed --------------- + + JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ); + + // ------------ utils ------------------ + + + bool isSpecialName( const string& name ){ + static set<string> names; + if ( names.size() == 0 ){ + names.insert( "tojson" ); + names.insert( "toJson" ); + names.insert( "toString" ); + } + + if ( name.length() == 0 ) + return false; + + if ( name[0] == '_' ) + return true; + + return names.count( name ) > 0; + } + + + // ------ cursor ------ + + class CursorHolder { + public: + CursorHolder( auto_ptr< DBClientCursor > &cursor, const shared_ptr< DBClientWithCommands > &connection ) : + connection_( connection ), + cursor_( cursor ) { + assert( cursor_.get() ); + } + DBClientCursor *get() const { return cursor_.get(); } + private: + shared_ptr< DBClientWithCommands > connection_; + auto_ptr< DBClientCursor > cursor_; + }; + + DBClientCursor *getCursor( JSContext *cx, JSObject *obj ) { + CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj ); + uassert( 10235 , "no cursor!" , holder ); + return holder->get(); + } + + JSBool internal_cursor_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + uassert( 10236 , "no args to internal_cursor_constructor" , argc == 0 ); + assert( JS_SetPrivate( cx , obj , 0 ) ); // just for safety + return JS_TRUE; + } + + void internal_cursor_finalize( JSContext * cx , JSObject * obj ){ + CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj ); + if ( holder ){ + delete holder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSBool internal_cursor_hasNext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + DBClientCursor *cursor = getCursor( cx, obj ); + *rval = cursor->more() ? JSVAL_TRUE : JSVAL_FALSE; + return JS_TRUE; + } + + JSBool internal_cursor_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + DBClientCursor *cursor = getCursor( cx, obj ); + if ( ! cursor->more() ){ + JS_ReportError( cx , "cursor at the end" ); + return JS_FALSE; + } + Convertor c(cx); + + BSONObj n = cursor->next(); + *rval = c.toval( &n ); + return JS_TRUE; + } + + + JSFunctionSpec internal_cursor_functions[] = { + { "hasNext" , internal_cursor_hasNext , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "next" , internal_cursor_next , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + JSClass internal_cursor_class = { + "InternalCursor" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, internal_cursor_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + // ------ mongo stuff ------ + + JSBool mongo_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + uassert( 10237 , "mongo_constructor not implemented yet" , 0 ); + throw -1; + } + + JSBool mongo_local_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + shared_ptr< DBClientWithCommands > client( createDirectClient() ); + assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( client ) ) ) ); + + jsval host = c.toval( "EMBEDDED" ); + assert( JS_SetProperty( cx , obj , "host" , &host ) ); + + return JS_TRUE; + } + + JSBool mongo_external_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + uassert( 10238 , "0 or 1 args to Mongo" , argc <= 1 ); + + string host = "127.0.0.1"; + if ( argc > 0 ) + host = c.toString( argv[0] ); + + shared_ptr< DBClientWithCommands > conn; + + string errmsg; + if ( host.find( "," ) == string::npos ){ + DBClientConnection * c = new DBClientConnection( true ); + conn.reset( c ); + if ( ! c->connect( host , errmsg ) ){ + JS_ReportError( cx , ((string)"couldn't connect: " + errmsg).c_str() ); + return JS_FALSE; + } + } + else { // paired + int numCommas = 0; + for ( uint i=0; i<host.size(); i++ ) + if ( host[i] == ',' ) + numCommas++; + + assert( numCommas > 0 ); + + if ( numCommas == 1 ){ + DBClientPaired * c = new DBClientPaired(); + conn.reset( c ); + if ( ! c->connect( host ) ){ + JS_ReportError( cx , "couldn't connect to pair" ); + return JS_FALSE; + } + } + else if ( numCommas == 2 ){ + conn.reset( new SyncCluterConnection( host ) ); + } + else { + JS_ReportError( cx , "1 (paired) or 2(quorum) commas are allowed" ); + return JS_FALSE; + } + } + + + assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( conn ) ) ) ); + jsval host_val = c.toval( host.c_str() ); + assert( JS_SetProperty( cx , obj , "host" , &host_val ) ); + return JS_TRUE; + + } + + DBClientWithCommands *getConnection( JSContext *cx, JSObject *obj ) { + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + uassert( 10239 , "no connection!" , connHolder && connHolder->get() ); + return connHolder->get(); + } + + void mongo_finalize( JSContext * cx , JSObject * obj ){ + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + if ( connHolder ){ + delete connHolder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSClass mongo_class = { + "Mongo" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, mongo_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + uassert( 10240 , "mongo_find neesd 5 args" , argc == 5 ); + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + uassert( 10241 , "no connection!" , connHolder && connHolder->get() ); + DBClientWithCommands *conn = connHolder->get(); + + Convertor c( cx ); + + string ns = c.toString( argv[0] ); + + BSONObj q = c.toObject( argv[1] ); + BSONObj f = c.toObject( argv[2] ); + + int nToReturn = (int) c.toNumber( argv[3] ); + int nToSkip = (int) c.toNumber( argv[4] ); + bool slaveOk = c.getBoolean( obj , "slaveOk" ); + + try { + + auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , slaveOk ? QueryOption_SlaveOk : 0 ); + if ( ! cursor.get() ){ + JS_ReportError( cx , "error doing query: failed" ); + return JS_FALSE; + } + JSObject * mycursor = JS_NewObject( cx , &internal_cursor_class , 0 , 0 ); + assert( JS_SetPrivate( cx , mycursor , new CursorHolder( cursor, *connHolder ) ) ); + *rval = OBJECT_TO_JSVAL( mycursor ); + return JS_TRUE; + } + catch ( ... ){ + JS_ReportError( cx , "error doing query: unknown" ); + return JS_FALSE; + } + } + + JSBool mongo_update(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + smuassert( cx , "mongo_find needs at elast 3 args" , argc >= 3 ); + smuassert( cx , "2nd param to update has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + smuassert( cx , "3rd param to update has to be an object" , JSVAL_IS_OBJECT( argv[2] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ){ + JS_ReportError( cx , "js db in read only mode - mongo_update" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10245 , "no connection!" , conn ); + + string ns = c.toString( argv[0] ); + + bool upsert = argc > 3 && c.toBoolean( argv[3] ); + bool multi = argc > 4 && c.toBoolean( argv[4] ); + + try { + conn->update( ns , c.toObject( argv[1] ) , c.toObject( argv[2] ) , upsert , multi ); + return JS_TRUE; + } + catch ( ... ){ + JS_ReportError( cx , "error doing update" ); + return JS_FALSE; + } + } + + JSBool mongo_insert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + smuassert( cx , "mongo_insert needs 2 args" , argc == 2 ); + smuassert( cx , "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ){ + JS_ReportError( cx , "js db in read only mode - mongo_insert" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10248 , "no connection!" , conn ); + + + string ns = c.toString( argv[0] ); + BSONObj o = c.toObject( argv[1] ); + + // TODO: add _id + + try { + conn->insert( ns , o ); + return JS_TRUE; + } + catch ( std::exception& e ){ + stringstream ss; + ss << "error doing insert:" << e.what(); + string s = ss.str(); + JS_ReportError( cx , s.c_str() ); + return JS_FALSE; + } + catch ( ... ){ + JS_ReportError( cx , "error doing insert" ); + return JS_FALSE; + } + } + + JSBool mongo_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + smuassert( cx , "mongo_remove needs 2 arguments" , argc == 2 ); + smuassert( cx , "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ){ + JS_ReportError( cx , "js db in read only mode - mongo_remove" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10251 , "no connection!" , conn ); + + string ns = c.toString( argv[0] ); + BSONObj o = c.toObject( argv[1] ); + + try { + conn->remove( ns , o ); + return JS_TRUE; + } + catch ( ... ){ + JS_ReportError( cx , "error doing remove" ); + return JS_FALSE; + } + + } + + JSFunctionSpec mongo_functions[] = { + { "find" , mongo_find , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "update" , mongo_update , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "insert" , mongo_insert , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "remove" , mongo_remove , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + + // ------------- db_collection ------------- + + JSBool db_collection_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + smuassert( cx , "db_collection_constructor wrong args" , argc == 4 ); + assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "_db" , &(argv[1]) ) ); + assert( JS_SetProperty( cx , obj , "_shortName" , &(argv[2]) ) ); + assert( JS_SetProperty( cx , obj , "_fullName" , &(argv[3]) ) ); + + Convertor c(cx); + if ( haveLocalShardingInfo( c.toString( argv[3] ) ) ){ + JS_ReportError( cx , "can't use sharded collection from db.eval" ); + return JS_FALSE; + } + + return JS_TRUE; + } + + JSBool db_collection_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + Convertor c( cx ); + string collname = c.toString( id ); + + if ( isSpecialName( collname ) ) + return JS_TRUE; + + if ( obj == c.getGlobalPrototype( "DBCollection" ) ) + return JS_TRUE; + + JSObject * proto = JS_GetPrototype( cx , obj ); + if ( c.hasProperty( obj , collname.c_str() ) || ( proto && c.hasProperty( proto , collname.c_str() ) ) ) + return JS_TRUE; + + string name = c.toString( c.getProperty( obj , "_shortName" ) ); + name += "."; + name += collname; + + jsval db = c.getProperty( obj , "_db" ); + if ( ! JSVAL_IS_OBJECT( db ) ) + return JS_TRUE; + + JSObject * coll = doCreateCollection( cx , JSVAL_TO_OBJECT( db ) , name ); + if ( ! coll ) + return JS_FALSE; + c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); + *objp = obj; + return JS_TRUE; + } + + JSClass db_collection_class = { + "DBCollection" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&db_collection_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ){ + Convertor c(cx); + + assert( c.hasProperty( db , "_mongo" ) ); + assert( c.hasProperty( db , "_name" ) ); + + JSObject * coll = JS_NewObject( cx , &db_collection_class , 0 , 0 ); + c.setProperty( coll , "_mongo" , c.getProperty( db , "_mongo" ) ); + c.setProperty( coll , "_db" , OBJECT_TO_JSVAL( db ) ); + c.setProperty( coll , "_shortName" , c.toval( shortName.c_str() ) ); + + string name = c.toString( c.getProperty( db , "_name" ) ); + name += "." + shortName; + c.setProperty( coll , "_fullName" , c.toval( name.c_str() ) ); + + if ( haveLocalShardingInfo( name ) ){ + JS_ReportError( cx , "can't use sharded collection from db.eval" ); + return 0; + } + + return coll; + } + + // -------------- DB --------------- + + + JSBool db_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + smuassert( cx, "wrong number of arguments to DB" , argc == 2 ); + assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "_name" , &(argv[1]) ) ); + + return JS_TRUE; + } + + JSBool db_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + Convertor c( cx ); + + if ( obj == c.getGlobalPrototype( "DB" ) ) + return JS_TRUE; + + string collname = c.toString( id ); + + if ( isSpecialName( collname ) ) + return JS_TRUE; + + JSObject * proto = JS_GetPrototype( cx , obj ); + if ( proto && c.hasProperty( proto , collname.c_str() ) ) + return JS_TRUE; + + JSObject * coll = doCreateCollection( cx , obj , collname ); + if ( ! coll ) + return JS_FALSE; + c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); + + *objp = obj; + return JS_TRUE; + } + + JSClass db_class = { + "DB" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&db_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + // -------------- object id ------------- + + JSBool object_id_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + OID oid; + if ( argc == 0 ){ + oid.init(); + } + else { + smuassert( cx , "object_id_constructor can't take more than 1 param" , argc == 1 ); + string s = c.toString( argv[0] ); + + try { + Scope::validateObjectIdString( s ); + } catch ( const MsgAssertionException &m ) { + static string error = m.toString(); + JS_ReportError( cx, error.c_str() ); + return JS_FALSE; + } + oid.init( s ); + } + + if ( ! JS_InstanceOf( cx , obj , &object_id_class , 0 ) ){ + obj = JS_NewObject( cx , &object_id_class , 0 , 0 ); + assert( obj ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + jsval v = c.toval( oid.str().c_str() ); + assert( JS_SetProperty( cx , obj , "str" , &v ) ); + + return JS_TRUE; + } + + JSClass object_id_class = { + "ObjectId" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool object_id_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + Convertor c(cx); + return *rval = c.getProperty( obj , "str" ); + } + + JSFunctionSpec object_id_functions[] = { + { "toString" , object_id_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + // dbpointer + + JSBool dbpointer_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + if ( argc == 2 ){ + + if ( ! JSVAL_IS_OID( argv[1] ) ){ + JS_ReportError( cx , "2nd arg to DBPointer needs to be oid" ); + return JS_FALSE; + } + + assert( JS_SetProperty( cx , obj , "ns" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "id" , &(argv[1]) ) ); + return JS_TRUE; + } + else { + JS_ReportError( cx , "DBPointer needs 2 arguments" ); + return JS_FALSE; + } + } + + JSClass dbpointer_class = { + "DBPointer" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec dbpointer_functions[] = { + { 0 } + }; + + + JSBool dbref_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + if ( argc == 2 ){ + assert( JS_SetProperty( cx , obj , "$ref" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "$id" , &(argv[1]) ) ); + return JS_TRUE; + } + else { + JS_ReportError( cx , "DBRef needs 2 arguments" ); + return JS_FALSE; + } + } + + JSClass dbref_class = { + "DBRef" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec dbref_functions[] = { + { 0 } + }; + + + // BinData + + + JSBool bindata_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JS_ReportError( cx , "can't create a BinData yet" ); + return JS_FALSE; + } + + JSClass bindata_class = { + "BinData" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec bindata_functions[] = { + { 0 } + }; + + // Map + + bool specialMapString( const string& s ){ + return s == "put" || s == "get" || s == "_get" || s == "values" || s == "_data" || s == "constructor" ; + } + + JSBool map_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + if ( argc > 0 ){ + JS_ReportError( cx , "Map takes no arguments" ); + return JS_FALSE; + } + + JSObject * array = JS_NewObject( cx , 0 , 0 , 0 ); + assert( array ); + + jsval a = OBJECT_TO_JSVAL( array ); + JS_SetProperty( cx , obj , "_data" , &a ); + + return JS_TRUE; + } + + JSBool map_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp ){ + Convertor c(cx); + if ( specialMapString( c.toString( idval ) ) ) + return JS_TRUE; + + log() << "illegal prop access: " << c.toString( idval ) << endl; + JS_ReportError( cx , "can't use array access with Map" ); + return JS_FALSE; + } + + JSClass map_class = { + "Map" , JSCLASS_HAS_PRIVATE , + map_prop, JS_PropertyStub, map_prop, map_prop, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec map_functions[] = { + { 0 } + }; + + + // ----- + + JSClass timestamp_class = { + "Timestamp" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSClass minkey_class = { + "MinKey" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSClass maxkey_class = { + "MaxKey" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // dbquery + + JSBool dbquery_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + smuassert( cx , "DDQuery needs at least 4 args" , argc >= 4 ); + + Convertor c(cx); + c.setProperty( obj , "_mongo" , argv[0] ); + c.setProperty( obj , "_db" , argv[1] ); + c.setProperty( obj , "_collection" , argv[2] ); + c.setProperty( obj , "_ns" , argv[3] ); + + if ( argc > 4 && JSVAL_IS_OBJECT( argv[4] ) ) + c.setProperty( obj , "_query" , argv[4] ); + else + c.setProperty( obj , "_query" , OBJECT_TO_JSVAL( JS_NewObject( cx , 0 , 0 , 0 ) ) ); + + if ( argc > 5 && JSVAL_IS_OBJECT( argv[5] ) ) + c.setProperty( obj , "_fields" , argv[5] ); + else + c.setProperty( obj , "_fields" , JSVAL_NULL ); + + + if ( argc > 6 && JSVAL_IS_NUMBER( argv[6] ) ) + c.setProperty( obj , "_limit" , argv[6] ); + else + c.setProperty( obj , "_limit" , JSVAL_ZERO ); + + if ( argc > 7 && JSVAL_IS_NUMBER( argv[7] ) ) + c.setProperty( obj , "_skip" , argv[7] ); + else + c.setProperty( obj , "_skip" , JSVAL_ZERO ); + + c.setProperty( obj , "_cursor" , JSVAL_NULL ); + c.setProperty( obj , "_numReturned" , JSVAL_ZERO ); + c.setProperty( obj , "_special" , JSVAL_FALSE ); + + return JS_TRUE; + } + + JSBool dbquery_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + if ( ! JSVAL_IS_NUMBER( id ) ) + return JS_TRUE; + + jsval val = JSVAL_VOID; + assert( JS_CallFunctionName( cx , obj , "arrayAccess" , 1 , &id , &val ) ); + Convertor c(cx); + c.setProperty( obj , c.toString( id ).c_str() , val ); + *objp = obj; + return JS_TRUE; + } + + JSClass dbquery_class = { + "DBQuery" , JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&dbquery_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // ---- other stuff ---- + + void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ){ + + assert( JS_InitClass( cx , global , 0 , &mongo_class , local ? mongo_local_constructor : mongo_external_constructor , 0 , 0 , mongo_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &object_id_class , object_id_constructor , 0 , 0 , object_id_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &db_class , db_constructor , 2 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &db_collection_class , db_collection_constructor , 4 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &internal_cursor_class , internal_cursor_constructor , 0 , 0 , internal_cursor_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &dbquery_class , dbquery_constructor , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &dbpointer_class , dbpointer_constructor , 0 , 0 , dbpointer_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &dbref_class , dbref_constructor , 0 , 0 , dbref_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &bindata_class , bindata_constructor , 0 , 0 , bindata_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , ×tamp_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &minkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &maxkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &map_class , map_constructor , 0 , 0 , map_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &bson_ro_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &bson_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); + + scope->exec( jsconcatcode ); + } + + bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ){ + + if ( JS_InstanceOf( c->_context , o , &object_id_class , 0 ) ){ + OID oid; + oid.init( c->getString( o , "str" ) ); + b.append( name.c_str() , oid ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &minkey_class , 0 ) ){ + b.appendMinKey( name.c_str() ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &maxkey_class , 0 ) ){ + b.appendMaxKey( name.c_str() ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , ×tamp_class , 0 ) ){ + b.appendTimestamp( name.c_str() , (unsigned long long)c->getNumber( o , "t" ) , (unsigned int )c->getNumber( o , "i" ) ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &dbpointer_class , 0 ) ){ + b.appendDBRef( name.c_str() , c->getString( o , "ns" ).c_str() , c->toOID( c->getProperty( o , "id" ) ) ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &bindata_class , 0 ) ){ + b.appendBinData( name.c_str() , + (int)(c->getNumber( o , "len" )) , (BinDataType)((char)(c->getNumber( o , "type" ) ) ) , + (char*)JS_GetPrivate( c->_context , o ) + 1 + ); + return true; + } + +#if defined( SM16 ) || defined( MOZJS ) +#warning dates do not work in your version of spider monkey + { + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + if ( d ){ + b.appendDate( name.c_str() , Date_t(d) ); + return true; + } + } +#elif defined( XULRUNNER ) + if ( JS_InstanceOf( c->_context , o, globalSMEngine->_dateClass , 0 ) ){ + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + b.appendDate( name.c_str() , Date_t(d) ); + return true; + } +#else + if ( JS_InstanceOf( c->_context , o, &js_DateClass , 0 ) ){ + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + //TODO: make signed + b.appendDate( name.c_str() , Date_t((unsigned long long)d) ); + return true; + } +#endif + + + if ( JS_InstanceOf( c->_context , o , &dbquery_class , 0 ) || + JS_InstanceOf( c->_context , o , &mongo_class , 0 ) || + JS_InstanceOf( c->_context , o , &db_collection_class , 0 ) ){ + b.append( name.c_str() , c->toString( val ) ); + return true; + } + +#if defined( XULRUNNER ) + if ( JS_InstanceOf( c->_context , o , globalSMEngine->_regexClass , 0 ) ){ + c->appendRegex( b , name , c->toString( val ) ); + return true; + } +#elif defined( SM18 ) + if ( JS_InstanceOf( c->_context , o , &js_RegExpClass , 0 ) ){ + c->appendRegex( b , name , c->toString( val ) ); + return true; + } +#endif + + return false; + } + + bool isDate( JSContext * cx , JSObject * o ){ +#if defined( SM16 ) || defined( MOZJS ) || defined( XULRUNNER ) + return js_DateGetMsecSinceEpoch( cx , o ) != 0; +#else + return JS_InstanceOf( cx , o, &js_DateClass, 0 ); +#endif + } + +} diff --git a/scripting/v8_db.cpp b/scripting/v8_db.cpp new file mode 100644 index 0000000..6d859d4 --- /dev/null +++ b/scripting/v8_db.cpp @@ -0,0 +1,542 @@ +// 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. + */ + +#include "v8_wrapper.h" +#include "v8_utils.h" +#include "v8_db.h" +#include "engine.h" + +#include <iostream> + +using namespace std; +using namespace v8; + +namespace mongo { + +#define CONN_STRING (v8::String::New( "_conn" )) + +#define DDD(x) + + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( bool local ){ + v8::Local<v8::FunctionTemplate> mongo = FunctionTemplate::New( local ? mongoConsLocal : mongoConsExternal ); + + v8::Local<v8::Template> proto = mongo->PrototypeTemplate(); + + proto->Set( v8::String::New( "find" ) , FunctionTemplate::New( mongoFind ) ); + proto->Set( v8::String::New( "insert" ) , FunctionTemplate::New( mongoInsert ) ); + proto->Set( v8::String::New( "remove" ) , FunctionTemplate::New( mongoRemove ) ); + proto->Set( v8::String::New( "update" ) , FunctionTemplate::New( mongoUpdate ) ); + + Local<FunctionTemplate> ic = FunctionTemplate::New( internalCursorCons ); + ic->PrototypeTemplate()->Set( v8::String::New("next") , FunctionTemplate::New( internalCursorNext ) ); + ic->PrototypeTemplate()->Set( v8::String::New("hasNext") , FunctionTemplate::New( internalCursorHasNext ) ); + proto->Set( v8::String::New( "internalCursor" ) , ic ); + + return mongo; + } + + void installDBTypes( Handle<ObjectTemplate>& global ){ + v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); + db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + global->Set(v8::String::New("DB") , db ); + + v8::Local<v8::FunctionTemplate> dbCollection = FunctionTemplate::New( collectionInit ); + dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + global->Set(v8::String::New("DBCollection") , dbCollection ); + + + v8::Local<v8::FunctionTemplate> dbQuery = FunctionTemplate::New( dbQueryInit ); + dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); + global->Set(v8::String::New("DBQuery") , dbQuery ); + + global->Set( v8::String::New("ObjectId") , FunctionTemplate::New( objectIdInit ) ); + + global->Set( v8::String::New("DBRef") , FunctionTemplate::New( dbRefInit ) ); + + global->Set( v8::String::New("DBPointer") , FunctionTemplate::New( dbPointerInit ) ); + + global->Set( v8::String::New("BinData") , FunctionTemplate::New( binDataInit ) ); + + } + + void installDBTypes( Handle<v8::Object>& global ){ + v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); + db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + global->Set(v8::String::New("DB") , db->GetFunction() ); + + v8::Local<v8::FunctionTemplate> dbCollection = FunctionTemplate::New( collectionInit ); + dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + global->Set(v8::String::New("DBCollection") , dbCollection->GetFunction() ); + + + v8::Local<v8::FunctionTemplate> dbQuery = FunctionTemplate::New( dbQueryInit ); + dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); + global->Set(v8::String::New("DBQuery") , dbQuery->GetFunction() ); + + global->Set( v8::String::New("ObjectId") , FunctionTemplate::New( objectIdInit )->GetFunction() ); + + global->Set( v8::String::New("DBRef") , FunctionTemplate::New( dbRefInit )->GetFunction() ); + + global->Set( v8::String::New("DBPointer") , FunctionTemplate::New( dbPointerInit )->GetFunction() ); + + global->Set( v8::String::New("BinData") , FunctionTemplate::New( binDataInit )->GetFunction() ); + + BSONObjBuilder b; + b.appendMaxKey( "" ); + b.appendMinKey( "" ); + BSONObj o = b.obj(); + BSONObjIterator i( o ); + global->Set( v8::String::New("MaxKey"), mongoToV8Element( i.next() ) ); + global->Set( v8::String::New("MinKey"), mongoToV8Element( i.next() ) ); + + global->Get( v8::String::New( "Object" ) )->ToObject()->Set( v8::String::New("bsonsize") , FunctionTemplate::New( bsonsize )->GetFunction() ); + } + + void destroyConnection( Persistent<Value> object, void* parameter){ + cout << "Yo ho ho" << endl; + } + + Handle<Value> mongoConsExternal(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 ); + + Persistent<v8::Object> self = Persistent<v8::Object>::New( args.This() ); + self.MakeWeak( conn , destroyConnection ); + + string errmsg; + if ( ! conn->connect( host , errmsg ) ){ + return v8::ThrowException( v8::String::New( "couldn't connect" ) ); + } + + // 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 ) ); + args.This()->Set( v8::String::New( "host" ) , v8::String::New( host ) ); + + return v8::Undefined(); + } + + Handle<Value> mongoConsLocal(const Arguments& args){ + + if ( args.Length() > 0 ) + return v8::ThrowException( v8::String::New( "local Mongo constructor takes no args" ) ); + + DBClientBase * 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()->Set( CONN_STRING , External::New( conn ) ); + args.This()->Set( v8::String::New( "slaveOk" ) , Boolean::New( false ) ); + args.This()->Set( v8::String::New( "host" ) , v8::String::New( "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()->Get( CONN_STRING )) ); + DBClientBase * conn = (DBClientBase*)(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" ); + DBClientBase * 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 ? QueryOption_SlaveOk : 0 ); + } + v8::Function * cons = (v8::Function*)( *( mongo->Get( v8::String::New( "internalCursor" ) ) ) ); + assert( cons ); + 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" ); + + DBClientBase * 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 { + v8::Unlocker u; + conn->insert( ns , o ); + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on insert" ) ); + } + + return v8::Undefined(); + } + + 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" ); + + DBClientBase * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + BSONObj o = v8ToMongo( in ); + + DDD( "want to remove : " << o.jsonString() ); + try { + v8::Unlocker u; + 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" ); + + 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 = v8ToMongo( q ); + BSONObj o1 = v8ToMongo( o ); + v8::Unlocker u; + conn->update( ns , q1 , o1 , upsert, multi ); + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on remove" ) ); + } + + return v8::Undefined(); + } + + + + + // --- cursor --- + + mongo::DBClientCursor * getCursor( const Arguments& args ){ + Local<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 ret; + { + v8::Unlocker u; + ret = cursor->more(); + } + return Boolean::New( ret ); + } + + + // --- 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::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] ); + 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( v8::String::New( "str" ) , v8::String::New( oid.str().c_str() ) ); + + return it; + } + + v8::Handle<v8::Value> dbRefInit( const v8::Arguments& args ) { + + if (args.Length() != 2) { + return v8::ThrowException( v8::String::New( "DBRef needs 2 arguments" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function* f = getNamedCons( "DBRef" ); + it = f->NewInstance(); + } + + it->Set( v8::String::New( "$ref" ) , args[0] ); + it->Set( v8::String::New( "$id" ) , args[1] ); + + return it; + } + + v8::Handle<v8::Value> dbPointerInit( const v8::Arguments& args ) { + + if (args.Length() != 2) { + return v8::ThrowException( v8::String::New( "DBPointer needs 2 arguments" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function* f = getNamedCons( "DBPointer" ); + it = f->NewInstance(); + } + + it->Set( v8::String::New( "ns" ) , args[0] ); + it->Set( v8::String::New( "id" ) , args[1] ); + it->SetHiddenValue( v8::String::New( "__DBPointer" ), v8::Number::New( 1 ) ); + + return it; + } + + v8::Handle<v8::Value> binDataInit( const v8::Arguments& args ) { + + if (args.Length() != 3) { + return v8::ThrowException( v8::String::New( "BinData needs 3 arguments" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function* f = getNamedCons( "BinData" ); + it = f->NewInstance(); + } + + it->Set( v8::String::New( "len" ) , args[0] ); + it->Set( v8::String::New( "type" ) , args[1] ); + it->Set( v8::String::New( "data" ), args[2] ); + it->SetHiddenValue( v8::String::New( "__BinData" ), v8::Number::New( 1 ) ); + + return it; + } + + v8::Handle<v8::Value> bsonsize( const v8::Arguments& args ) { + + if (args.Length() != 1 || !args[ 0 ]->IsObject()) { + return v8::ThrowException( v8::String::New( "bonsisze needs 1 object" ) ); + } + + return v8::Number::New( v8ToMongo( args[ 0 ]->ToObject() ).objsize() ); + } +} diff --git a/scripting/v8_db.h b/scripting/v8_db.h new file mode 100644 index 0000000..c3f2ef1 --- /dev/null +++ b/scripting/v8_db.h @@ -0,0 +1,71 @@ +// v8_db.h + +/* 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. + */ + +#pragma once + +#include <v8.h> +#include <cstring> +#include <cstdio> +#include <cstdlib> + +#include "../client/dbclient.h" + +namespace mongo { + + // These functions may depend on the caller creating a handle scope and context scope. + + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( bool local ); + void installDBTypes( v8::Handle<v8::ObjectTemplate>& global ); + void installDBTypes( v8::Handle<v8::Object>& global ); + + // the actual globals + + mongo::DBClientBase * getConnection( const v8::Arguments& args ); + + // Mongo members + v8::Handle<v8::Value> mongoConsLocal(const v8::Arguments& args); + v8::Handle<v8::Value> mongoConsExternal(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> dbRefInit( const v8::Arguments& args ); + v8::Handle<v8::Value> dbPointerInit( const v8::Arguments& args ); + + v8::Handle<v8::Value> binDataInit( 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::Handle<v8::Value> bsonsize( const v8::Arguments& args ); + +} diff --git a/scripting/v8_utils.cpp b/scripting/v8_utils.cpp new file mode 100644 index 0000000..5e56245 --- /dev/null +++ b/scripting/v8_utils.cpp @@ -0,0 +1,305 @@ +// v8_utils.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. + */ + +#include "v8_utils.h" +#include <iostream> +#include <map> +#include <sstream> +#include <vector> +#include <sys/socket.h> +#include <netinet/in.h> +#include <boost/smart_ptr.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/xtime.hpp> +#include "engine_v8.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::string toSTLString( const v8::TryCatch * try_catch ){ + + stringstream ss; + + //while ( try_catch ){ // disabled for v8 bleeding edge + + v8::String::Utf8Value exception(try_catch->Exception()); + Handle<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + ss << *exception << endl; + } + else { + + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + ss << *filename << ":" << linenum << " " << *exception << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + ss << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + ss << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + ss << "^"; + + ss << endl; + } + + //try_catch = try_catch->next_; + //} + + return ss.str(); + } + + + 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_ ) // disabled for v8 bleeding edge + // s << try_catch->next_; + + return s; + } + + + Handle<v8::Value> Version(const Arguments& args) { + HandleScope handle_scope; + return handle_scope.Close( v8::String::New(v8::V8::GetVersion()) ); + } + + void ReportException(v8::TryCatch* try_catch) { + cout << try_catch << endl; + } + + Handle< Context > baseContext_; + + class JSThreadConfig { + public: + JSThreadConfig( const Arguments &args, bool newScope = false ) : started_(), done_(), newScope_( newScope ) { + jsassert( args.Length() > 0, "need at least one argument" ); + jsassert( args[ 0 ]->IsFunction(), "first argument must be a function" ); + Local< v8::Function > f = v8::Function::Cast( *args[ 0 ] ); + f_ = Persistent< v8::Function >::New( f ); + for( int i = 1; i < args.Length(); ++i ) + args_.push_back( Persistent< Value >::New( args[ i ] ) ); + } + ~JSThreadConfig() { + f_.Dispose(); + for( vector< Persistent< Value > >::iterator i = args_.begin(); i != args_.end(); ++i ) + i->Dispose(); + returnData_.Dispose(); + } + void start() { + jsassert( !started_, "Thread already started" ); + JSThread jt( *this ); + thread_.reset( new boost::thread( jt ) ); + started_ = true; + } + void join() { + jsassert( started_ && !done_, "Thread not running" ); + Unlocker u; + thread_->join(); + done_ = true; + } + Local< Value > returnData() { + if ( !done_ ) + join(); + return Local< Value >::New( returnData_ ); + } + private: + class JSThread { + public: + JSThread( JSThreadConfig &config ) : config_( config ) {} + void operator()() { + Locker l; + HandleScope handle_scope; + Handle< Context > context; + Handle< v8::Function > fun; + auto_ptr< V8Scope > scope; + if ( config_.newScope_ ) { + scope.reset( dynamic_cast< V8Scope * >( globalScriptEngine->newScope() ) ); + context = scope->context(); + // A v8::Function tracks the context in which it was created, so we have to + // create a new function in the new context. + Context::Scope baseScope( baseContext_ ); + string fCode = toSTLString( config_.f_->ToString() ); + Context::Scope context_scope( context ); + fun = scope->__createFunction( fCode.c_str() ); + } else { + context = baseContext_; + Context::Scope context_scope( context ); + fun = config_.f_; + } + Context::Scope context_scope( context ); + boost::scoped_array< Local< Value > > argv( new Local< Value >[ config_.args_.size() ] ); + for( unsigned int i = 0; i < config_.args_.size(); ++i ) + argv[ i ] = Local< Value >::New( config_.args_[ i ] ); + TryCatch try_catch; + Handle< Value > ret = fun->Call( context->Global(), config_.args_.size(), argv.get() ); + if ( ret.IsEmpty() ) { + string e = toSTLString( &try_catch ); + log() << "js thread raised exception: " << e << endl; + // v8 probably does something sane if ret is empty, but not going to assume that for now + ret = v8::Undefined(); + } + config_.returnData_ = Persistent< Value >::New( ret ); + } + private: + JSThreadConfig &config_; + }; + + bool started_; + bool done_; + bool newScope_; + Persistent< v8::Function > f_; + vector< Persistent< Value > > args_; + auto_ptr< boost::thread > thread_; + Persistent< Value > returnData_; + }; + + Handle< Value > ThreadInit( const Arguments &args ) { + Handle<v8::Object> it = args.This(); + // NOTE I believe the passed JSThreadConfig will never be freed. If this + // policy is changed, JSThread may no longer be able to store JSThreadConfig + // by reference. + it->SetHiddenValue( v8::String::New( "_JSThreadConfig" ), External::New( new JSThreadConfig( args ) ) ); + return v8::Undefined(); + } + + Handle< Value > ScopedThreadInit( const Arguments &args ) { + Handle<v8::Object> it = args.This(); + // NOTE I believe the passed JSThreadConfig will never be freed. If this + // policy is changed, JSThread may no longer be able to store JSThreadConfig + // by reference. + it->SetHiddenValue( v8::String::New( "_JSThreadConfig" ), External::New( new JSThreadConfig( args, true ) ) ); + return v8::Undefined(); + } + + JSThreadConfig *thisConfig( const Arguments &args ) { + Local< External > c = External::Cast( *(args.This()->GetHiddenValue( v8::String::New( "_JSThreadConfig" ) ) ) ); + JSThreadConfig *config = (JSThreadConfig *)( c->Value() ); + return config; + } + + Handle< Value > ThreadStart( const Arguments &args ) { + thisConfig( args )->start(); + return v8::Undefined(); + } + + Handle< Value > ThreadJoin( const Arguments &args ) { + thisConfig( args )->join(); + return v8::Undefined(); + } + + Handle< Value > ThreadReturnData( const Arguments &args ) { + HandleScope handle_scope; + return handle_scope.Close( thisConfig( args )->returnData() ); + } + + Handle< Value > ThreadInject( const Arguments &args ) { + jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" ); + jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" ); + + Local<v8::Object> o = args[0]->ToObject(); + + o->Set( v8::String::New( "init" ) , FunctionTemplate::New( ThreadInit )->GetFunction() ); + o->Set( v8::String::New( "start" ) , FunctionTemplate::New( ThreadStart )->GetFunction() ); + o->Set( v8::String::New( "join" ) , FunctionTemplate::New( ThreadJoin )->GetFunction() ); + o->Set( v8::String::New( "returnData" ) , FunctionTemplate::New( ThreadReturnData )->GetFunction() ); + + return v8::Undefined(); + } + + Handle< Value > ScopedThreadInject( const Arguments &args ) { + jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" ); + jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" ); + + Local<v8::Object> o = args[0]->ToObject(); + + o->Set( v8::String::New( "init" ) , FunctionTemplate::New( ScopedThreadInit )->GetFunction() ); + // inheritance takes care of other member functions + + return v8::Undefined(); + } + + void installFork( v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ) { + if ( baseContext_.IsEmpty() ) // if this is the shell, first call will be with shell context, otherwise don't expect to use fork() anyway + baseContext_ = context; + global->Set( v8::String::New( "_threadInject" ), FunctionTemplate::New( ThreadInject )->GetFunction() ); + global->Set( v8::String::New( "_scopedThreadInject" ), FunctionTemplate::New( ScopedThreadInject )->GetFunction() ); + } + +} diff --git a/scripting/v8_utils.h b/scripting/v8_utils.h new file mode 100644 index 0000000..8218455 --- /dev/null +++ b/scripting/v8_utils.h @@ -0,0 +1,46 @@ +// v8_utils.h + +/* 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. + */ + +#pragma once + +#include <v8.h> + +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include <assert.h> +#include <iostream> + +namespace mongo { + + v8::Handle<v8::Value> Print(const v8::Arguments& args); + v8::Handle<v8::Value> Version(const v8::Arguments& args); + + void ReportException(v8::TryCatch* handler); + +#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 ); + std::string toSTLString( const v8::TryCatch * try_catch ); + + class V8Scope; + void installFork( v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ); +} + diff --git a/scripting/v8_wrapper.cpp b/scripting/v8_wrapper.cpp new file mode 100644 index 0000000..29a70ba --- /dev/null +++ b/scripting/v8_wrapper.cpp @@ -0,0 +1,575 @@ +// v8_wrapper.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. + */ + +#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) + + 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 ){ + + // handle DBRef. needs to come first. isn't it? (metagoto) + static string ref = "$ref"; + if ( ref == m.firstElement().fieldName() ) { + const BSONElement& id = m["$id"]; + if (!id.eoo()) { // there's no check on $id exitence in sm implementation. risky ? + v8::Function* dbRef = getNamedCons( "DBRef" ); + v8::Handle<v8::Value> argv[2]; + argv[0] = mongoToV8Element(m.firstElement()); + argv[1] = mongoToV8Element(m["$id"]); + return dbRef->NewInstance(2, argv); + } + } + + Local< v8::ObjectTemplate > readOnlyObjects; + // Hoping template construction is fast... + Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); + internalFieldObjects->SetInternalFieldCount( 1 ); + + Local<v8::Object> o; + if ( array ) { + // NOTE Looks like it's impossible to add interceptors to non array objects in v8. + 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: + case mongo::NumberLong: // may lose information here - just copying sm engine behavior + o->Set( v8::String::New( f.fieldName() ) , v8::Number::New( f.number() ) ); + break; + + 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: + 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( "time" ) , v8::Date::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::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::Undefined: + o->Set( v8::String::New( f.fieldName() ), v8::Undefined() ); + break; + + case mongo::DBRef: { + v8::Function* dbPointer = getNamedCons( "DBPointer" ); + v8::Handle<v8::Value> argv[2]; + 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 ( !array && 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: + 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( "time" ) , v8::Date::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::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::Undefined: + return v8::Undefined(); + + 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 ){ + + if ( value->IsString() ){ + 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() ){ + if ( value->IsInt32() ) + b.append( sname.c_str(), int( value->ToInt32()->Value() ) ); + else + 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() , Date_t(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.c_str(), + Date_t( v8::Date::Cast( *obj->Get( v8::String::New( "time" ) ) )->NumberValue() ), + obj->Get( v8::String::New( "i" ) )->ToInt32()->Value() ); + return; + case MinKey: + b.appendMinKey( sname.c_str() ); + return; + case MaxKey: + b.appendMaxKey( sname.c_str() ); + 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.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 if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__DBPointer" ) ).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. + 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.c_str(), ns.c_str(), 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.c_str(), + len, + mongo::BinDataType( obj->Get( v8::String::New( "type" ) )->ToInt32()->Value() ), + dataArray ); + } 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() ){ + b.appendUndefined( sname.c_str() ); + return; + } + + else if ( value->IsNull() ){ + b.appendNull( sname.c_str() ); + return; + } + + cout << "don't know how to convert 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(); + } + + // --- object wrapper --- + + class WrapperHolder { + public: + WrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ) + : _o(o), _readOnly( readOnly ), _iDelete( iDelete ) { + } + + ~WrapperHolder(){ + if ( _o && _iDelete ){ + delete _o; + } + _o = 0; + } + + v8::Handle<v8::Value> get( v8::Local<v8::String> name ){ + const string& s = toSTLString( name ); + const BSONElement& e = _o->getField( s ); + return mongoToV8Element(e); + } + + const BSONObj * _o; + bool _readOnly; + bool _iDelete; + }; + + WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ){ + return new WrapperHolder( 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 ); + assert( t->IsExternal() ); + Local<External> c = External::Cast( *t ); + WrapperHolder * w = (WrapperHolder*)(c->Value()); + assert( w ); + return w; + } + + + Handle<Value> wrapperCons(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] ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> wrapperGetHandler( v8::Local<v8::String> name, const v8::AccessorInfo &info){ + return getWrapper( info.This() )->get( name ); + } + + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(){ + v8::Local<v8::FunctionTemplate> t = FunctionTemplate::New( 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" ); + } + +} diff --git a/scripting/v8_wrapper.h b/scripting/v8_wrapper.h new file mode 100644 index 0000000..1d67cf1 --- /dev/null +++ b/scripting/v8_wrapper.h @@ -0,0 +1,43 @@ +// v8_wrapper.h + +/* 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. + */ + +#pragma once + +#include <v8.h> +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include "../db/jsobj.h" + +namespace mongo { + + v8::Local<v8::Object> mongoToV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false ); + mongo::BSONObj v8ToMongo( v8::Handle<v8::Object> o ); + + 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 ); + + v8::Function * getNamedCons( const char * name ); + v8::Function * getObjectIdCons(); + + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(); + + class WrapperHolder; + WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ); + +} |