summaryrefslogtreecommitdiff
path: root/scripting
diff options
context:
space:
mode:
authorAntonin Kral <a.kral@bobek.cz>2010-01-31 08:32:52 +0100
committerAntonin Kral <a.kral@bobek.cz>2010-01-31 08:32:52 +0100
commit4eefaf421bfeddf040d96a3dafb12e09673423d7 (patch)
treecb2e5ccc7f98158894f977ff131949da36673591 /scripting
downloadmongodb-4eefaf421bfeddf040d96a3dafb12e09673423d7.tar.gz
Imported Upstream version 1.3.1
Diffstat (limited to 'scripting')
-rw-r--r--scripting/engine.cpp399
-rw-r--r--scripting/engine.h154
-rw-r--r--scripting/engine_java.cpp763
-rw-r--r--scripting/engine_java.h224
-rw-r--r--scripting/engine_none.cpp24
-rw-r--r--scripting/engine_spidermonkey.cpp1464
-rw-r--r--scripting/engine_spidermonkey.h116
-rw-r--r--scripting/engine_v8.cpp436
-rw-r--r--scripting/engine_v8.h116
-rw-r--r--scripting/sm_db.cpp854
-rw-r--r--scripting/v8_db.cpp542
-rw-r--r--scripting/v8_db.h71
-rw-r--r--scripting/v8_utils.cpp305
-rw-r--r--scripting/v8_utils.h46
-rw-r--r--scripting/v8_wrapper.cpp575
-rw-r--r--scripting/v8_wrapper.h43
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 , &timestamp_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 , &timestamp_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 , &timestamp_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 );
+
+}