summaryrefslogtreecommitdiff
path: root/src/mongo/scripting
diff options
context:
space:
mode:
authorEliot Horowitz <eliot@10gen.com>2011-12-24 15:33:26 -0500
committerEliot Horowitz <eliot@10gen.com>2011-12-24 15:33:45 -0500
commitae1ecd9c786911f9f1f0242f0f7d702b3e5dfeba (patch)
tree92f8e1649e6f080b251ff5f1763679a72eb59b34 /src/mongo/scripting
parentdfa4cd7e2cf109b072440155fabc08a93c8045a0 (diff)
downloadmongo-ae1ecd9c786911f9f1f0242f0f7d702b3e5dfeba.tar.gz
bulk move of code to src/ SERVER-4551
Diffstat (limited to 'src/mongo/scripting')
-rw-r--r--src/mongo/scripting/bench.cpp785
-rw-r--r--src/mongo/scripting/engine.cpp519
-rw-r--r--src/mongo/scripting/engine.h235
-rw-r--r--src/mongo/scripting/engine_java.cpp764
-rw-r--r--src/mongo/scripting/engine_java.h223
-rw-r--r--src/mongo/scripting/engine_none.cpp24
-rw-r--r--src/mongo/scripting/engine_spidermonkey.cpp1766
-rw-r--r--src/mongo/scripting/engine_spidermonkey.h105
-rw-r--r--src/mongo/scripting/engine_v8.cpp1634
-rw-r--r--src/mongo/scripting/engine_v8.h254
-rw-r--r--src/mongo/scripting/sm_db.cpp1284
-rw-r--r--src/mongo/scripting/utils.cpp77
-rw-r--r--src/mongo/scripting/v8_db.cpp1128
-rw-r--r--src/mongo/scripting/v8_db.h94
-rw-r--r--src/mongo/scripting/v8_utils.cpp295
-rw-r--r--src/mongo/scripting/v8_utils.h43
-rw-r--r--src/mongo/scripting/v8_wrapper.cpp99
-rw-r--r--src/mongo/scripting/v8_wrapper.h34
18 files changed, 9363 insertions, 0 deletions
diff --git a/src/mongo/scripting/bench.cpp b/src/mongo/scripting/bench.cpp
new file mode 100644
index 00000000000..01291b1e1f0
--- /dev/null
+++ b/src/mongo/scripting/bench.cpp
@@ -0,0 +1,785 @@
+/** @file bench.cpp */
+
+/*
+ * Copyright (C) 2010 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "engine.h"
+#include "../util/md5.hpp"
+#include "../util/version.h"
+#include "../client/dbclient.h"
+#include "../client/connpool.h"
+#include <pcrecpp.h>
+
+// ---------------------------------
+// ---- benchmarking system --------
+// ---------------------------------
+
+// TODO: Maybe extract as library to avoid code duplication?
+namespace {
+ inline pcrecpp::RE_Options flags2options(const char* flags) {
+ pcrecpp::RE_Options options;
+ options.set_utf8(true);
+ while ( flags && *flags ) {
+ if ( *flags == 'i' )
+ options.set_caseless(true);
+ else if ( *flags == 'm' )
+ options.set_multiline(true);
+ else if ( *flags == 'x' )
+ options.set_extended(true);
+ flags++;
+ }
+ return options;
+ }
+}
+
+namespace mongo {
+
+ struct BenchRunConfig {
+ BenchRunConfig() : _mutex( "BenchRunConfig" ) {
+ host = "localhost";
+ db = "test";
+ username = "";
+ password = "";
+
+ parallel = 1;
+ seconds = 1;
+ handleErrors = false;
+ hideErrors = false;
+ hideResults = true;
+
+ active = true;
+ threadsReady = 0;
+ error = false;
+ errCount = 0;
+ throwGLE = false;
+ breakOnTrap = true;
+ }
+
+ string host;
+ string db;
+ string username;
+ string password;
+
+ unsigned parallel;
+ double seconds;
+
+ bool hideResults;
+ bool handleErrors;
+ bool hideErrors;
+
+ shared_ptr< pcrecpp::RE > trapPattern;
+ shared_ptr< pcrecpp::RE > noTrapPattern;
+ shared_ptr< pcrecpp::RE > watchPattern;
+ shared_ptr< pcrecpp::RE > noWatchPattern;
+
+ BSONObj ops;
+
+ volatile bool active; // true at starts, gets set to false when should stop
+ AtomicUInt threadsReady;
+
+ bool error;
+ bool throwGLE;
+ bool breakOnTrap;
+
+ AtomicUInt threadsActive;
+
+ mongo::mutex _mutex;
+ long long errCount;
+ BSONArrayBuilder trapped;
+ };
+
+ static bool _hasSpecial( const BSONObj& obj ) {
+ BSONObjIterator i( obj );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+ if ( e.fieldName()[0] == '#' )
+ return true;
+
+ if ( ! e.isABSONObj() )
+ continue;
+
+ if ( _hasSpecial( e.Obj() ) )
+ return true;
+ }
+ return false;
+ }
+
+ static void _fixField( BSONObjBuilder& b , const BSONElement& e ) {
+ assert( e.type() == Object );
+
+ BSONObj sub = e.Obj();
+ assert( sub.nFields() == 1 );
+
+ BSONElement f = sub.firstElement();
+ if ( str::equals( "#RAND_INT" , f.fieldName() ) ) {
+ BSONObjIterator i( f.Obj() );
+ int min = i.next().numberInt();
+ int max = i.next().numberInt();
+
+ int x = min + ( rand() % ( max - min ) );
+ b.append( e.fieldName() , x );
+ }
+ else {
+ uasserted( 14811 , str::stream() << "invalid bench dynamic piece: " << f.fieldName() );
+ }
+
+ }
+
+ static void fixQuery( BSONObjBuilder& b , const BSONObj& obj ) {
+ BSONObjIterator i( obj );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+
+ if ( ! e.isABSONObj() ) {
+ b.append( e );
+ continue;
+ }
+
+ BSONObj sub = e.Obj();
+ if ( sub.firstElement().fieldName()[0] == '#' ) {
+ _fixField( b , e );
+ }
+ else {
+ BSONObjBuilder xx( e.type() == Object ? b.subobjStart( e.fieldName() ) : b.subarrayStart( e.fieldName() ) );
+ fixQuery( xx , sub );
+ xx.done();
+ }
+
+ }
+ }
+
+ static BSONObj fixQuery( const BSONObj& obj ) {
+
+ if ( ! _hasSpecial( obj ) )
+ return obj;
+
+ BSONObjBuilder b( obj.objsize() + 128 );
+ fixQuery( b , obj );
+ return b.obj();
+ }
+
+
+ static void _benchThread( BenchRunConfig * config, ScopedDbConnection& conn ){
+
+ long long count = 0;
+ while ( config->active ) {
+ BSONObjIterator i( config->ops );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+
+ string ns = e["ns"].String();
+ string op = e["op"].String();
+
+ int delay = e["delay"].eoo() ? 0 : e["delay"].Int();
+
+ auto_ptr<Scope> scope;
+ ScriptingFunction scopeFunc = 0;
+ BSONObj scopeObj;
+
+ if (config->username != "") {
+ string errmsg;
+ if (!conn.get()->auth(config->db, config->username, config->password, errmsg)) {
+ uasserted(15931, "Authenticating to connection for _benchThread failed: " + errmsg);
+ }
+ }
+
+ bool check = ! e["check"].eoo();
+ if( check ){
+ if ( e["check"].type() == CodeWScope || e["check"].type() == Code || e["check"].type() == String ) {
+ scope = globalScriptEngine->getPooledScope( ns );
+ assert( scope.get() );
+
+ if ( e.type() == CodeWScope ) {
+ scopeFunc = scope->createFunction( e["check"].codeWScopeCode() );
+ scopeObj = BSONObj( e.codeWScopeScopeData() );
+ }
+ else {
+ scopeFunc = scope->createFunction( e["check"].valuestr() );
+ }
+
+ scope->init( &scopeObj );
+ assert( scopeFunc );
+ }
+ else {
+ warning() << "Invalid check type detected in benchRun op : " << e << endl;
+ check = false;
+ }
+ }
+
+ try {
+ if ( op == "findOne" ) {
+
+ BSONObj result = conn->findOne( ns , fixQuery( e["query"].Obj() ) );
+
+ if( check ){
+ int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false );
+ if( err ){
+ log() << "Error checking in benchRun thread [findOne]" << causedBy( scope->getError() ) << endl;
+ return;
+ }
+ }
+
+ if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [findOne] : " << result << endl;
+
+ }
+ else if ( op == "command" ) {
+
+ BSONObj result;
+ // TODO
+ /* bool ok = */ conn->runCommand( ns , fixQuery( e["command"].Obj() ), result, e["options"].numberInt() );
+
+ if( check ){
+ int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false );
+ if( err ){
+ log() << "Error checking in benchRun thread [command]" << causedBy( scope->getError() ) << endl;
+ return;
+ }
+ }
+
+ if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [command] : " << result << endl;
+
+ }
+ else if( op == "find" || op == "query" ) {
+
+ int limit = e["limit"].eoo() ? 0 : e["limit"].numberInt();
+ int skip = e["skip"].eoo() ? 0 : e["skip"].Int();
+ int options = e["options"].eoo() ? 0 : e["options"].Int();
+ int batchSize = e["batchSize"].eoo() ? 0 : e["batchSize"].Int();
+ BSONObj filter = e["filter"].eoo() ? BSONObj() : e["filter"].Obj();
+
+ auto_ptr<DBClientCursor> cursor = conn->query( ns, fixQuery( e["query"].Obj() ), limit, skip, &filter, options, batchSize );
+
+ int count = cursor->itcount();
+
+ if( check ){
+ BSONObj thisValue = BSON( "count" << count );
+ int err = scope->invoke( scopeFunc , 0 , &thisValue, 1000 * 60 , false );
+ if( err ){
+ log() << "Error checking in benchRun thread [find]" << causedBy( scope->getError() ) << endl;
+ return;
+ }
+ }
+
+ if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [query] : " << count << endl;
+
+ }
+ else if( op == "update" ) {
+
+ bool multi = e["multi"].trueValue();
+ bool upsert = e["upsert"].trueValue();
+ BSONObj query = e["query"].eoo() ? BSONObj() : e["query"].Obj();
+ BSONObj update = e["update"].Obj();
+
+ conn->update( ns, fixQuery( query ), update, upsert , multi );
+
+ bool safe = e["safe"].trueValue();
+ if( safe ){
+ BSONObj result = conn->getLastErrorDetailed();
+
+ if( check ){
+ int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false );
+ if( err ){
+ log() << "Error checking in benchRun thread [update]" << causedBy( scope->getError() ) << endl;
+ return;
+ }
+ }
+
+ if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe update] : " << result << endl;
+
+ if( ! result["err"].eoo() && result["err"].type() == String && ( config->throwGLE || e["throwGLE"].trueValue() ) )
+ throw DBException( (string)"From benchRun GLE" + causedBy( result["err"].String() ),
+ result["code"].eoo() ? 0 : result["code"].Int() );
+ }
+ }
+ else if( op == "insert" ) {
+
+ conn->insert( ns, fixQuery( e["doc"].Obj() ) );
+
+ bool safe = e["safe"].trueValue();
+ if( safe ){
+ BSONObj result = conn->getLastErrorDetailed();
+
+ if( check ){
+ int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false );
+ if( err ){
+ log() << "Error checking in benchRun thread [insert]" << causedBy( scope->getError() ) << endl;
+ return;
+ }
+ }
+
+ if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe insert] : " << result << endl;
+
+ if( ! result["err"].eoo() && result["err"].type() == String && ( config->throwGLE || e["throwGLE"].trueValue() ) )
+ throw DBException( (string)"From benchRun GLE" + causedBy( result["err"].String() ),
+ result["code"].eoo() ? 0 : result["code"].Int() );
+ }
+ }
+ else if( op == "delete" || op == "remove" ) {
+
+ bool multi = e["multi"].eoo() ? true : e["multi"].trueValue();
+ BSONObj query = e["query"].eoo() ? BSONObj() : e["query"].Obj();
+
+ conn->remove( ns, fixQuery( query ), ! multi );
+
+ bool safe = e["safe"].trueValue();
+ if( safe ){
+ BSONObj result = conn->getLastErrorDetailed();
+
+ if( check ){
+ int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false );
+ if( err ){
+ log() << "Error checking in benchRun thread [delete]" << causedBy( scope->getError() ) << endl;
+ return;
+ }
+ }
+
+ if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe remove] : " << result << endl;
+
+ if( ! result["err"].eoo() && result["err"].type() == String && ( config->throwGLE || e["throwGLE"].trueValue() ) )
+ throw DBException( (string)"From benchRun GLE " + causedBy( result["err"].String() ),
+ result["code"].eoo() ? 0 : result["code"].Int() );
+ }
+ }
+ else if ( op == "createIndex" ) {
+ conn->ensureIndex( ns , e["key"].Obj() , false , "" , false );
+ }
+ else if ( op == "dropIndex" ) {
+ conn->dropIndex( ns , e["key"].Obj() );
+ }
+ else {
+ log() << "don't understand op: " << op << endl;
+ config->error = true;
+ return;
+ }
+ }
+ catch( DBException& ex ){
+ if( ! config->hideErrors || e["showError"].trueValue() ){
+
+ bool yesWatch = ( config->watchPattern && config->watchPattern->FullMatch( ex.what() ) );
+ bool noWatch = ( config->noWatchPattern && config->noWatchPattern->FullMatch( ex.what() ) );
+
+ if( ( ! config->watchPattern && config->noWatchPattern && ! noWatch ) || // If we're just ignoring things
+ ( ! config->noWatchPattern && config->watchPattern && yesWatch ) || // If we're just watching things
+ ( config->watchPattern && config->noWatchPattern && yesWatch && ! noWatch ) )
+ log() << "Error in benchRun thread for op " << e << causedBy( ex ) << endl;
+ }
+
+ bool yesTrap = ( config->trapPattern && config->trapPattern->FullMatch( ex.what() ) );
+ bool noTrap = ( config->noTrapPattern && config->noTrapPattern->FullMatch( ex.what() ) );
+
+ if( ( ! config->trapPattern && config->noTrapPattern && ! noTrap ) ||
+ ( ! config->noTrapPattern && config->trapPattern && yesTrap ) ||
+ ( config->trapPattern && config->noTrapPattern && yesTrap && ! noTrap ) ){
+ {
+ scoped_lock lock( config->_mutex );
+ config->trapped.append( BSON( "error" << ex.what() << "op" << e << "count" << count ) );
+ }
+ if( config->breakOnTrap ) return;
+ }
+ if( ! config->handleErrors && ! e["handleError"].trueValue() ) return;
+
+ {
+ scoped_lock lock( config->_mutex );
+ config->errCount++;
+ }
+ }
+ catch( ... ){
+ if( ! config->hideErrors || e["showError"].trueValue() ) log() << "Error in benchRun thread caused by unknown error for op " << e << endl;
+ if( ! config->handleErrors && ! e["handleError"].trueValue() ) return;
+
+ {
+ scoped_lock lock( config->_mutex );
+ config->errCount++;
+ }
+ }
+
+ if ( ++count % 100 == 0 ) {
+ conn->getLastError();
+ }
+
+ sleepmillis( delay );
+
+ }
+ }
+ }
+
+ static void benchThread( BenchRunConfig * config ) {
+
+ ScopedDbConnection conn( config->host );
+ config->threadsReady++;
+ config->threadsActive++;
+
+ try {
+ if (config->username != "") {
+ string errmsg;
+ if (!conn.get()->auth(config->db, config->username, config->password, errmsg)) {
+ uasserted(15932, "Authenticating to connection for benchThread failed: " + errmsg);
+ }
+ }
+
+ _benchThread( config, conn );
+ }
+ catch( DBException& e ){
+ error() << "DBException not handled in benchRun thread" << causedBy( e ) << endl;
+ }
+ catch( std::exception& e ){
+ error() << "Exception not handled in benchRun thread" << causedBy( e ) << endl;
+ }
+ catch( ... ){
+ error() << "Exception not handled in benchRun thread." << endl;
+ }
+ conn->getLastError();
+ config->threadsActive--;
+ conn.done();
+
+ }
+
+
+ class BenchRunner {
+ public:
+
+ BenchRunner( ) {
+ }
+
+ ~BenchRunner() {
+ }
+
+ void init( BSONObj& args ){
+
+ oid.init();
+ activeRuns[ oid ] = this;
+
+ if ( args["host"].type() == String )
+ config.host = args["host"].String();
+ if ( args["db"].type() == String )
+ config.db = args["db"].String();
+ if ( args["username"].type() == String )
+ config.username = args["username"].String();
+ if ( args["password"].type() == String )
+ config.db = args["password"].String();
+
+ if ( args["parallel"].isNumber() )
+ config.parallel = args["parallel"].numberInt();
+ if ( args["seconds"].isNumber() )
+ config.seconds = args["seconds"].numberInt();
+ if ( ! args["hideResults"].eoo() )
+ config.hideResults = args["hideResults"].trueValue();
+ if ( ! args["handleErrors"].eoo() )
+ config.handleErrors = args["handleErrors"].trueValue();
+ if ( ! args["hideErrors"].eoo() )
+ config.hideErrors = args["hideErrors"].trueValue();
+ if ( ! args["throwGLE"].eoo() )
+ config.throwGLE = args["throwGLE"].trueValue();
+ if ( ! args["breakOnTrap"].eoo() )
+ config.breakOnTrap = args["breakOnTrap"].trueValue();
+
+
+ if ( ! args["trapPattern"].eoo() ){
+ const char* regex = args["trapPattern"].regex();
+ const char* flags = args["trapPattern"].regexFlags();
+ config.trapPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) );
+ }
+
+ if ( ! args["noTrapPattern"].eoo() ){
+ const char* regex = args["noTrapPattern"].regex();
+ const char* flags = args["noTrapPattern"].regexFlags();
+ config.noTrapPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) );
+ }
+
+ if ( ! args["watchPattern"].eoo() ){
+ const char* regex = args["watchPattern"].regex();
+ const char* flags = args["watchPattern"].regexFlags();
+ config.watchPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) );
+ }
+
+ if ( ! args["noWatchPattern"].eoo() ){
+ const char* regex = args["noWatchPattern"].regex();
+ const char* flags = args["noWatchPattern"].regexFlags();
+ config.noWatchPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) );
+ }
+
+ config.ops = args["ops"].Obj().getOwned();
+ conn = shared_ptr< ScopedDbConnection >( new ScopedDbConnection( config.host ) );
+
+ // Get initial stats
+ conn->get()->simpleCommand( "admin" , &before , "serverStatus" );
+
+ // Start threads
+ for ( unsigned i = 0; i < config.parallel; i++ )
+ threads.push_back( shared_ptr< boost::thread >( new boost::thread( boost::bind( benchThread , &config ) ) ) );
+
+ // Give them time to init
+ while ( config.threadsReady < config.parallel ) sleepmillis( 1 );
+
+ }
+
+ void done(){
+
+ log() << "Ending! (waiting for " << threads.size() << " threads)" << endl;
+
+ {
+ scoped_lock lock( config._mutex );
+ config.active = false;
+ }
+
+ for ( unsigned i = 0; i < threads.size(); i++ ) threads[i]->join();
+
+ // Get final stats
+ conn->get()->simpleCommand( "admin" , &after , "serverStatus" );
+ after.getOwned();
+
+ conn.get()->done();
+
+ activeRuns.erase( oid );
+
+ }
+
+ BSONObj status(){
+ scoped_lock lock( config._mutex );
+ return BSON( "errCount" << config.errCount <<
+ "trappedCount" << config.trapped.arrSize() <<
+ "threadsActive" << config.threadsActive.get() );
+ }
+
+ static BenchRunner* get( BSONObj args ){
+ BenchRunner* runner = new BenchRunner();
+ runner->init( args );
+ return runner;
+ }
+
+ static BenchRunner* get( OID oid ){
+ return activeRuns[ oid ];
+ }
+
+ static BSONObj finish( BenchRunner* runner ){
+
+ runner->done();
+
+ // vector<BSONOBj> errors = runner->config.errors;
+ bool error = runner->config.error;
+
+ if ( error )
+ return BSON( "err" << 1 );
+
+ // compute actual ops/sec
+ BSONObj before = runner->before["opcounters"].Obj();
+ BSONObj after = runner->after["opcounters"].Obj();
+
+ BSONObjBuilder buf;
+ buf.append( "note" , "values per second" );
+ buf.append( "errCount", (long long) runner->config.errCount );
+ buf.append( "trapped", runner->config.trapped.arr() );
+ {
+ BSONObjIterator i( after );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+ double x = e.number();
+ x = x - before[e.fieldName()].number();
+ buf.append( e.fieldName() , x / runner->config.seconds );
+ }
+ }
+
+ BSONObj zoo = buf.obj();
+
+ delete runner;
+ return zoo;
+ }
+
+ static map< OID, BenchRunner* > activeRuns;
+
+ OID oid;
+ BenchRunConfig config;
+ vector< shared_ptr< boost::thread > > threads;
+
+ shared_ptr< ScopedDbConnection > conn;
+ BSONObj before;
+ BSONObj after;
+
+ };
+
+ map< OID, BenchRunner* > BenchRunner::activeRuns;
+
+
+ /**
+ * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 }
+ */
+ BSONObj benchRun( const BSONObj& argsFake, void* data ) {
+ assert( argsFake.firstElement().isABSONObj() );
+ BSONObj args = argsFake.firstElement().Obj();
+
+ // setup
+
+ BenchRunConfig config;
+
+ if ( args["host"].type() == String )
+ config.host = args["host"].String();
+ if ( args["db"].type() == String )
+ config.db = args["db"].String();
+ if ( args["username"].type() == String )
+ config.username = args["username"].String();
+ if ( args["password"].type() == String )
+ config.password = args["password"].String();
+
+ if ( args["parallel"].isNumber() )
+ config.parallel = args["parallel"].numberInt();
+ if ( args["seconds"].isNumber() )
+ config.seconds = args["seconds"].number();
+
+
+ config.ops = args["ops"].Obj();
+
+ // execute
+
+ ScopedDbConnection conn( config.host );
+
+ if (config.username != "") {
+ string errmsg;
+ if (!conn.get()->auth(config.db, config.username, config.password, errmsg)) {
+ uasserted(15930, "Authenticating to connection for bench run failed: " + errmsg);
+ }
+ }
+
+
+ // start threads
+ vector<boost::thread*> all;
+ for ( unsigned i=0; i<config.parallel; i++ )
+ all.push_back( new boost::thread( boost::bind( benchThread , &config ) ) );
+
+ // give them time to init
+ while ( config.threadsReady < config.parallel )
+ sleepmillis( 1 );
+
+ BSONObj before;
+ conn->simpleCommand( "admin" , &before , "serverStatus" );
+
+ sleepmillis( (int)(1000.0 * config.seconds) );
+
+ BSONObj after;
+ conn->simpleCommand( "admin" , &after , "serverStatus" );
+
+ conn.done();
+
+ config.active = false;
+
+ for ( unsigned i=0; i<all.size(); i++ )
+ all[i]->join();
+
+ if ( config.error )
+ return BSON( "err" << 1 );
+
+ // compute actual ops/sec
+
+ before = before["opcounters"].Obj().copy();
+ after = after["opcounters"].Obj().copy();
+
+ bool totals = args["totals"].trueValue();
+
+ BSONObjBuilder buf;
+ if ( ! totals )
+ buf.append( "note" , "values per second" );
+
+ {
+ BSONObjIterator i( after );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+ double x = e.number();
+ x = x - before[e.fieldName()].number();
+ if ( ! totals )
+ x = x / config.seconds;
+ buf.append( e.fieldName() , x );
+ }
+ }
+ BSONObj zoo = buf.obj();
+ return BSON( "" << zoo );
+ }
+
+ /**
+ * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 }
+ */
+ BSONObj benchRunSync( const BSONObj& argsFake, void* data ) {
+
+ assert( argsFake.firstElement().isABSONObj() );
+ BSONObj args = argsFake.firstElement().Obj();
+
+ // Get new BenchRunner object
+ BenchRunner* runner = BenchRunner::get( args );
+
+ sleepsecs( static_cast<int>( runner->config.seconds ) );
+
+ return BenchRunner::finish( runner );
+
+ }
+
+ /**
+ * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 }
+ */
+ BSONObj benchStart( const BSONObj& argsFake, void* data ) {
+
+ assert( argsFake.firstElement().isABSONObj() );
+ BSONObj args = argsFake.firstElement().Obj();
+
+ // Get new BenchRunner object
+ BenchRunner* runner = BenchRunner::get( args );
+
+ log() << "Starting benchRun test " << runner->oid << endl;
+
+ return BSON( "" << runner->oid.toString() );
+ }
+
+ /**
+ * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 }
+ */
+ BSONObj benchStatus( const BSONObj& argsFake, void* data ) {
+
+ OID oid = OID( argsFake.firstElement().String() );
+
+ log() << "Getting status for benchRun test " << oid << endl;
+
+ // Get new BenchRunner object
+ BenchRunner* runner = BenchRunner::get( oid );
+
+ BSONObj statusObj = runner->status();
+
+ return BSON( "" << statusObj );
+ }
+
+ /**
+ * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 }
+ */
+ BSONObj benchFinish( const BSONObj& argsFake, void* data ) {
+
+ OID oid = OID( argsFake.firstElement().String() );
+
+ log() << "Finishing benchRun test " << oid << endl;
+
+ // Get new BenchRunner object
+ BenchRunner* runner = BenchRunner::get( oid );
+
+ BSONObj finalObj = BenchRunner::finish( runner );
+
+ return BSON( "" << finalObj );
+ }
+
+ void installBenchmarkSystem( Scope& scope ) {
+ scope.injectNative( "benchRun" , benchRun );
+ scope.injectNative( "benchRunSync" , benchRunSync );
+ scope.injectNative( "benchStart" , benchStart );
+ scope.injectNative( "benchStatus" , benchStatus );
+ scope.injectNative( "benchFinish" , benchFinish );
+ }
+
+}
diff --git a/src/mongo/scripting/engine.cpp b/src/mongo/scripting/engine.cpp
new file mode 100644
index 00000000000..13fe681ebe5
--- /dev/null
+++ b/src/mongo/scripting/engine.cpp
@@ -0,0 +1,519 @@
+// 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 "pch.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), _numTimeUsed(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;
+ case Code:
+ builder.appendCode( fieldName , getString( 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, const BSONObj* recv, int timeoutMs ) {
+ ScriptingFunction func = createFunction( code );
+ uassert( 10207 , "compile failed" , func );
+ return invoke( func , args, recv, timeoutMs );
+ }
+
+ bool Scope::execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs ) {
+
+ path p( filename );
+
+ if ( ! exists( p ) ) {
+ log() << "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) {
+ log() << "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 );
+
+ unsigned L;
+ {
+ fileofs fo = f.len();
+ assert( fo <= 0x7ffffffe );
+ L = (unsigned) fo;
+ }
+ boost::scoped_array<char> data (new char[L+1]);
+ data[L] = 0;
+ f.read( 0 , data.get() , L );
+
+ int offset = 0;
+ if (data[0] == '#' && data[1] == '!') {
+ const char* newline = strchr(data.get(), '\n');
+ if (! newline)
+ return true; // file of just shebang treated same as empty file
+ offset = newline - data.get();
+ }
+
+ StringData code (data.get() + offset, L - offset);
+
+ return exec( code , 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(), 0, 0, NULL, QueryOption_SlaveOk, 0 );
+ assert( c.get() );
+
+ set<string> thisTime;
+
+ while ( c->more() ) {
+ BSONObj o = c->nextSafe();
+
+ BSONElement n = o["_id"];
+ BSONElement v = o["value"];
+
+ uassert( 10209 , str::stream() << "name has to be a string: " << n , n.type() == String );
+ uassert( 10210 , "value has to be set" , v.type() != EOO );
+
+ setElement( n.valuestr() , v );
+
+ thisTime.insert( n.valuestr() );
+ _storedNames.insert( n.valuestr() );
+
+ }
+
+ // --- remove things from scope that were removed
+
+ list<string> toremove;
+
+ for ( set<string>::iterator i=_storedNames.begin(); i!=_storedNames.end(); i++ ) {
+ string n = *i;
+ if ( thisTime.count( n ) == 0 )
+ toremove.push_back( n );
+ }
+
+ for ( list<string>::iterator i=toremove.begin(); i!=toremove.end(); i++ ) {
+ string n = *i;
+ _storedNames.erase( n );
+ execSetup( (string)"delete " + n , "clean up scope" );
+ }
+
+ }
+
+ 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;
+ }
+
+ namespace JSFiles {
+ extern const JSFile collection;
+ extern const JSFile db;
+ extern const JSFile mongo;
+ extern const JSFile mr;
+ extern const JSFile query;
+ extern const JSFile utils;
+ extern const JSFile utils_sh;
+ }
+
+ void Scope::execCoreFiles() {
+ // keeping same order as in SConstruct
+ execSetup(JSFiles::utils);
+ execSetup(JSFiles::utils_sh);
+ execSetup(JSFiles::db);
+ execSetup(JSFiles::mongo);
+ execSetup(JSFiles::mr);
+ execSetup(JSFiles::query);
+ execSetup(JSFiles::collection);
+ }
+
+ typedef map< string , list<Scope*> > PoolToScopes;
+
+ class ScopeCache {
+ public:
+
+ ScopeCache() : _mutex("ScopeCache") {
+ _magic = 17;
+ }
+
+ ~ScopeCache() {
+ assert( _magic == 17 );
+ _magic = 1;
+
+ if ( inShutdown() )
+ return;
+
+ clear();
+ }
+
+ void done( const string& pool , Scope * s ) {
+ scoped_lock lk( _mutex );
+ list<Scope*> & l = _pools[pool];
+ bool oom = s->hasOutOfMemoryException();
+
+ // do not keep too many contexts, or use them for too long
+ if ( l.size() > 10 || s->getTimeUsed() > 100 || oom ) {
+ delete s;
+ }
+ else {
+ l.push_back( s );
+ s->reset();
+ }
+
+ if (oom) {
+ // out of mem, make some room
+ log() << "Clearing all idle JS contexts due to out of memory" << endl;
+ clear();
+ }
+ }
+
+ Scope * get( const string& pool ) {
+ scoped_lock lk( _mutex );
+ list<Scope*> & l = _pools[pool];
+ if ( l.size() == 0 )
+ return 0;
+
+ Scope * s = l.back();
+ l.pop_back();
+ s->reset();
+ s->incTimeUsed();
+ 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;
+ mongo::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 {
+ // this means that the Scope was killed from a different thread
+ // for example a cursor got timed out that has a $where clause
+ log(3) << "warning: scopeCache is empty!" << endl;
+ delete _real;
+ _real = 0;
+ }
+ }
+
+ void reset() {
+ _real->reset();
+ }
+ void init( const 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 );
+// }
+
+ void setFunction( const char *field , const char * code ) {
+ _real->setFunction(field, code);
+ }
+
+ ScriptingFunction createFunction( const char * code ) {
+ return _real->createFunction( code );
+ }
+
+ ScriptingFunction _createFunction( const char * code ) {
+ return _real->createFunction( code );
+ }
+
+ void rename( const char * from , const char * to ) {
+ _real->rename( from , to );
+ }
+
+ /**
+ * @return 0 on success
+ */
+ int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) {
+ return _real->invoke( func , args , recv, timeoutMs , ignoreReturn, readOnlyArgs, readOnlyRecv );
+ }
+
+ string getError() {
+ return _real->getError();
+ }
+
+ bool hasOutOfMemoryException() {
+ return _real->hasOutOfMemoryException();
+ }
+
+ bool exec( const StringData& 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, void* data ) {
+ _real->injectNative( field , func, data );
+ }
+
+ void gc() {
+ _real->gc();
+ }
+
+ void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ) {
+ _real->append(builder, fieldName, scopeName);
+ }
+
+ 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();
+ }
+ }
+
+ void ( *ScriptEngine::_connectCallback )( DBClientWithCommands & ) = 0;
+ const char * ( *ScriptEngine::_checkInterruptCallback )() = 0;
+ unsigned ( *ScriptEngine::_getInterruptSpecCallback )() = 0;
+
+ ScriptEngine * globalScriptEngine = 0;
+
+ bool hasJSReturn( const string& code ) {
+ size_t x = code.find( "return" );
+ if ( x == string::npos )
+ return false;
+
+ return
+ ( x == 0 || ! isalpha( code[x-1] ) ) &&
+ ! isalpha( code[x+6] );
+ }
+
+ const char * jsSkipWhiteSpace( const char * raw ) {
+ while ( raw[0] ) {
+ while (isspace(*raw)) {
+ raw++;
+ }
+
+ if ( raw[0] != '/' || raw[1] != '/' )
+ break;
+
+ while ( raw[0] && raw[0] != '\n' )
+ raw++;
+ }
+ return raw;
+ }
+}
+
diff --git a/src/mongo/scripting/engine.h b/src/mongo/scripting/engine.h
new file mode 100644
index 00000000000..f4b39740001
--- /dev/null
+++ b/src/mongo/scripting/engine.h
@@ -0,0 +1,235 @@
+// 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 "../pch.h"
+#include "../db/jsobj.h"
+
+namespace mongo {
+
+ struct JSFile {
+ const char* name;
+ const StringData& source;
+ };
+
+ typedef unsigned long long ScriptingFunction;
+ typedef BSONObj (*NativeFunction) ( const BSONObj &args, void* data );
+
+ class Scope : boost::noncopyable {
+ public:
+ Scope();
+ virtual ~Scope();
+
+ virtual void reset() = 0;
+ virtual void init( const BSONObj * data ) = 0;
+ void init( const char * data ) {
+ BSONObj o( data );
+ init( &o );
+ }
+
+ virtual void localConnect( const char * dbName ) = 0;
+ virtual void externalSetup() = 0;
+
+ class NoDBAccess {
+ Scope * _s;
+ public:
+ NoDBAccess( Scope * s ) {
+ _s = s;
+ }
+ ~NoDBAccess() {
+ _s->rename( "____db____" , "db" );
+ }
+ };
+ NoDBAccess disableDBAccess( const char * why ) {
+ rename( "db" , "____db____" );
+ return NoDBAccess( this );
+ }
+
+ 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;
+
+ virtual 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 setFunction( const char *field , const char * code ) = 0;
+// virtual void setThis( const BSONObj * obj ) = 0;
+
+ virtual ScriptingFunction createFunction( const char * code );
+
+ virtual void rename( const char * from , const char * to ) = 0;
+ /**
+ * @return 0 on success
+ */
+ virtual int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool readOnlyArgs = false, bool readOnlyRecv = false ) = 0;
+ void invokeSafe( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool readOnlyArgs = false, bool readOnlyRecv = false ) {
+ int res = invoke( func , args , recv, timeoutMs, ignoreReturn, readOnlyArgs, readOnlyRecv );
+ if ( res == 0 )
+ return;
+ throw UserException( 9004 , (string)"invoke failed: " + getError() );
+ }
+ virtual string getError() = 0;
+ virtual bool hasOutOfMemoryException() = 0;
+
+ int invoke( const char* code , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 );
+ void invokeSafe( const char* code , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 ) {
+ if ( invoke( code , args , recv, timeoutMs ) == 0 )
+ return;
+ throw UserException( 9005 , (string)"invoke failed: " + getError() );
+ }
+
+ virtual bool exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) = 0;
+ virtual void execSetup( const StringData& code , const string& name = "setup" ) {
+ exec( code , name , false , true , true , 0 );
+ }
+
+ void execSetup( const JSFile& file) {
+ execSetup(file.source, file.name);
+ }
+
+ void execCoreFiles();
+
+ virtual bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 );
+
+ virtual void injectNative( const char *field, NativeFunction func, void* data = 0 ) = 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 );
+
+ /** increments the number of times a scope was used */
+ void incTimeUsed() { ++_numTimeUsed; }
+ /** gets the number of times a scope was used */
+ int getTimeUsed() { return _numTimeUsed; }
+
+ protected:
+
+ virtual ScriptingFunction _createFunction( const char * code ) = 0;
+
+ string _localDBName;
+ long long _loadedVersion;
+ set<string> _storedNames;
+ static long long _lastVersion;
+ map<string,ScriptingFunction> _cachedFunctions;
+ int _numTimeUsed;
+
+ static int _numScopes;
+ };
+
+ void installGlobalUtils( Scope& scope );
+
+ class DBClientWithCommands;
+
+ class ScriptEngine : boost::noncopyable {
+ public:
+ ScriptEngine();
+ virtual ~ScriptEngine();
+
+ virtual Scope * newScope() {
+ Scope *s = createScope();
+ if ( s && _scopeInitCallback )
+ _scopeInitCallback( *s );
+ installGlobalUtils( *s );
+ return s;
+ }
+
+ virtual void runTest() = 0;
+
+ virtual bool utf8Ok() const = 0;
+
+ static void setup();
+
+ /** gets a scope from the pool or a new one if pool is empty
+ * @param pool An identifier for the pool, usually the db name
+ * @return the scope */
+ auto_ptr<Scope> getPooledScope( const string& pool );
+
+ /** call this method to release some JS resources when a thread is done */
+ void threadDone();
+
+ struct Unlocker { virtual ~Unlocker() {} };
+ virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new Unlocker ); }
+
+ void setScopeInitCallback( void ( *func )( Scope & ) ) { _scopeInitCallback = func; }
+ static void setConnectCallback( void ( *func )( DBClientWithCommands& ) ) { _connectCallback = func; }
+ static void runConnectCallback( DBClientWithCommands &c ) {
+ if ( _connectCallback )
+ _connectCallback( c );
+ }
+
+ // engine implementation may either respond to interrupt events or
+ // poll for interrupts
+
+ // the interrupt functions must not wait indefinitely on a lock
+ virtual void interrupt( unsigned opSpec ) {}
+ virtual void interruptAll() {}
+
+ static void setGetInterruptSpecCallback( unsigned ( *func )() ) { _getInterruptSpecCallback = func; }
+ static bool haveGetInterruptSpecCallback() { return _getInterruptSpecCallback; }
+ static unsigned getInterruptSpec() {
+ massert( 13474, "no _getInterruptSpecCallback", _getInterruptSpecCallback );
+ return _getInterruptSpecCallback();
+ }
+
+ static void setCheckInterruptCallback( const char * ( *func )() ) { _checkInterruptCallback = func; }
+ static bool haveCheckInterruptCallback() { return _checkInterruptCallback; }
+ static const char * checkInterrupt() {
+ return _checkInterruptCallback ? _checkInterruptCallback() : "";
+ }
+ static bool interrupted() {
+ const char *r = checkInterrupt();
+ return r && r[ 0 ];
+ }
+
+ protected:
+ virtual Scope * createScope() = 0;
+
+ private:
+ void ( *_scopeInitCallback )( Scope & );
+ static void ( *_connectCallback )( DBClientWithCommands & );
+ static const char * ( *_checkInterruptCallback )();
+ static unsigned ( *_getInterruptSpecCallback )();
+ };
+
+ bool hasJSReturn( const string& s );
+
+ const char * jsSkipWhiteSpace( const char * raw );
+
+ extern ScriptEngine * globalScriptEngine;
+}
diff --git a/src/mongo/scripting/engine_java.cpp b/src/mongo/scripting/engine_java.cpp
new file mode 100644
index 00000000000..57388166e98
--- /dev/null
+++ b/src/mongo/scripting/engine_java.cpp
@@ -0,0 +1,764 @@
+// 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 "pch.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/net/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();
+
+ BSONObj::Holder* holder = (BSONObj::Holder*) malloc(guess + sizeof(unsigned));
+ holder->zero()
+
+ jobject bb = _getEnv()->NewDirectByteBuffer( (void*)holder->data , guess );
+ jassert( bb );
+
+ int len = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGetObject , id , _getEnv()->NewStringUTF( field ) , bb );
+ _getEnv()->DeleteLocalRef( bb );
+ jassert( len > 0 && len < guess );
+
+ BSONObj obj(holder);
+ 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/src/mongo/scripting/engine_java.h b/src/mongo/scripting/engine_java.h
new file mode 100644
index 00000000000..b8245ba6f22
--- /dev/null
+++ b/src/mongo/scripting/engine_java.h
@@ -0,0 +1,223 @@
+// 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 "../pch.h"
+
+#include <jni.h>
+#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/src/mongo/scripting/engine_none.cpp b/src/mongo/scripting/engine_none.cpp
new file mode 100644
index 00000000000..d13dbecc06e
--- /dev/null
+++ b/src/mongo/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/src/mongo/scripting/engine_spidermonkey.cpp b/src/mongo/scripting/engine_spidermonkey.cpp
new file mode 100644
index 00000000000..70b89cddbb5
--- /dev/null
+++ b/src/mongo/scripting/engine_spidermonkey.cpp
@@ -0,0 +1,1766 @@
+// 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 "pch.h"
+#include "engine_spidermonkey.h"
+#include "../client/dbclient.h"
+
+#ifndef _WIN32
+#include <boost/date_time/posix_time/posix_time.hpp>
+#undef assert
+#define assert MONGO_assert
+#endif
+
+#define smuassert( cx , msg , val ) \
+ if ( ! ( val ) ){ \
+ JS_ReportError( cx , msg ); \
+ return JS_FALSE; \
+ }
+
+#define CHECKNEWOBJECT(xx,ctx,w) \
+ if ( ! xx ){ \
+ massert(13072,(string)"JS_NewObject failed: " + w ,xx); \
+ }
+
+#define CHECKJSALLOC( newthing ) \
+ massert( 13615 , "JS allocation failed, either memory leak or using too much memory" , newthing )
+
+namespace mongo {
+
+ class InvalidUTF8Exception : public UserException {
+ public:
+ InvalidUTF8Exception() : UserException( 9006 , "invalid utf8" ) {
+ }
+ };
+
+ 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 = *( new boost::recursive_mutex );
+#define smlock recursive_scoped_lock ___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 TraverseStack {
+ public:
+ TraverseStack() {
+ _o = 0;
+ _parent = 0;
+ }
+
+ TraverseStack( JSObject * o , const TraverseStack * parent ) {
+ _o = o;
+ _parent = parent;
+ }
+
+ TraverseStack dive( JSObject * o ) const {
+ if ( o ) {
+ uassert( 13076 , (string)"recursive toObject" , ! has( o ) );
+ }
+ return TraverseStack( o , this );
+ }
+
+ int depth() const {
+ int d = 0;
+ const TraverseStack * s = _parent;
+ while ( s ) {
+ s = s->_parent;
+ d++;
+ }
+ return d;
+ }
+
+ bool isTop() const {
+ return _parent == 0;
+ }
+
+ bool has( JSObject * o ) const {
+ if ( ! o )
+ return false;
+ const TraverseStack * s = this;
+ while ( s ) {
+ if ( s->_o == o )
+ return true;
+ s = s->_parent;
+ }
+ return false;
+ }
+
+ JSObject * _o;
+ const TraverseStack * _parent;
+ };
+
+ 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.
+
+ if ( !JS_EncodeCharacters( _context , s , srclen , dst , &len) ) {
+ StringBuilder temp;
+ temp << "Not proper UTF-16: ";
+ for ( size_t i=0; i<srclen; i++ ) {
+ if ( i > 0 )
+ temp << ",";
+ temp << s[i];
+ }
+ uasserted( 13498 , temp.str() );
+ }
+
+ 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 ) );
+ }
+
+ // NOTE No validation of passed in object
+ long long toNumberLongUnsafe( JSObject *o ) {
+ boost::uint64_t val;
+ if ( hasProperty( o, "top" ) ) {
+ val =
+ ( (boost::uint64_t)(boost::uint32_t)getNumber( o , "top" ) << 32 ) +
+ ( boost::uint32_t)( getNumber( o , "bottom" ) );
+ }
+ else {
+ val = (boost::uint64_t)(boost::int64_t) getNumber( o, "floatApprox" );
+ }
+ return val;
+ }
+
+ int toNumberInt( JSObject *o ) {
+ return (boost::uint32_t)(boost::int32_t) getNumber( o, "floatApprox" );
+ }
+
+ 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 , const TraverseStack& stack=TraverseStack() ) {
+ 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 ) ) {
+
+ if ( stack.isTop() ) {
+ jsval theid = getProperty( o , "_id" );
+ if ( ! JSVAL_IS_VOID( theid ) ) {
+ append( b , "_id" , theid , EOO , stack.dive( o ) );
+ }
+ }
+
+ 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 ( stack.isTop() && name == "_id" )
+ continue;
+
+ append( b , name , getProperty( o , name.c_str() ) , orig[name].type() , stack.dive( o ) );
+ }
+
+ 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 , s.substr( 0 , end ) , s.substr( end + 1 ) );
+ }
+
+ void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO , const TraverseStack& stack=TraverseStack() ) {
+ //cout << "name: " << name << "\t" << typeString( val ) << " oldType: " << oldType << endl;
+ switch ( JS_TypeOfValue( _context , val ) ) {
+
+ case JSTYPE_VOID: b.appendUndefined( name ); break;
+ case JSTYPE_NULL: b.appendNull( name ); break;
+
+ case JSTYPE_NUMBER: {
+ double d = toNumber( val );
+ if ( oldType == NumberInt && ((int)d) == d )
+ b.append( name , (int)d );
+ else
+ b.append( name , d );
+ break;
+ }
+ case JSTYPE_STRING: b.append( name , toString( val ) ); break;
+ case JSTYPE_BOOLEAN: b.appendBool( name , toBoolean( val ) ); break;
+
+ case JSTYPE_OBJECT: {
+ JSObject * o = JSVAL_TO_OBJECT( val );
+ if ( ! o || o == JSVAL_NULL ) {
+ b.appendNull( name );
+ }
+ else if ( ! appendSpecialDBObject( this , b , name , val , o ) ) {
+ BSONObj sub = toObject( o , stack );
+ if ( JS_IsArrayObject( _context , o ) ) {
+ b.appendArray( name , sub );
+ }
+ else {
+ b.append( name , sub );
+ }
+ }
+ break;
+ }
+
+ case JSTYPE_FUNCTION: {
+ string s = toString(val);
+ if ( s[0] == '/' ) {
+ appendRegex( b , name , s );
+ }
+ else {
+ b.appendCode( name , getFunctionCode( val ) );
+ }
+ 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 ( hasJSReturn( code ) )
+ return false;
+
+ if ( code.find( ';' ) != string::npos &&
+ code.find( ';' ) != code.rfind( ';' ) )
+ return false;
+
+ if ( code.find( '\n') != string::npos )
+ 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 );
+
+ raw = jsSkipWhiteSpace( raw );
+
+ //cout << "RAW\n---\n" << raw << "\n---" << endl;
+
+ static int fnum = 1;
+ stringstream fname;
+ fname << "__cf__" << 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() , s.size() , "nofile_a" , 0 );
+ }
+
+ string code = raw;
+
+ size_t start = code.find( '(' );
+ assert( start != string::npos );
+
+ string fbase;
+ if ( start > 9 ) {
+ fbase = trim( code.substr( 9 , start - 9 ) );
+ }
+ if ( fbase.length() == 0 ) {
+ fbase = "anonymous_function";
+ }
+ fname << "f__" << fbase;
+
+ 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 );
+ }
+
+ boost::scoped_array<const char *> paramArray (new const char*[params.size()]);
+ for ( size_t i=0; i<params.size(); i++ )
+ paramArray[i] = params[i].c_str();
+
+ // avoid munging previously munged name (kludge; switching to v8 fixes underlying issue)
+ if ( fbase.find("__cf__") != 0 && fbase.find("__f__") == string::npos ) {
+ fbase = fname.str();
+ }
+
+ JSFunction * func = JS_CompileFunction( _context , assoc , fbase.c_str() , params.size() , paramArray.get() , code.c_str() , code.size() , "nofile_b" , 0 );
+
+ if ( ! func ) {
+ log() << "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 ) {
+ tlog() << "decode failed. probably invalid utf-8 string [" << c << "]" << endl;
+ jsval v;
+ if ( JS_GetPendingException( _context , &v ) )
+ tlog() << "\t why: " << toString( v ) << endl;
+ throw InvalidUTF8Exception();
+ }
+
+ CHECKJSALLOC( s );
+ return STRING_TO_JSVAL( s );
+ }
+
+ JSObject * toJSObject( const BSONObj * obj , bool readOnly=false ) {
+ static string ref = "$ref";
+ if ( ref == obj->firstElementFieldName() ) {
+ JSObject * o = JS_NewObject( _context , &dbref_class , NULL, NULL);
+ CHECKNEWOBJECT(o,_context,"toJSObject1");
+ assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) );
+ return o;
+ }
+ JSObject * o = JS_NewObject( _context , readOnly ? &bson_ro_class : &bson_class , NULL, NULL);
+ CHECKNEWOBJECT(o,_context,"toJSObject2");
+ 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 );
+ }
+
+ void makeLongObj( long long n, JSObject * o ) {
+ boost::uint64_t val = (boost::uint64_t)n;
+ CHECKNEWOBJECT(o,_context,"NumberLong1");
+ double floatApprox = (double)(boost::int64_t)val;
+ setProperty( o , "floatApprox" , toval( floatApprox ) );
+ if ( (boost::int64_t)val != (boost::int64_t)floatApprox ) {
+ // using 2 doubles here instead of a single double because certain double
+ // bit patterns represent undefined values and sm might trash them
+ setProperty( o , "top" , toval( (double)(boost::uint32_t)( val >> 32 ) ) );
+ setProperty( o , "bottom" , toval( (double)(boost::uint32_t)( val & 0x00000000ffffffff ) ) );
+ }
+ }
+
+ jsval toval( long long n ) {
+ JSObject * o = JS_NewObject( _context , &numberlong_class , 0 , 0 );
+ makeLongObj( n, o );
+ return OBJECT_TO_JSVAL( o );
+ }
+
+ void makeIntObj( int n, JSObject * o ) {
+ boost::uint32_t val = (boost::uint32_t)n;
+ CHECKNEWOBJECT(o,_context,"NumberInt1");
+ double floatApprox = (double)(boost::int32_t)val;
+ setProperty( o , "floatApprox" , toval( floatApprox ) );
+ }
+
+ jsval toval( int n ) {
+ JSObject * o = JS_NewObject( _context , &numberint_class , 0 , 0 );
+ makeIntObj( n, o );
+ 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:
+ return toval( e.number() );
+// case NumberInt:
+// return toval( e.numberInt() );
+ 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 ) );
+ }
+
+ JSObject * array = JS_NewArrayObject( _context , 1 , 0 );
+ CHECKJSALLOC( array );
+
+ jsval myarray = OBJECT_TO_JSVAL( array );
+
+ BSONObjIterator i( embed );
+ while ( i.more() ){
+ const BSONElement& e = i.next();
+ jsval v = toval( e );
+ assert( JS_SetElement( _context , array , atoi(e.fieldName()) , &v ) );
+ }
+
+ return myarray;
+ }
+ case jstOID: {
+ OID oid = e.__oid();
+ JSObject * o = JS_NewObject( _context , &object_id_class , 0 , 0 );
+ CHECKNEWOBJECT(o,_context,"jstOID");
+ 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() );
+ if ( func )
+ return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) );
+ return JSVAL_NULL;
+ }
+ case CodeWScope: {
+ JSFunction * func = compileFunction( e.codeWScopeCode() );
+ if ( !func )
+ return JSVAL_NULL;
+
+ 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) ((long long)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 );
+ CHECKNEWOBJECT(o,_context,"Timestamp1");
+ setProperty( o , "t" , toval( (double)(e.timestampTime()) ) );
+ setProperty( o , "i" , toval( (double)(e.timestampInc()) ) );
+ return OBJECT_TO_JSVAL( o );
+ }
+ case NumberLong: {
+ return toval( e.numberLong() );
+ }
+ case DBRef: {
+ JSObject * o = JS_NewObject( _context , &dbpointer_class , 0 , 0 );
+ CHECKNEWOBJECT(o,_context,"DBRef1");
+ setProperty( o , "ns" , toval( e.dbrefNS() ) );
+
+ JSObject * oid = JS_NewObject( _context , &object_id_class , 0 , 0 );
+ CHECKNEWOBJECT(oid,_context,"DBRef2");
+ 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 );
+ CHECKNEWOBJECT(o,_context,"Bindata_BinData1");
+ int len;
+ const char * data = e.binData( len );
+ assert( data );
+ assert( JS_SetPrivate( _context , o , new BinDataHolder( data , len ) ) );
+
+ setProperty( o , "len" , toval( (double)len ) );
+ setProperty( o , "type" , toval( (double)e.binDataType() ) );
+ return OBJECT_TO_JSVAL( o );
+ }
+ }
+
+ log() << "toval: unknown type: " << (int) 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 ) // needed when we're messing with DBRef.prototype
+ return JS_TRUE;
+ 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 hexToBinData(JSContext * cx, jsval *rval, int subtype, string s) {
+ JSObject * o = JS_NewObject( cx , &bindata_class , 0 , 0 );
+ CHECKNEWOBJECT(o,_context,"Bindata_BinData1");
+ int len = s.size() / 2;
+ char * data = new char[len];
+ char *p = data;
+ const char *src = s.c_str();
+ for( size_t i = 0; i+1 < s.size(); i += 2 ) {
+ *p++ = fromHex(src + i);
+ }
+ assert( JS_SetPrivate( cx , o , new BinDataHolder( data , len ) ) );
+ Convertor c(cx);
+ c.setProperty( o, "len", c.toval((double)len) );
+ c.setProperty( o, "type", c.toval((double)subtype) );
+ *rval = OBJECT_TO_JSVAL( o );
+ delete data;
+ return JS_TRUE;
+ }
+
+ JSBool _HexData( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) {
+ Convertor c( cx );
+ if ( argc != 2 ) {
+ JS_ReportError( cx , "HexData needs 2 arguments -- HexData(subtype,hexstring)" );
+ return JS_FALSE;
+ }
+ int type = (int)c.toNumber( argv[ 0 ] );
+ if ( type == 2 ) {
+ JS_ReportError( cx , "BinData subtype 2 is deprecated" );
+ return JS_FALSE;
+ }
+ string s = c.toString(argv[1]);
+ return hexToBinData(cx, rval, type, s);
+ }
+
+ JSBool _UUID( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) {
+ Convertor c( cx );
+ if ( argc != 1 ) {
+ JS_ReportError( cx , "UUID needs argument -- UUID(hexstring)" );
+ return JS_FALSE;
+ }
+ string s = c.toString(argv[0]);
+ if( s.size() != 32 ) {
+ JS_ReportError( cx , "bad UUID hex string len" );
+ return JS_FALSE;
+ }
+ return hexToBinData(cx, rval, 3, s);
+ }
+
+ JSBool _MD5( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) {
+ Convertor c( cx );
+ if ( argc != 1 ) {
+ JS_ReportError( cx , "MD5 needs argument -- MD5(hexstring)" );
+ return JS_FALSE;
+ }
+ string s = c.toString(argv[0]);
+ if( s.size() != 32 ) {
+ JS_ReportError( cx , "bad MD5 hex string len" );
+ return JS_FALSE;
+ }
+ return hexToBinData(cx, rval, 5, s);
+ }
+
+ JSBool native_print( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) {
+ stringstream ss;
+ Convertor c( cx );
+ for ( uintN i=0; i<argc; i++ ) {
+ if ( i > 0 )
+ ss << " ";
+ ss << c.toString( argv[i] );
+ }
+ ss << "\n";
+ Logstream::logLockless( ss.str() );
+ 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" ) );
+ void* data = (void*)((long long)c.getNumber( obj , "y" ) );
+ 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;
+ try {
+ out = func( a, data );
+ }
+ catch ( std::exception& e ) {
+ JS_ReportError( cx , e.what() );
+ return JS_FALSE;
+ }
+
+ 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 } ,
+ { "UUID", &_UUID, 0, 0, 0 } ,
+ { "MD5", &_MD5, 0, 0, 0 } ,
+ { "HexData", &_HexData, 0, 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;
+ }
+
+ Convertor c(cx);
+
+ if ( argv[0] == JSVAL_VOID || argv[0] == JSVAL_NULL ) {
+ *rval = c.toval( 0.0 );
+ return JS_TRUE;
+ }
+
+ JSObject * o = JSVAL_TO_OBJECT( argv[0] );
+
+ double size = 0;
+
+ if ( JS_InstanceOf( cx , o , &bson_ro_class , 0 ) ||
+ JS_InstanceOf( cx , o , &bson_class , 0 ) ) {
+ BSONHolder * h = GETHOLDER( cx , o );
+ if ( h ) {
+ size = h->_obj.objsize();
+ }
+ }
+ else {
+ BSONObj temp = c.toObject( o );
+ size = temp.objsize();
+ }
+
+ *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;
+ try {
+ val = c.toval( e );
+ }
+ catch ( InvalidUTF8Exception& ) {
+ JS_LeaveLocalRootScope( cx );
+ JS_ReportError( cx , "invalid utf8" );
+ return JS_FALSE;
+ }
+
+ 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
+ // basically right now whenever a sub object is read we mark whole obj as possibly 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(64L * 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 ) {
+ // This is expected to reclaim _global as well.
+ 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( const 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() );
+ }
+
+ }
+
+ bool hasOutOfMemoryException() {
+ string err = getError();
+ return err.find("out of memory") != string::npos;
+ }
+
+ 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" );
+ }
+ }
+
+ void setFunction( const char *field , const char * code ) {
+ smlock;
+ jsval v = OBJECT_TO_JSVAL(JS_GetFunctionObject(_convertor->compileFunction(code)));
+ JS_SetProperty( _context , _global , field , &v );
+ }
+
+ void rename( const char * from , const char * to ) {
+ smlock;
+ jsval v;
+ assert( JS_GetProperty( _context , _global , from , &v ) );
+ assert( JS_SetProperty( _context , _global , to , &v ) );
+ v = JSVAL_VOID;
+ assert( JS_SetProperty( _context , _global , from , &v ) );
+ }
+
+ // ---- 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;
+ };
+
+ // should not generate exceptions, as those can be caught in
+ // javascript code; returning false without an exception exits
+ // immediately
+ static JSBool _interrupt( JSContext *cx ) {
+ TimeoutSpec &spec = *(TimeoutSpec *)( JS_GetContextPrivate( cx ) );
+ if ( ++spec.count % 1000 != 0 )
+ return JS_TRUE;
+ const char * interrupt = ScriptEngine::checkInterrupt();
+ if ( interrupt && interrupt[ 0 ] ) {
+ return JS_FALSE;
+ }
+ if ( spec.timeout.ticks() == 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;
+ }
+ return JS_FALSE;
+
+ }
+
+ static JSBool interrupt( JSContext *cx, JSScript *script ) {
+ return _interrupt( cx );
+ }
+
+ void installInterrupt( int timeoutMs ) {
+ if ( timeoutMs != 0 || ScriptEngine::haveCheckInterruptCallback() ) {
+ 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, _interrupt );
+#else
+ JS_SetBranchCallback( _context, interrupt );
+#endif
+ }
+ }
+
+ void uninstallInterrupt( int timeoutMs ) {
+ if ( timeoutMs != 0 || ScriptEngine::haveCheckInterruptCallback() ) {
+#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 StringData& code , const string& name = "(anon)" , bool printResult = false , bool reportError = true , bool assertOnError = true, int timeoutMs = 0 ) {
+ smlock;
+ precall();
+
+ jsval ret = JSVAL_VOID;
+
+ installInterrupt( timeoutMs );
+ JSBool worked = JS_EvaluateScript( _context , _global , code.data() , code.size() , name.c_str() , 1 , &ret );
+ uninstallInterrupt( timeoutMs );
+
+ if ( ! worked && _error.size() == 0 ) {
+ jsval v;
+ if ( JS_GetPendingException( _context , &v ) ) {
+ _error = _convertor->toString( v );
+ if ( reportError )
+ cout << _error << endl;
+ }
+ }
+
+ uassert( 10228 , str::stream() << name + " exec failed: " << _error , worked || ! assertOnError );
+
+ 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, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) {
+ smlock;
+ precall();
+
+ assert( JS_EnterLocalRootScope( _context ) );
+
+ int nargs = args ? args->nFields() : 0;
+ 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 ) {
+ _convertor->setProperty( _global , "args" , JSVAL_NULL );
+ }
+ else {
+ setObject( "args" , *args , true ); // this is for backwards compatability
+ }
+
+ JS_LeaveLocalRootScope( _context );
+
+ installInterrupt( timeoutMs );
+ jsval rval;
+ setThis(recv);
+ JSBool ret = JS_CallFunction( _context , _this ? _this : _global , func , nargs , smargsPtr.get() , &rval );
+ setThis(0);
+ uninstallInterrupt( timeoutMs );
+
+ if ( !ret ) {
+ return -3;
+ }
+
+ if ( ! ignoreReturn ) {
+ assert( JS_SetProperty( _context , _global , "return" , &rval ) );
+ }
+
+ return 0;
+ }
+
+ int invoke( ScriptingFunction funcAddr , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = 0, bool readOnlyArgs = false, bool readOnlyRecv = false ) {
+ return invoke( (JSFunction*)funcAddr , args , recv, timeoutMs , ignoreReturn, readOnlyArgs, readOnlyRecv);
+ }
+
+ void gotError( string s ) {
+ _error = s;
+ }
+
+ string getError() {
+ return _error;
+ }
+
+ void injectNative( const char *field, NativeFunction func, void* data ) {
+ smlock;
+ string name = field;
+ _convertor->setProperty( _global , (name + "_").c_str() , _convertor->toval( (double)(long long)func ) );
+
+ stringstream code;
+ if (data) {
+ _convertor->setProperty( _global , (name + "_data_").c_str() , _convertor->toval( (double)(long long)data ) );
+ code << field << "_" << " = { x : " << field << "_ , y: " << field << "_data_ }; ";
+ } else {
+ code << field << "_" << " = { x : " << field << "_ }; ";
+ }
+ code << field << " = function(){ return nativeHelper.apply( " << field << "_ , arguments ); }";
+ exec( code.str() );
+ }
+
+ virtual void gc() {
+ smlock;
+ 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;
+
+ };
+
+ /* used to make the logging not overly chatty in the mongo shell. */
+ extern bool isShell;
+
+ void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ) {
+ stringstream ss;
+ if( !isShell )
+ ss << "JS Error: ";
+ ss << message;
+
+ if ( report && report->filename ) {
+ ss << " " << report->filename << ":" << report->lineno;
+ }
+
+ tlog() << 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 << "load [" << filename << "]" << endl;
+
+ if ( ! s->execFile( filename , false , true , false ) ) {
+ JS_ReportError( cx , ((string)"error loading js 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/src/mongo/scripting/engine_spidermonkey.h b/src/mongo/scripting/engine_spidermonkey.h
new file mode 100644
index 00000000000..9fd430d853d
--- /dev/null
+++ b/src/mongo/scripting/engine_spidermonkey.h
@@ -0,0 +1,105 @@
+// 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
+
+#ifdef WIN32
+#include "jstypes.h"
+#undef JS_PUBLIC_API
+#undef JS_PUBLIC_DATA
+#define JS_PUBLIC_API(t) t __cdecl
+#define JS_PUBLIC_DATA(t) t
+#endif
+
+#include "jsapi.h"
+#include "jsobj.h"
+#include "jsdate.h"
+#include "jsregexp.h"
+
+// END inc hacking
+
+// -- SM 1.6 hacks ---
+#ifndef JSCLASS_GLOBAL_FLAGS
+#error old version of spider monkey ( probably 1.6 ) you should upgrade to at least 1.7
+#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 numberlong_class;
+ extern JSClass numberint_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 );
+
+ // JS private data must be 2byte aligned, so we use a holder to refer to an unaligned pointer.
+ struct BinDataHolder {
+ BinDataHolder( const char *c, int copyLen = -1 ) :
+ c_( const_cast< char * >( c ) ),
+ iFree_( copyLen != -1 ) {
+ if ( copyLen != -1 ) {
+ c_ = (char*)malloc( copyLen );
+ memcpy( c_, c, copyLen );
+ }
+ }
+ ~BinDataHolder() {
+ if ( iFree_ )
+ free( c_ );
+ }
+ char *c_;
+ bool iFree_;
+ };
+}
diff --git a/src/mongo/scripting/engine_v8.cpp b/src/mongo/scripting/engine_v8.cpp
new file mode 100644
index 00000000000..53539c2f75c
--- /dev/null
+++ b/src/mongo/scripting/engine_v8.cpp
@@ -0,0 +1,1634 @@
+//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.
+ */
+
+#if defined(_WIN32)
+/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with
+ our usage of boost */
+#include "boost/cstdint.hpp"
+using namespace boost;
+#define V8STDINT_H_
+#endif
+
+#include "engine_v8.h"
+
+#include "v8_wrapper.h"
+#include "v8_utils.h"
+#include "v8_db.h"
+
+#define V8_SIMPLE_HEADER v8::Isolate::Scope iscope(_isolate); v8::Locker l(_isolate); HandleScope handle_scope; Context::Scope context_scope( _context );
+
+namespace mongo {
+
+ // guarded by v8 mutex
+ map< unsigned, int > __interruptSpecToThreadId;
+ map< unsigned, v8::Isolate* > __interruptSpecToIsolate;
+
+ /**
+ * Unwraps a BSONObj from the JS wrapper
+ */
+ static BSONObj* unwrapBSONObj(const Handle<v8::Object>& obj) {
+ Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0));
+ if (field.IsEmpty() || !field->IsExternal())
+ return 0;
+ void* ptr = field->Value();
+ return (BSONObj*)ptr;
+ }
+
+ static void weakRefBSONCallback(v8::Persistent<v8::Value> p, void* scope) {
+ // should we lock here? no idea, and no doc from v8 of course
+ HandleScope handle_scope;
+ if (!p.IsNearDeath())
+ return;
+ Handle<External> field = Handle<External>::Cast(p->ToObject()->GetInternalField(0));
+ BSONObj* data = (BSONObj*) field->Value();
+ delete data;
+ p.Dispose();
+ }
+
+ Persistent<v8::Object> V8Scope::wrapBSONObject(Local<v8::Object> obj, BSONObj* data) {
+ obj->SetInternalField(0, v8::External::New(data));
+ Persistent<v8::Object> p = Persistent<v8::Object>::New(obj);
+ p.MakeWeak(this, weakRefBSONCallback);
+ return p;
+ }
+
+ static void weakRefArrayCallback(v8::Persistent<v8::Value> p, void* scope) {
+ // should we lock here? no idea, and no doc from v8 of course
+ HandleScope handle_scope;
+ if (!p.IsNearDeath())
+ return;
+ Handle<External> field = Handle<External>::Cast(p->ToObject()->GetInternalField(0));
+ char* data = (char*) field->Value();
+ delete [] data;
+ p.Dispose();
+ }
+
+ Persistent<v8::Object> V8Scope::wrapArrayObject(Local<v8::Object> obj, char* data) {
+ obj->SetInternalField(0, v8::External::New(data));
+ Persistent<v8::Object> p = Persistent<v8::Object>::New(obj);
+ p.MakeWeak(this, weakRefArrayCallback);
+ return p;
+ }
+
+ static Handle<v8::Value> namedGet(Local<v8::String> name, const v8::AccessorInfo &info) {
+ // all properties should be set, otherwise means builtin or deleted
+ if (!(info.This()->HasRealNamedProperty(name)))
+ return v8::Handle<v8::Value>();
+
+ Handle<v8::Value> val = info.This()->GetRealNamedProperty(name);
+ if (!val->IsUndefined()) {
+ // value already cached
+ return val;
+ }
+
+ string key = toSTLString(name);
+ BSONObj *obj = unwrapBSONObj(info.Holder());
+ BSONElement elmt = obj->getField(key.c_str());
+ if (elmt.eoo())
+ return Handle<Value>();
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ val = scope->mongoToV8Element(elmt, false);
+ info.This()->ForceSet(name, val);
+
+ if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) {
+ // if accessing a subobject, it may get modified and base obj would not know
+ // have to set base as modified, which means some optim is lost
+ info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true));
+ }
+ return val;
+ }
+
+ static Handle<v8::Value> namedGetRO(Local<v8::String> name, const v8::AccessorInfo &info) {
+ string key = toSTLString(name);
+ BSONObj *obj = unwrapBSONObj(info.Holder());
+ BSONElement elmt = obj->getField(key.c_str());
+ if (elmt.eoo())
+ return Handle<Value>();
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ Handle<v8::Value> val = scope->mongoToV8Element(elmt, true);
+ return val;
+ }
+
+ static Handle<v8::Value> namedSet(Local<v8::String> name, Local<v8::Value> value_obj, const v8::AccessorInfo& info) {
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true));
+ return Handle<Value>();
+ }
+
+ static Handle<v8::Array> namedEnumerator(const AccessorInfo &info) {
+ BSONObj *obj = unwrapBSONObj(info.Holder());
+ Handle<v8::Array> arr = Handle<v8::Array>(v8::Array::New(obj->nFields()));
+ int i = 0;
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ // note here that if keys are parseable number, v8 will access them using index
+ for ( BSONObjIterator it(*obj); it.more(); ++i) {
+ const BSONElement& f = it.next();
+// arr->Set(i, v8::String::NewExternal(new ExternalString(f.fieldName())));
+ Handle<v8::String> name = scope->getV8Str(f.fieldName());
+ arr->Set(i, name);
+ }
+ return arr;
+ }
+
+ Handle<Boolean> namedDelete( Local<v8::String> property, const AccessorInfo& info ) {
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true));
+ return Handle<Boolean>();
+ }
+
+// v8::Handle<v8::Integer> namedQuery(Local<v8::String> property, const AccessorInfo& info) {
+// string key = ToString(property);
+// return v8::Integer::New(None);
+// }
+
+ static Handle<v8::Value> indexedGet(uint32_t index, const v8::AccessorInfo &info) {
+ // all properties should be set, otherwise means builtin or deleted
+ if (!(info.This()->HasRealIndexedProperty(index)))
+ return v8::Handle<v8::Value>();
+
+ StringBuilder ss;
+ ss << index;
+ string key = ss.str();
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ // cannot get v8 to properly cache the indexed val in the js object
+// Handle<v8::String> name = scope->getV8Str(key);
+// // v8 API really confusing here, must check existence on index, but then fetch with name
+// if (info.This()->HasRealIndexedProperty(index)) {
+// Handle<v8::Value> val = info.This()->GetRealNamedProperty(name);
+// if (!val.IsEmpty() && !val->IsNull())
+// return val;
+// }
+ BSONObj *obj = unwrapBSONObj(info.Holder());
+ BSONElement elmt = obj->getField(key);
+ if (elmt.eoo())
+ return Handle<Value>();
+ Handle<Value> val = scope->mongoToV8Element(elmt, false);
+// info.This()->ForceSet(name, val);
+
+ if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) {
+ // if accessing a subobject, it may get modified and base obj would not know
+ // have to set base as modified, which means some optim is lost
+ info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true));
+ }
+ return val;
+ }
+
+ Handle<Boolean> indexedDelete( uint32_t index, const AccessorInfo& info ) {
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true));
+ return Handle<Boolean>();
+ }
+
+ static Handle<v8::Value> indexedGetRO(uint32_t index, const v8::AccessorInfo &info) {
+ StringBuilder ss;
+ ss << index;
+ string key = ss.str();
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ // cannot get v8 to properly cache the indexed val in the js object
+// Handle<v8::String> name = scope->getV8Str(key);
+// // v8 API really confusing here, must check existence on index, but then fetch with name
+// if (info.This()->HasRealIndexedProperty(index)) {
+// Handle<v8::Value> val = info.This()->GetRealNamedProperty(name);
+// if (!val.IsEmpty() && !val->IsNull())
+// return val;
+// }
+ BSONObj *obj = unwrapBSONObj(info.Holder());
+ BSONElement elmt = obj->getField(key);
+ if (elmt.eoo())
+ return Handle<Value>();
+ Handle<Value> val = scope->mongoToV8Element(elmt, true);
+// info.This()->ForceSet(name, val);
+ return val;
+ }
+
+ static Handle<v8::Value> indexedSet(uint32_t index, Local<v8::Value> value_obj, const v8::AccessorInfo& info) {
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true));
+ return Handle<Value>();
+ }
+
+// static Handle<v8::Array> indexedEnumerator(const AccessorInfo &info) {
+// BSONObj *obj = unwrapBSONObj(info.Holder());
+// Handle<v8::Array> arr = Handle<v8::Array>(v8::Array::New(obj->nFields()));
+// Local< External > scp = External::Cast( *info.Data() );
+// V8Scope* scope = (V8Scope*)(scp->Value());
+// int i = 0;
+// for ( BSONObjIterator it(*obj); it.more(); ++i) {
+// const BSONElement& f = it.next();
+//// arr->Set(i, v8::String::NewExternal(new ExternalString(f.fieldName())));
+// arr->Set(i, scope->getV8Str(f.fieldName()));
+// }
+// return arr;
+// }
+
+ Handle<Value> NamedReadOnlySet( Local<v8::String> property, Local<Value> value, const AccessorInfo& info ) {
+ string key = toSTLString(property);
+ cout << "cannot write property " << key << " to read-only object" << endl;
+ return value;
+ }
+
+ Handle<Boolean> NamedReadOnlyDelete( Local<v8::String> property, const AccessorInfo& info ) {
+ string key = toSTLString(property);
+ cout << "cannot delete property " << key << " from read-only object" << endl;
+ return Boolean::New( false );
+ }
+
+ Handle<Value> IndexedReadOnlySet( uint32_t index, Local<Value> value, const AccessorInfo& info ) {
+ cout << "cannot write property " << index << " to read-only array" << endl;
+ return value;
+ }
+
+ Handle<Boolean> IndexedReadOnlyDelete( uint32_t index, const AccessorInfo& info ) {
+ cout << "cannot delete property " << index << " from read-only array" << endl;
+ return Boolean::New( false );
+ }
+
+ // --- engine ---
+
+// void fatalHandler(const char* s1, const char* s2) {
+// cout << "Fatal handler " << s1 << " " << s2;
+// }
+
+ V8ScriptEngine::V8ScriptEngine() {
+ v8::V8::Initialize();
+ v8::Locker l;
+// v8::Locker::StartPreemption( 10 );
+
+ int K = 1024;
+ v8::ResourceConstraints rc;
+ rc.set_max_young_space_size(4 * K * K);
+ rc.set_max_old_space_size(64 * K * K);
+ v8::SetResourceConstraints(&rc);
+// v8::V8::IgnoreOutOfMemoryException();
+// v8::V8::SetFatalErrorHandler(fatalHandler);
+ }
+
+ V8ScriptEngine::~V8ScriptEngine() {
+ }
+
+ void ScriptEngine::setup() {
+ if ( !globalScriptEngine ) {
+ globalScriptEngine = new V8ScriptEngine();
+ }
+ }
+
+ void V8ScriptEngine::interrupt( unsigned opSpec ) {
+ v8::Locker l;
+ v8Locks::InterruptLock il;
+ if ( __interruptSpecToThreadId.count( opSpec ) ) {
+ int thread = __interruptSpecToThreadId[ opSpec ];
+ if ( thread == -2 || thread == -3) {
+ // just mark as interrupted
+ __interruptSpecToThreadId[ opSpec ] = -3;
+ return;
+ }
+
+ V8::TerminateExecution( __interruptSpecToIsolate[ opSpec ] );
+ }
+ }
+
+ void V8ScriptEngine::interruptAll() {
+ v8::Locker l;
+ v8Locks::InterruptLock il;
+ vector< Isolate* > toKill; // v8 mutex could potentially be yielded during the termination call
+
+ for( map< unsigned, Isolate* >::const_iterator i = __interruptSpecToIsolate.begin(); i != __interruptSpecToIsolate.end(); ++i ) {
+ toKill.push_back( i->second );
+ }
+ for( vector< Isolate* >::const_iterator i = toKill.begin(); i != toKill.end(); ++i ) {
+ V8::TerminateExecution( *i );
+ }
+ }
+
+ // --- scope ---
+
+ V8Scope::V8Scope( V8ScriptEngine * engine )
+ : _engine( engine ) ,
+ _connectState( NOT ) {
+
+ _isolate = v8::Isolate::New();
+ v8::Isolate::Scope iscope(_isolate);
+ v8::Locker l(_isolate);
+
+ HandleScope handleScope;
+ _context = Context::New();
+ Context::Scope context_scope( _context );
+ _global = Persistent< v8::Object >::New( _context->Global() );
+ _emptyObj = Persistent< v8::Object >::New( v8::Object::New() );
+
+ // initialize lazy object template
+ lzObjectTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ lzObjectTemplate->SetInternalFieldCount( 1 );
+ lzObjectTemplate->SetNamedPropertyHandler(namedGet, namedSet, 0, namedDelete, 0, v8::External::New(this));
+ lzObjectTemplate->SetIndexedPropertyHandler(indexedGet, indexedSet, 0, indexedDelete, 0, v8::External::New(this));
+
+ roObjectTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ roObjectTemplate->SetInternalFieldCount( 1 );
+ roObjectTemplate->SetNamedPropertyHandler(namedGetRO, NamedReadOnlySet, 0, NamedReadOnlyDelete, namedEnumerator, v8::External::New(this));
+ roObjectTemplate->SetIndexedPropertyHandler(indexedGetRO, IndexedReadOnlySet, 0, IndexedReadOnlyDelete, 0, v8::External::New(this));
+
+ // initialize lazy array template
+ // unfortunately it is not possible to create true v8 array from a template
+ // this means we use an object template and copy methods over
+ // this it creates issues when calling certain methods that check array type
+ lzArrayTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ lzArrayTemplate->SetInternalFieldCount( 1 );
+ lzArrayTemplate->SetIndexedPropertyHandler(indexedGet, 0, 0, 0, 0, v8::External::New(this));
+
+ internalFieldObjects = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ internalFieldObjects->SetInternalFieldCount( 1 );
+
+ V8STR_CONN = getV8Str( "_conn" );
+ V8STR_ID = getV8Str( "_id" );
+ V8STR_LENGTH = getV8Str( "length" );
+ V8STR_LEN = getV8Str( "len" );
+ V8STR_TYPE = getV8Str( "type" );
+ V8STR_ISOBJECTID = getV8Str( "isObjectId" );
+ V8STR_RETURN = getV8Str( "return" );
+ V8STR_ARGS = getV8Str( "args" );
+ V8STR_T = getV8Str( "t" );
+ V8STR_I = getV8Str( "i" );
+ V8STR_EMPTY = getV8Str( "" );
+ V8STR_MINKEY = getV8Str( "$MinKey" );
+ V8STR_MAXKEY = getV8Str( "$MaxKey" );
+ V8STR_NUMBERLONG = getV8Str( "__NumberLong" );
+ V8STR_NUMBERINT = getV8Str( "__NumberInt" );
+ V8STR_DBPTR = getV8Str( "__DBPointer" );
+ V8STR_BINDATA = getV8Str( "__BinData" );
+ V8STR_NATIVE_FUNC = getV8Str( "_native_function" );
+ V8STR_NATIVE_DATA = getV8Str( "_native_data" );
+ V8STR_V8_FUNC = getV8Str( "_v8_function" );
+ V8STR_RO = getV8Str( "_ro" );
+ V8STR_MODIFIED = getV8Str( "_mod" );
+ V8STR_FULLNAME = getV8Str( "_fullName" );
+
+ injectV8Function("print", Print);
+ injectV8Function("version", Version);
+ injectV8Function("load", load);
+
+ _wrapper = Persistent< v8::Function >::New( getObjectWrapperTemplate(this)->GetFunction() );
+
+ injectV8Function("gc", GCV8);
+
+ installDBTypes( this, _global );
+ }
+
+ V8Scope::~V8Scope() {
+ // make sure to disable interrupt, otherwise can get segfault on race condition
+ disableV8Interrupt();
+
+ {
+ V8_SIMPLE_HEADER
+ _wrapper.Dispose();
+ _emptyObj.Dispose();
+ for( unsigned i = 0; i < _funcs.size(); ++i )
+ _funcs[ i ].Dispose();
+ _funcs.clear();
+ _global.Dispose();
+ std::map <string, v8::Persistent <v8::String> >::iterator it = _strCache.begin();
+ std::map <string, v8::Persistent <v8::String> >::iterator end = _strCache.end();
+ while (it != end) {
+ it->second.Dispose();
+ ++it;
+ }
+ lzObjectTemplate.Dispose();
+ lzArrayTemplate.Dispose();
+ roObjectTemplate.Dispose();
+ internalFieldObjects.Dispose();
+ _context.Dispose();
+ }
+
+ _isolate->Dispose();
+ }
+
+ bool V8Scope::hasOutOfMemoryException() {
+ if (!_context.IsEmpty())
+ return _context->HasOutOfMemoryException();
+ return false;
+ }
+
+ /**
+ * JS Callback that will call a c++ function with BSON arguments.
+ */
+ Handle< Value > V8Scope::nativeCallback( V8Scope* scope, const Arguments &args ) {
+ V8Lock l;
+ HandleScope handle_scope;
+ Local< External > f = External::Cast( *args.Callee()->Get( scope->V8STR_NATIVE_FUNC ) );
+ NativeFunction function = (NativeFunction)(f->Value());
+ Local< External > data = External::Cast( *args.Callee()->Get( scope->V8STR_NATIVE_DATA ) );
+ BSONObjBuilder b;
+ for( int i = 0; i < args.Length(); ++i ) {
+ stringstream ss;
+ ss << i;
+ scope->v8ToMongoElement( b, ss.str(), args[ i ] );
+ }
+ BSONObj nativeArgs = b.obj();
+ BSONObj ret;
+ try {
+ ret = function( nativeArgs, data->Value() );
+ }
+ 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( scope->mongoToV8Element( ret.firstElement() ) );
+ }
+
+ Handle< Value > V8Scope::load( V8Scope* scope, const Arguments &args ) {
+ Context::Scope context_scope(scope->_context);
+ for (int i = 0; i < args.Length(); ++i) {
+ std::string filename(toSTLString(args[i]));
+ if (!scope->execFile(filename, false , true , false)) {
+ return v8::ThrowException(v8::String::New((std::string("error loading file: ") + filename).c_str()));
+ }
+ }
+ return v8::True();
+ }
+
+ /**
+ * JS Callback that will call a c++ function with the v8 scope and v8 arguments.
+ * Handles interrupts, exception handling, etc
+ *
+ * The implementation below assumes that SERVER-1816 has been fixed - in
+ * particular, interrupted() must return true if an interrupt was ever
+ * sent; currently that is not the case if a new killop overwrites the data
+ * for an old one
+ */
+ v8::Handle< v8::Value > V8Scope::v8Callback( const v8::Arguments &args ) {
+ Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_v8_function" ) ) );
+ v8Function function = (v8Function)(f->Value());
+ Local< External > scp = External::Cast( *args.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+
+ // originally v8 interrupt where disabled here cause: don't want to have to audit all v8 calls for termination exceptions
+ // but we do need to keep interrupt because much time may be spent here (e.g. sleep)
+ bool paused = scope->pauseV8Interrupt();
+
+ v8::Handle< v8::Value > ret;
+ string exception;
+ try {
+ ret = function( scope, args );
+ }
+ catch( const std::exception &e ) {
+ exception = e.what();
+ }
+ catch( ... ) {
+ exception = "unknown exception";
+ }
+ if (paused) {
+ bool resume = scope->resumeV8Interrupt();
+ if ( !resume || globalScriptEngine->interrupted() ) {
+ v8::V8::TerminateExecution(scope->_isolate);
+ return v8::ThrowException( v8::String::New( "Interruption in V8 native callback" ) );
+ }
+ }
+ if ( !exception.empty() ) {
+ return v8::ThrowException( v8::String::New( exception.c_str() ) );
+ }
+ return ret;
+ }
+
+ // ---- global stuff ----
+
+ void V8Scope::init( const BSONObj * data ) {
+ V8Lock 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( getV8Str( field ) , v8::Number::New( val ) );
+ }
+
+ void V8Scope::setString( const char * field , const char * val ) {
+ V8_SIMPLE_HEADER
+ _global->Set( getV8Str( field ) , v8::String::New( val ) );
+ }
+
+ void V8Scope::setBoolean( const char * field , bool val ) {
+ V8_SIMPLE_HEADER
+ _global->Set( getV8Str( field ) , v8::Boolean::New( val ) );
+ }
+
+ void V8Scope::setElement( const char *field , const BSONElement& e ) {
+ V8_SIMPLE_HEADER
+ _global->Set( getV8Str( 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( getV8Str( field ) , mongoToLZV8( 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;
+ // needs to be explicit NumberInt to use integer
+// 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( getV8Str( 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 -----
+
+ bool hasFunctionIdentifier( const string& code ) {
+ if ( code.size() < 9 || code.find( "function" ) != 0 )
+ return false;
+
+ return code[8] == ' ' || code[8] == '(';
+ }
+
+ Local< v8::Function > V8Scope::__createFunction( const char * raw ) {
+ raw = jsSkipWhiteSpace( raw );
+ string code = raw;
+ if ( !hasFunctionIdentifier( code ) ) {
+ if ( code.find( "\n" ) == string::npos &&
+ ! hasJSReturn( code ) &&
+ ( 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;
+ // this might be time consuming, consider allowing an interrupt
+ 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::setFunction( const char *field , const char * code ) {
+ V8_SIMPLE_HEADER
+ _global->Set( getV8Str( field ) , __createFunction(code) );
+ }
+
+// 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( this, obj , true , false ) );
+// _this = Persistent< v8::Object >::New( _wrapper->NewInstance( 1, argv ) );
+// }
+
+ void V8Scope::rename( const char * from , const char * to ) {
+ V8_SIMPLE_HEADER;
+ Handle<v8::String> f = getV8Str( from );
+ Handle<v8::String> t = getV8Str( to );
+ _global->Set( t , _global->Get( f ) );
+ _global->Set( f , v8::Undefined() );
+ }
+
+ int V8Scope::invoke( ScriptingFunction func , const BSONObj* argsObject, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) {
+ V8_SIMPLE_HEADER
+ Handle<Value> funcValue = _funcs[func-1];
+
+ TryCatch try_catch;
+ int nargs = argsObject ? argsObject->nFields() : 0;
+ 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, readOnlyArgs );
+ }
+ setObject( "args", *argsObject, readOnlyArgs); // for backwards compatibility
+ }
+ else {
+ _global->Set( V8STR_ARGS, v8::Undefined() );
+ }
+ if ( globalScriptEngine->interrupted() ) {
+ stringstream ss;
+ ss << "error in invoke: " << globalScriptEngine->checkInterrupt();
+ _error = ss.str();
+ log() << _error << endl;
+ return 1;
+ }
+ Handle<v8::Object> v8recv;
+ if (recv != 0)
+ v8recv = mongoToLZV8(*recv, false, readOnlyRecv);
+ else
+ v8recv = _global;
+
+ enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable
+ Local<Value> result = ((v8::Function*)(*funcValue))->Call( v8recv , nargs , nargs ? args.get() : 0 );
+ disableV8Interrupt();
+
+ if ( result.IsEmpty() ) {
+ stringstream ss;
+ if ( try_catch.HasCaught() && !try_catch.CanContinue() ) {
+ ss << "error in invoke: " << globalScriptEngine->checkInterrupt();
+ }
+ else {
+ ss << "error in invoke: " << toSTLString( &try_catch );
+ }
+ _error = ss.str();
+ log() << _error << endl;
+ return 1;
+ }
+
+ if ( ! ignoreReturn ) {
+ _global->Set( V8STR_RETURN , result );
+ }
+
+ return 0;
+ }
+
+ bool V8Scope::exec( const StringData& 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 code: " << code << endl;
+ t = 0;
+ }
+ }
+
+ V8_SIMPLE_HEADER
+
+ TryCatch try_catch;
+
+ Handle<Script> script = v8::Script::Compile( v8::String::New( code.data() ) ,
+ 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;
+ }
+
+ if ( globalScriptEngine->interrupted() ) {
+ _error = (string)"exec error: " + globalScriptEngine->checkInterrupt();
+ if ( reportError ) {
+ log() << _error << endl;
+ }
+ if ( assertOnError ) {
+ uassert( 13475 , _error , 0 );
+ }
+ return false;
+ }
+ enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable
+ Handle<v8::Value> result = script->Run();
+ disableV8Interrupt();
+ if ( result.IsEmpty() ) {
+ if ( try_catch.HasCaught() && !try_catch.CanContinue() ) {
+ _error = (string)"exec error: " + globalScriptEngine->checkInterrupt();
+ }
+ else {
+ _error = (string)"exec error: " + toSTLString( &try_catch );
+ }
+ if ( reportError )
+ log() << _error << endl;
+ if ( assertOnError )
+ uassert( 10234 , _error , 0 );
+ return false;
+ }
+
+ _global->Set( getV8Str( "__lastres__" ) , result );
+
+ if ( printResult && ! result->IsUndefined() ) {
+ cout << toSTLString( result ) << endl;
+ }
+
+ return true;
+ }
+
+ void V8Scope::injectNative( const char *field, NativeFunction func, void* data ) {
+ injectNative(field, func, _global, data);
+ }
+
+ void V8Scope::injectNative( const char *field, NativeFunction func, Handle<v8::Object>& obj, void* data ) {
+ V8_SIMPLE_HEADER
+
+ Handle< FunctionTemplate > ft = createV8Function(nativeCallback);
+ ft->Set( this->V8STR_NATIVE_FUNC, External::New( (void*)func ) );
+ ft->Set( this->V8STR_NATIVE_DATA, External::New( data ) );
+ obj->Set( getV8Str( field ), ft->GetFunction() );
+ }
+
+ void V8Scope::injectV8Function( const char *field, v8Function func ) {
+ injectV8Function(field, func, _global);
+ }
+
+ void V8Scope::injectV8Function( const char *field, v8Function func, Handle<v8::Object>& obj ) {
+ V8_SIMPLE_HEADER
+
+ Handle< FunctionTemplate > ft = createV8Function(func);
+ Handle<v8::Function> f = ft->GetFunction();
+ obj->Set( getV8Str( field ), f );
+ }
+
+ void V8Scope::injectV8Function( const char *field, v8Function func, Handle<v8::Template>& t ) {
+ V8_SIMPLE_HEADER
+
+ Handle< FunctionTemplate > ft = createV8Function(func);
+ Handle<v8::Function> f = ft->GetFunction();
+ t->Set( getV8Str( field ), f );
+ }
+
+ Handle<FunctionTemplate> V8Scope::createV8Function( v8Function func ) {
+ Handle< FunctionTemplate > ft = v8::FunctionTemplate::New(v8Callback, External::New( this ));
+ ft->Set( this->V8STR_V8_FUNC, External::New( (void*)func ) );
+ return ft;
+ }
+
+ void V8Scope::gc() {
+ cout << "in gc" << endl;
+ V8Lock l;
+ V8::LowMemoryNotification();
+ }
+
+ // ----- 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( getV8Str( "Mongo" ) , getMongoFunctionTemplate( this, true )->GetFunction() );
+ execCoreFiles();
+ 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( this, _global, _context );
+ _global->Set( getV8Str( "Mongo" ) , getMongoFunctionTemplate( this, false )->GetFunction() );
+ execCoreFiles();
+ _connectState = EXTERNAL;
+ }
+
+ // ----- internal -----
+
+ void V8Scope::reset() {
+ _startCall();
+ }
+
+ void V8Scope::_startCall() {
+ _error = "";
+ }
+
+ 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 > V8Scope::newId( const OID &id ) {
+ v8::Function * idCons = this->getObjectIdCons();
+ v8::Handle<v8::Value> argv[1];
+ argv[0] = v8::String::New( id.str().c_str() );
+ return idCons->NewInstance( 1 , argv );
+ }
+
+ Local<v8::Object> V8Scope::mongoToV8( const BSONObj& m , bool array, bool readOnly ) {
+
+ Local<v8::Object> o;
+
+ // handle DBRef. needs to come first. isn't it? (metagoto)
+ static string ref = "$ref";
+ if ( ref == m.firstElement().fieldName() ) {
+ const BSONElement& id = m["$id"];
+ if (!id.eoo()) { // there's no check on $id exitence in sm implementation. risky ?
+ v8::Function* dbRef = getNamedCons( "DBRef" );
+ o = dbRef->NewInstance();
+ }
+ }
+
+ Local< v8::ObjectTemplate > readOnlyObjects;
+
+ if ( !o.IsEmpty() ) {
+ readOnly = false;
+ }
+ else if ( array ) {
+ // NOTE Looks like it's impossible to add interceptors to v8 arrays.
+ // so array itself will never be read only, but its values can be
+ 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;
+ Handle<v8::String> name = getV8Str(f.fieldName());
+
+ switch ( f.type() ) {
+
+ case mongo::Code:
+ o->Set( name, newFunction( f.valuestr() ) );
+ break;
+
+ case CodeWScope:
+ if ( !f.codeWScopeObject().isEmpty() )
+ log() << "warning: CodeWScope doesn't transfer to db.eval" << endl;
+ o->Set( name, newFunction( f.codeWScopeCode() ) );
+ break;
+
+ case mongo::String:
+ o->Set( name , 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( name ,
+ idCons->NewInstance( 1 , argv ) );
+ break;
+ }
+
+ case mongo::NumberDouble:
+ case mongo::NumberInt:
+ o->Set( name , v8::Number::New( f.number() ) );
+ break;
+
+// case mongo::NumberInt: {
+// Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance();
+// int val = f.numberInt();
+// v8::Function* numberInt = getNamedCons( "NumberInt" );
+// v8::Handle<v8::Value> argv[1];
+// argv[0] = v8::Int32::New( val );
+// o->Set( name, numberInt->NewInstance( 1, argv ) );
+// break;
+// }
+
+ case mongo::Array:
+ sub = f.embeddedObject();
+ o->Set( name , mongoToV8( sub , true, readOnly ) );
+ break;
+ case mongo::Object:
+ sub = f.embeddedObject();
+ o->Set( name , mongoToLZV8( sub , false, readOnly ) );
+ break;
+
+ case mongo::Date:
+ o->Set( name , v8::Date::New( (double) ((long long)f.date().millis) ));
+ break;
+
+ case mongo::Bool:
+ o->Set( name , v8::Boolean::New( f.boolean() ) );
+ break;
+
+ case mongo::jstNULL:
+ case mongo::Undefined: // duplicate sm behavior
+ o->Set( name , 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( name , 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 );
+ o->Set( name, binData->NewInstance(3, argv) );
+ break;
+ }
+
+ case mongo::Timestamp: {
+ Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance();
+
+ sub->Set( V8STR_T , v8::Number::New( f.timestampTime() ) );
+ sub->Set( V8STR_I , v8::Number::New( f.timestampInc() ) );
+ sub->SetInternalField( 0, v8::Uint32::New( f.type() ) );
+
+ o->Set( name , sub );
+ break;
+ }
+
+ case mongo::NumberLong: {
+ unsigned long long val = f.numberLong();
+ v8::Function* numberLong = getNamedCons( "NumberLong" );
+ double floatApprox = (double)(long long)val;
+ // values above 2^53 are not accurately represented in JS
+ if ( (long long)val == (long long)floatApprox && val < 9007199254740992ULL ) {
+ v8::Handle<v8::Value> argv[1];
+ argv[0] = v8::Number::New( floatApprox );
+ o->Set( name, numberLong->NewInstance( 1, argv ) );
+ }
+ else {
+ v8::Handle<v8::Value> argv[3];
+ argv[0] = v8::Number::New( floatApprox );
+ argv[1] = v8::Integer::New( val >> 32 );
+ argv[2] = v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) );
+ o->Set( name, numberLong->NewInstance(3, argv) );
+ }
+ break;
+ }
+
+ case mongo::MinKey: {
+ Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance();
+ sub->Set( V8STR_MINKEY, v8::Boolean::New( true ) );
+ sub->SetInternalField( 0, v8::Uint32::New( f.type() ) );
+ o->Set( name , sub );
+ break;
+ }
+
+ case mongo::MaxKey: {
+ Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance();
+ sub->Set( V8STR_MAXKEY, v8::Boolean::New( true ) );
+ sub->SetInternalField( 0, v8::Uint32::New( f.type() ) );
+ o->Set( name , sub );
+ break;
+ }
+
+ case mongo::DBRef: {
+ v8::Function* dbPointer = getNamedCons( "DBPointer" );
+ v8::Handle<v8::Value> argv[2];
+ argv[0] = getV8Str( f.dbrefNS() );
+ argv[1] = newId( f.dbrefOID() );
+ o->Set( name, 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;
+ }
+
+ /**
+ * converts a BSONObj to a Lazy V8 object
+ */
+ Handle<v8::Object> V8Scope::mongoToLZV8( const BSONObj& m , bool array, bool readOnly ) {
+ Local<v8::Object> o;
+
+ if (readOnly) {
+ o = roObjectTemplate->NewInstance();
+ o->SetHiddenValue(V8STR_RO, v8::Boolean::New(true));
+ } else {
+ if (array) {
+ o = lzArrayTemplate->NewInstance();
+ o->SetPrototype(v8::Array::New(1)->GetPrototype());
+ o->Set(V8STR_LENGTH, v8::Integer::New(m.nFields()), DontEnum);
+ // o->Set(ARRAY_STRING, v8::Boolean::New(true), DontEnum);
+ } else {
+ o = lzObjectTemplate->NewInstance();
+
+ static string ref = "$ref";
+ if ( ref == m.firstElement().fieldName() ) {
+ const BSONElement& id = m["$id"];
+ if (!id.eoo()) {
+ v8::Function* dbRef = getNamedCons( "DBRef" );
+ o->SetPrototype(dbRef->NewInstance()->GetPrototype());
+ }
+ }
+ }
+
+ // need to set all keys with dummy values, so that order of keys is correct during enumeration
+ // otherwise v8 will list any newly set property in JS before the ones of underlying BSON obj.
+ for (BSONObjIterator it(m); it.more();) {
+ const BSONElement& f = it.next();
+ o->ForceSet(getV8Str(f.fieldName()), v8::Undefined());
+ }
+ }
+
+ BSONObj* own = new BSONObj(m.getOwned());
+// BSONObj* own = new BSONObj(m);
+ Persistent<v8::Object> p = wrapBSONObject(o, own);
+ return p;
+ }
+
+ Handle<v8::Value> V8Scope::mongoToV8Element( const BSONElement &f, bool readOnly ) {
+// 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::NewExternal( new ExternalString( f.valuestr() ));
+ return v8::String::New( f.valuestr() );
+// return getV8Str( f.valuestr() );
+
+ case mongo::jstOID:
+ return newId( f.__oid() );
+
+ case mongo::NumberDouble:
+ case mongo::NumberInt:
+ return v8::Number::New( f.number() );
+
+ case mongo::Array:
+ // for arrays it's better to use non lazy object because:
+ // - the lazy array is not a true v8 array and requires some v8 src change for all methods to work
+ // - it made several tests about 1.5x slower
+ // - most times when an array is accessed, all its values will be used
+ return mongoToV8( f.embeddedObject() , true, readOnly );
+ case mongo::Object:
+ return mongoToLZV8( f.embeddedObject() , false, readOnly);
+
+ case mongo::Date:
+ return v8::Date::New( (double) ((long long)f.date().millis) );
+
+ case mongo::Bool:
+ return v8::Boolean::New( f.boolean() );
+
+ case mongo::EOO:
+ case mongo::jstNULL:
+ case mongo::Undefined: // duplicate sm behavior
+ return v8::Null();
+
+ case mongo::RegEx: {
+ v8::Function * regex = getNamedCons( "RegExp" );
+
+ v8::Handle<v8::Value> argv[2];
+ argv[0] = v8::String::New( f.regex() );
+ argv[1] = v8::String::New( f.regexFlags() );
+
+ return regex->NewInstance( 2 , argv );
+ break;
+ }
+
+ case mongo::BinData: {
+ int len;
+ const char *data = f.binData( len );
+
+ v8::Function* binData = getNamedCons( "BinData" );
+ v8::Handle<v8::Value> argv[3];
+ argv[0] = v8::Number::New( len );
+ argv[1] = v8::Number::New( f.binDataType() );
+ argv[2] = v8::String::New( data, len );
+ return binData->NewInstance( 3, argv );
+ };
+
+ case mongo::Timestamp: {
+ Local<v8::Object> sub = internalFieldObjects->NewInstance();
+
+ sub->Set( V8STR_T , v8::Number::New( f.timestampTime() ) );
+ sub->Set( V8STR_I , v8::Number::New( f.timestampInc() ) );
+ sub->SetInternalField( 0, v8::Uint32::New( f.type() ) );
+
+ return sub;
+ }
+
+ case mongo::NumberLong: {
+ unsigned long long val = f.numberLong();
+ v8::Function* numberLong = getNamedCons( "NumberLong" );
+ // values above 2^53 are not accurately represented in JS
+ if ( (long long)val == (long long)(double)(long long)(val) && val < 9007199254740992ULL ) {
+ v8::Handle<v8::Value> argv[1];
+ argv[0] = v8::Number::New( (double)(long long)( val ) );
+ return numberLong->NewInstance( 1, argv );
+ }
+ else {
+ v8::Handle<v8::Value> argv[3];
+ argv[0] = v8::Number::New( (double)(long long)( val ) );
+ argv[1] = v8::Integer::New( val >> 32 );
+ argv[2] = v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) );
+ return numberLong->NewInstance( 3, argv );
+ }
+ }
+
+// case mongo::NumberInt: {
+// Local<v8::Object> sub = internalFieldObjects->NewInstance();
+// int val = f.numberInt();
+// v8::Function* numberInt = getNamedCons( "NumberInt" );
+// v8::Handle<v8::Value> argv[1];
+// argv[0] = v8::Int32::New(val);
+// return numberInt->NewInstance( 1, argv );
+// }
+
+ case mongo::MinKey: {
+ Local<v8::Object> sub = internalFieldObjects->NewInstance();
+ sub->Set( V8STR_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( V8STR_MAXKEY, v8::Boolean::New( true ) );
+ sub->SetInternalField( 0, v8::Uint32::New( f.type() ) );
+ return sub;
+ }
+
+ case mongo::DBRef: {
+ v8::Function* dbPointer = getNamedCons( "DBPointer" );
+ v8::Handle<v8::Value> argv[2];
+ argv[0] = getV8Str( 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 V8Scope::append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ) {
+ V8_SIMPLE_HEADER
+ Handle<v8::String> v8name = getV8Str(scopeName);
+ Handle<Value> value = _global->Get( v8name );
+ v8ToMongoElement(builder, fieldName, value);
+ }
+
+ void V8Scope::v8ToMongoElement( BSONObjBuilder & b , const string sname , v8::Handle<v8::Value> value , int depth, BSONObj* originalParent ) {
+
+ if ( value->IsString() ) {
+// Handle<v8::String> str = Handle<v8::String>::Cast(value);
+// ExternalString* es = (ExternalString*) (str->GetExternalAsciiStringResource());
+// b.append( sname , es->data() );
+ b.append( sname , toSTLString( value ).c_str() );
+ return;
+ }
+
+ if ( value->IsFunction() ) {
+ b.appendCode( sname , toSTLString( value ) );
+ return;
+ }
+
+ if ( value->IsNumber() ) {
+ double val = value->ToNumber()->Value();
+ // if previous type was integer, keep it
+ int intval = (int)val;
+ if (val == intval && originalParent) {
+ BSONElement elmt = originalParent->getField(sname);
+ if (elmt.type() == mongo::NumberInt) {
+ b.append( sname , intval );
+ return;
+ }
+ }
+
+ b.append( sname , val );
+ return;
+ }
+
+ if ( value->IsArray() ) {
+ BSONObj sub = v8ToMongo( value->ToObject() , depth );
+ b.appendArray( sname , sub );
+ return;
+ }
+
+ if ( value->IsDate() ) {
+ long long dateval = (long long)(v8::Date::Cast( *value )->NumberValue());
+ b.appendDate( sname , Date_t( (unsigned long long) dateval ) );
+ return;
+ }
+
+ if ( value->IsExternal() )
+ return;
+
+ if ( value->IsObject() ) {
+ // The user could potentially modify the fields of these special objects,
+ // wreaking havoc when we attempt to reinterpret them. Not doing any validation
+ // for now...
+ Local< v8::Object > obj = value->ToObject();
+ if ( obj->InternalFieldCount() && obj->GetInternalField( 0 )->IsNumber() ) {
+ switch( obj->GetInternalField( 0 )->ToInt32()->Value() ) { // NOTE Uint32's Value() gave me a linking error, so going with this instead
+ case Timestamp:
+ b.appendTimestamp( sname,
+ Date_t( (unsigned long long)(obj->Get( V8STR_T )->ToNumber()->Value() )),
+ obj->Get( V8STR_I )->ToInt32()->Value() );
+ return;
+ case MinKey:
+ b.appendMinKey( sname );
+ return;
+ case MaxKey:
+ b.appendMaxKey( sname );
+ return;
+ default:
+ assert( "invalid internal field" == 0 );
+ }
+ }
+ string s = toSTLString( value );
+ if ( s.size() && s[0] == '/' ) {
+ s = s.substr( 1 );
+ string r = s.substr( 0 , s.rfind( "/" ) );
+ string o = s.substr( s.rfind( "/" ) + 1 );
+ b.appendRegex( sname , r , o );
+ }
+ else if ( value->ToObject()->GetPrototype()->IsObject() &&
+ value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( V8STR_ISOBJECTID ) ) {
+ OID oid;
+ oid.init( toSTLString( value->ToObject()->Get(getV8Str("str")) ) );
+ b.appendOID( sname , &oid );
+ }
+ else if ( !value->ToObject()->GetHiddenValue( V8STR_NUMBERLONG ).IsEmpty() ) {
+ // TODO might be nice to potentially speed this up with an indexed internal
+ // field, but I don't yet know how to use an ObjectTemplate with a
+ // constructor.
+ v8::Handle< v8::Object > it = value->ToObject();
+ long long val;
+ if ( !it->Has( getV8Str( "top" ) ) ) {
+ val = (long long)( it->Get( getV8Str( "floatApprox" ) )->NumberValue() );
+ }
+ else {
+ val = (long long)
+ ( (unsigned long long)( it->Get( getV8Str( "top" ) )->ToInt32()->Value() ) << 32 ) +
+ (unsigned)( it->Get( getV8Str( "bottom" ) )->ToInt32()->Value() );
+ }
+
+ b.append( sname, val );
+ }
+ else if ( !value->ToObject()->GetHiddenValue( V8STR_NUMBERINT ).IsEmpty() ) {
+ v8::Handle< v8::Object > it = value->ToObject();
+ b.append(sname, it->GetHiddenValue(V8STR_NUMBERINT)->Int32Value());
+ }
+ else if ( !value->ToObject()->GetHiddenValue( V8STR_DBPTR ).IsEmpty() ) {
+ OID oid;
+ Local<Value> theid = value->ToObject()->Get( getV8Str( "id" ) );
+ oid.init( toSTLString( theid->ToObject()->Get(getV8Str("str")) ) );
+ string ns = toSTLString( value->ToObject()->Get( getV8Str( "ns" ) ) );
+ b.appendDBRef( sname, ns, oid );
+ }
+ else if ( !value->ToObject()->GetHiddenValue( V8STR_BINDATA ).IsEmpty() ) {
+ int len = obj->Get( getV8Str( "len" ) )->ToInt32()->Value();
+ Local<External> c = External::Cast( *(obj->GetInternalField( 0 )) );
+ const char* dataArray = (char*)(c->Value());;
+ b.appendBinData( sname,
+ len,
+ mongo::BinDataType( obj->Get( getV8Str( "type" ) )->ToInt32()->Value() ),
+ dataArray );
+ }
+ else {
+ BSONObj sub = v8ToMongo( value->ToObject() , depth );
+ b.append( sname , sub );
+ }
+ return;
+ }
+
+ if ( value->IsBoolean() ) {
+ b.appendBool( sname , value->ToBoolean()->Value() );
+ return;
+ }
+
+ else if ( value->IsUndefined() ) {
+ b.appendUndefined( sname );
+ return;
+ }
+
+ else if ( value->IsNull() ) {
+ b.appendNull( sname );
+ return;
+ }
+
+ cout << "don't know how to convert to mongo field [" << sname << "]\t" << value << endl;
+ }
+
+ BSONObj V8Scope::v8ToMongo( v8::Handle<v8::Object> o , int depth ) {
+ BSONObj* originalBSON = 0;
+ if (o->InternalFieldCount() > 0) {
+ originalBSON = unwrapBSONObj(o);
+
+ if ( !o->GetHiddenValue( V8STR_RO ).IsEmpty() ||
+ (o->HasNamedLookupInterceptor() && o->GetHiddenValue( V8STR_MODIFIED ).IsEmpty()) ) {
+ // object was readonly, use bson as is
+ return *originalBSON;
+ }
+ }
+
+ BSONObjBuilder b;
+
+ if ( depth == 0 ) {
+ if ( o->HasRealNamedProperty( V8STR_ID ) ) {
+ v8ToMongoElement( b , "_id" , o->Get( V8STR_ID ), 0, originalBSON );
+ }
+ }
+
+ Local<v8::Array> names = o->GetPropertyNames();
+ for ( unsigned int i=0; i<names->Length(); i++ ) {
+ v8::Local<v8::String> name = names->Get( i )->ToString();
+
+// if ( o->GetPrototype()->IsObject() &&
+// o->GetPrototype()->ToObject()->HasRealNamedProperty( name ) )
+// continue;
+
+ v8::Local<v8::Value> value = o->Get( name );
+
+ const string sname = toSTLString( name );
+ if ( depth == 0 && sname == "_id" )
+ continue;
+
+ v8ToMongoElement( b , sname , value , depth + 1, originalBSON );
+ }
+ return b.obj();
+ }
+
+ // --- random utils ----
+
+ v8::Function * V8Scope::getNamedCons( const char * name ) {
+ return v8::Function::Cast( *(v8::Context::GetCurrent()->Global()->Get( getV8Str( name ) ) ) );
+ }
+
+ v8::Function * V8Scope::getObjectIdCons() {
+ return getNamedCons( "ObjectId" );
+ }
+
+ Handle<v8::Value> V8Scope::Print(V8Scope* scope, 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();
+ }
+
+ Handle<v8::Value> V8Scope::Version(V8Scope* scope, const Arguments& args) {
+ HandleScope handle_scope;
+ return handle_scope.Close( v8::String::New(v8::V8::GetVersion()) );
+ }
+
+ Handle<v8::Value> V8Scope::GCV8(V8Scope* scope, const Arguments& args) {
+ V8Lock l;
+ v8::V8::LowMemoryNotification();
+ return v8::Undefined();
+ }
+
+ /**
+ * Gets a V8 strings from the scope's cache, creating one if needed
+ */
+ v8::Handle<v8::String> V8Scope::getV8Str(string str) {
+ Persistent<v8::String> ptr = _strCache[str];
+ if (ptr.IsEmpty()) {
+ ptr = Persistent<v8::String>::New(v8::String::New(str.c_str()));
+ _strCache[str] = ptr;
+// cout << "Adding str " + str << endl;
+ }
+// cout << "Returning str " + str << endl;
+ return ptr;
+ }
+
+ // to be called with v8 mutex
+ void V8Scope::enableV8Interrupt() {
+ v8Locks::InterruptLock l;
+ if ( globalScriptEngine->haveGetInterruptSpecCallback() ) {
+ unsigned op = globalScriptEngine->getInterruptSpec();
+ __interruptSpecToThreadId[ op ] = v8::V8::GetCurrentThreadId();
+ __interruptSpecToIsolate[ op ] = _isolate;
+ }
+ }
+
+ // to be called with v8 mutex
+ void V8Scope::disableV8Interrupt() {
+ v8Locks::InterruptLock l;
+ if ( globalScriptEngine->haveGetInterruptSpecCallback() ) {
+ unsigned op = globalScriptEngine->getInterruptSpec();
+ __interruptSpecToIsolate.erase( op );
+ __interruptSpecToThreadId.erase( op );
+ }
+ }
+
+ // to be called with v8 mutex
+ bool V8Scope::pauseV8Interrupt() {
+ v8Locks::InterruptLock l;
+ if ( globalScriptEngine->haveGetInterruptSpecCallback() ) {
+ unsigned op = globalScriptEngine->getInterruptSpec();
+ int thread = __interruptSpecToThreadId[ op ];
+ if ( thread == -2 || thread == -3) {
+ // already paused
+ return false;
+ }
+ __interruptSpecToThreadId[ op ] = -2;
+ }
+ return true;
+ }
+
+ // to be called with v8 mutex
+ bool V8Scope::resumeV8Interrupt() {
+ v8Locks::InterruptLock l;
+ if ( globalScriptEngine->haveGetInterruptSpecCallback() ) {
+ unsigned op = globalScriptEngine->getInterruptSpec();
+ if (__interruptSpecToThreadId[ op ] == -3) {
+ // was interrupted
+ return false;
+ }
+ __interruptSpecToThreadId[ op ] = v8::V8::GetCurrentThreadId();
+ }
+ return true;
+ }
+
+} // namespace mongo
diff --git a/src/mongo/scripting/engine_v8.h b/src/mongo/scripting/engine_v8.h
new file mode 100644
index 00000000000..48a9858c63b
--- /dev/null
+++ b/src/mongo/scripting/engine_v8.h
@@ -0,0 +1,254 @@
+//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;
+
+ typedef Handle< Value > (*v8Function) ( V8Scope* scope, const v8::Arguments& args );
+
+ // Preemption is going to be allowed for the v8 mutex, and some of our v8
+ // usage is not preemption safe. So we are using an additional mutex that
+ // will not be preempted. The V8Lock should be used in place of v8::Locker
+ // except in certain special cases involving interrupts.
+ namespace v8Locks {
+ struct InterruptLock {
+ InterruptLock();
+ ~InterruptLock();
+ };
+
+ // the implementations are quite simple - objects must be destroyed in
+ // reverse of the order created, and should not be shared between threads
+ struct RecursiveLock {
+ RecursiveLock();
+ ~RecursiveLock();
+ bool _unlock;
+ };
+ struct RecursiveUnlock {
+ RecursiveUnlock();
+ ~RecursiveUnlock();
+ bool _lock;
+ };
+ } // namespace v8Locks
+ class V8Lock {
+ public:
+ V8Lock() : _preemptionLock(Isolate::GetCurrent()){}
+
+ private:
+ v8Locks::RecursiveLock _noPreemptionLock;
+ v8::Locker _preemptionLock;
+ };
+ struct V8Unlock {
+ public:
+ V8Unlock() : _preemptionUnlock(Isolate::GetCurrent()){}
+
+ private:
+ v8::Unlocker _preemptionUnlock;
+ v8Locks::RecursiveUnlock _noPreemptionUnlock;
+ };
+
+ class V8Scope : public Scope {
+ public:
+
+ V8Scope( V8ScriptEngine * engine );
+ ~V8Scope();
+
+ virtual void reset();
+ virtual void init( const 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 );
+ Handle<v8::Object> getGlobalObject() { return _global; };
+
+ 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 setFunction( const char *field , const char * code );
+// virtual void setThis( const BSONObj * obj );
+
+ virtual void rename( const char * from , const char * to );
+
+ virtual ScriptingFunction _createFunction( const char * code );
+ Local< v8::Function > __createFunction( const char * code );
+ virtual int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool readOnlyArgs = false, bool readOnlyRecv = false );
+ virtual bool exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs );
+ virtual string getError() { return _error; }
+ virtual bool hasOutOfMemoryException();
+
+ virtual void injectNative( const char *field, NativeFunction func, void* data = 0 );
+ void injectNative( const char *field, NativeFunction func, Handle<v8::Object>& obj, void* data = 0 );
+ void injectV8Function( const char *field, v8Function func );
+ void injectV8Function( const char *field, v8Function func, Handle<v8::Object>& obj );
+ void injectV8Function( const char *field, v8Function func, Handle<v8::Template>& t );
+ Handle<v8::FunctionTemplate> createV8Function( v8Function func );
+
+ void gc();
+
+ Handle< Context > context() const { return _context; }
+
+ v8::Local<v8::Object> mongoToV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false );
+ v8::Handle<v8::Object> mongoToLZV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false );
+ mongo::BSONObj v8ToMongo( v8::Handle<v8::Object> o , int depth = 0 );
+
+ void v8ToMongoElement( BSONObjBuilder & b , const string sname , v8::Handle<v8::Value> value , int depth = 0, BSONObj* originalParent=0 );
+ v8::Handle<v8::Value> mongoToV8Element( const BSONElement &f, bool readOnly = false );
+ virtual void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName );
+
+ v8::Function * getNamedCons( const char * name );
+ v8::Function * getObjectIdCons();
+ Local< v8::Value > newId( const OID &id );
+
+ Persistent<v8::Object> wrapBSONObject(Local<v8::Object> obj, BSONObj* data);
+ Persistent<v8::Object> wrapArrayObject(Local<v8::Object> obj, char* data);
+
+ v8::Handle<v8::String> getV8Str(string str);
+// inline v8::Handle<v8::String> getV8Str(string str) { return v8::String::New(str.c_str()); }
+ inline v8::Handle<v8::String> getLocalV8Str(string str) { return v8::String::New(str.c_str()); }
+
+ v8::Isolate* getIsolate() { return _isolate; }
+ Persistent<Context> getContext() { return _context; }
+
+ // call with v8 mutex:
+ void enableV8Interrupt();
+ void disableV8Interrupt();
+ bool pauseV8Interrupt();
+ bool resumeV8Interrupt();
+
+ Handle<v8::String> V8STR_CONN;
+ Handle<v8::String> V8STR_ID;
+ Handle<v8::String> V8STR_LENGTH;
+ Handle<v8::String> V8STR_LEN;
+ Handle<v8::String> V8STR_TYPE;
+ Handle<v8::String> V8STR_ISOBJECTID;
+ Handle<v8::String> V8STR_NATIVE_FUNC;
+ Handle<v8::String> V8STR_NATIVE_DATA;
+ Handle<v8::String> V8STR_V8_FUNC;
+ Handle<v8::String> V8STR_RETURN;
+ Handle<v8::String> V8STR_ARGS;
+ Handle<v8::String> V8STR_T;
+ Handle<v8::String> V8STR_I;
+ Handle<v8::String> V8STR_EMPTY;
+ Handle<v8::String> V8STR_MINKEY;
+ Handle<v8::String> V8STR_MAXKEY;
+ Handle<v8::String> V8STR_NUMBERLONG;
+ Handle<v8::String> V8STR_NUMBERINT;
+ Handle<v8::String> V8STR_DBPTR;
+ Handle<v8::String> V8STR_BINDATA;
+ Handle<v8::String> V8STR_WRAPPER;
+ Handle<v8::String> V8STR_RO;
+ Handle<v8::String> V8STR_MODIFIED;
+ Handle<v8::String> V8STR_FULLNAME;
+
+ private:
+ void _startCall();
+
+ static Handle< Value > nativeCallback( V8Scope* scope, const Arguments &args );
+ static v8::Handle< v8::Value > v8Callback( const v8::Arguments &args );
+ static Handle< Value > load( V8Scope* scope, const Arguments &args );
+ static Handle< Value > Print(V8Scope* scope, const v8::Arguments& args);
+ static Handle< Value > Version(V8Scope* scope, const v8::Arguments& args);
+ static Handle< Value > GCV8(V8Scope* scope, const v8::Arguments& args);
+
+
+ V8ScriptEngine * _engine;
+
+ Persistent<Context> _context;
+ Persistent<v8::Object> _global;
+
+ string _error;
+ vector< Persistent<Value> > _funcs;
+ v8::Persistent<v8::Object> _emptyObj;
+
+ v8::Persistent<v8::Function> _wrapper;
+
+ enum ConnectState { NOT , LOCAL , EXTERNAL };
+ ConnectState _connectState;
+
+ std::map <string, v8::Persistent <v8::String> > _strCache;
+
+ Persistent<v8::ObjectTemplate> lzObjectTemplate;
+ Persistent<v8::ObjectTemplate> roObjectTemplate;
+ Persistent<v8::ObjectTemplate> lzArrayTemplate;
+ Persistent<v8::ObjectTemplate> internalFieldObjects;
+ v8::Isolate* _isolate;
+ };
+
+ class V8ScriptEngine : public ScriptEngine {
+ public:
+ V8ScriptEngine();
+ virtual ~V8ScriptEngine();
+
+ virtual Scope * createScope() { return new V8Scope( this ); }
+
+ virtual void runTest() {}
+
+ bool utf8Ok() const { return true; }
+
+ class V8UnlockForClient : public Unlocker {
+ V8Unlock u_;
+ };
+
+ virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new V8UnlockForClient ); }
+
+ virtual void interrupt( unsigned opSpec );
+ virtual void interruptAll();
+
+ private:
+ friend class V8Scope;
+ };
+
+ class ExternalString : public v8::String::ExternalAsciiStringResource {
+ public:
+ ExternalString(std::string str) : _data(str) {
+ }
+
+ ~ExternalString() {
+ }
+
+ const char* data () const { return _data.c_str(); }
+ size_t length () const { return _data.length(); }
+ private:
+// string _str;
+// const char* _data;
+ std::string _data;
+// size_t _len;
+ };
+
+ extern ScriptEngine * globalScriptEngine;
+
+}
diff --git a/src/mongo/scripting/sm_db.cpp b/src/mongo/scripting/sm_db.cpp
new file mode 100644
index 00000000000..ea8780fa7c0
--- /dev/null
+++ b/src/mongo/scripting/sm_db.cpp
@@ -0,0 +1,1284 @@
+// 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"
+#include "../util/base64.h"
+#include "../util/text.h"
+#include "../util/hex.h"
+
+#if( BOOST_VERSION >= 104200 )
+//#include <boost/uuid/uuid.hpp>
+#define HAVE_UUID 1
+#else
+;
+#endif
+
+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 );
+ try {
+ *rval = cursor->more() ? JSVAL_TRUE : JSVAL_FALSE;
+ }
+ catch ( std::exception& e ) {
+ JS_ReportError( cx , e.what() );
+ return JS_FALSE;
+ }
+ return JS_TRUE;
+ }
+
+ JSBool internal_cursor_objsLeftInBatch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ DBClientCursor *cursor = getCursor( cx, obj );
+ Convertor c(cx);
+ *rval = c.toval((double) cursor->objsLeftInBatch() );
+ return JS_TRUE;
+ }
+
+ JSBool internal_cursor_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ DBClientCursor *cursor = getCursor( cx, obj );
+
+ BSONObj n;
+
+ try {
+ if ( ! cursor->more() ) {
+ JS_ReportError( cx , "cursor at the end" );
+ return JS_FALSE;
+ }
+
+ n = cursor->next();
+ }
+ catch ( std::exception& e ) {
+ JS_ReportError( cx , e.what() );
+ return JS_FALSE;
+ }
+
+ Convertor c(cx);
+ *rval = c.toval( &n );
+ return JS_TRUE;
+ }
+
+ JSFunctionSpec internal_cursor_functions[] = {
+ { "hasNext" , internal_cursor_hasNext , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "objsLeftInBatch" , internal_cursor_objsLeftInBatch , 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 );
+
+ smuassert( cx , "0 or 1 args to Mongo" , argc <= 1 );
+
+ string host = "127.0.0.1";
+ if ( argc > 0 )
+ host = c.toString( argv[0] );
+
+ string errmsg;
+
+ ConnectionString cs = ConnectionString::parse( host , errmsg );
+ if ( ! cs.isValid() ) {
+ JS_ReportError( cx , errmsg.c_str() );
+ return JS_FALSE;
+ }
+
+ shared_ptr< DBClientWithCommands > conn( cs.connect( errmsg ) );
+ if ( ! conn ) {
+ JS_ReportError( cx , errmsg.c_str() );
+ return JS_FALSE;
+ }
+
+ try{
+ ScriptEngine::runConnectCallback( *conn );
+ }
+ catch( std::exception& e ){
+ // Can happen if connection goes down while we're starting up here
+ // Catch so that we don't get a hard-to-trace segfault from SM
+ JS_ReportError( cx, ((string)( str::stream() << "Error during mongo startup." << causedBy( e ) )).c_str() );
+ 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_auth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ smuassert( cx , "mongo_auth needs 3 args" , argc == 3 );
+ shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj );
+ smuassert( cx , "no connection!" , connHolder && connHolder->get() );
+ DBClientWithCommands *conn = connHolder->get();
+
+ Convertor c( cx );
+
+ string db = c.toString( argv[0] );
+ string username = c.toString( argv[1] );
+ string password = c.toString( argv[2] );
+ string errmsg = "";
+
+ try {
+ if (conn->auth(db, username, password, errmsg)) {
+ return JS_TRUE;
+ }
+ JS_ReportError( cx, errmsg.c_str() );
+ }
+ catch ( ... ) {
+ JS_ReportError( cx , "error doing query: unknown" );
+ }
+ return JS_FALSE;
+ }
+
+ JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ smuassert( cx , "mongo_find needs 7 args" , argc == 7 );
+ shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj );
+ smuassert( cx , "no connection!" , connHolder && connHolder->get() );
+ DBClientWithCommands *conn = connHolder->get();
+
+ Convertor c( cx );
+
+ string 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] );
+ int batchSize = (int) c.toNumber( argv[5] );
+ int options = (int)c.toNumber( argv[6] );
+
+ try {
+
+ auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , options , batchSize );
+ if ( ! cursor.get() ) {
+ log() << "query failed : " << ns << " " << q << " to: " << conn->toString() << endl;
+ JS_ReportError( cx , "error doing query: failed" );
+ return JS_FALSE;
+ }
+ JSObject * mycursor = JS_NewObject( cx , &internal_cursor_class , 0 , 0 );
+ CHECKNEWOBJECT( mycursor, cx, "internal_cursor_class" );
+ 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_update needs at least 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] );
+
+ try {
+ JSObject * insertObj = JSVAL_TO_OBJECT( argv[1] );
+
+ if( JS_IsArrayObject( cx, insertObj ) ){
+ vector<BSONObj> bos;
+
+ jsuint len;
+ JSBool gotLen = JS_GetArrayLength( cx, insertObj, &len );
+ smuassert( cx, "could not get length of array", gotLen );
+
+ for( jsuint i = 0; i < len; i++ ){
+
+ jsval el;
+ JSBool inserted = JS_GetElement( cx, insertObj, i, &el);
+ smuassert( cx, "could not find element in array object", inserted );
+
+ bos.push_back( c.toObject( el ) );
+ }
+
+ conn->insert( ns, bos );
+
+ return JS_TRUE;
+ }
+ else {
+ BSONObj o = c.toObject( argv[1] );
+ // TODO: add _id
+
+ 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 or 3 arguments" , argc == 2 || argc == 3 );
+ 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] );
+ bool justOne = false;
+ if ( argc > 2 )
+ justOne = c.toBoolean( argv[2] );
+
+ try {
+ conn->remove( ns , o , justOne );
+ return JS_TRUE;
+ }
+ catch ( std::exception& e ) {
+ JS_ReportError( cx , e.what() );
+ return JS_FALSE;
+ }
+
+ catch ( ... ) {
+ JS_ReportError( cx , "error doing remove" );
+ return JS_FALSE;
+ }
+
+ }
+
+ JSFunctionSpec mongo_functions[] = {
+ { "auth" , mongo_auth , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "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 );
+ CHECKNEWOBJECT( coll, cx, "doCreateCollection" );
+ 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 );
+ CHECKNEWOBJECT( obj, cx, "object_id_constructor" );
+ *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
+ };
+
+ // dbpointer
+
+ JSBool dbpointer_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ Convertor c( cx );
+ if ( ! JS_InstanceOf( cx , obj , &dbpointer_class , 0 ) ) {
+ obj = JS_NewObject( cx , &dbpointer_class , 0 , 0 );
+ CHECKNEWOBJECT( obj, cx, "dbpointer_constructor" );
+ *rval = OBJECT_TO_JSVAL( obj );
+ }
+
+ 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 ( ! JS_InstanceOf( cx , obj , &dbref_class , 0 ) ) {
+ obj = JS_NewObject( cx , &dbref_class , 0 , 0 );
+ CHECKNEWOBJECT( obj, cx, "dbref_constructor" );
+ *rval = OBJECT_TO_JSVAL( obj );
+ }
+
+ if ( argc == 2 ) {
+ JSObject * o = JS_NewObject( cx , NULL , NULL, NULL );
+ CHECKNEWOBJECT( o, cx, "dbref_constructor" );
+ assert( JS_SetProperty( cx, o , "$ref" , &argv[ 0 ] ) );
+ assert( JS_SetProperty( cx, o , "$id" , &argv[ 1 ] ) );
+ BSONObj bo = c.toObject( o );
+ assert( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( bo.getOwned() ) ) ) );
+ return JS_TRUE;
+ }
+ else {
+ JS_ReportError( cx , "DBRef needs 2 arguments" );
+ assert( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( BSONObj().getOwned() ) ) ) );
+ return JS_FALSE;
+ }
+ }
+
+ JSClass dbref_class = bson_class; // name will be fixed later
+
+ // UUID **************************
+
+#if 0
+ JSBool uuid_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ Convertor c( cx );
+
+ if( argc == 0 ) {
+#if defined(HAVE_UUID)
+ //uuids::uuid
+#else
+#endif
+ JS_ReportError( cx , "UUID needs 1 argument -- UUID(hexstr)" );
+ return JS_FALSE;
+ }
+ else if ( argc == 1 ) {
+
+ string encoded = c.toString( argv[ 0 ] );
+ if( encoded.size() != 32 ) {
+ JS_ReportError( cx, "expect 32 char hex string to UUID()" );
+ return JS_FALSE;
+ }
+
+ char buf[16];
+ for( int i = 0; i < 16; i++ ) {
+ buf[i] = fromHex(encoded.c_str() + i * 2);
+ }
+
+zzz
+
+ assert( JS_SetPrivate( cx, obj, new BinDataHolder( buf, 16 ) ) );
+ c.setProperty( obj, "len", c.toval( (double)16 ) );
+ c.setProperty( obj, "type", c.toval( (double)3 ) );
+
+ return JS_TRUE;
+ }
+ else {
+ JS_ReportError( cx , "UUID needs 1 argument -- UUID(hexstr)" );
+ return JS_FALSE;
+ }
+ }
+
+ JSBool uuid_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ Convertor c(cx);
+ void *holder = JS_GetPrivate( cx, obj );
+ assert( holder );
+ const char *data = ( ( BinDataHolder* )( holder ) )->c_;
+ stringstream ss;
+ ss << "UUID(\"" << toHex(data, 16);
+ ss << "\")";
+ string ret = ss.str();
+ return *rval = c.toval( ret.c_str() );
+ }
+
+ void uuid_finalize( JSContext * cx , JSObject * obj ) {
+ Convertor c(cx);
+ void *holder = JS_GetPrivate( cx, obj );
+ if ( holder ) {
+ delete ( BinDataHolder* )holder;
+ assert( JS_SetPrivate( cx , obj , 0 ) );
+ }
+ }
+
+ JSClass uuid_class = {
+ "UUID" , JSCLASS_HAS_PRIVATE ,
+ JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+ JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, uuid_finalize,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+ };
+
+ JSFunctionSpec uuid_functions[] = {
+ { "toString" , uuid_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { 0 }
+ };
+
+#endif
+
+ // BinData **************************
+
+ JSBool bindata_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ Convertor c( cx );
+ if ( ! JS_InstanceOf( cx , obj , &bindata_class , 0 ) ) {
+ obj = JS_NewObject( cx , &bindata_class , 0 , 0 );
+ CHECKNEWOBJECT( obj, cx, "bindata_constructor" );
+ *rval = OBJECT_TO_JSVAL( obj );
+ }
+
+ if ( argc == 2 ) {
+
+ int type = (int)c.toNumber( argv[ 0 ] );
+ if( type < 0 || type > 255 ) {
+ JS_ReportError( cx , "invalid BinData subtype -- range is 0..255 see bsonspec.org" );
+ return JS_FALSE;
+ }
+ string encoded = c.toString( argv[ 1 ] );
+ string decoded;
+ try {
+ decoded = base64::decode( encoded );
+ }
+ catch(...) {
+ JS_ReportError(cx, "BinData could not decode base64 parameter");
+ return JS_FALSE;
+ }
+
+ assert( JS_SetPrivate( cx, obj, new BinDataHolder( decoded.data(), decoded.length() ) ) );
+ c.setProperty( obj, "len", c.toval( (double)decoded.length() ) );
+ c.setProperty( obj, "type", c.toval( (double)type ) );
+
+ return JS_TRUE;
+ }
+ else {
+ JS_ReportError( cx , "BinData needs 2 arguments -- BinData(subtype,data)" );
+ return JS_FALSE;
+ }
+ }
+
+ JSBool bindata_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ Convertor c(cx);
+ int type = (int)c.getNumber( obj , "type" );
+ int len = (int)c.getNumber( obj, "len" );
+ void *holder = JS_GetPrivate( cx, obj );
+ assert( holder );
+ const char *data = ( ( BinDataHolder* )( holder ) )->c_;
+ stringstream ss;
+ ss << "BinData(" << type << ",\"";
+ base64::encode( ss, (const char *)data, len );
+ ss << "\")";
+ string ret = ss.str();
+ return *rval = c.toval( ret.c_str() );
+ }
+
+ JSBool bindataBase64(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ Convertor c(cx);
+ int len = (int)c.getNumber( obj, "len" );
+ void *holder = JS_GetPrivate( cx, obj );
+ assert( holder );
+ const char *data = ( ( BinDataHolder* )( holder ) )->c_;
+ stringstream ss;
+ base64::encode( ss, (const char *)data, len );
+ string ret = ss.str();
+ return *rval = c.toval( ret.c_str() );
+ }
+
+ JSBool bindataAsHex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ Convertor c(cx);
+ int len = (int)c.getNumber( obj, "len" );
+ void *holder = JS_GetPrivate( cx, obj );
+ assert( holder );
+ const char *data = ( ( BinDataHolder* )( holder ) )->c_;
+ stringstream ss;
+ ss.setf (ios_base::hex , ios_base::basefield);
+ ss.fill ('0');
+ ss.setf (ios_base::right , ios_base::adjustfield);
+ for( int i = 0; i < len; i++ ) {
+ unsigned v = (unsigned char) data[i];
+ ss << setw(2) << v;
+ }
+ string ret = ss.str();
+ return *rval = c.toval( ret.c_str() );
+ }
+
+ void bindata_finalize( JSContext * cx , JSObject * obj ) {
+ Convertor c(cx);
+ void *holder = JS_GetPrivate( cx, obj );
+ if ( holder ) {
+ delete ( BinDataHolder* )holder;
+ assert( JS_SetPrivate( cx , obj , 0 ) );
+ }
+ }
+
+ JSClass bindata_class = {
+ "BinData" , JSCLASS_HAS_PRIVATE ,
+ JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+ JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, bindata_finalize,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+ };
+
+ JSFunctionSpec bindata_functions[] = {
+ { "toString" , bindata_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "hex", bindataAsHex, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "base64", bindataBase64, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { 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 );
+ CHECKNEWOBJECT( array, cx, "map_constructor" );
+
+ 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
+ };
+
+ JSBool timestamp_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ smuassert( cx , "Timestamp needs 0 or 2 args" , argc == 0 || argc == 2 );
+
+ if ( ! JS_InstanceOf( cx , obj , &timestamp_class , 0 ) ) {
+ obj = JS_NewObject( cx , &timestamp_class , 0 , 0 );
+ CHECKNEWOBJECT( obj, cx, "timestamp_constructor" );
+ *rval = OBJECT_TO_JSVAL( obj );
+ }
+
+ Convertor c( cx );
+ if ( argc == 0 ) {
+ c.setProperty( obj, "t", c.toval( 0.0 ) );
+ c.setProperty( obj, "i", c.toval( 0.0 ) );
+ }
+ else {
+ c.setProperty( obj, "t", argv[ 0 ] );
+ c.setProperty( obj, "i", argv[ 1 ] );
+ }
+
+ return JS_TRUE;
+ }
+
+ JSClass numberlong_class = {
+ "NumberLong" , JSCLASS_HAS_PRIVATE ,
+ JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+ JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+ };
+
+ JSBool numberlong_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ smuassert( cx , "NumberLong needs 0 or 1 args" , argc == 0 || argc == 1 );
+
+ if ( ! JS_InstanceOf( cx , obj , &numberlong_class , 0 ) ) {
+ obj = JS_NewObject( cx , &numberlong_class , 0 , 0 );
+ CHECKNEWOBJECT( obj, cx, "numberlong_constructor" );
+ *rval = OBJECT_TO_JSVAL( obj );
+ }
+
+ Convertor c( cx );
+ if ( argc == 0 ) {
+ c.setProperty( obj, "floatApprox", c.toval( 0.0 ) );
+ }
+ else if ( JSVAL_IS_NUMBER( argv[ 0 ] ) ) {
+ c.setProperty( obj, "floatApprox", argv[ 0 ] );
+ }
+ else {
+ string num = c.toString( argv[ 0 ] );
+ //PRINT(num);
+ const char *numStr = num.c_str();
+ long long n;
+ try {
+ n = parseLL( numStr );
+ //PRINT(n);
+ }
+ catch ( const AssertionException & ) {
+ smuassert( cx , "could not convert string to long long" , false );
+ }
+ c.makeLongObj( n, obj );
+ }
+
+ return JS_TRUE;
+ }
+
+ JSBool numberlong_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ Convertor c(cx);
+ return *rval = c.toval( double( c.toNumberLongUnsafe( obj ) ) );
+ }
+
+ JSBool numberlong_tonumber(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ return numberlong_valueof( cx, obj, argc, argv, rval );
+ }
+
+ JSBool numberlong_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ Convertor c(cx);
+ stringstream ss;
+ long long val = c.toNumberLongUnsafe( obj );
+ const long long limit = 2LL << 30;
+
+ if ( val <= -limit || limit <= val )
+ ss << "NumberLong(\"" << val << "\")";
+ else
+ ss << "NumberLong(" << val << ")";
+
+ string ret = ss.str();
+ return *rval = c.toval( ret.c_str() );
+ }
+
+ JSFunctionSpec numberlong_functions[] = {
+ { "valueOf" , numberlong_valueof , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "toNumber" , numberlong_tonumber , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "toString" , numberlong_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { 0 }
+ };
+
+ JSClass numberint_class = {
+ "NumberInt" , JSCLASS_HAS_PRIVATE ,
+ JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+ JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+ };
+
+ JSBool numberint_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ smuassert( cx , "NumberInt needs 0 or 1 args" , argc == 0 || argc == 1 );
+
+ if ( ! JS_InstanceOf( cx , obj , &numberint_class , 0 ) ) {
+ obj = JS_NewObject( cx , &numberint_class , 0 , 0 );
+ CHECKNEWOBJECT( obj, cx, "numberint_constructor" );
+ *rval = OBJECT_TO_JSVAL( obj );
+ }
+
+ Convertor c( cx );
+ if ( argc == 0 ) {
+ c.setProperty( obj, "floatApprox", c.toval( 0.0 ) );
+ }
+ else if ( JSVAL_IS_NUMBER( argv[ 0 ] ) ) {
+ c.setProperty( obj, "floatApprox", argv[ 0 ] );
+ }
+ else {
+ string num = c.toString( argv[ 0 ] );
+ //PRINT(num);
+ const char *numStr = num.c_str();
+ int n;
+ try {
+ n = (int) parseLL( numStr );
+ //PRINT(n);
+ }
+ catch ( const AssertionException & ) {
+ smuassert( cx , "could not convert string to integer" , false );
+ }
+ c.makeIntObj( n, obj );
+ }
+
+ return JS_TRUE;
+ }
+
+ JSBool numberint_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ Convertor c(cx);
+ return *rval = c.toval( double( c.toNumberInt( obj ) ) );
+ }
+
+ JSBool numberint_tonumber(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ return numberint_valueof( cx, obj, argc, argv, rval );
+ }
+
+ JSBool numberint_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ Convertor c(cx);
+ int val = c.toNumberInt( obj );
+ string ret = str::stream() << "NumberInt(" << val << ")";
+ return *rval = c.toval( ret.c_str() );
+ }
+
+ JSFunctionSpec numberint_functions[] = {
+ { "valueOf" , numberint_valueof , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "toNumber" , numberint_tonumber , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "toString" , numberint_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { 0 }
+ };
+
+ 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 {
+ JSObject * temp = JS_NewObject( cx , 0 , 0 , 0 );
+ CHECKNEWOBJECT( temp, cx, "dbquery_constructor" );
+ c.setProperty( obj , "_query" , OBJECT_TO_JSVAL( temp ) );
+ }
+
+ 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 );
+
+ if ( argc > 8 && JSVAL_IS_NUMBER( argv[8] ) )
+ c.setProperty( obj , "_batchSize" , argv[8] );
+ else
+ c.setProperty( obj , "_batchSize" , JSVAL_ZERO );
+
+ if ( argc > 9 && JSVAL_IS_NUMBER( argv[9] ) )
+ c.setProperty( obj , "_options" , argv[9] );
+ else
+ c.setProperty( obj , "_options" , 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 , 0 , 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 , &bindata_class , bindata_constructor , 0 , 0 , bindata_functions , 0 , 0 ) );
+// assert( JS_InitClass( cx , global , 0 , &uuid_class , uuid_constructor , 0 , 0 , uuid_functions , 0 , 0 ) );
+
+ assert( JS_InitClass( cx , global , 0 , &timestamp_class , timestamp_constructor , 0 , 0 , 0 , 0 , 0 ) );
+ assert( JS_InitClass( cx , global , 0 , &numberlong_class , numberlong_constructor , 0 , 0 , numberlong_functions , 0 , 0 ) );
+ assert( JS_InitClass( cx , global , 0 , &numberint_class , numberint_constructor , 0 , 0 , numberint_functions , 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 ) );
+
+ static const char *dbrefName = "DBRef";
+ dbref_class.name = dbrefName;
+ assert( JS_InitClass( cx , global , 0 , &dbref_class , dbref_constructor , 2 , 0 , bson_functions , 0 , 0 ) );
+
+ scope->execCoreFiles();
+ }
+
+ 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 , oid );
+ return true;
+ }
+
+ if ( JS_InstanceOf( c->_context , o , &minkey_class , 0 ) ) {
+ b.appendMinKey( name );
+ return true;
+ }
+
+ if ( JS_InstanceOf( c->_context , o , &maxkey_class , 0 ) ) {
+ b.appendMaxKey( name );
+ return true;
+ }
+
+ if ( JS_InstanceOf( c->_context , o , &timestamp_class , 0 ) ) {
+ b.appendTimestamp( name , (unsigned long long)c->getNumber( o , "t" ) , (unsigned int )c->getNumber( o , "i" ) );
+ return true;
+ }
+
+ if ( JS_InstanceOf( c->_context , o , &numberlong_class , 0 ) ) {
+ b.append( name , c->toNumberLongUnsafe( o ) );
+ return true;
+ }
+
+ if ( JS_InstanceOf( c->_context , o , &numberint_class , 0 ) ) {
+ b.append( name , c->toNumberInt( o ) );
+ return true;
+ }
+
+ if ( JS_InstanceOf( c->_context , o , &dbpointer_class , 0 ) ) {
+ b.appendDBRef( name , c->getString( o , "ns" ) , c->toOID( c->getProperty( o , "id" ) ) );
+ return true;
+ }
+
+ if ( JS_InstanceOf( c->_context , o , &bindata_class , 0 ) ) {
+ void *holder = JS_GetPrivate( c->_context , o );
+ const char *data = ( ( BinDataHolder * )( holder ) )->c_;
+ b.appendBinData( name ,
+ (int)(c->getNumber( o , "len" )) , (BinDataType)((char)(c->getNumber( o , "type" ) ) ) ,
+ data
+ );
+ 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 , 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 , Date_t(d) );
+ return true;
+ }
+#else
+ if ( JS_InstanceOf( c->_context , o, &js_DateClass , 0 ) ) {
+ jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o );
+ long long d2 = (long long)d;
+ b.appendDate( name , Date_t((unsigned long long)d2) );
+ 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->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/src/mongo/scripting/utils.cpp b/src/mongo/scripting/utils.cpp
new file mode 100644
index 00000000000..612b173fdf8
--- /dev/null
+++ b/src/mongo/scripting/utils.cpp
@@ -0,0 +1,77 @@
+// utils.cpp
+/*
+ * Copyright (C) 2010 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "pch.h"
+#include "engine.h"
+#include "../util/md5.hpp"
+#include "../util/version.h"
+
+namespace mongo {
+
+ void installBenchmarkSystem( Scope& scope );
+
+ BSONObj jsmd5( const BSONObj &a, void* data ) {
+ uassert( 10261 , "js md5 needs a string" , a.firstElement().type() == String );
+ const char * s = a.firstElement().valuestrsafe();
+
+ md5digest d;
+ md5_state_t st;
+ md5_init(&st);
+ md5_append( &st , (const md5_byte_t*)s , strlen( s ) );
+ md5_finish(&st, d);
+
+ return BSON( "" << digestToString( d ) );
+ }
+
+ BSONObj JSVersion( const BSONObj& args, void* data ) {
+ cout << "version: " << versionString << endl;
+ if ( strstr( versionString , "+" ) )
+ printGitVersion();
+ return BSONObj();
+ }
+
+
+ BSONObj JSSleep(const mongo::BSONObj &args, void* data) {
+ assert( args.nFields() == 1 );
+ assert( args.firstElement().isNumber() );
+ int ms = int( args.firstElement().number() );
+ {
+ auto_ptr< ScriptEngine::Unlocker > u = globalScriptEngine->newThreadUnlocker();
+ sleepmillis( ms );
+ }
+
+ BSONObjBuilder b;
+ b.appendUndefined( "" );
+ return b.obj();
+ }
+
+ // ---------------------------------
+ // ---- installer --------
+ // ---------------------------------
+
+ void installGlobalUtils( Scope& scope ) {
+ scope.injectNative( "hex_md5" , jsmd5 );
+ scope.injectNative( "version" , JSVersion );
+ scope.injectNative( "sleep" , JSSleep );
+
+ installBenchmarkSystem( scope );
+ }
+
+}
+
+
diff --git a/src/mongo/scripting/v8_db.cpp b/src/mongo/scripting/v8_db.cpp
new file mode 100644
index 00000000000..de419b368d9
--- /dev/null
+++ b/src/mongo/scripting/v8_db.cpp
@@ -0,0 +1,1128 @@
+// v8_db.cpp
+
+/* Copyright 2009 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(_WIN32)
+/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with
+ our usage of boost */
+#include "boost/cstdint.hpp"
+using namespace boost;
+#define V8STDINT_H_
+#endif
+
+#include "v8_wrapper.h"
+#include "v8_utils.h"
+#include "engine_v8.h"
+#include "v8_db.h"
+#include "util/base64.h"
+#include "util/text.h"
+#include "../client/syncclusterconnection.h"
+#include "../s/d_logic.h"
+#include <iostream>
+
+using namespace std;
+using namespace v8;
+
+namespace mongo {
+
+#define DDD(x)
+
+ static v8::Handle<v8::Value> newInstance( v8::Function* f, const v8::Arguments& args ) {
+ // need to translate arguments into an array
+ int argc = args.Length();
+ scoped_array< Handle<Value> > argv( new Handle<Value>[argc] );
+ for (int i = 0; i < argc; ++i) {
+ argv[i] = args[i];
+ }
+ return f->NewInstance(argc, argv.get());
+ }
+
+ v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( V8Scope* scope, bool local ) {
+ v8::Handle<v8::FunctionTemplate> mongo;
+ if ( local ) {
+ mongo = scope->createV8Function(mongoConsLocal);
+ }
+ else {
+ mongo = scope->createV8Function(mongoConsExternal);
+ }
+ mongo->InstanceTemplate()->SetInternalFieldCount( 1 );
+ v8::Handle<v8::Template> proto = mongo->PrototypeTemplate();
+ scope->injectV8Function("find", mongoFind, proto);
+ scope->injectV8Function("insert", mongoInsert, proto);
+ scope->injectV8Function("remove", mongoRemove, proto);
+ scope->injectV8Function("update", mongoUpdate, proto);
+ scope->injectV8Function("auth", mongoAuth, proto);
+
+ v8::Handle<FunctionTemplate> ic = scope->createV8Function(internalCursorCons);
+ ic->InstanceTemplate()->SetInternalFieldCount( 1 );
+ v8::Handle<v8::Template> icproto = ic->PrototypeTemplate();
+ scope->injectV8Function("next", internalCursorNext, icproto);
+ scope->injectV8Function("hasNext", internalCursorHasNext, icproto);
+ scope->injectV8Function("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto);
+ scope->injectV8Function("readOnly", internalCursorReadOnly, icproto);
+ proto->Set( scope->getV8Str( "internalCursor" ) , ic );
+
+ return mongo;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> numberLong = scope->createV8Function(numberLongInit);
+ v8::Local<v8::Template> proto = numberLong->PrototypeTemplate();
+ scope->injectV8Function("valueOf", numberLongValueOf, proto);
+ scope->injectV8Function("toNumber", numberLongToNumber, proto);
+ scope->injectV8Function("toString", numberLongToString, proto);
+
+ return numberLong;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getNumberIntFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> numberInt = scope->createV8Function(numberIntInit);
+ v8::Local<v8::Template> proto = numberInt->PrototypeTemplate();
+ scope->injectV8Function("valueOf", numberIntValueOf, proto);
+ scope->injectV8Function("toNumber", numberIntToNumber, proto);
+ scope->injectV8Function("toString", numberIntToString, proto);
+
+ return numberInt;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getBinDataFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> binData = scope->createV8Function(binDataInit);
+ binData->InstanceTemplate()->SetInternalFieldCount(1);
+ v8::Local<v8::Template> proto = binData->PrototypeTemplate();
+ scope->injectV8Function("toString", binDataToString, proto);
+ scope->injectV8Function("base64", binDataToBase64, proto);
+ scope->injectV8Function("hex", binDataToHex, proto);
+ return binData;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getUUIDFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(uuidInit);
+ return templ;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getMD5FunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(md5Init);
+ return templ;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getHexDataFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(hexDataInit);
+ return templ;
+ }
+
+ v8::Handle<v8::FunctionTemplate> getTimestampFunctionTemplate(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> ts = scope->createV8Function(dbTimestampInit);
+ ts->InstanceTemplate()->SetInternalFieldCount( 1 );
+ return ts;
+ }
+
+// void installDBTypes( V8Scope* scope, Handle<ObjectTemplate>& global ) {
+// v8::Handle<v8::FunctionTemplate> db = scope->createV8Function(dbInit);
+// db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback );
+// global->Set(v8::String::New("DB") , db );
+//
+// v8::Handle<v8::FunctionTemplate> dbCollection = scope->createV8Function(collectionInit);
+// dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback );
+// global->Set(v8::String::New("DBCollection") , dbCollection );
+//
+//
+// v8::Handle<v8::FunctionTemplate> dbQuery = scope->createV8Function(dbQueryInit);
+// dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess );
+// global->Set(v8::String::New("DBQuery") , dbQuery );
+//
+// global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >(scope) );
+//
+// global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >(scope) );
+//
+// global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >(scope) );
+//
+// global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate(scope) );
+//
+// global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate(scope) );
+//
+// global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate(scope) );
+// }
+
+ void installDBTypes( V8Scope* scope, v8::Handle<v8::Object>& global ) {
+ v8::Handle<v8::FunctionTemplate> db = scope->createV8Function(dbInit);
+ db->InstanceTemplate()->SetNamedPropertyHandler( collectionGetter, collectionSetter );
+ global->Set(scope->getV8Str("DB") , db->GetFunction() );
+ v8::Handle<v8::FunctionTemplate> dbCollection = scope->createV8Function(collectionInit);
+ dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionGetter, collectionSetter );
+ global->Set(scope->getV8Str("DBCollection") , dbCollection->GetFunction() );
+
+
+ v8::Handle<v8::FunctionTemplate> dbQuery = scope->createV8Function(dbQueryInit);
+ dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess );
+ global->Set(scope->getV8Str("DBQuery") , dbQuery->GetFunction() );
+
+ scope->injectV8Function("ObjectId", objectIdInit, global);
+ scope->injectV8Function("DBRef", dbRefInit, global);
+ scope->injectV8Function("DBPointer", dbPointerInit, global);
+
+ global->Set( scope->getV8Str("BinData") , getBinDataFunctionTemplate(scope)->GetFunction() );
+ global->Set( scope->getV8Str("UUID") , getUUIDFunctionTemplate(scope)->GetFunction() );
+ global->Set( scope->getV8Str("MD5") , getMD5FunctionTemplate(scope)->GetFunction() );
+ global->Set( scope->getV8Str("HexData") , getHexDataFunctionTemplate(scope)->GetFunction() );
+ global->Set( scope->getV8Str("NumberLong") , getNumberLongFunctionTemplate(scope)->GetFunction() );
+ global->Set( scope->getV8Str("NumberInt") , getNumberIntFunctionTemplate(scope)->GetFunction() );
+ global->Set( scope->getV8Str("Timestamp") , getTimestampFunctionTemplate(scope)->GetFunction() );
+
+ BSONObjBuilder b;
+ b.appendMaxKey( "" );
+ b.appendMinKey( "" );
+ BSONObj o = b.obj();
+ BSONObjIterator i( o );
+ global->Set( scope->getV8Str("MaxKey"), scope->mongoToV8Element( i.next() ) );
+ global->Set( scope->getV8Str("MinKey"), scope->mongoToV8Element( i.next() ) );
+
+ global->Get( scope->getV8Str( "Object" ) )->ToObject()->Set( scope->getV8Str("bsonsize") , scope->createV8Function(bsonsize)->GetFunction() );
+ }
+
+ void destroyConnection( Persistent<Value> self, void* parameter) {
+ delete static_cast<DBClientBase*>(parameter);
+ self.Dispose();
+ self.Clear();
+ }
+
+ Handle<Value> mongoConsExternal(V8Scope* scope, const Arguments& args) {
+
+ char host[255];
+
+ if ( args.Length() > 0 && args[0]->IsString() ) {
+ assert( args[0]->ToString()->Utf8Length() < 250 );
+ args[0]->ToString()->WriteAscii( host );
+ }
+ else {
+ strcpy( host , "127.0.0.1" );
+ }
+
+ string errmsg;
+ ConnectionString cs = ConnectionString::parse( host , errmsg );
+ if ( ! cs.isValid() )
+ return v8::ThrowException( v8::String::New( errmsg.c_str() ) );
+
+
+ DBClientWithCommands * conn;
+ {
+ V8Unlock ul;
+ conn = cs.connect( errmsg );
+ }
+ if ( ! conn )
+ return v8::ThrowException( v8::String::New( errmsg.c_str() ) );
+
+ Persistent<v8::Object> self = Persistent<v8::Object>::New( args.Holder() );
+ self.MakeWeak( conn , destroyConnection );
+
+ {
+ V8Unlock ul;
+ ScriptEngine::runConnectCallback( *conn );
+ }
+
+ args.This()->SetInternalField( 0 , External::New( conn ) );
+ args.This()->Set( scope->getV8Str( "slaveOk" ) , Boolean::New( false ) );
+ args.This()->Set( scope->getV8Str( "host" ) , scope->getV8Str( host ) );
+
+ return v8::Undefined();
+ }
+
+ Handle<Value> mongoConsLocal(V8Scope* scope, const Arguments& args) {
+
+ if ( args.Length() > 0 )
+ return v8::ThrowException( v8::String::New( "local Mongo constructor takes no args" ) );
+
+ DBClientBase * conn;
+ {
+ V8Unlock ul;
+ conn = createDirectClient();
+ }
+
+ Persistent<v8::Object> self = Persistent<v8::Object>::New( args.This() );
+ self.MakeWeak( conn , destroyConnection );
+
+ // NOTE I don't believe the conn object will ever be freed.
+ args.This()->SetInternalField( 0 , External::New( conn ) );
+ args.This()->Set( scope->getV8Str( "slaveOk" ) , Boolean::New( false ) );
+ args.This()->Set( scope->getV8Str( "host" ) , scope->getV8Str( "EMBEDDED" ) );
+
+ return v8::Undefined();
+ }
+
+
+ // ---
+
+#ifdef _WIN32
+#define GETNS char * ns = new char[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns );
+#else
+#define GETNS char ns[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns );
+#endif
+
+ DBClientBase * getConnection( const Arguments& args ) {
+ Local<External> c = External::Cast( *(args.This()->GetInternalField( 0 )) );
+ DBClientBase * conn = (DBClientBase*)(c->Value());
+ assert( conn );
+ return conn;
+ }
+
+ // ---- real methods
+
+ void destroyCursor( Persistent<Value> self, void* parameter) {
+ delete static_cast<mongo::DBClientCursor*>(parameter);
+ self.Dispose();
+ self.Clear();
+ }
+
+ /**
+ 0 - namespace
+ 1 - query
+ 2 - fields
+ 3 - limit
+ 4 - skip
+ */
+ Handle<Value> mongoFind(V8Scope* scope, const Arguments& args) {
+ HandleScope handle_scope;
+
+ jsassert( args.Length() == 7 , "find needs 7 args" );
+ jsassert( args[1]->IsObject() , "needs to be an object" );
+ DBClientBase * conn = getConnection( args );
+ GETNS;
+
+ BSONObj q = scope->v8ToMongo( args[1]->ToObject() );
+ DDD( "query:" << q );
+
+ BSONObj fields;
+ bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0;
+ if ( haveFields )
+ fields = scope->v8ToMongo( args[2]->ToObject() );
+
+ Local<v8::Object> mongo = args.This();
+
+ try {
+ auto_ptr<mongo::DBClientCursor> cursor;
+ int nToReturn = (int)(args[3]->ToNumber()->Value());
+ int nToSkip = (int)(args[4]->ToNumber()->Value());
+ int batchSize = (int)(args[5]->ToNumber()->Value());
+ int options = (int)(args[6]->ToNumber()->Value());
+ {
+ V8Unlock u;
+ cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, options , batchSize );
+ if ( ! cursor.get() )
+ return v8::ThrowException( v8::String::New( "error doing query: failed" ) );
+ }
+ v8::Function * cons = (v8::Function*)( *( mongo->Get( scope->getV8Str( "internalCursor" ) ) ) );
+ if ( !cons ) {
+ // may get here in case of thread termination
+ return v8::ThrowException( v8::String::New( "Could not create a cursor" ) );
+ }
+
+ Persistent<v8::Object> c = Persistent<v8::Object>::New( cons->NewInstance() );
+ c.MakeWeak( cursor.get() , destroyCursor );
+ c->SetInternalField( 0 , External::New( cursor.release() ) );
+ return handle_scope.Close(c);
+ }
+ catch ( ... ) {
+ return v8::ThrowException( v8::String::New( "socket error on query" ) );
+ }
+ }
+
+ v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args) {
+ jsassert( args.Length() == 2 , "insert needs 2 args" );
+ jsassert( args[1]->IsObject() , "have to insert an object" );
+
+ if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() )
+ return v8::ThrowException( v8::String::New( "js db in read only mode" ) );
+
+ DBClientBase * conn = getConnection( args );
+ GETNS;
+
+ v8::Handle<v8::Object> in = args[1]->ToObject();
+
+ if( args[1]->IsArray() ){
+
+ v8::Local<v8::Array> arr = v8::Array::Cast( *args[1] );
+ vector<BSONObj> bos;
+ uint32_t len = arr->Length();
+
+ for( uint32_t i = 0; i < len; i++ ){
+
+ v8::Local<v8::Object> el = arr->CloneElementAt( i );
+
+ // Set ID on the element if necessary
+ if ( ! el->Has( scope->getV8Str( "_id" ) ) ) {
+ v8::Handle<v8::Value> argv[1];
+ el->Set( scope->getV8Str( "_id" ) , scope->getObjectIdCons()->NewInstance( 0 , argv ) );
+ }
+
+ bos.push_back( scope->v8ToMongo( arr->CloneElementAt( i ) ) );
+ }
+
+ DDD( "want to save batch : " << bos.length );
+ try {
+ V8Unlock u;
+ conn->insert( ns , bos );
+ }
+ catch ( ... ) {
+ return v8::ThrowException( v8::String::New( "socket error on bulk insert" ) );
+ }
+
+ }
+ else {
+
+ if ( ! in->Has( scope->getV8Str( "_id" ) ) ) {
+ v8::Handle<v8::Value> argv[1];
+ in->Set( scope->getV8Str( "_id" ) , scope->getObjectIdCons()->NewInstance( 0 , argv ) );
+ }
+
+ BSONObj o = scope->v8ToMongo( in );
+
+ DDD( "want to save : " << o.jsonString() );
+ try {
+ V8Unlock u;
+ conn->insert( ns , o );
+ }
+ catch ( ... ) {
+ return v8::ThrowException( v8::String::New( "socket error on insert" ) );
+ }
+
+ }
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args) {
+ jsassert( args.Length() == 2 || args.Length() == 3 , "remove needs 2 args" );
+ jsassert( args[1]->IsObject() , "have to remove an object template" );
+
+ if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() )
+ return v8::ThrowException( v8::String::New( "js db in read only mode" ) );
+
+ DBClientBase * conn = getConnection( args );
+ GETNS;
+
+ v8::Handle<v8::Object> in = args[1]->ToObject();
+ BSONObj o = scope->v8ToMongo( in );
+
+ bool justOne = false;
+ if ( args.Length() > 2 ) {
+ justOne = args[2]->BooleanValue();
+ }
+
+ DDD( "want to remove : " << o.jsonString() );
+ try {
+ V8Unlock u;
+ conn->remove( ns , o , justOne );
+ }
+ catch ( ... ) {
+ return v8::ThrowException( v8::String::New( "socket error on remove" ) );
+ }
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, const v8::Arguments& args) {
+ jsassert( args.Length() >= 3 , "update needs at least 3 args" );
+ jsassert( args[1]->IsObject() , "1st param to update has to be an object" );
+ jsassert( args[2]->IsObject() , "2nd param to update has to be an object" );
+
+ if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() )
+ return v8::ThrowException( v8::String::New( "js db in read only mode" ) );
+
+ DBClientBase * conn = getConnection( args );
+ GETNS;
+
+ v8::Handle<v8::Object> q = args[1]->ToObject();
+ v8::Handle<v8::Object> o = args[2]->ToObject();
+
+ bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value();
+ bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value();
+
+ try {
+ BSONObj q1 = scope->v8ToMongo( q );
+ BSONObj o1 = scope->v8ToMongo( o );
+ V8Unlock u;
+ conn->update( ns , q1 , o1 , upsert, multi );
+ }
+ catch ( ... ) {
+ return v8::ThrowException( v8::String::New( "socket error on remove" ) );
+ }
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args) {
+ jsassert( args.Length() >= 3 , "update needs at least 3 args" );
+ DBClientBase * conn = getConnection( args );
+ string db = toSTLString(args[0]);
+ string username = toSTLString(args[1]);
+ string password = toSTLString(args[2]);
+ string errmsg = "";
+
+ try {
+ if (conn->auth(db, username, password, errmsg)) {
+ return v8::Boolean::New(true);
+ }
+ } catch ( ... ) {
+ }
+ return v8::ThrowException( v8::String::New( errmsg.c_str() ) );
+ }
+
+// + JSBool mongo_auth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+// + smuassert( cx , "mongo_auth needs 3 args" , argc == 3 );
+// + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj );
+// + smuassert( cx , "no connection!" , connHolder && connHolder->get() );
+// + DBClientWithCommands *conn = connHolder->get();
+// +
+// + Convertor c( cx );
+// +
+// + string db = c.toString( argv[0] );
+// + string username = c.toString( argv[1] );
+// + string password = c.toString( argv[2] );
+// + string errmsg = "";
+// +
+// + try {
+// + if (conn->auth(db, username, password, errmsg)) {
+// + return JS_TRUE;
+// + }
+// + JS_ReportError( cx, errmsg.c_str() );
+// + }
+// + catch ( ... ) {
+// + JS_ReportError( cx , "error doing query: unknown" );
+// + }
+// + return JS_FALSE;
+// + }
+
+
+ // --- cursor ---
+
+ mongo::DBClientCursor * getCursor( const Arguments& args ) {
+ Local<External> c = External::Cast( *(args.This()->GetInternalField( 0 ) ) );
+
+ mongo::DBClientCursor * cursor = (mongo::DBClientCursor*)(c->Value());
+ return cursor;
+ }
+
+ v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args) {
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args) {
+ mongo::DBClientCursor * cursor = getCursor( args );
+ if ( ! cursor )
+ return v8::Undefined();
+ BSONObj o;
+ {
+ V8Unlock u;
+ o = cursor->next();
+ }
+ bool ro = false;
+ if (args.This()->Has(scope->V8STR_RO))
+ ro = args.This()->Get(scope->V8STR_RO)->BooleanValue();
+ return scope->mongoToLZV8( o, false, ro );
+ }
+
+ v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args) {
+ mongo::DBClientCursor * cursor = getCursor( args );
+ if ( ! cursor )
+ return Boolean::New( false );
+ bool ret;
+ {
+ V8Unlock u;
+ ret = cursor->more();
+ }
+ return Boolean::New( ret );
+ }
+
+ v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, const v8::Arguments& args) {
+ mongo::DBClientCursor * cursor = getCursor( args );
+ if ( ! cursor )
+ return v8::Number::New( (double) 0 );
+ int ret;
+ {
+ V8Unlock u;
+ ret = cursor->objsLeftInBatch();
+ }
+ return v8::Number::New( (double) ret );
+ }
+
+ v8::Handle<v8::Value> internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args) {
+ Local<v8::Object> cursor = args.This();
+ cursor->Set(scope->V8STR_RO, v8::Boolean::New(true));
+ return cursor;
+ }
+
+ // --- DB ----
+
+ v8::Handle<v8::Value> dbInit(V8Scope* scope, const v8::Arguments& args) {
+ assert( args.Length() == 2 );
+
+ args.This()->Set( scope->getV8Str( "_mongo" ) , args[0] );
+ args.This()->Set( scope->getV8Str( "_name" ) , args[1] );
+
+ for ( int i=0; i<args.Length(); i++ )
+ assert( ! args[i]->IsUndefined() );
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> collectionInit( V8Scope* scope, const v8::Arguments& args ) {
+ assert( args.Length() == 4 );
+
+ args.This()->Set( scope->getV8Str( "_mongo" ) , args[0] );
+ args.This()->Set( scope->getV8Str( "_db" ) , args[1] );
+ args.This()->Set( scope->getV8Str( "_shortName" ) , args[2] );
+ args.This()->Set( scope->V8STR_FULLNAME , args[3] );
+
+ if ( haveLocalShardingInfo( toSTLString( args[3] ) ) )
+ return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) );
+
+ for ( int i=0; i<args.Length(); i++ )
+ assert( ! args[i]->IsUndefined() );
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> dbQueryInit( V8Scope* scope, const v8::Arguments& args ) {
+
+ v8::Handle<v8::Object> t = args.This();
+
+ assert( args.Length() >= 4 );
+
+ t->Set( scope->getV8Str( "_mongo" ) , args[0] );
+ t->Set( scope->getV8Str( "_db" ) , args[1] );
+ t->Set( scope->getV8Str( "_collection" ) , args[2] );
+ t->Set( scope->getV8Str( "_ns" ) , args[3] );
+
+ if ( args.Length() > 4 && args[4]->IsObject() )
+ t->Set( scope->getV8Str( "_query" ) , args[4] );
+ else
+ t->Set( scope->getV8Str( "_query" ) , v8::Object::New() );
+
+ if ( args.Length() > 5 && args[5]->IsObject() )
+ t->Set( scope->getV8Str( "_fields" ) , args[5] );
+ else
+ t->Set( scope->getV8Str( "_fields" ) , v8::Null() );
+
+
+ if ( args.Length() > 6 && args[6]->IsNumber() )
+ t->Set( scope->getV8Str( "_limit" ) , args[6] );
+ else
+ t->Set( scope->getV8Str( "_limit" ) , Number::New( 0 ) );
+
+ if ( args.Length() > 7 && args[7]->IsNumber() )
+ t->Set( scope->getV8Str( "_skip" ) , args[7] );
+ else
+ t->Set( scope->getV8Str( "_skip" ) , Number::New( 0 ) );
+
+ if ( args.Length() > 8 && args[8]->IsNumber() )
+ t->Set( scope->getV8Str( "_batchSize" ) , args[8] );
+ else
+ t->Set( scope->getV8Str( "_batchSize" ) , Number::New( 0 ) );
+
+ if ( args.Length() > 9 && args[9]->IsNumber() )
+ t->Set( scope->getV8Str( "_options" ) , args[9] );
+ else
+ t->Set( scope->getV8Str( "_options" ) , Number::New( 0 ) );
+
+
+ t->Set( scope->getV8Str( "_cursor" ) , v8::Null() );
+ t->Set( scope->getV8Str( "_numReturned" ) , v8::Number::New(0) );
+ t->Set( scope->getV8Str( "_special" ) , Boolean::New(false) );
+
+ return v8::Undefined();
+ }
+
+ Handle<Value> collectionSetter( Local<v8::String> name, Local<Value> value, const AccessorInfo& info ) {
+ // a collection name cannot be overwritten by a variable
+ string sname = toSTLString( name );
+ if ( sname.length() == 0 || sname[0] == '_' ) {
+ // if starts with '_' we allow overwrite
+ return Handle<Value>();
+ }
+ // dont set
+ return value;
+ }
+
+ v8::Handle<v8::Value> collectionGetter( v8::Local<v8::String> name, const v8::AccessorInfo &info) {
+ DDD( "collectionFallback [" << name << "]" );
+
+ // first look in prototype, may be a function
+ v8::Handle<v8::Value> real = info.This()->GetPrototype()->ToObject()->Get( name );
+ if ( !real->IsUndefined() )
+ return real;
+
+ // 2nd look into real values, may be cached collection object
+ string sname = toSTLString( name );
+ if (info.This()->HasRealNamedProperty(name)) {
+ v8::Local<v8::Value> prop = info.This()->GetRealNamedProperty( name );
+ if (prop->IsObject() && prop->ToObject()->HasRealNamedProperty(v8::String::New("_fullName"))) {
+ // need to check every time that the collection did not get sharded
+ if ( haveLocalShardingInfo( toSTLString( prop->ToObject()->GetRealNamedProperty(v8::String::New("_fullName")) ) ) )
+ return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) );
+ }
+ return prop;
+ } else if ( sname.length() == 0 || sname[0] == '_' ) {
+ // if starts with '_' we dont return collection, one must use getCollection()
+ return v8::Undefined();
+ }
+
+ // no hit, create new collection
+ v8::Handle<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "getCollection" ) );
+ assert( getCollection->IsFunction() );
+
+ TryCatch tryCatch;
+ v8::Function * f = (v8::Function*)(*getCollection);
+ v8::Handle<v8::Value> argv[1];
+ argv[0] = name;
+ v8::Local<v8::Value> coll = f->Call( info.This() , 1 , argv );
+ if (coll.IsEmpty()) {
+ if (tryCatch.HasCaught()) {
+ return v8::ThrowException( tryCatch.Exception() );
+ }
+ return Handle<Value>();
+ }
+
+ // cache collection for reuse, dont enumerate
+ info.This()->ForceSet(name, coll, v8::DontEnum);
+ return coll;
+ }
+
+ v8::Handle<v8::Value> dbQueryIndexAccess( unsigned int index , const v8::AccessorInfo& info ) {
+ v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "arrayAccess" ) );
+ assert( arrayAccess->IsFunction() );
+
+ v8::Function * f = (v8::Function*)(*arrayAccess);
+ v8::Handle<v8::Value> argv[1];
+ argv[0] = v8::Number::New( index );
+
+ return f->Call( info.This() , 1 , argv );
+ }
+
+ v8::Handle<v8::Value> objectIdInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getObjectIdCons();
+ return newInstance(f, args);
+ }
+
+ OID oid;
+
+ if ( args.Length() == 0 ) {
+ oid.init();
+ }
+ else {
+ string s = toSTLString( args[0] );
+ try {
+ Scope::validateObjectIdString( s );
+ }
+ catch ( const MsgAssertionException &m ) {
+ string error = m.toString();
+ return v8::ThrowException( v8::String::New( error.c_str() ) );
+ }
+ oid.init( s );
+ }
+
+ it->Set( scope->getV8Str( "str" ) , v8::String::New( oid.str().c_str() ) );
+
+ return it;
+ }
+
+ v8::Handle<v8::Value> dbRefInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "DBRef" );
+ return newInstance(f, args);
+ }
+
+ if (args.Length() != 2 && args.Length() != 0) {
+ return v8::ThrowException( v8::String::New( "DBRef needs 2 arguments" ) );
+ }
+
+ if ( args.Length() == 2 ) {
+ it->Set( scope->getV8Str( "$ref" ) , args[0] );
+ it->Set( scope->getV8Str( "$id" ) , args[1] );
+ }
+
+ return it;
+ }
+
+ v8::Handle<v8::Value> dbPointerInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "DBPointer" );
+ return newInstance(f, args);
+ }
+
+ if (args.Length() != 2) {
+ return v8::ThrowException( v8::String::New( "DBPointer needs 2 arguments" ) );
+ }
+
+ it->Set( scope->getV8Str( "ns" ) , args[0] );
+ it->Set( scope->getV8Str( "id" ) , args[1] );
+ it->SetHiddenValue( scope->getV8Str( "__DBPointer" ), v8::Number::New( 1 ) );
+
+ return it;
+ }
+
+ v8::Handle<v8::Value> dbTimestampInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "Timestamp" );
+ return newInstance(f, args);
+ }
+
+ if ( args.Length() == 0 ) {
+ it->Set( scope->getV8Str( "t" ) , v8::Number::New( 0 ) );
+ it->Set( scope->getV8Str( "i" ) , v8::Number::New( 0 ) );
+ }
+ else if ( args.Length() == 2 ) {
+ it->Set( scope->getV8Str( "t" ) , args[0] );
+ it->Set( scope->getV8Str( "i" ) , args[1] );
+ }
+ else {
+ return v8::ThrowException( v8::String::New( "Timestamp needs 0 or 2 arguments" ) );
+ }
+
+ it->SetInternalField( 0, v8::Uint32::New( Timestamp ) );
+
+ return it;
+ }
+
+
+ v8::Handle<v8::Value> binDataInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Local<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function* f = scope->getNamedCons( "BinData" );
+ return newInstance(f, args);
+ }
+
+ Handle<Value> type;
+ Handle<Value> len;
+ int rlen;
+ char* data;
+ if (args.Length() == 3) {
+ // 3 args: len, type, data
+ len = args[0];
+ rlen = len->IntegerValue();
+ type = args[1];
+ v8::String::Utf8Value utf( args[ 2 ] );
+ char* tmp = *utf;
+ data = new char[rlen];
+ memcpy(data, tmp, rlen);
+ }
+ else if ( args.Length() == 2 ) {
+ // 2 args: type, base64 string
+ type = args[0];
+ v8::String::Utf8Value utf( args[ 1 ] );
+ string decoded = base64::decode( *utf );
+ const char* tmp = decoded.data();
+ rlen = decoded.length();
+ data = new char[rlen];
+ memcpy(data, tmp, rlen);
+ len = v8::Number::New(rlen);
+// it->Set( scope->getV8Str( "data" ), v8::String::New( decoded.data(), decoded.length() ) );
+ } else if (args.Length() == 0) {
+ // this is called by subclasses that will fill properties
+ return it;
+ } else {
+ return v8::ThrowException( v8::String::New( "BinData needs 2 or 3 arguments" ) );
+ }
+
+ it->Set( scope->getV8Str( "len" ) , len );
+ it->Set( scope->getV8Str( "type" ) , type );
+ it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) );
+ Persistent<v8::Object> res = scope->wrapArrayObject(it, data);
+ return res;
+ }
+
+ v8::Handle<v8::Value> binDataToString( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ int len = it->Get( scope->V8STR_LEN )->Int32Value();
+ int type = it->Get( scope->V8STR_TYPE )->Int32Value();
+ Local<External> c = External::Cast( *(it->GetInternalField( 0 )) );
+ char* data = (char*)(c->Value());
+
+ stringstream ss;
+ ss << "BinData(" << type << ",\"";
+ base64::encode( ss, data, len );
+ ss << "\")";
+ string ret = ss.str();
+ return v8::String::New( ret.c_str() );
+ }
+
+ v8::Handle<v8::Value> binDataToBase64( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ int len = Handle<v8::Number>::Cast(it->Get(scope->V8STR_LEN))->Int32Value();
+ Local<External> c = External::Cast( *(it->GetInternalField( 0 )) );
+ char* data = (char*)(c->Value());
+ stringstream ss;
+ base64::encode( ss, (const char *)data, len );
+ return v8::String::New(ss.str().c_str());
+ }
+
+ v8::Handle<v8::Value> binDataToHex( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ int len = Handle<v8::Number>::Cast(it->Get(scope->V8STR_LEN))->Int32Value();
+ Local<External> c = External::Cast( *(it->GetInternalField( 0 )) );
+ char* data = (char*)(c->Value());
+ stringstream ss;
+ ss.setf (ios_base::hex , ios_base::basefield);
+ ss.fill ('0');
+ ss.setf (ios_base::right , ios_base::adjustfield);
+ for( int i = 0; i < len; i++ ) {
+ unsigned v = (unsigned char) data[i];
+ ss << setw(2) << v;
+ }
+ return v8::String::New(ss.str().c_str());
+ }
+
+ static v8::Handle<v8::Value> hexToBinData( V8Scope* scope, v8::Local<v8::Object> it, int type, string hexstr ) {
+ int len = hexstr.length() / 2;
+ char* data = new char[len];
+ const char* src = hexstr.c_str();
+ for( int i = 0; i < 16; i++ ) {
+ data[i] = fromHex(src + i * 2);
+ }
+
+ it->Set( scope->V8STR_LEN , v8::Number::New(len) );
+ it->Set( scope->V8STR_TYPE , v8::Number::New(type) );
+ it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) );
+ Persistent<v8::Object> res = scope->wrapArrayObject(it, data);
+ return res;
+ }
+
+ v8::Handle<v8::Value> uuidInit( V8Scope* scope, const v8::Arguments& args ) {
+ if (args.Length() != 1) {
+ return v8::ThrowException( v8::String::New( "UUIS needs 1 argument" ) );
+ }
+ v8::String::Utf8Value utf( args[ 0 ] );
+ if( utf.length() != 32 ) {
+ return v8::ThrowException( v8::String::New( "UUIS string must have 32 characters" ) );
+ }
+
+ v8::Function * f = scope->getNamedCons("BinData");
+ Local<v8::Object> it = f->NewInstance();
+ return hexToBinData(scope, it, bdtUUID, *utf);
+ }
+
+ v8::Handle<v8::Value> md5Init( V8Scope* scope, const v8::Arguments& args ) {
+ if (args.Length() != 1) {
+ return v8::ThrowException( v8::String::New( "MD5 needs 1 argument" ) );
+ }
+ v8::String::Utf8Value utf( args[ 0 ] );
+ if( utf.length() != 32 ) {
+ return v8::ThrowException( v8::String::New( "MD5 string must have 32 characters" ) );
+ }
+
+ v8::Function * f = scope->getNamedCons("BinData");
+ Local<v8::Object> it = f->NewInstance();
+ return hexToBinData(scope, it, MD5Type, *utf);
+ }
+
+ v8::Handle<v8::Value> hexDataInit( V8Scope* scope, const v8::Arguments& args ) {
+ if (args.Length() != 2) {
+ return v8::ThrowException( v8::String::New( "HexData needs 2 arguments" ) );
+ }
+ v8::String::Utf8Value utf( args[ 1 ] );
+ v8::Function * f = scope->getNamedCons("BinData");
+ Local<v8::Object> it = f->NewInstance();
+ return hexToBinData(scope, it, args[0]->IntegerValue(), *utf);
+ }
+
+ v8::Handle<v8::Value> numberLongInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "NumberLong" );
+ return newInstance(f, args);
+ }
+
+ if (args.Length() != 0 && args.Length() != 1 && args.Length() != 3) {
+ return v8::ThrowException( v8::String::New( "NumberLong needs 0, 1 or 3 arguments" ) );
+ }
+
+ if ( args.Length() == 0 ) {
+ it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( 0 ) );
+ }
+ else if ( args.Length() == 1 ) {
+ if ( args[ 0 ]->IsNumber() ) {
+ it->Set( scope->getV8Str( "floatApprox" ), args[ 0 ] );
+ }
+ else {
+ v8::String::Utf8Value data( args[ 0 ] );
+ string num = *data;
+ const char *numStr = num.c_str();
+ long long n;
+ try {
+ n = parseLL( numStr );
+ }
+ catch ( const AssertionException & ) {
+ return v8::ThrowException( v8::String::New( "could not convert string to long long" ) );
+ }
+ unsigned long long val = n;
+ // values above 2^53 are not accurately represented in JS
+ if ( (long long)val == (long long)(double)(long long)(val) && val < 9007199254740992ULL ) {
+ it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) );
+ }
+ else {
+ it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) );
+ it->Set( scope->getV8Str( "top" ), v8::Integer::New( val >> 32 ) );
+ it->Set( scope->getV8Str( "bottom" ), v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ) );
+ }
+ }
+ }
+ else {
+ it->Set( scope->getV8Str( "floatApprox" ) , args[0] );
+ it->Set( scope->getV8Str( "top" ) , args[1] );
+ it->Set( scope->getV8Str( "bottom" ) , args[2] );
+ }
+ it->SetHiddenValue( scope->V8STR_NUMBERLONG, v8::Number::New( 1 ) );
+
+ return it;
+ }
+
+ long long numberLongVal( const v8::Handle< v8::Object > &it ) {
+ if ( !it->Has( v8::String::New( "top" ) ) )
+ return (long long)( it->Get( v8::String::New( "floatApprox" ) )->NumberValue() );
+ return
+ (long long)
+ ( (unsigned long long)( it->Get( v8::String::New( "top" ) )->ToInt32()->Value() ) << 32 ) +
+ (unsigned)( it->Get( v8::String::New( "bottom" ) )->ToInt32()->Value() );
+ }
+
+ v8::Handle<v8::Value> numberLongValueOf( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ long long val = numberLongVal( it );
+ return v8::Number::New( double( val ) );
+ }
+
+ v8::Handle<v8::Value> numberLongToNumber( V8Scope* scope, const v8::Arguments& args ) {
+ return numberLongValueOf( scope, args );
+ }
+
+ v8::Handle<v8::Value> numberLongToString( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+
+ stringstream ss;
+ long long val = numberLongVal( it );
+ const long long limit = 2LL << 30;
+
+ if ( val <= -limit || limit <= val )
+ ss << "NumberLong(\"" << val << "\")";
+ else
+ ss << "NumberLong(" << val << ")";
+
+ string ret = ss.str();
+ return v8::String::New( ret.c_str() );
+ }
+
+ v8::Handle<v8::Value> numberIntInit( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) {
+ v8::Function * f = scope->getNamedCons( "NumberInt" );
+ return newInstance(f, args);
+ }
+
+ if (args.Length() != 0 && args.Length() != 1) {
+ return v8::ThrowException( v8::String::New( "NumberInt needs 0, 1 argument" ) );
+ }
+
+ if ( args.Length() == 0 ) {
+ it->SetHiddenValue( scope->V8STR_NUMBERINT, v8::Number::New( 0 ) );
+ }
+ else if ( args.Length() == 1 ) {
+ it->SetHiddenValue( scope->V8STR_NUMBERINT, args[0]->ToInt32() );
+ }
+
+ return it;
+ }
+
+ v8::Handle<v8::Value> numberIntValueOf( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+ int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value();
+ return v8::Number::New( double( val ) );
+ }
+
+ v8::Handle<v8::Value> numberIntToNumber( V8Scope* scope, const v8::Arguments& args ) {
+ return numberIntValueOf( scope, args );
+ }
+
+ v8::Handle<v8::Value> numberIntToString( V8Scope* scope, const v8::Arguments& args ) {
+ v8::Handle<v8::Object> it = args.This();
+
+ stringstream ss;
+ int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value();
+ ss << "NumberInt(" << val << ")";
+
+ string ret = ss.str();
+ return v8::String::New( ret.c_str() );
+ }
+
+ v8::Handle<v8::Value> bsonsize( V8Scope* scope, const v8::Arguments& args ) {
+
+ if ( args.Length() != 1 )
+ return v8::ThrowException( v8::String::New( "bsonsize needs 1 argument" ) );
+
+ if ( args[0]->IsNull() )
+ return v8::Number::New(0);
+
+ if ( ! args[ 0 ]->IsObject() )
+ return v8::ThrowException( v8::String::New( "argument to bsonsize has to be an object" ) );
+
+ return v8::Number::New( scope->v8ToMongo( args[ 0 ]->ToObject() ).objsize() );
+ }
+
+ namespace v8Locks {
+ boost::mutex& __interruptMutex = *( new boost::mutex );
+
+ InterruptLock::InterruptLock() {
+ __interruptMutex.lock();
+ }
+
+ InterruptLock::~InterruptLock() {
+ __interruptMutex.unlock();
+ }
+
+ boost::mutex& __v8Mutex = *( new boost::mutex );
+ ThreadLocalValue< bool > __locked;
+
+ RecursiveLock::RecursiveLock() : _unlock() {
+ if ( !__locked.get() ) {
+ __v8Mutex.lock();
+ __locked.set( true );
+ _unlock = true;
+ }
+ }
+ RecursiveLock::~RecursiveLock() {
+ if ( _unlock ) {
+ __v8Mutex.unlock();
+ __locked.set( false );
+ }
+ }
+
+ RecursiveUnlock::RecursiveUnlock() : _lock() {
+ if ( __locked.get() ) {
+ __v8Mutex.unlock();
+ __locked.set( false );
+ _lock = true;
+ }
+ }
+ RecursiveUnlock::~RecursiveUnlock() {
+ if ( _lock ) {
+ __v8Mutex.lock();
+ __locked.set( true );
+ }
+ }
+ } // namespace v8Locks
+}
diff --git a/src/mongo/scripting/v8_db.h b/src/mongo/scripting/v8_db.h
new file mode 100644
index 00000000000..68946e0ed06
--- /dev/null
+++ b/src/mongo/scripting/v8_db.h
@@ -0,0 +1,94 @@
+// 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 "engine_v8.h"
+#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( V8Scope * scope, bool local );
+// void installDBTypes( V8Scope * scope, v8::Handle<v8::ObjectTemplate>& global );
+ void installDBTypes( V8Scope * scope, v8::Handle<v8::Object>& global );
+
+ // the actual globals
+
+ mongo::DBClientBase * getConnection( const v8::Arguments& args );
+
+ // Mongo members
+ v8::Handle<v8::Value> mongoConsLocal(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> mongoConsExternal(V8Scope* scope, const v8::Arguments& args);
+
+ v8::Handle<v8::Value> mongoFind(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args);
+
+ v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args);
+
+ // DB members
+
+ v8::Handle<v8::Value> dbInit(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> collectionInit(V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> objectIdInit( V8Scope* scope, const v8::Arguments& args );
+
+ v8::Handle<v8::Value> dbRefInit( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> dbPointerInit( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> dbTimestampInit( V8Scope* scope, const v8::Arguments& args );
+
+ v8::Handle<v8::Value> binDataInit( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> binDataToString( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> binDataToBase64( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> binDataToHex( V8Scope* scope, const v8::Arguments& args );
+
+ v8::Handle<v8::Value> uuidInit( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> md5Init( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> hexDataInit( V8Scope* scope, const v8::Arguments& args );
+
+ v8::Handle<v8::Value> numberLongInit( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> numberLongToNumber(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> numberLongValueOf(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> numberLongToString(V8Scope* scope, const v8::Arguments& args);
+
+ v8::Handle<v8::Value> numberIntInit( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> numberIntToNumber(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> numberIntValueOf(V8Scope* scope, const v8::Arguments& args);
+ v8::Handle<v8::Value> numberIntToString(V8Scope* scope, const v8::Arguments& args);
+
+ v8::Handle<v8::Value> dbQueryInit( V8Scope* scope, const v8::Arguments& args );
+ v8::Handle<v8::Value> dbQueryIndexAccess( uint32_t index , const v8::AccessorInfo& info );
+
+ v8::Handle<v8::Value> collectionGetter( v8::Local<v8::String> name, const v8::AccessorInfo &info);
+ v8::Handle<v8::Value> collectionSetter( Local<v8::String> name, Local<Value> value, const AccessorInfo& info );
+
+ v8::Handle<v8::Value> bsonsize( V8Scope* scope, const v8::Arguments& args );
+
+}
+
diff --git a/src/mongo/scripting/v8_utils.cpp b/src/mongo/scripting/v8_utils.cpp
new file mode 100644
index 00000000000..9e7e8072220
--- /dev/null
+++ b/src/mongo/scripting/v8_utils.cpp
@@ -0,0 +1,295 @@
+// 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.
+ */
+
+#if defined(_WIN32)
+/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with
+ our usage of boost */
+#include "boost/cstdint.hpp"
+using namespace boost;
+#define V8STDINT_H_
+#endif
+
+#include "v8_utils.h"
+#include "v8_db.h"
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <vector>
+#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 {
+
+ 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;
+ }
+
+ void ReportException(v8::TryCatch* try_catch) {
+ cout << try_catch << endl;
+ }
+
+ Handle< Context > baseContext_;
+
+ class JSThreadConfig {
+ public:
+ JSThreadConfig( V8Scope* scope, 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" );
+
+ // arguments need to be copied into the isolate, go through bson
+ BSONObjBuilder b;
+ for( int i = 0; i < args.Length(); ++i ) {
+ scope->v8ToMongoElement(b, "arg" + i, args[i]);
+ }
+ args_ = b.obj();
+ }
+
+ ~JSThreadConfig() {
+ }
+
+ void start() {
+ jsassert( !started_, "Thread already started" );
+ // obtain own scope for execution
+ // do it here, not in constructor, otherwise it creates an infinite recursion from ScopedThread
+ _scope.reset( dynamic_cast< V8Scope * >( globalScriptEngine->newScope() ) );
+
+ JSThread jt( *this );
+ thread_.reset( new boost::thread( jt ) );
+ started_ = true;
+ }
+ void join() {
+ jsassert( started_ && !done_, "Thread not running" );
+ thread_->join();
+ done_ = true;
+ }
+
+ BSONObj returnData() {
+ if ( !done_ )
+ join();
+ return returnData_;
+ }
+
+ private:
+ class JSThread {
+ public:
+ JSThread( JSThreadConfig &config ) : config_( config ) {}
+
+ void operator()() {
+ V8Scope* scope = config_._scope.get();
+ v8::Isolate::Scope iscope(scope->getIsolate());
+ v8::Locker l(scope->getIsolate());
+ HandleScope handle_scope;
+ Context::Scope context_scope( scope->getContext() );
+
+ BSONObj args = config_.args_;
+ Local< v8::Function > f = v8::Function::Cast( *(scope->mongoToV8Element(args.firstElement(), true)) );
+ int argc = args.nFields() - 1;
+
+ boost::scoped_array< Local< Value > > argv( new Local< Value >[ argc ] );
+ BSONObjIterator it(args);
+ it.next();
+ for( int i = 0; i < argc; ++i ) {
+ argv[ i ] = Local< Value >::New( scope->mongoToV8Element(*it, true) );
+ it.next();
+ }
+ TryCatch try_catch;
+ Handle< Value > ret = f->Call( scope->getContext()->Global(), argc, 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();
+ }
+ // ret is translated to BSON to switch isolate
+ BSONObjBuilder b;
+ scope->v8ToMongoElement(b, "ret", ret);
+ config_.returnData_ = b.obj();
+ }
+
+ private:
+ JSThreadConfig &config_;
+ };
+
+ bool started_;
+ bool done_;
+ bool newScope_;
+ BSONObj args_;
+ scoped_ptr< boost::thread > thread_;
+ scoped_ptr< V8Scope > _scope;
+ BSONObj returnData_;
+ };
+
+ Handle< Value > ThreadInit( V8Scope* scope, 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( scope, args ) ) );
+ return v8::Undefined();
+ }
+
+ Handle< Value > ScopedThreadInit( V8Scope* scope, 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( scope, args, true ) ) );
+ return v8::Undefined();
+ }
+
+ JSThreadConfig *thisConfig( V8Scope* scope, 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( V8Scope* scope, const Arguments &args ) {
+ thisConfig( scope, args )->start();
+ return v8::Undefined();
+ }
+
+ Handle< Value > ThreadJoin( V8Scope* scope, const Arguments &args ) {
+ thisConfig( scope, args )->join();
+ return v8::Undefined();
+ }
+
+ Handle< Value > ThreadReturnData( V8Scope* scope, const Arguments &args ) {
+ BSONObj data = thisConfig( scope, args )->returnData();
+ return scope->mongoToV8Element(data.firstElement(), true);
+ }
+
+ Handle< Value > ThreadInject( V8Scope* scope, 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();
+
+ // install method on the Thread object
+ scope->injectV8Function("init", ThreadInit, o);
+ scope->injectV8Function("start", ThreadStart, o);
+ scope->injectV8Function("join", ThreadJoin, o);
+ scope->injectV8Function("returnData", ThreadReturnData, o);
+ return v8::Undefined();
+ }
+
+ Handle< Value > ScopedThreadInject( V8Scope* scope, 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();
+
+ scope->injectV8Function("init", ScopedThreadInit, o);
+ // inheritance takes care of other member functions
+
+ return v8::Undefined();
+ }
+
+ void installFork( V8Scope* scope, 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;
+ scope->injectV8Function("_threadInject", ThreadInject, global);
+ scope->injectV8Function("_scopedThreadInject", ScopedThreadInject, global);
+ }
+
+}
diff --git a/src/mongo/scripting/v8_utils.h b/src/mongo/scripting/v8_utils.h
new file mode 100644
index 00000000000..ca5d317885f
--- /dev/null
+++ b/src/mongo/scripting/v8_utils.h
@@ -0,0 +1,43 @@
+// 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 {
+
+ 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( V8Scope* scope, v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context );
+}
+
diff --git a/src/mongo/scripting/v8_wrapper.cpp b/src/mongo/scripting/v8_wrapper.cpp
new file mode 100644
index 00000000000..7c28a39cceb
--- /dev/null
+++ b/src/mongo/scripting/v8_wrapper.cpp
@@ -0,0 +1,99 @@
+// 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.
+ */
+
+#if defined(_WIN32)
+/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with
+ our usage of boost */
+#include "boost/cstdint.hpp"
+using namespace boost;
+#define V8STDINT_H_
+#endif
+
+#include "v8_wrapper.h"
+#include "v8_utils.h"
+#include "v8_db.h"
+#include "engine_v8.h"
+
+#include <iostream>
+
+using namespace std;
+using namespace v8;
+
+namespace mongo {
+
+#define DDD(x)
+
+ // --- object wrapper ---
+
+ class WrapperHolder {
+ public:
+ WrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete )
+ : _scope(scope), _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 _scope->mongoToV8Element(e);
+ }
+
+ V8Scope* _scope;
+ const BSONObj * _o;
+ bool _readOnly;
+ bool _iDelete;
+ };
+
+ WrapperHolder * createWrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ) {
+ return new WrapperHolder( scope, o , readOnly , iDelete );
+ }
+
+ WrapperHolder * getWrapper( v8::Handle<v8::Object> o ) {
+ Handle<v8::Value> t = o->GetRealNamedProperty( v8::String::New( "_wrapper" ) );
+ assert( t->IsExternal() );
+ Local<External> c = External::Cast( *t );
+ WrapperHolder * w = (WrapperHolder*)(c->Value());
+ assert( w );
+ return w;
+ }
+
+
+ Handle<Value> wrapperCons(V8Scope* scope, const Arguments& args) {
+ if ( ! ( args.Length() == 1 && args[0]->IsExternal() ) )
+ return v8::ThrowException( v8::String::New( "wrapperCons needs 1 External arg" ) );
+
+ args.This()->Set( v8::String::New( "_wrapper" ) , 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(V8Scope* scope) {
+ v8::Handle<v8::FunctionTemplate> t = scope->createV8Function(wrapperCons);
+ t->InstanceTemplate()->SetNamedPropertyHandler( wrapperGetHandler );
+ return t;
+ }
+}
diff --git a/src/mongo/scripting/v8_wrapper.h b/src/mongo/scripting/v8_wrapper.h
new file mode 100644
index 00000000000..22f14e6ae94
--- /dev/null
+++ b/src/mongo/scripting/v8_wrapper.h
@@ -0,0 +1,34 @@
+// 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"
+#include "engine_v8.h"
+
+namespace mongo {
+
+ v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(V8Scope* scope);
+
+ class WrapperHolder;
+ WrapperHolder * createWrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete );
+
+}