summaryrefslogtreecommitdiff
path: root/src/mongo/scripting
diff options
context:
space:
mode:
authorAntonin Kral <a.kral@bobek.cz>2012-08-29 20:54:51 +0200
committerAntonin Kral <a.kral@bobek.cz>2012-08-29 20:54:51 +0200
commit83957b73f9177f6e38bd5375bd93ca1f6a47188c (patch)
treef20b7d6ac9a9c64ff5bb6b5910a24abbb356b1d5 /src/mongo/scripting
parent5071d203970edd4c995493d810abe20987e76fe9 (diff)
Imported Upstream version 2.2.0upstream/2.2.0
Diffstat (limited to 'src/mongo/scripting')
-rw-r--r--src/mongo/scripting/bench.cpp884
-rw-r--r--src/mongo/scripting/bench.h412
-rw-r--r--src/mongo/scripting/bson_template_evaluator.cpp157
-rw-r--r--src/mongo/scripting/bson_template_evaluator.h152
-rw-r--r--src/mongo/scripting/bson_template_evaluator_test.cpp360
-rw-r--r--src/mongo/scripting/engine.cpp521
-rw-r--r--src/mongo/scripting/engine.h231
-rw-r--r--src/mongo/scripting/engine_none.cpp24
-rw-r--r--src/mongo/scripting/engine_spidermonkey.cpp2010
-rw-r--r--src/mongo/scripting/engine_spidermonkey.h101
-rw-r--r--src/mongo/scripting/engine_v8.cpp1706
-rw-r--r--src/mongo/scripting/engine_v8.h269
-rw-r--r--src/mongo/scripting/sm_db.cpp1716
-rw-r--r--src/mongo/scripting/utils.cpp71
-rw-r--r--src/mongo/scripting/v8_db.cpp1145
-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.cpp91
-rw-r--r--src/mongo/scripting/v8_wrapper.h34
20 files changed, 10316 insertions, 0 deletions
diff --git a/src/mongo/scripting/bench.cpp b/src/mongo/scripting/bench.cpp
new file mode 100644
index 00000000000..db3ca7351a7
--- /dev/null
+++ b/src/mongo/scripting/bench.cpp
@@ -0,0 +1,884 @@
+/** @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 "mongo/scripting/bench.h"
+
+#include <boost/thread/thread.hpp>
+
+#include "mongo/client/dbclientcursor.h"
+#include "mongo/scripting/engine.h"
+#include "mongo/util/md5.h"
+#include "mongo/util/timer.h"
+#include "mongo/util/version.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 {
+
+ BenchRunEventCounter::BenchRunEventCounter() {
+ reset();
+ }
+
+ BenchRunEventCounter::~BenchRunEventCounter() {}
+
+ void BenchRunEventCounter::reset() {
+ _numEvents = 0;
+ _totalTimeMicros = 0;
+ }
+
+ void BenchRunEventCounter::updateFrom(const BenchRunEventCounter &other) {
+ _numEvents += other._numEvents;
+ _totalTimeMicros += other._totalTimeMicros;
+ }
+
+ BenchRunStats::BenchRunStats() {
+ reset();
+ }
+
+ BenchRunStats::~BenchRunStats() {}
+
+ void BenchRunStats::reset() {
+ error = false;
+ errCount = 0;
+
+ findOneCounter.reset();
+ updateCounter.reset();
+ insertCounter.reset();
+ deleteCounter.reset();
+ queryCounter.reset();
+
+ trappedErrors.clear();
+ }
+
+ void BenchRunStats::updateFrom(const BenchRunStats &other) {
+ if (other.error)
+ error = true;
+ errCount += other.errCount;
+
+ findOneCounter.updateFrom(other.findOneCounter);
+ updateCounter.updateFrom(other.updateCounter);
+ insertCounter.updateFrom(other.insertCounter);
+ deleteCounter.updateFrom(other.deleteCounter);
+ queryCounter.updateFrom(other.queryCounter);
+
+ for (size_t i = 0; i < other.trappedErrors.size(); ++i)
+ trappedErrors.push_back(other.trappedErrors[i]);
+ }
+
+ BenchRunConfig::BenchRunConfig() {
+ initializeToDefaults();
+ }
+
+ void BenchRunConfig::initializeToDefaults() {
+ host = "localhost";
+ db = "test";
+ username = "";
+ password = "";
+
+ parallel = 1;
+ seconds = 1;
+ hideResults = true;
+ handleErrors = false;
+ hideErrors = false;
+
+ trapPattern.reset();
+ noTrapPattern.reset();
+ watchPattern.reset();
+ noWatchPattern.reset();
+
+ ops = BSONObj();
+
+ throwGLE = false;
+ breakOnTrap = true;
+ }
+
+ BenchRunConfig *BenchRunConfig::createFromBson( const BSONObj &args ) {
+ BenchRunConfig *config = new BenchRunConfig();
+ config->initializeFromBson( args );
+ return config;
+ }
+
+ void BenchRunConfig::initializeFromBson( const BSONObj &args ) {
+ initializeToDefaults();
+
+ if ( args["host"].type() == String )
+ this->host = args["host"].String();
+ if ( args["db"].type() == String )
+ this->db = args["db"].String();
+ if ( args["username"].type() == String )
+ this->username = args["username"].String();
+ if ( args["password"].type() == String )
+ this->db = args["password"].String();
+
+ if ( args["parallel"].isNumber() )
+ this->parallel = args["parallel"].numberInt();
+ if ( args["seconds"].isNumber() )
+ this->seconds = args["seconds"].number();
+ if ( ! args["hideResults"].eoo() )
+ this->hideResults = args["hideResults"].trueValue();
+ if ( ! args["handleErrors"].eoo() )
+ this->handleErrors = args["handleErrors"].trueValue();
+ if ( ! args["hideErrors"].eoo() )
+ this->hideErrors = args["hideErrors"].trueValue();
+ if ( ! args["throwGLE"].eoo() )
+ this->throwGLE = args["throwGLE"].trueValue();
+ if ( ! args["breakOnTrap"].eoo() )
+ this->breakOnTrap = args["breakOnTrap"].trueValue();
+
+ uassert(16164, "loopCommands config not supported", args["loopCommands"].eoo());
+
+ if ( ! args["trapPattern"].eoo() ){
+ const char* regex = args["trapPattern"].regex();
+ const char* flags = args["trapPattern"].regexFlags();
+ this->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();
+ this->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();
+ this->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();
+ this->noWatchPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) );
+ }
+
+ this->ops = args["ops"].Obj().getOwned();
+ }
+
+ DBClientBase *BenchRunConfig::createConnection() const {
+ std::string errorMessage;
+ ConnectionString connectionString = ConnectionString::parse( host, errorMessage );
+ uassert( 16157, errorMessage, connectionString.isValid() );
+ DBClientBase *connection = connectionString.connect(errorMessage);
+ uassert( 16158, errorMessage, connection != NULL );
+ return connection;
+ }
+
+ BenchRunState::BenchRunState( unsigned numWorkers )
+ : _mutex(),
+ _numUnstartedWorkers( numWorkers ),
+ _numActiveWorkers( 0 ),
+ _isShuttingDown( 0 ) {
+ }
+
+ BenchRunState::~BenchRunState() {
+ wassert(_numActiveWorkers == 0 && _numUnstartedWorkers == 0);
+ }
+
+ void BenchRunState::waitForState(State awaitedState) {
+ boost::mutex::scoped_lock lk(_mutex);
+
+ switch ( awaitedState ) {
+ case BRS_RUNNING:
+ while ( _numUnstartedWorkers > 0 ) {
+ massert( 16147, "Already finished.", _numUnstartedWorkers + _numActiveWorkers > 0 );
+ _stateChangeCondition.wait( _mutex );
+ }
+ break;
+ case BRS_FINISHED:
+ while ( _numUnstartedWorkers + _numActiveWorkers > 0 ) {
+ _stateChangeCondition.wait( _mutex );
+ }
+ break;
+ default:
+ msgasserted(16152, mongoutils::str::stream() << "Cannot wait for state " << awaitedState);
+ }
+ }
+
+ void BenchRunState::tellWorkersToFinish() {
+ _isShuttingDown.set( 1 );
+ }
+
+ void BenchRunState::assertFinished() {
+ boost::mutex::scoped_lock lk(_mutex);
+ verify(0 == _numUnstartedWorkers + _numActiveWorkers);
+ }
+
+ bool BenchRunState::shouldWorkerFinish() {
+ return bool(_isShuttingDown.get());
+ }
+
+ void BenchRunState::onWorkerStarted() {
+ boost::mutex::scoped_lock lk(_mutex);
+ verify( _numUnstartedWorkers > 0 );
+ --_numUnstartedWorkers;
+ ++_numActiveWorkers;
+ if (_numUnstartedWorkers == 0) {
+ _stateChangeCondition.notify_all();
+ }
+ }
+
+ void BenchRunState::onWorkerFinished() {
+ boost::mutex::scoped_lock lk(_mutex);
+ verify( _numActiveWorkers > 0 );
+ --_numActiveWorkers;
+ if (_numActiveWorkers + _numUnstartedWorkers == 0) {
+ _stateChangeCondition.notify_all();
+ }
+ }
+
+ BSONObj benchStart( const BSONObj& , void* );
+ BSONObj benchFinish( const BSONObj& , void* );
+
+ 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 ) {
+ verify( e.type() == Object );
+
+ BSONObj sub = e.Obj();
+ verify( 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 ) );
+
+ if ( i.more() )
+ x *= i.next().numberInt();
+
+ 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();
+ }
+
+ BenchRunWorker::BenchRunWorker(const BenchRunConfig *config, BenchRunState *brState)
+ : _config(config), _brState(brState) {
+ }
+
+ BenchRunWorker::~BenchRunWorker() {}
+
+ void BenchRunWorker::start() {
+ boost::thread(boost::bind(&BenchRunWorker::run, this));
+ }
+
+ bool BenchRunWorker::shouldStop() const {
+ return _brState->shouldWorkerFinish();
+ }
+
+ void doNothing(const BSONObj&) { }
+
+ void BenchRunWorker::generateLoadOnConnection( DBClientBase* conn ) {
+ verify( conn );
+ long long count = 0;
+ mongo::Timer timer;
+
+ while ( !shouldStop() ) {
+ BSONObjIterator i( _config->ops );
+ while ( i.more() ) {
+
+ if ( shouldStop() ) break;
+
+ BSONElement e = i.next();
+
+ string ns = e["ns"].String();
+ string op = e["op"].String();
+
+ int delay = e["delay"].eoo() ? 0 : e["delay"].Int();
+
+ BSONObj context = e["context"].eoo() ? BSONObj() : e["context"].Obj();
+
+ auto_ptr<Scope> scope;
+ ScriptingFunction scopeFunc = 0;
+ BSONObj scopeObj;
+
+ if (_config->username != "") {
+ string errmsg;
+ if (!conn->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 );
+ verify( scope.get() );
+
+ if ( e.type() == CodeWScope ) {
+ scopeFunc = scope->createFunction( e["check"].codeWScopeCode() );
+ scopeObj = BSONObj( e.codeWScopeScopeDataUnsafe() );
+ }
+ else {
+ scopeFunc = scope->createFunction( e["check"].valuestr() );
+ }
+
+ scope->init( &scopeObj );
+ verify( scopeFunc );
+ }
+ else {
+ warning() << "Invalid check type detected in benchRun op : " << e << endl;
+ check = false;
+ }
+ }
+
+ try {
+ if ( op == "findOne" ) {
+
+ BSONObj result;
+ {
+ BenchRunEventTrace _bret(&_stats.findOneCounter);
+ 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;
+
+ _stats.errCount++;
+
+ 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;
+
+ _stats.errCount++;
+
+ 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();
+ int expected = e["expected"].eoo() ? -1 : e["expected"].Int();
+
+ auto_ptr<DBClientCursor> cursor;
+ int count;
+
+ BSONObj fixedQuery = fixQuery(e["query"].Obj());
+
+ // use special query function for exhaust query option
+ if (options & QueryOption_Exhaust) {
+ BenchRunEventTrace _bret(&_stats.queryCounter);
+ boost::function<void (const BSONObj&)> castedDoNothing(doNothing);
+ count = conn->query(castedDoNothing, ns, fixedQuery, &filter, options);
+ }
+ else {
+ BenchRunEventTrace _bret(&_stats.queryCounter);
+ cursor = conn->query( ns, fixedQuery, limit, skip, &filter, options, batchSize );
+ count = cursor->itcount();
+ }
+
+ if ( expected >= 0 && count != expected ) {
+ cout << "bench query on: " << ns << " expected: " << expected << " got: " << count << endl;
+ verify(false);
+ }
+
+ if( check ){
+ BSONObj thisValue = BSON( "count" << count << "context" << context );
+ int err = scope->invoke( scopeFunc , 0 , &thisValue, 1000 * 60 , false );
+ if( err ){
+ log() << "Error checking in benchRun thread [find]" << causedBy( scope->getError() ) << endl;
+
+ _stats.errCount++;
+
+ 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();
+ BSONObj result;
+ bool safe = e["safe"].trueValue();
+
+ {
+ BenchRunEventTrace _bret(&_stats.updateCounter);
+ conn->update( ns, fixQuery( query ), update, upsert , multi );
+ if (safe)
+ result = conn->getLastErrorDetailed();
+ }
+
+ if( safe ){
+ 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;
+
+ _stats.errCount++;
+
+ 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" ) {
+ bool safe = e["safe"].trueValue();
+ BSONObj result;
+ {
+ BenchRunEventTrace _bret(&_stats.insertCounter);
+ conn->insert( ns, fixQuery( e["doc"].Obj() ) );
+ if (safe)
+ result = conn->getLastErrorDetailed();
+ }
+
+ if( safe ){
+ 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;
+
+ _stats.errCount++;
+
+ 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();
+ bool safe = e["safe"].trueValue();
+ BSONObj result;
+
+ {
+ BenchRunEventTrace _bret(&_stats.deleteCounter);
+ conn->remove( ns, fixQuery( query ), ! multi );
+ if (safe)
+ result = conn->getLastErrorDetailed();
+ }
+
+ if( safe ){
+ 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;
+
+ _stats.errCount++;
+
+ 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;
+ _stats.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 ) ){
+ {
+ _stats.trappedErrors.push_back( BSON( "error" << ex.what() << "op" << e << "count" << count ) );
+ }
+ if( _config->breakOnTrap ) return;
+ }
+ if( ! _config->handleErrors && ! e["handleError"].trueValue() ) return;
+
+ _stats.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;
+
+ _stats.errCount++;
+ }
+
+ if ( ++count % 100 == 0 ) {
+ conn->getLastError();
+ }
+
+ sleepmillis( delay );
+ }
+ }
+
+ conn->getLastError();
+ }
+
+ namespace {
+ class BenchRunWorkerStateGuard : private boost::noncopyable {
+ public:
+ explicit BenchRunWorkerStateGuard( BenchRunState *brState ) : _brState( brState ) {
+ _brState->onWorkerStarted();
+ }
+
+ ~BenchRunWorkerStateGuard() {
+ _brState->onWorkerFinished();
+ }
+
+ private:
+ BenchRunState *_brState;
+ };
+ } // namespace
+
+ void BenchRunWorker::run() {
+ BenchRunWorkerStateGuard _workerStateGuard( _brState );
+
+ boost::scoped_ptr<DBClientBase> conn( _config->createConnection() );
+
+ try {
+ if ( !_config->username.empty() ) {
+ string errmsg;
+ if (!conn->auth(_config->db, _config->username, _config->password, errmsg)) {
+ uasserted(15932, "Authenticating to connection for benchThread failed: " + errmsg);
+ }
+ }
+ generateLoadOnConnection( conn.get() );
+ }
+ catch( DBException& e ){
+ error() << "DBException not handled in benchRun thread" << causedBy( e ) << endl;
+ }
+ catch( std::exception& e ){
+ error() << "std::exception not handled in benchRun thread" << causedBy( e ) << endl;
+ }
+ catch( ... ){
+ error() << "Unknown exception not handled in benchRun thread." << endl;
+ }
+ }
+
+ BenchRunner::BenchRunner( BenchRunConfig *config )
+ : _brState(config->parallel),
+ _config(config) {
+
+ _oid.init();
+ boost::mutex::scoped_lock lk(_staticMutex);
+ _activeRuns[_oid] = this;
+ }
+
+ BenchRunner::~BenchRunner() {
+ for (size_t i = 0; i < _workers.size(); ++i)
+ delete _workers[i];
+ }
+
+ void BenchRunner::start( ) {
+
+
+ {
+ boost::scoped_ptr<DBClientBase> conn( _config->createConnection() );
+ // Get initial stats
+ conn->simpleCommand( "admin" , &before , "serverStatus" );
+ before = before.getOwned();
+ }
+
+ // Start threads
+ for ( unsigned i = 0; i < _config->parallel; i++ ) {
+ BenchRunWorker *worker = new BenchRunWorker(_config.get(), &_brState);
+ worker->start();
+ _workers.push_back(worker);
+ }
+
+ _brState.waitForState(BenchRunState::BRS_RUNNING);
+ }
+
+ void BenchRunner::stop() {
+ _brState.tellWorkersToFinish();
+ _brState.waitForState(BenchRunState::BRS_FINISHED);
+
+ {
+ boost::scoped_ptr<DBClientBase> conn( _config->createConnection() );
+ // Get final stats
+ conn->simpleCommand( "admin" , &after , "serverStatus" );
+ after = after.getOwned();
+ }
+
+ {
+ boost::mutex::scoped_lock lk(_staticMutex);
+ _activeRuns.erase( _oid );
+ }
+ }
+
+ BenchRunner* BenchRunner::createWithConfig( const BSONObj &configArgs ) {
+ BenchRunConfig *config = BenchRunConfig::createFromBson( configArgs );
+ return new BenchRunner(config);
+ }
+
+ BenchRunner* BenchRunner::get( OID oid ) {
+ boost::mutex::scoped_lock lk(_staticMutex);
+ return _activeRuns[ oid ];
+ }
+
+ void BenchRunner::populateStats( BenchRunStats *stats ) {
+ _brState.assertFinished();
+ stats->reset();
+ for ( size_t i = 0; i < _workers.size(); ++i )
+ stats->updateFrom( _workers[i]->stats() );
+ BSONObj before = this->before["opcounters"].Obj();
+ BSONObj after = this->after["opcounters"].Obj();
+ {
+ BSONObjIterator i( after );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+ long long delta = e.numberLong();
+ delta -= before[e.fieldName()].numberLong();
+ stats->opcounters[e.fieldName()] = delta;
+ }
+ }
+ }
+
+ static void appendAverageMicrosIfAvailable(
+ BSONObjBuilder &buf, const std::string &name, const BenchRunEventCounter &counter) {
+
+ if (counter.getNumEvents() > 0)
+ buf.append(name,
+ static_cast<double>(counter.getTotalTimeMicros()) / counter.getNumEvents());
+ }
+
+ BSONObj BenchRunner::finish( BenchRunner* runner ) {
+
+ runner->stop();
+
+ BenchRunStats stats;
+ runner->populateStats(&stats);
+
+ // vector<BSONOBj> errors = runner->config.errors;
+ bool error = stats.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) stats.errCount );
+ buf.append( "trapped", "error: not implemented" );
+ appendAverageMicrosIfAvailable(buf, "findOneLatencyAverageMicros", stats.findOneCounter);
+ appendAverageMicrosIfAvailable(buf, "insertLatencyAverageMicros", stats.insertCounter);
+ appendAverageMicrosIfAvailable(buf, "deleteLatencyAverageMicros", stats.deleteCounter);
+ appendAverageMicrosIfAvailable(buf, "updateLatencyAverageMicros", stats.updateCounter);
+ appendAverageMicrosIfAvailable(buf, "queryLatencyAverageMicros", stats.queryCounter);
+
+ {
+ BSONObjIterator i( after );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+ double x = e.number();
+ x -= before[e.fieldName()].number();
+ buf.append( e.fieldName() , x / runner->_config->seconds );
+ }
+ }
+
+ BSONObj zoo = buf.obj();
+
+ delete runner;
+ return zoo;
+ }
+
+ boost::mutex BenchRunner::_staticMutex;
+ map< OID, BenchRunner* > BenchRunner::_activeRuns;
+
+ /**
+ * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 }
+ */
+ BSONObj benchRunSync( const BSONObj& argsFake, void* data ) {
+
+ BSONObj start = benchStart( argsFake, data );
+
+ OID oid = OID( start.firstElement().String() );
+ BenchRunner* runner = BenchRunner::get( oid );
+ sleepmillis( (int)(1000.0 * runner->config().seconds) );
+
+ return benchFinish( start, data );
+ }
+
+ /**
+ * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 }
+ */
+ BSONObj benchStart( const BSONObj& argsFake, void* data ) {
+
+ verify( argsFake.firstElement().isABSONObj() );
+ BSONObj args = argsFake.firstElement().Obj();
+
+ // Get new BenchRunner object
+ BenchRunner* runner = BenchRunner::createWithConfig( args );
+
+ runner->start();
+ return BSON( "" << runner->oid().toString() );
+ }
+
+ /**
+ * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 }
+ */
+ BSONObj benchFinish( const BSONObj& argsFake, void* data ) {
+
+ OID oid = OID( argsFake.firstElement().String() );
+
+ // Get new BenchRunner object
+ BenchRunner* runner = BenchRunner::get( oid );
+
+ BSONObj finalObj = BenchRunner::finish( runner );
+
+ return BSON( "" << finalObj );
+ }
+
+ void installBenchmarkSystem( Scope& scope ) {
+ scope.injectNative( "benchRun" , benchRunSync );
+ scope.injectNative( "benchRunSync" , benchRunSync );
+ scope.injectNative( "benchStart" , benchStart );
+ scope.injectNative( "benchFinish" , benchFinish );
+ }
+
+}
diff --git a/src/mongo/scripting/bench.h b/src/mongo/scripting/bench.h
new file mode 100644
index 00000000000..98c0ebc4666
--- /dev/null
+++ b/src/mongo/scripting/bench.h
@@ -0,0 +1,412 @@
+/*
+ * 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/>.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/thread/condition.hpp>
+#include <boost/thread/mutex.hpp>
+#include <pcrecpp.h>
+
+#include "mongo/bson/util/atomic_int.h"
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/util/timer.h"
+
+namespace mongo {
+
+ /**
+ * Configuration object describing a bench run activity.
+ */
+ class BenchRunConfig : private boost::noncopyable {
+ public:
+
+ /**
+ * Create a new BenchRunConfig object, and initialize it from the BSON
+ * document, "args".
+ *
+ * Caller owns the returned object, and is responsible for its deletion.
+ */
+ static BenchRunConfig *createFromBson( const BSONObj &args );
+
+ BenchRunConfig();
+
+ void initializeFromBson( const BSONObj &args );
+
+ // Create a new connection to the mongo instance specified by this configuration.
+ DBClientBase *createConnection() const;
+
+ /**
+ * Connection string describing the host to which to connect.
+ */
+ std::string host;
+
+ /**
+ * Name of the database on which to operate.
+ */
+ std::string db;
+
+ /**
+ * Optional username for authenticating to the database.
+ */
+ std::string username;
+
+ /**
+ * Optional password for authenticating to the database.
+ *
+ * Only useful if username is non-empty.
+ */
+ std::string password;
+
+ /**
+ * Number of parallel threads to perform the bench run activity.
+ */
+ unsigned parallel;
+
+ /**
+ * Desired duration of the bench run activity, in seconds.
+ *
+ * NOTE: Only used by the javascript benchRun() and benchRunSync() functions.
+ */
+ double seconds;
+
+ bool hideResults;
+ bool handleErrors;
+ bool hideErrors;
+
+ boost::shared_ptr< pcrecpp::RE > trapPattern;
+ boost::shared_ptr< pcrecpp::RE > noTrapPattern;
+ boost::shared_ptr< pcrecpp::RE > watchPattern;
+ boost::shared_ptr< pcrecpp::RE > noWatchPattern;
+
+ /**
+ * Operation description. A BSON array of objects, each describing a single
+ * operation.
+ *
+ * Every thread in a benchRun job will perform these operations in sequence, restarting at
+ * the beginning when the end is reached, until the job is stopped.
+ *
+ * TODO: Document the operation objects.
+ *
+ * TODO: Introduce support for performing each operation exactly N times.
+ */
+ BSONObj ops;
+
+ bool throwGLE;
+ bool breakOnTrap;
+
+ private:
+ /// Initialize a config object to its default values.
+ void initializeToDefaults();
+ };
+
+ /**
+ * An event counter for events that have an associated duration.
+ *
+ * Not thread safe. Expected use is one instance per thread during parallel execution.
+ */
+ class BenchRunEventCounter : private boost::noncopyable {
+ public:
+ /// Constructs a zeroed out counter.
+ BenchRunEventCounter();
+ ~BenchRunEventCounter();
+
+ /**
+ * Zero out the counter.
+ */
+ void reset();
+
+ /**
+ * Conceptually the equivalent of "+=". Adds "other" into this.
+ */
+ void updateFrom( const BenchRunEventCounter &other );
+
+ /**
+ * Count one instance of the event, which took "timeMicros" microseconds.
+ */
+ void countOne(unsigned long long timeMicros) {
+ ++_numEvents;
+ _totalTimeMicros += timeMicros;
+ }
+
+ /**
+ * Get the total number of microseconds ellapsed during all observed events.
+ */
+ unsigned long long getTotalTimeMicros() const { return _totalTimeMicros; }
+
+ /**
+ * Get the number of observed events.
+ */
+ unsigned long long getNumEvents() const { return _numEvents; }
+
+ private:
+ unsigned long long _numEvents;
+ unsigned long long _totalTimeMicros;
+ };
+
+ /**
+ * RAII object for tracing an event.
+ *
+ * Construct an instance of this at the beginning of an event, and have it go out of scope at
+ * the end, to facilitate tracking events.
+ *
+ * This type can be used to separately count failures and successes by passing two event
+ * counters to the BenchRunEventCounter constructor, and calling "succeed()" on the object at
+ * the end of a successful event. If an exception is thrown, the fail counter will receive the
+ * event, and otherwise, the succes counter will.
+ *
+ * In all cases, the counter objects must outlive the trace object.
+ */
+ class BenchRunEventTrace : private boost::noncopyable {
+ public:
+ explicit BenchRunEventTrace(BenchRunEventCounter *eventCounter) {
+ initialize(eventCounter, eventCounter, false);
+ }
+
+ BenchRunEventTrace(BenchRunEventCounter *successCounter,
+ BenchRunEventCounter *failCounter,
+ bool defaultToFailure=true) {
+ initialize(successCounter, failCounter, defaultToFailure);
+ }
+
+ ~BenchRunEventTrace() {
+ (_succeeded ? _successCounter : _failCounter)->countOne(_timer.micros());
+ }
+
+ void succeed() { _succeeded = true; }
+ void fail() { _succeeded = false; }
+
+ private:
+ void initialize(BenchRunEventCounter *successCounter,
+ BenchRunEventCounter *failCounter,
+ bool defaultToFailure) {
+ _successCounter = successCounter;
+ _failCounter = failCounter;
+ _succeeded = !defaultToFailure;
+ }
+
+ Timer _timer;
+ BenchRunEventCounter *_successCounter;
+ BenchRunEventCounter *_failCounter;
+ bool _succeeded;
+ };
+
+ /**
+ * Statistics object representing the result of a bench run activity.
+ */
+ class BenchRunStats : private boost::noncopyable {
+ public:
+ BenchRunStats();
+ ~BenchRunStats();
+
+ void reset();
+
+ void updateFrom( const BenchRunStats &other );
+
+ bool error;
+ unsigned long long errCount;
+
+ BenchRunEventCounter findOneCounter;
+ BenchRunEventCounter updateCounter;
+ BenchRunEventCounter insertCounter;
+ BenchRunEventCounter deleteCounter;
+ BenchRunEventCounter queryCounter;
+
+ std::map<std::string, long long> opcounters;
+ std::vector<BSONObj> trappedErrors;
+ };
+
+ /**
+ * State of a BenchRun activity.
+ *
+ * Logically, the states are "starting up", "running" and "finished."
+ */
+ class BenchRunState : private boost::noncopyable {
+ public:
+ enum State { BRS_STARTING_UP, BRS_RUNNING, BRS_FINISHED };
+
+ explicit BenchRunState(unsigned numWorkers);
+ ~BenchRunState();
+
+ //
+ // Functions called by the job-controlling thread, through an instance of BenchRunner.
+ //
+
+ /**
+ * Block until the current state is "awaitedState."
+ *
+ * massert() (uassert()?) if "awaitedState" is unreachable from
+ * the current state.
+ */
+ void waitForState(State awaitedState);
+
+ /**
+ * Notify the worker threads to wrap up. Does not block.
+ */
+ void tellWorkersToFinish();
+
+ /// Check that the current state is BRS_FINISHED.
+ void assertFinished();
+
+ //
+ // Functions called by the worker threads, through instances of BenchRunWorker.
+ //
+
+ /**
+ * Predicate that workers call to see if they should finish (as a result of a call
+ * to tellWorkersToFinish()).
+ */
+ bool shouldWorkerFinish();
+
+ /**
+ * Called by each BenchRunWorker from within its thread context, immediately before it
+ * starts sending requests to the configured mongo instance.
+ */
+ void onWorkerStarted();
+
+ /**
+ * Called by each BenchRunWorker from within its thread context, shortly after it finishes
+ * sending requests to the configured mongo instance.
+ */
+ void onWorkerFinished();
+
+ private:
+ boost::mutex _mutex;
+ boost::condition _stateChangeCondition;
+ unsigned _numUnstartedWorkers;
+ unsigned _numActiveWorkers;
+ AtomicUInt _isShuttingDown;
+ };
+
+ /**
+ * A single worker in the bench run activity.
+ *
+ * Represents the behavior of one thread working in a bench run activity.
+ */
+ class BenchRunWorker : private boost::noncopyable {
+ public:
+
+ /**
+ * Create a new worker, performing one thread's worth of the activity described in
+ * "config", and part of the larger activity with state "brState". Both "config"
+ * and "brState" must exist for the life of this object.
+ */
+ BenchRunWorker(const BenchRunConfig *config, BenchRunState *brState);
+ ~BenchRunWorker();
+
+ /**
+ * Start performing the "work" behavior in a new thread.
+ */
+ void start();
+
+ /**
+ * Get the run statistics for a worker.
+ *
+ * Should only be observed _after_ the worker has signaled its completion by calling
+ * onWorkerFinished() on the BenchRunState passed into its constructor.
+ */
+ const BenchRunStats &stats() const { return _stats; }
+
+ private:
+ /// The main method of the worker, executed inside the thread launched by start().
+ void run();
+
+ /// The function that actually sets about generating the load described in "_config".
+ void generateLoadOnConnection( DBClientBase *conn );
+
+ /// Predicate, used to decide whether or not it's time to terminate the worker.
+ bool shouldStop() const;
+
+ const BenchRunConfig *_config;
+ BenchRunState *_brState;
+ BenchRunStats _stats;
+ };
+
+ /**
+ * Object representing a "bench run" activity.
+ */
+ class BenchRunner : private boost::noncopyable {
+ public:
+ /**
+ * Utility method to create a new bench runner from a BSONObj representation
+ * of a configuration.
+ *
+ * TODO: This is only really for the use of the javascript benchRun() methods,
+ * and should probably move out of the BenchRunner class.
+ */
+ static BenchRunner* createWithConfig( const BSONObj &configArgs );
+
+ /**
+ * Look up a bench runner object by OID.
+ *
+ * TODO: Same todo as for "createWithConfig".
+ */
+ static BenchRunner* get( OID oid );
+
+ /**
+ * Stop a running "runner", and return a BSON representation of its resultant
+ * BenchRunStats.
+ *
+ * TODO: Same as for "createWithConfig".
+ */
+ static BSONObj finish( BenchRunner* runner );
+
+ /**
+ * Create a new bench runner, to perform the activity described by "*config."
+ *
+ * Takes ownership of "config", and will delete it.
+ */
+ explicit BenchRunner( BenchRunConfig *config );
+ ~BenchRunner();
+
+ /**
+ * Start the activity. Only call once per instance of BenchRunner.
+ */
+ void start();
+
+ /**
+ * Stop the activity. Block until the activitiy has stopped.
+ */
+ void stop();
+
+ /**
+ * Store the collected event data from a completed bench run activity into "stats."
+ *
+ * Illegal to call until after stop() returns.
+ */
+ void populateStats(BenchRunStats *stats);
+
+ OID oid() const { return _oid; }
+
+ const BenchRunConfig &config() const { return *_config; } // TODO: Remove this function.
+
+ private:
+ // TODO: Same as for createWithConfig.
+ static boost::mutex _staticMutex;
+ static map< OID, BenchRunner* > _activeRuns;
+
+ OID _oid;
+ BenchRunState _brState;
+ boost::scoped_ptr<BenchRunConfig> _config;
+ vector<BenchRunWorker *> _workers;
+
+ BSONObj before;
+ BSONObj after;
+ };
+
+} // namespace mongo
diff --git a/src/mongo/scripting/bson_template_evaluator.cpp b/src/mongo/scripting/bson_template_evaluator.cpp
new file mode 100644
index 00000000000..b38bed37051
--- /dev/null
+++ b/src/mongo/scripting/bson_template_evaluator.cpp
@@ -0,0 +1,157 @@
+/** @file mongo/scripting/templateevaluator.cpp */
+
+/*
+ * Copyright (C) 2012 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 "mongo/scripting/bson_template_evaluator.h"
+
+#include <cstddef>
+#include <cstdlib>
+
+#include "mongo/util/map_util.h"
+
+namespace mongo {
+
+ void BsonTemplateEvaluator::initializeEvaluator() {
+ addOperator("RAND_INT", &BsonTemplateEvaluator::evalRandInt);
+ addOperator("RAND_STRING", &BsonTemplateEvaluator::evalRandString);
+ addOperator("CONCAT", &BsonTemplateEvaluator::evalConcat);
+ }
+
+ BsonTemplateEvaluator::BsonTemplateEvaluator() {
+ initializeEvaluator();
+ }
+
+ void BsonTemplateEvaluator::addOperator(const std::string& name, const OperatorFn& op) {
+ _operatorFunctions[name] = op;
+ }
+
+ BsonTemplateEvaluator::OperatorFn BsonTemplateEvaluator::operatorEvaluator(
+ const std::string& op) const {
+ return mapFindWithDefault(_operatorFunctions, op, OperatorFn());
+ }
+
+ /* This is the top level method for using this library. It takes a BSON Object as input,
+ * evaluates the templates and saves the result in the builder object.
+ * The method returns appropriate Status on success/error condition.
+ */
+ BsonTemplateEvaluator::Status BsonTemplateEvaluator::evaluate(const BSONObj& in,
+ BSONObjBuilder& builder) {
+ BSONForEach(e, in) {
+ Status st = _evalElem(e, builder);
+ if (st != StatusSuccess)
+ return st;
+ }
+ return StatusSuccess;
+ }
+
+ BsonTemplateEvaluator::Status BsonTemplateEvaluator::_evalElem(BSONElement in,
+ BSONObjBuilder& out) {
+ if (in.type() != Object) {
+ out.append(in);
+ return StatusSuccess;
+ }
+ BSONObj subObj = in.embeddedObject();
+ const char* opOrNot = subObj.firstElementFieldName();
+ if (opOrNot[0] != '#') {
+ out.append(in);
+ return StatusSuccess;
+ }
+ const char* op = opOrNot+1;
+ OperatorFn fn = operatorEvaluator(op);
+ if (!fn)
+ return StatusBadOperator;
+ Status st = fn(this, in.fieldName(), subObj, out);
+ if (st != StatusSuccess)
+ return st;
+ return StatusSuccess;
+ }
+
+ BsonTemplateEvaluator::Status BsonTemplateEvaluator::evalRandInt(BsonTemplateEvaluator* btl,
+ const char* fieldName,
+ const BSONObj in,
+ BSONObjBuilder& out) {
+ // in = { #RAND_INT: [10, 20] }
+ BSONObj range = in.firstElement().embeddedObject();
+ if (!range[0].isNumber() || !range[1].isNumber())
+ return StatusOpEvaluationError;
+ const int min = range["0"].numberInt();
+ const int max = range["1"].numberInt();
+ if (max <= min)
+ return StatusOpEvaluationError;
+ int randomNum = min + (rand() % (max - min));
+ if (range.nFields() == 3) {
+ if (!range[2].isNumber())
+ return StatusOpEvaluationError;
+ randomNum *= range[2].numberInt();
+ }
+ out.append(fieldName, randomNum);
+ return StatusSuccess;
+ }
+
+ BsonTemplateEvaluator::Status BsonTemplateEvaluator::evalRandString(BsonTemplateEvaluator* btl,
+ const char* fieldName,
+ const BSONObj in,
+ BSONObjBuilder& out) {
+ // in = { #RAND_STRING: [10] }
+ BSONObj range = in.firstElement().embeddedObject();
+ if (range.nFields() != 1)
+ return StatusOpEvaluationError;
+ if (!range[0].isNumber())
+ return StatusOpEvaluationError;
+ const int length = range["0"].numberInt();
+ if (length <= 0)
+ return StatusOpEvaluationError;
+ static const char alphanum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+ static const size_t alphaNumLength = sizeof(alphanum) - 1;
+ BOOST_STATIC_ASSERT(alphaNumLength == 64);
+ unsigned currentRand = 0;
+ std::string str;
+ for (int i = 0; i < length; ++i, currentRand >>= 6) {
+ if (i % 5 == 0)
+ currentRand = rand();
+ str.push_back(alphanum[currentRand % alphaNumLength]);
+ }
+ out.append(fieldName, str);
+ return StatusSuccess;
+ }
+
+ BsonTemplateEvaluator::Status BsonTemplateEvaluator::evalConcat(BsonTemplateEvaluator* btl,
+ const char* fieldName,
+ const BSONObj in,
+ BSONObjBuilder& out) {
+ // in = { #CONCAT: ["hello", " ", "world"] }
+ BSONObjBuilder objectBuilder;
+ Status status = btl->evaluate(in.firstElement().embeddedObject(), objectBuilder);
+ if (status != StatusSuccess)
+ return status;
+ BSONObj parts = objectBuilder.obj();
+ if (parts.nFields() <= 1)
+ return StatusOpEvaluationError;
+ StringBuilder stringBuilder;
+ BSONForEach(part, parts) {
+ if (part.type() == String)
+ stringBuilder << part.String();
+ else
+ part.toString(stringBuilder,false);
+ }
+ out.append(fieldName, stringBuilder.str());
+ return StatusSuccess;
+ }
+
+} // end namespace mongo
diff --git a/src/mongo/scripting/bson_template_evaluator.h b/src/mongo/scripting/bson_template_evaluator.h
new file mode 100644
index 00000000000..2c60df73514
--- /dev/null
+++ b/src/mongo/scripting/bson_template_evaluator.h
@@ -0,0 +1,152 @@
+/** @file templateevaluator.h */
+
+/*
+ * Copyright (C) 2012 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/>.
+ */
+
+/*
+ * This library supports a templating language that helps in generating BSON documents from a
+ * template. The language supports the following template:
+ * #RAND_INT, #RAND_STRING and #CONCAT.
+ *
+ * The language will help in quickly expressing richer documents for use in benchRun.
+ * Ex. : { key : { #RAND_INT: [10, 20] } } or { key : { #CONCAT: ["hello", " ", "world"] } }
+ *
+ * Where possible, the templates can also be combined together and evaluated. For eg.
+ * { key : { #CONCAT: [{ #RAND_INT: [10, 20] }, " ", "world"] } }
+ *
+ * This library DOES NOT support combining or nesting the templates in an arbitrary fashion.
+ * eg. { key : { #RAND_INT: [{ #RAND_INT: [10, 15] }, 20] } } is not supported.
+ *
+ */
+#pragma once
+
+#include <map>
+#include <string>
+
+#include <boost/function.hpp>
+#include <boost/noncopyable.hpp>
+
+#include "mongo/db/jsobj.h"
+
+namespace mongo {
+
+ /*
+ * BsonTemplateEvaluator Object for evaluating the templates. The Object exposes
+ * methods to evaluate existing template operators (#RAND_INT) and add new template operators.
+ *
+ * To evaluate a template, call the object's 'evaluate' method and pass it as arguments, the
+ * template object that you want to evaluate and a BSONObjBuilder object that will contain the
+ * resultant BSON object. Eg.
+ *
+ * Status st = bsonTemplEvalObj->evaluate(inputTemplateObj, outputBSONObjBuilder)
+ *
+ * The 'evaluate' method will never throw an exception and will return an appropriate Status
+ * code on success/error scenario.
+ *
+ * High level working : The evaluate() method takes in a BSONObj as input, iterates over the
+ * BSON elements in the input BSONObj, and calls _evalElem() method. The _evalElem() method
+ * figures out the specific template and then calls the corresponding template function.
+ * The evaluated result is added to the BSONObjBuilder object and is returned to the evaluate()
+ * method.
+ *
+ */
+ class BsonTemplateEvaluator : private boost::noncopyable {
+ public:
+ /* Status of template evaluation. Logically the the status are "success", "bad operator"
+ * and "operation evaluation error." */
+ enum Status {
+ StatusSuccess = 0,
+ StatusBadOperator,
+ StatusOpEvaluationError
+ };
+
+ /*
+ * OperatorFn : function object wrappers that define a call interface.
+ * All template operators have this signature.
+ * @params btl : pointer to the BsonTemplateEvaluator Object
+ * fieldName : key of the object being evaluated
+ * in : the embedded BSONObj
+ * builder : the output BSONObj
+ * Eg. for object { key : { #RAND_INT: [10, 20] } }
+ * fieldName : key
+ * in : { #RAND_INT: [10, 20] }
+ */
+ typedef boost::function< Status (BsonTemplateEvaluator* btl, const char* fieldName,
+ const BSONObj& in, BSONObjBuilder& builder) > OperatorFn;
+
+ BsonTemplateEvaluator();
+ ~BsonTemplateEvaluator();
+
+ /*
+ * "Add a new operator, "name" with behavior "op" to this evaluator.
+ */
+ void addOperator(const std::string& name, const OperatorFn& op);
+
+ /*
+ * Returns the OperatorFn registered for the operator named "op", or
+ * OperatorFn() if there is no such operator.
+ */
+ OperatorFn operatorEvaluator(const std::string& op) const;
+
+ /* This is the top level method for using this library. It takes a BSON Object as input,
+ * evaluates the templates and saves the result in the builder object.
+ * The method returns a status code on success/error condition.
+ * The templates cannot be used at the top level.
+ * So this is okay as an input { key : {#RAND_INT: [10, 20]} }
+ * but not this { {#RAND_INT : [10, 20]} : some_value }
+ */
+ Status evaluate(const BSONObj& src, BSONObjBuilder& builder);
+
+ private:
+ void initializeEvaluator();
+ // map that holds operators along with their respective function pointers
+ typedef std::map< std::string, OperatorFn > OperatorMap;
+ OperatorMap _operatorFunctions;
+
+ // evaluates a BSON element. This is internally called by the top level evaluate method.
+ Status _evalElem(BSONElement in, BSONObjBuilder& out);
+
+ /*
+ * Operator method to support #RAND_INT : { key : { #RAND_INT: [10, 20] } }
+ * The array arguments to #RAND_INT are the min and mix range between which a random number
+ * will be chosen. The chosen random number is inclusive at the lower end but not at the
+ * upper end.
+ * This will evaluate to something like { key : 14 }
+ * #RAND_INT also supports a third optional argument which is a multiplier.
+ * Thus for an input { key : { #RAND_INT: [10, 20, 4] } }, the method will
+ * choose a random number between 10 and 20 and then multiple the chosen value with 4.
+ */
+ static Status evalRandInt(BsonTemplateEvaluator* btl, const char* fieldName,
+ const BSONObj in, BSONObjBuilder& out);
+ /*
+ * Operator method to support #RAND_STRING : { key : { #RAND_STRING: [12] } }
+ * The array argument to RAND_STRING is the length of the string that is desired.
+ * This will evaluate to something like { key : "randomstring" }
+ */
+ static Status evalRandString(BsonTemplateEvaluator* btl, const char* fieldName,
+ const BSONObj in, BSONObjBuilder& out);
+ /*
+ * Operator method to support #CONCAT : { key : { #CONCAT: ["hello", " ", "world", 2012] } }
+ * The array argument to CONCAT are the strings to be concatenated. If the argument is not
+ * a string it will be stringified and concatendated.
+ * This will evaluate to { key : "hello world2012" }
+ */
+ static Status evalConcat(BsonTemplateEvaluator* btl, const char* fieldName,
+ const BSONObj in, BSONObjBuilder& out);
+
+ };
+
+} // end namespace
diff --git a/src/mongo/scripting/bson_template_evaluator_test.cpp b/src/mongo/scripting/bson_template_evaluator_test.cpp
new file mode 100644
index 00000000000..5b334e0404f
--- /dev/null
+++ b/src/mongo/scripting/bson_template_evaluator_test.cpp
@@ -0,0 +1,360 @@
+/** @file mongo/scripting/bson_template_evaluator_tests : tests related to bson template language */
+
+/**
+ * Copyright (C) 2012 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 "mongo/db/jsobj.h"
+#include "mongo/scripting/bson_template_evaluator.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+
+ namespace {
+
+ TEST(BSONTemplateEvaluatorTest, RAND_INT) {
+
+ BsonTemplateEvaluator *t = new BsonTemplateEvaluator();
+ int randValue1, randValue2;
+
+ // Test failure when the arguments to RAND_INT are not integers
+ BSONObjBuilder builder1;
+ BSONObj randObj = BSON( "#RAND_INT" << BSON_ARRAY("hello" << "world") );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
+ t->evaluate(BSON("randField" << randObj), builder1) );
+
+ // Test failure when operator does not exists
+ BSONObjBuilder builder2;
+ randObj = BSON( "#RAND_OP_NOT_EXISTS" << BSON_ARRAY( 5 << 0 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusBadOperator,
+ t->evaluate(BSON("randField" << randObj), builder2) );
+
+ // Test failure when arguments to RAND_INT are not correct (max < min)
+ BSONObjBuilder builder3;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 5 << 0 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
+ t->evaluate(BSON("randField" << randObj), builder3) );
+
+ // Test failure when operators are arbitrarily nested
+ // {id: { #RAND_INT: [ { #RAND_INT: [10, 20] }, 20] }
+ BSONObjBuilder builder4;
+ BSONObj innerRandObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ BSONObj outerRandObj = BSON( "#RAND_INT" << BSON_ARRAY( innerRandObj << 10 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
+ t->evaluate(BSON("randField" << outerRandObj), builder4) );
+
+ // Test success with a single element
+ BSONObjBuilder builder5;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randField" << randObj), builder5) );
+ BSONObj obj5 = builder5.obj();
+ ASSERT_EQUALS(obj5.nFields(), 1);
+ ASSERT_GREATER_THAN_OR_EQUALS(obj5.firstElement().numberInt(), 0);
+ ASSERT_LESS_THAN(obj5.firstElement().numberInt(), 5);
+
+ // Test success with two #RAND_INT elements
+ BSONObjBuilder builder6;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randField1" << randObj <<
+ "randField2" << randObj), builder6) );
+ BSONObj obj6 = builder6.obj();
+ ASSERT_EQUALS(obj6.nFields(), 2);
+ BSONObjIterator iter6(obj6);
+ randValue1 = iter6.next().numberInt();
+ randValue2 = iter6.next().numberInt();
+ ASSERT_GREATER_THAN_OR_EQUALS(randValue1, 0);
+ ASSERT_LESS_THAN(randValue1, 5);
+ ASSERT_GREATER_THAN_OR_EQUALS(randValue2, 0);
+ ASSERT_LESS_THAN(randValue2, 5);
+
+ // Test success with #RAND_INT as the last element
+ BSONObjBuilder builder7;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("id" << 1 << "hello" << "world" <<
+ "randField" << randObj), builder7) );
+ BSONObj obj7 = builder7.obj();
+ ASSERT_EQUALS(obj7.nFields(), 3);
+ BSONObjIterator iter7(obj7);
+ iter7++;
+ randValue1 = iter7.next().numberInt();
+ ASSERT_GREATER_THAN_OR_EQUALS(randValue1, 0);
+ ASSERT_LESS_THAN(randValue1, 5);
+
+ // Test success with #RAND_INT as first element
+ BSONObjBuilder builder8;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randField" << randObj << "hello" << "world" <<
+ "id" << 1), builder8) );
+ BSONObj obj8 = builder8.obj();
+ ASSERT_EQUALS(obj8.nFields(), 3);
+ ASSERT_GREATER_THAN_OR_EQUALS(obj8.firstElement().numberInt(), 0);
+ ASSERT_LESS_THAN(obj8.firstElement().numberInt(), 5);
+
+ // Test success with #RAND_INT as the middle element
+ BSONObjBuilder builder9;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("id" << 1 << "randField" << randObj << "hello" <<
+ "world"), builder9) );
+ BSONObj obj9 = builder9.obj();
+ ASSERT_EQUALS(obj9.nFields(), 3);
+ BSONObjIterator iter9(obj9);
+ randValue1 = iter9.next().numberInt();
+ ASSERT_GREATER_THAN_OR_EQUALS(randValue1, 0);
+ ASSERT_LESS_THAN(randValue1, 5);
+
+ // Test success with #RAND_INT as the first and the last element
+ BSONObjBuilder builder10;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randField1" << randObj << "hello" <<
+ "world" << "randField2" << randObj), builder10) );
+ BSONObj obj10 = builder10.obj();
+ ASSERT_EQUALS(obj10.nFields(), 3);
+
+ BSONObjIterator iter10(obj10);
+ randValue1 = (*iter10).numberInt();
+ ASSERT_GREATER_THAN_OR_EQUALS(randValue1, 0);
+ ASSERT_LESS_THAN(randValue1, 5);
+ iter10++;
+ randValue2 = iter10.next().numberInt();
+ ASSERT_GREATER_THAN_OR_EQUALS(randValue2, 0);
+ ASSERT_LESS_THAN(randValue2, 5);
+
+ // Test success when one of the element is an array
+ BSONObjBuilder builder11;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("testArray" << BSON_ARRAY( 0 << 5 << 10 << 20 ) <<
+ "hello" << "world" <<
+ "randField" << randObj), builder11) );
+ BSONObj obj11 = builder11.obj();
+ ASSERT_EQUALS(obj11.nFields(), 3);
+ BSONObjIterator iter11(obj11);
+ iter11++;
+ randValue1 = iter11.next().numberInt();
+ ASSERT_GREATER_THAN_OR_EQUALS(randValue1, 0);
+ ASSERT_LESS_THAN(randValue1, 5);
+
+ // Test success with a 3rd argument to #RAND_INT
+ BSONObjBuilder builder12;
+ randObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 << 4 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("id" << randObj), builder12) );
+ BSONObj obj12 = builder12.obj();
+ ASSERT_EQUALS(obj12.nFields(), 1);
+ ASSERT_GREATER_THAN_OR_EQUALS(obj12.firstElement().numberInt(), 0);
+ ASSERT_LESS_THAN_OR_EQUALS(obj12.firstElement().numberInt(), 16);
+ }
+
+ TEST(BSONTemplateEvaluatorTest, RAND_STRING) {
+
+ BsonTemplateEvaluator *t = new BsonTemplateEvaluator();
+
+ // Test failure when the arguments to RAND_STRING is not an integer
+ BSONObjBuilder builder1;
+ BSONObj randObj = BSON( "#RAND_STRING" << BSON_ARRAY("hello") );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
+ t->evaluate(BSON("randField" << randObj), builder1) );
+
+ // Test failure when there is more than 1 argument to RAND_STRING
+ BSONObjBuilder builder2;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY( 2 << 8 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
+ t->evaluate(BSON("randField" << randObj), builder2) );
+
+ // Test failure when length argument to RAND_STRING is 0
+ BSONObjBuilder builder3;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY(0 ) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
+ t->evaluate(BSON("randField" << randObj), builder3) );
+
+ // Test success with a single element
+ BSONObjBuilder builder4;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randField" << randObj), builder4) );
+ BSONObj obj4 = builder4.obj();
+ ASSERT_EQUALS(obj4.nFields(), 1);
+ ASSERT_EQUALS(obj4.firstElement().str().length(), 5U);
+
+ // Test success with two #RAND_STRING elements
+ BSONObjBuilder builder5;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randField1" << randObj <<
+ "randField2" << randObj), builder5) );
+ BSONObj obj5 = builder5.obj();
+ ASSERT_EQUALS(obj5.nFields(), 2);
+ BSONObjIterator iter5(obj5);
+ ASSERT_EQUALS(iter5.next().str().length(), 5U);
+ ASSERT_EQUALS(iter5.next().str().length(), 5U);
+
+ // Test success with #RAND_STRING as the last element
+ BSONObjBuilder builder6;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("id" << 1 << "hello" << "world" <<
+ "randField" << randObj), builder6) );
+ BSONObj obj6 = builder6.obj();
+ ASSERT_EQUALS(obj6.nFields(), 3);
+ BSONObjIterator iter6(obj6);
+ iter6++;
+ ASSERT_EQUALS(iter6.next().str().length(), 5U);
+
+ // Test success with #RAND_STRING as first element
+ BSONObjBuilder builder7;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randField" << randObj << "hello" << "world" <<
+ "id" << 1), builder7) );
+ BSONObj obj7 = builder7.obj();
+ ASSERT_EQUALS(obj7.nFields(), 3);
+ ASSERT_EQUALS(obj7.firstElement().str().length(), 5U);
+
+ // Test success with #RAND_STRING as the middle element
+ BSONObjBuilder builder8;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("id" << 1 << "randField" << randObj << "hello" <<
+ "world"), builder8) );
+ BSONObj obj8 = builder8.obj();
+ ASSERT_EQUALS(obj8.nFields(), 3);
+ BSONObjIterator iter8(obj8);
+ iter8++;
+ ASSERT_EQUALS((*iter8).str().length(), 5U);
+
+ // Test success with #RAND_INT as the first and the last element
+ BSONObjBuilder builder10;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randField1" << randObj << "hello" <<
+ "world" << "randField2" << randObj), builder10) );
+ BSONObj obj10 = builder10.obj();
+ ASSERT_EQUALS(obj10.nFields(), 3);
+
+ BSONObjIterator iter10(obj10);
+ ASSERT_EQUALS((*iter10).str().length(), 5U);
+ iter10++;
+ ASSERT_EQUALS(iter10.next().str().length(), 5U);
+
+ // Test success when one of the element is an array
+ BSONObjBuilder builder11;
+ randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("testArray" << BSON_ARRAY( 0 << 5 << 10 << 20 ) <<
+ "hello" << "world" <<
+ "randField" << randObj), builder11) );
+ BSONObj obj11 = builder11.obj();
+ ASSERT_EQUALS(obj11.nFields(), 3);
+ BSONObjIterator iter11(obj11);
+ iter11++;
+ ASSERT_EQUALS(iter11.next().str().length(), 5U);
+ }
+
+ TEST(BSONTemplateEvaluatorTest, CONCAT) {
+
+ BsonTemplateEvaluator *t = new BsonTemplateEvaluator();
+
+ // Test failure when the arguments to #CONCAT has only one argument
+ BSONObjBuilder builder1;
+ BSONObj concatObj = BSON( "#CONCAT" << BSON_ARRAY("hello") );
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
+ t->evaluate(BSON("concatField" << concatObj), builder1) );
+
+ // Test success when all arguments to #CONCAT are strings
+ BSONObjBuilder builder2;
+ concatObj = BSON( "#CONCAT" << BSON_ARRAY("hello" << " " << "world"));
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("concatField" << concatObj), builder2) );
+ BSONObj obj2 = builder2.obj();
+ ASSERT_EQUALS(obj2.nFields(), 1);
+ BSONObj expectedObj = BSON("concatField" << "hello world");
+ ASSERT_EQUALS(obj2.equal(expectedObj), true);
+
+ // Test success when some arguments to #CONCAT are integers
+ BSONObjBuilder builder3;
+ concatObj = BSON( "#CONCAT" << BSON_ARRAY("F" << 1 << "racing"));
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("concatField" << concatObj), builder3) );
+ BSONObj obj3 = builder3.obj();
+ ASSERT_EQUALS(obj3.nFields(), 1);
+ expectedObj = BSON("concatField" << "F1racing");
+ ASSERT_EQUALS(obj3.equal(expectedObj), true);
+
+ // Test success with #CONCAT as first element and last element
+ BSONObjBuilder builder4;
+ concatObj = BSON( "#CONCAT" << BSON_ARRAY("hello" << " " << "world"));
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("concatField1" << concatObj <<
+ "middleKey" << 1 <<
+ "concatField2" << concatObj), builder4) );
+ BSONObj obj4 = builder4.obj();
+ ASSERT_EQUALS(obj4.nFields(), 3);
+ expectedObj = BSON("concatField1" << "hello world" <<
+ "middleKey" << 1 <<
+ "concatField2" << "hello world");
+ ASSERT_EQUALS(obj4.equal(expectedObj), true);
+
+ // Test success when one of the arguments to #CONCAT is an array
+ BSONObjBuilder builder5;
+ concatObj = BSON( "#CONCAT" << BSON_ARRAY("hello" << BSON_ARRAY(1 << 10) << "world"));
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("concatField" << concatObj), builder5) );
+ BSONObj obj5 = builder5.obj();
+ ASSERT_EQUALS(obj5.nFields(), 1);
+ expectedObj = BSON("concatField" << "hello[ 1, 10 ]world");
+ ASSERT_EQUALS(obj5.equal(expectedObj), true);
+ }
+
+ TEST(BSONTemplateEvaluatorTest, COMBINED_OPERATORS) {
+
+ BsonTemplateEvaluator *t = new BsonTemplateEvaluator();
+ BSONObj randIntObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
+ BSONObj randStrObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
+
+ // Test success when #RAND_INT, and #RAND_STRING are combined
+ BSONObjBuilder builder1;
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("randInt" << randIntObj <<
+ "randStr" << randStrObj), builder1) );
+ BSONObj obj1 = builder1.obj();
+ ASSERT_EQUALS(obj1.nFields(), 2);
+ BSONObjIterator iter1(obj1);
+ int randInt = iter1.next().numberInt();
+ ASSERT_GREATER_THAN_OR_EQUALS(randInt, 0);
+ ASSERT_LESS_THAN(randInt, 5);
+ string randStr = iter1.next().str();
+ ASSERT_EQUALS(randStr.length(), 5U);
+
+ // Test success when the #CONCAT and #RAND_INT and #RAND_STRING are combined
+ BSONObjBuilder builder2;
+ BSONObj concatObj = BSON("#CONCAT" << BSON_ARRAY(randIntObj << " hello world " <<
+ randStrObj));
+ ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
+ t->evaluate(BSON("concatField" << concatObj), builder2) );
+ BSONObj obj2 = builder2.obj();
+ ASSERT_EQUALS(obj2.nFields(), 1);
+ // check that the resulting string has a length of 19.
+ // randIntObj.length = 1, " hello world " = 13, randStrObj.length = 5
+ // so total string length should 1 + 13 + 5 = 19
+ ASSERT_EQUALS(obj2.firstElement().str().length(), 19U);
+ }
+ } // end anonymous namespace
+} // end namespace mongo
diff --git a/src/mongo/scripting/engine.cpp b/src/mongo/scripting/engine.cpp
new file mode 100644
index 00000000000..9da44fe9b3e
--- /dev/null
+++ b/src/mongo/scripting/engine.cpp
@@ -0,0 +1,521 @@
+// 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 "mongo/client/dbclientinterface.h"
+#include "mongo/client/dbclientcursor.h"
+#include <boost/filesystem/operations.hpp>
+
+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 ) {
+
+ boost::filesystem::path p( filename );
+
+ if ( ! exists( p ) ) {
+ log() << "file [" << filename << "] doesn't exist" << endl;
+ if ( assertOnError )
+ verify( 0 );
+ return false;
+ }
+
+ // iterate directories and recurse using all *.js files in the directory
+ if ( boost::filesystem::is_directory( p ) ) {
+ boost::filesystem::directory_iterator end;
+ bool empty = true;
+ for (boost::filesystem::directory_iterator it (p); it != end; it++) {
+ empty = false;
+ boost::filesystem::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 )
+ verify( 0 );
+ return false;
+ }
+
+ return true;
+ }
+
+ File f;
+ f.open( filename.c_str() , true );
+
+ unsigned L;
+ {
+ fileofs fo = f.len();
+ verify( 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 );
+ verify( 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() {
+ verify( _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() > 10 || 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;
+ verify( ! 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..6b0064d28e6
--- /dev/null
+++ b/src/mongo/scripting/engine.h
@@ -0,0 +1,231 @@
+// 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 "mongo/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();
+
+ 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_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..4909ddd9763
--- /dev/null
+++ b/src/mongo/scripting/engine_spidermonkey.cpp
@@ -0,0 +1,2010 @@
+// 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 "mongo/scripting/engine_spidermonkey.h"
+
+#include <boost/smart_ptr/scoped_array.hpp>
+#include <boost/thread/recursive_mutex.hpp>
+#ifndef _WIN32
+#include <boost/date_time/posix_time/posix_time.hpp>
+#endif
+
+#include <third_party/js-1.7/jsdate.h>
+
+#include "mongo/util/mongoutils/str.h"
+
+#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 {
+
+ 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 boost::recursive_mutex::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* jsString ) {
+ size_t srclen = JS_GetStringLength( jsString );
+ if( srclen == 0 )
+ return "";
+
+ size_t len = (srclen * 6) + 1;
+ boost::scoped_array<char> utf8Chars( new char[len] );
+ jschar* utf16Chars = JS_GetStringChars( jsString );
+ if ( !JS_EncodeCharacters( _context, utf16Chars, srclen, utf8Chars.get(), &len ) ) {
+ uasserted( 16268, "error converting UTF-16 string to UTF-8" );
+ }
+ return string( utf8Chars.get(), len );
+ }
+
+ 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;
+ verify( JS_ValueToBoolean( _context, v , &b ) );
+ return b;
+ }
+
+ OID toOID( jsval v ) {
+ JSContext * cx = _context;
+ verify( 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 );
+ verify( holder );
+ return holder->_obj.getOwned();
+ }
+
+ BSONObj orig;
+ if ( JS_InstanceOf( _context , o , &bson_class , 0 ) ) {
+ BSONHolder * holder = GETHOLDER(_context,o);
+ verify( 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 );
+ verify( properties );
+
+ try {
+ for ( jsint i=0; i<properties->length; i++ ) {
+ jsid id = properties->vector[i];
+ jsval nameval;
+ verify( 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 ) );
+ }
+ }
+ catch ( const AssertionException& ) {
+ JS_DestroyIdArray( _context , properties );
+ throw;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16269 );
+ }
+ 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 ) {
+ verify( 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;
+ }
+
+ JSFunction * compileFunction( const char * code, JSObject * assoc = 0 ) {
+ const char * gcName = "unknown";
+ JSFunction * f = _compileFunction( code , assoc , 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( '(' );
+ verify( 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( ')' );
+ verify( 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;
+ verify( 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;
+ uassert( 16270, "conversion from string to JavaScript value failed", false );
+ }
+
+ 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");
+ verify( 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");
+ verify( 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 );
+ verify( 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 );
+ verify( 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 );
+ verify( data );
+ verify( 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;
+ verify( 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;
+ verify( 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;
+ verify( JS_GetProperty( _context , o , field , &v ) );
+ return v;
+ }
+
+ void setProperty( JSObject * o , const char * field , jsval v ) {
+ verify( 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;
+ verify( 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 ) {
+ try {
+ BSONHolder * o = GETHOLDER( cx , obj );
+ if ( o ) {
+ delete o;
+ verify( JS_SetPrivate( cx , obj , 0 ) );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) JS_ReportError( cx, e.what() );
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16271 );
+ }
+ }
+
+ JSBool bson_enumerate( JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp ) {
+ try {
+ 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);
+ verify( 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;
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16272 );
+ }
+ }
+
+ JSBool noaccess( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) {
+ try {
+ 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;
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16273 );
+ }
+ }
+
+ JSClass bson_ro_class = {
+ "bson_ro_object", // class name
+ JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE, // flags
+ noaccess, // addProperty
+ noaccess, // delProperty
+ JS_PropertyStub, // getProperty
+ noaccess, // setProperty
+ (JSEnumerateOp)bson_enumerate, // enumerate
+ (JSResolveOp)resolveBSONField, // resolve
+ JS_ConvertStub, // convert
+ bson_finalize, // finalize
+ JSCLASS_NO_OPTIONAL_MEMBERS // optional members
+ };
+
+ JSBool bson_cons( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ 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) {
+ try {
+ 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;
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16274 );
+ }
+ return JS_TRUE;
+ }
+
+
+ JSBool mark_modified( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) {
+ try {
+ 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 ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16275 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool mark_modified_remove( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) {
+ try {
+ Convertor c(cx);
+ BSONHolder * holder = GETHOLDER( cx , obj );
+ if ( holder->_inResolve )
+ return JS_TRUE;
+ holder->_modified = true;
+ holder->_removed.insert( c.toString( idval ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16276 );
+ }
+ return JS_TRUE;
+ }
+
+ JSClass bson_class = {
+ "bson_object", // class name
+ JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE, // flags
+ bson_add_prop, // addProperty
+ mark_modified_remove, // delProperty
+ JS_PropertyStub, // getProperty
+ mark_modified, // setProperty
+ (JSEnumerateOp)bson_enumerate, // enumerate
+ (JSResolveOp)resolveBSONField, // resolve
+ JS_ConvertStub, // convert
+ bson_finalize, // finalize
+ JSCLASS_NO_OPTIONAL_MEMBERS // optional members
+ };
+
+ static JSClass global_class = {
+ "global", // class name
+ JSCLASS_GLOBAL_FLAGS, // flags
+ JS_PropertyStub, // addProperty
+ JS_PropertyStub, // delProperty
+ JS_PropertyStub, // getProperty
+ JS_PropertyStub, // setProperty
+ JS_EnumerateStub, // enumerate
+ JS_ResolveStub, // resolve
+ JS_ConvertStub, // convert
+ JS_FinalizeStub, // finalize
+ JSCLASS_NO_OPTIONAL_MEMBERS // optional members
+ };
+
+ // --- global helpers ---
+
+ static void hexToBinData( JSContext* cx,
+ Convertor* c,
+ jsval* rval,
+ int subtype,
+ const string& s ) {
+ JSObject * o = JS_NewObject( cx , &bindata_class , 0 , 0 );
+ CHECKNEWOBJECT(o,_context,"Bindata_BinData1");
+ size_t s_size = s.size();
+ int len = s_size / 2;
+ boost::scoped_array<char> data( new char[len] );
+ char* p = data.get();
+ const char *src = s.c_str();
+ for( size_t i = 0; i+1 < s_size; i += 2 ) {
+ *p++ = fromHex( src + i );
+ }
+ verify( JS_SetPrivate( cx, o, new BinDataHolder( data.get(), len ) ) );
+ c->setProperty( o, "len", c->toval( static_cast<double>(len) ) );
+ c->setProperty( o, "type", c->toval( static_cast<double>(subtype) ) );
+ *rval = OBJECT_TO_JSVAL( o );
+ }
+
+ static bool testHexString( JSContext* cx, const string& hexString ) {
+ size_t len = hexString.length();
+ for ( size_t i = 0; i < len; ++i ) {
+ char ch = hexString[i];
+ if ( (ch>='0' && ch<='9') || (ch>='a' && ch<='f') || (ch>='A' && ch<='F') ) {
+ continue;
+ }
+ JS_ReportError( cx, "invalid hexstring character '%c'", ch );
+ return false;
+ }
+ return true;
+ }
+
+ JSBool _HexData( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) {
+ try {
+ if ( argc != 2 ) {
+ JS_ReportError( cx , "HexData needs 2 arguments -- HexData(subtype,hexstring)" );
+ return JS_FALSE;
+ }
+ Convertor c( cx );
+ int subtype = static_cast<int>( c.toNumber( argv[ 0 ] ) );
+ if ( subtype == 2 ) {
+ JS_ReportError( cx , "BinData subtype 2 is deprecated" );
+ return JS_FALSE;
+ }
+ else if ( subtype < 0 || subtype > 255 ) {
+ JS_ReportError( cx, "subtype must be between 0 and 255" );
+ return JS_FALSE;
+ }
+ string s( c.toString( argv[1] ) );
+ if ( ! testHexString( cx, s ) ) {
+ return JS_FALSE;
+ }
+ size_t len = s.length();
+ if ( 0 != ( len % 2 ) ) {
+ JS_ReportError( cx, "hexstring must be even length" );
+ return JS_FALSE;
+ }
+ hexToBinData(cx, &c, rval, subtype, s);
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16277 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool _UUID( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) {
+ try {
+ if ( argc != 1 ) {
+ JS_ReportError( cx , "UUID needs argument -- UUID(hexstring)" );
+ return JS_FALSE;
+ }
+ Convertor c( cx );
+ string s( c.toString( argv[0] ) );
+ if ( ! testHexString( cx, s ) ) {
+ return JS_FALSE;
+ }
+ size_t len = s.length();
+ if ( len != 32 ) {
+ JS_ReportError( cx, "UUID hexstring length is %d, must be 32", len );
+ return JS_FALSE;
+ }
+ hexToBinData(cx, &c, rval, 3, s);
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16278 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool _MD5( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) {
+ try {
+ if ( argc != 1 ) {
+ JS_ReportError( cx , "MD5 needs argument -- MD5(hexstring)" );
+ return JS_FALSE;
+ }
+ Convertor c( cx );
+ string s( c.toString( argv[0] ) );
+ if ( ! testHexString( cx, s ) ) {
+ return JS_FALSE;
+ }
+ size_t len = s.length();
+ if ( len != 32 ) {
+ JS_ReportError( cx, "MD5 hexstring length is %d, must be 32", len );
+ return JS_FALSE;
+ }
+ hexToBinData(cx, &c, rval, 5, s);
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16279 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool native_print( JSContext * cx, JSObject * obj, uintN argc, jsval *argv, jsval *rval ) {
+ stringstream ss;
+ bool someWritten = false;
+ try {
+ Convertor c( cx );
+ for ( uintN i=0; i<argc; i++ ) {
+ if ( i > 0 )
+ ss << " ";
+ ss << c.toString( argv[i] );
+ someWritten = true;
+ }
+ ss << "\n";
+ Logstream::logLockless( ss.str() );
+ }
+ catch ( const AssertionException& ) {
+ if ( someWritten ) {
+ ss << "\n";
+ Logstream::logLockless( ss.str() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16280 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool native_helper( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) {
+ try {
+ Convertor c(cx);
+ NativeFunction func = reinterpret_cast<NativeFunction>(
+ static_cast<long long>( c.getNumber( obj , "x" ) ) );
+ void* data = reinterpret_cast<void*>(
+ static_cast<long long>( c.getNumber( obj , "y" ) ) );
+ verify( 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 ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+
+ if ( out.isEmpty() ) {
+ *rval = JSVAL_VOID;
+ }
+ else {
+ *rval = c.toval( out.firstElement() );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16281 );
+ }
+ 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) {
+ try {
+ 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.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 );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16282 );
+ }
+ 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 ) {
+ try {
+ verify( JS_EnterLocalRootScope( cx ) );
+ }
+ catch ( const AssertionException& e ) {
+ JS_ReportError( cx, e.what() );
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16283 );
+ }
+
+ try {
+ BSONHolder * holder = GETHOLDER( cx , obj );
+ if ( ! holder ) {
+ // static init
+ *objp = 0;
+ JS_LeaveLocalRootScope( cx );
+ return JS_TRUE;
+ }
+ holder->check();
+
+ Convertor c( cx );
+ string s( c.toString( id ) );
+ BSONElement e = holder->_obj[ s.c_str() ];
+ if ( e.type() == EOO || holder->_removed.count( s ) ) {
+ *objp = 0;
+ JS_LeaveLocalRootScope( cx );
+ return JS_TRUE;
+ }
+
+ jsval val = c.toval( e );
+
+ verify( ! holder->_inResolve );
+ holder->_inResolve = true;
+ verify( 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 );
+ }
+ catch ( const AssertionException& e ) {
+ JS_LeaveLocalRootScope( cx );
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16284 );
+ }
+ 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;
+ }
+ }
+
+ ~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 ),
+ _reportError( true ),
+ _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 );
+
+ 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;
+ verify( _convertor );
+ }
+
+ 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;
+ verify( JS_GetProperty( _context , _global , field , &val ) );
+ return _convertor->toNumber( val );
+ }
+
+ string getString( const char *field ) {
+ smlock;
+ jsval val;
+ verify( 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;
+ verify( 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 );
+ verify( JS_SetProperty( _context , _global , field , &v ) );
+ }
+
+ void setNumber( const char *field , double val ) {
+ smlock;
+ jsval v = _convertor->toval( val );
+ verify( JS_SetProperty( _context , _global , field , &v ) );
+ }
+
+ void setString( const char *field , const char * val ) {
+ smlock;
+ jsval v = _convertor->toval( val );
+ verify( 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 );
+ verify( 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;
+ verify( JS_GetProperty( _context , _global , from , &v ) );
+ verify( JS_SetProperty( _context , _global , to , &v ) );
+ v = JSVAL_VOID;
+ verify( 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 isReportingErrors() const { return _reportError; }
+
+ 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 );
+ _reportError = reportError;
+ JSBool worked = JS_EvaluateScript( _context,
+ _global,
+ code.data(),
+ code.size(),
+ name.c_str(),
+ 1,
+ &ret );
+ _reportError = true;
+ 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, mongoutils::str::stream() << name + " exec failed: " << _error,
+ worked || ! assertOnError );
+
+ 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();
+
+ try {
+ verify( JS_EnterLocalRootScope( _context ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( _context ) ) {
+ JS_ReportError( _context, e.what() );
+ }
+ return 0;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16285 );
+ }
+
+ int nargs = args ? args->nFields() : 0;
+ scoped_array<jsval> smargsPtr( new jsval[nargs] );
+ try {
+ 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
+ }
+ }
+ catch ( const AssertionException& e ) {
+ JS_LeaveLocalRootScope( _context );
+ if ( ! JS_IsExceptionPending( _context ) ) {
+ JS_ReportError( _context, e.what() );
+ }
+ return 0;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16286 );
+ }
+ 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 ) {
+ verify( 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( reinterpret_cast<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;
+ jsval v;
+ v = _convertor->toval( static_cast<double>( reinterpret_cast<long long>(func) ) );
+ _convertor->setProperty( _global, (name + "_").c_str(), v );
+
+ stringstream code;
+ if (data) {
+ v = _convertor->toval( static_cast<double>( reinterpret_cast<long long>(data) ) );
+ _convertor->setProperty( _global, (name + "_data_").c_str(), v );
+ 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;
+ bool _reportError;
+
+ 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;
+ }
+
+ if ( !currentScope.get() || currentScope->isReportingErrors() ) {
+ tlog() << ss.str() << endl;
+ }
+
+ if ( currentScope.get() ) {
+ currentScope->gotError( ss.str() );
+ }
+ }
+
+ JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) {
+ try {
+ 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;
+ }
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16287 );
+ }
+ return JS_TRUE;
+ }
+
+
+
+ void SMEngine::runTest() {
+ SMScope s;
+
+ s.localConnect( "foo" );
+
+ s.exec( "verify( db.getMongo() )" );
+ s.exec( "verify( db.bar , 'collection getting does not work' ); " );
+ s.exec( "assert.eq( db._name , 'foo' );" );
+ s.exec( "verify( _mongo == db.getMongo() ); " );
+ s.exec( "verify( _mongo == db._mongo ); " );
+ s.exec( "verify( typeof DB.bar == 'undefined' ); " );
+ s.exec( "verify( typeof DB.prototype.bar == 'undefined' , 'resolution is happening on prototype, not object' ); " );
+
+ s.exec( "verify( db.bar ); " );
+ s.exec( "verify( typeof db.addUser == 'function' )" );
+ s.exec( "verify( 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' )" );
+
+ verify( (string)"abc" == trim( "abc" ) );
+ verify( (string)"abc" == trim( " abc" ) );
+ verify( (string)"abc" == trim( "abc " ) );
+ verify( (string)"abc" == trim( " abc " ) );
+
+ }
+
+ Scope * SMEngine::createScope() {
+ return new SMScope();
+ }
+
+}
+#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..37dcda30975
--- /dev/null
+++ b/src/mongo/scripting/engine_spidermonkey.h
@@ -0,0 +1,101 @@
+// 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 "mongo/scripting/engine.h"
+
+// START inc hacking
+
+#ifdef WIN32
+#include <third_party/js-1.7/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 <third_party/js-1.7/jsapi.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..15d0fba2f29
--- /dev/null
+++ b/src/mongo/scripting/engine_v8.cpp
@@ -0,0 +1,1706 @@
+//engine_v8.cpp
+
+/* Copyright 2009 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "engine_v8.h"
+
+#include "v8_wrapper.h"
+#include "v8_utils.h"
+#include "v8_db.h"
+
+#define V8_SIMPLE_HEADER v8::Locker l(_isolate); v8::Isolate::Scope iscope(_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 BSONObj();
+ }
+ void* ptr = field->Value();
+ return ((BSONHolder*)ptr)->_obj;
+ }
+
+ static BSONHolder* unwrapHolder(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 (BSONHolder*)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));
+ BSONHolder* data = (BSONHolder*) field->Value();
+ delete data;
+ p.Dispose();
+ }
+
+ Persistent<v8::Object> V8Scope::wrapBSONObject(Local<v8::Object> obj, BSONHolder* 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) {
+ if ( info.This()->HasRealNamedProperty( name ) ) {
+ // value already cached
+ return info.This()->GetRealNamedProperty(name);
+ }
+
+ string key = toSTLString(name);
+ BSONHolder* holder = unwrapHolder(info.Holder());
+ if ( holder->_removed.count( key ) )
+ return Handle<Value>();
+
+ BSONObj obj = holder->_obj;
+ 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, false);
+ info.This()->ForceSet(name, val, DontEnum);
+
+ 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
+ unwrapHolder(info.Holder())->_modified = 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) {
+ string key = toSTLString( name );
+ BSONHolder* holder = unwrapHolder(info.Holder());
+ holder->_removed.erase( key );
+ holder->_extra.push_back( key );
+ holder->_modified = true;
+
+ // set into JS object
+ return Handle<Value>();
+ }
+
+ static Handle<v8::Array> namedEnumerator(const AccessorInfo &info) {
+ BSONHolder* holder = unwrapHolder(info.Holder());
+ BSONObj obj = holder->_obj;
+ 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());
+
+ set<string> added;
+ // 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())));
+ string sname = f.fieldName();
+ if ( holder->_removed.count( sname ) )
+ continue;
+
+ Handle<v8::String> name = scope->getV8Str( sname );
+ added.insert( sname );
+ arr->Set(i, name);
+ }
+
+ for ( list<string>::iterator it = holder->_extra.begin(); it != holder->_extra.end(); it++ ) {
+ string sname = *it;
+ if ( added.count( sname ) )
+ continue;
+ arr->Set(i++, scope->getV8Str( sname ));
+ }
+ return arr;
+ }
+
+ Handle<Boolean> namedDelete( Local<v8::String> name, const AccessorInfo& info ) {
+ string key = toSTLString( name );
+ BSONHolder* holder = unwrapHolder(info.Holder());
+ holder->_removed.insert( key );
+ holder->_extra.remove( key );
+ holder->_modified = true;
+
+ // also delete in JS obj
+ 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) {
+ StringBuilder ss;
+ ss << index;
+ string key = ss.str();
+ Local< External > scp = External::Cast( *info.Data() );
+ V8Scope* scope = (V8Scope*)(scp->Value());
+ Handle<v8::String> name = scope->getV8Str(key);
+
+ if ( info.This()->HasRealIndexedProperty( index ) ) {
+ // value already cached
+ return info.This()->GetRealNamedProperty( name );
+ }
+
+ BSONHolder* holder = unwrapHolder(info.Holder());
+ if ( holder->_removed.count( key ) )
+ return Handle<Value>();
+
+ BSONObj obj = holder->_obj;
+ BSONElement elmt = obj.getField(key);
+ if (elmt.eoo())
+ return Handle<Value>();
+ Handle<Value> val = scope->mongoToV8Element(elmt, false);
+ info.This()->ForceSet(name, val, DontEnum);
+
+ 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
+ unwrapHolder(info.Holder())->_modified = true;
+ }
+ return val;
+ }
+
+ Handle<Boolean> indexedDelete( ::uint32_t index, const AccessorInfo& info ) {
+ StringBuilder ss;
+ ss << index;
+ string key = ss.str();
+
+ BSONHolder* holder = unwrapHolder(info.Holder());
+ holder->_removed.insert( key );
+ holder->_extra.remove( key );
+ holder->_modified = true;
+
+ // also delete in JS obj
+ 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) {
+ StringBuilder ss;
+ ss << index;
+ string key = ss.str();
+ BSONHolder* holder = unwrapHolder(info.Holder());
+ holder->_removed.erase( key );
+ holder->_extra.push_back( key );
+ holder->_modified = true;
+
+ // set into JS object
+ 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 << endl;
+// }
+
+ void gcCallback(GCType type, GCCallbackFlags flags) {
+ HeapStatistics stats;
+ V8::GetHeapStatistics( &stats );
+ log(1) << "V8 GC heap stats - "
+ << " total: " << stats.total_heap_size()
+ << " exec: " << stats.total_heap_size_executable()
+ << " used: " << stats.used_heap_size()<< " limit: "
+ << stats.heap_size_limit()
+ << endl;
+ }
+
+ V8ScriptEngine::V8ScriptEngine() {
+ // set resource contraints before any call
+ 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 );
+
+ // keep engine up after OOM
+ v8::V8::IgnoreOutOfMemoryException();
+// v8::V8::SetFatalErrorHandler(fatalHandler);
+
+// v8::Locker l;
+// v8::Locker::StartPreemption( 10 );
+
+ v8::V8::Initialize();
+ }
+
+ 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 ) {
+
+ // create new isolate and enter it via a scope
+ _isolate = v8::Isolate::New();
+ v8::Isolate::Scope iscope(_isolate);
+
+ // resource constraints must be set on isolate, before any call or lock
+ 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::AddGCPrologueCallback(gcCallback, kGCTypeMarkSweepCompact);
+
+ // keep engine up after OOM
+ v8::V8::IgnoreOutOfMemoryException();
+// V8::SetFatalErrorHandler(fatalHandler);
+
+ 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() );
+
+ 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_FULLNAME = getV8Str( "_fullName" );
+ V8STR_BSON = getV8Str( "_bson" );
+
+ // initialize lazy object template
+ lzObjectTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ lzObjectTemplate->SetInternalFieldCount( 1 );
+ lzObjectTemplate->SetNamedPropertyHandler(namedGet, namedSet, 0, namedDelete, namedEnumerator, v8::External::New(this));
+ lzObjectTemplate->SetIndexedPropertyHandler(indexedGet, indexedSet, 0, indexedDelete, namedEnumerator, v8::External::New(this));
+ lzObjectTemplate->NewInstance()->GetPrototype()->ToObject()->Set(V8STR_BSON, v8::Boolean::New(true), DontEnum);
+
+ 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));
+ roObjectTemplate->NewInstance()->GetPrototype()->ToObject()->Set(V8STR_BSON, v8::Boolean::New(true), DontEnum);
+
+ // 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));
+ lzArrayTemplate->NewInstance()->GetPrototype()->ToObject()->Set(V8STR_BSON, v8::Boolean::New(true), DontEnum);
+
+ internalFieldObjects = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ internalFieldObjects->SetInternalFieldCount( 1 );
+
+ 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 << "____MongoToV8_newFunction_temp = " << code;
+ string codeStr = codeSS.str();
+ Local< Script > compiled = Script::New( v8::String::New( codeStr.c_str() ) );
+ if ( compiled.IsEmpty() ) {
+ warning() << "Could not compile function: " << codeStr.c_str() << endl;
+ return Local< v8::Value >::New( v8::Null() );
+ }
+ 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;
+ BSONHolder* own = new BSONHolder(m);
+
+ if ( readOnly ) {
+ o = roObjectTemplate->NewInstance();
+ } else {
+ if (array) {
+ o = lzArrayTemplate->NewInstance();
+ o->SetPrototype(v8::Array::New(1)->GetPrototype());
+ o->Set(V8STR_LENGTH, v8::Integer::New(m.nFields()), 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());
+ }
+ }
+ }
+ }
+
+ Persistent<v8::Object> p = wrapBSONObject(o, own);
+
+// if (!readOnly) {
+// // 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());
+// }
+// own->_modified = false;
+// }
+
+ 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:
+ verify( "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;
+ if (o->Has(V8STR_BSON)) {
+ originalBSON = unwrapBSONObj(o);
+ BSONHolder* holder = unwrapHolder(o);
+ if ( !holder->_modified ) {
+ // object was not modified, 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..2991022421b
--- /dev/null
+++ b/src/mongo/scripting/engine_v8.h
@@ -0,0 +1,269 @@
+//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 BSONHolder {
+ public:
+
+ BSONHolder( BSONObj obj ) {
+ _obj = obj.getOwned();
+ _modified = false;
+ }
+
+ ~BSONHolder() {
+ }
+
+ BSONObj _obj;
+ bool _modified;
+ list<string> _extra;
+ set<string> _removed;
+
+ };
+
+ 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, BSONHolder* 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_FULLNAME;
+ Handle<v8::String> V8STR_BSON;
+
+ 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::FunctionTemplate> lzFunctionTemplate;
+ 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; }
+
+ 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..e7b547b8df8
--- /dev/null
+++ b/src/mongo/scripting/sm_db.cpp
@@ -0,0 +1,1716 @@
+// 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 "mongo/client/dbclientcursor.h"
+#include "mongo/db/namespacestring.h"
+#include "mongo/util/base64.h"
+#include "mongo/util/text.h"
+
+#if( BOOST_VERSION >= 104200 )
+#define HAVE_UUID 1
+#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 ) {
+ verify( 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 ) {
+ try {
+ uassert( 10236 , "no args to internal_cursor_constructor" , argc == 0 );
+ verify( JS_SetPrivate( cx , obj , 0 ) ); // just for safety
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16288 );
+ }
+ return JS_TRUE;
+ }
+
+ void internal_cursor_finalize( JSContext * cx , JSObject * obj ) {
+ try {
+ CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj );
+ if ( holder ) {
+ delete holder;
+ verify( JS_SetPrivate( cx , obj , 0 ) );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16289 );
+ }
+ }
+
+ JSBool internal_cursor_hasNext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ DBClientCursor *cursor = getCursor( cx, obj );
+ *rval = cursor->more() ? JSVAL_TRUE : JSVAL_FALSE;
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16290 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool internal_cursor_objsLeftInBatch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ DBClientCursor *cursor = getCursor( cx, obj );
+ Convertor c(cx);
+ *rval = c.toval((double) cursor->objsLeftInBatch() );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16291 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool internal_cursor_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ DBClientCursor *cursor = getCursor( cx, obj );
+ if ( ! cursor->more() ) {
+ JS_ReportError( cx , "cursor at the end" );
+ return JS_FALSE;
+ }
+
+ BSONObj n = cursor->next();
+ Convertor c(cx);
+ *rval = c.toval( &n );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16292 );
+ }
+ 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", // class name
+ JSCLASS_HAS_PRIVATE, // flags
+ JS_PropertyStub, // addProperty
+ JS_PropertyStub, // delProperty
+ JS_PropertyStub, // getProperty
+ JS_PropertyStub, // setProperty
+ JS_EnumerateStub, // enumerate
+ JS_ResolveStub, // resolve
+ JS_ConvertStub, // convert
+ internal_cursor_finalize, // finalize
+ JSCLASS_NO_OPTIONAL_MEMBERS // optional members
+ };
+
+
+ // ------ mongo stuff ------
+
+ JSBool mongo_local_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ try {
+ shared_ptr< DBClientWithCommands > client( createDirectClient() );
+ verify( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( client ) ) ) );
+
+ Convertor c( cx );
+ jsval host = c.toval( "EMBEDDED" );
+ verify( JS_SetProperty( cx , obj , "host" , &host ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16293 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool mongo_external_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
+ try {
+ smuassert( cx , "0 or 1 args to Mongo" , argc <= 1 );
+
+ string host( "127.0.0.1" );
+ Convertor c( cx );
+ 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 ( const AssertionException& 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;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16294 );
+ }
+
+ verify( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( conn ) ) ) );
+ jsval host_val = c.toval( host.c_str() );
+ verify( JS_SetProperty( cx , obj , "host" , &host_val ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16295 );
+ }
+ 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 ) {
+ try {
+ shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj );
+ if ( connHolder ) {
+ delete connHolder;
+ verify( JS_SetPrivate( cx , obj , 0 ) );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16296 );
+ }
+ }
+
+ JSClass mongo_class = {
+ "Mongo", // class name
+ JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE, // flags
+ JS_PropertyStub, // addProperty
+ JS_PropertyStub, // delProperty
+ JS_PropertyStub, // getProperty
+ JS_PropertyStub, // setProperty
+ JS_EnumerateStub, // enumerate
+ JS_ResolveStub, // resolve
+ JS_ConvertStub, // convert
+ mongo_finalize, // finalize
+ JSCLASS_NO_OPTIONAL_MEMBERS // optional members
+ };
+
+ JSBool mongo_auth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ 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 = "";
+
+ if ( ! conn->auth( db, username, password, errmsg ) ) {
+ JS_ReportError( cx, errmsg.c_str() );
+ return JS_FALSE;
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const SocketException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.toString().c_str() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16297 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool mongo_logout(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ smuassert(context, "mongo_logout needs 1 arg" , argc == 1);
+ shared_ptr<DBClientWithCommands>* connHolder =
+ reinterpret_cast<shared_ptr<DBClientWithCommands>*>(
+ JS_GetPrivate(context , obj));
+ smuassert(context, "no connection!", connHolder && connHolder->get());
+ DBClientWithCommands *conn = connHolder->get();
+
+ Convertor convertor(context);
+ string db = convertor.toString(argv[0]);
+
+ BSONObj ret;
+ conn->logout(db, ret);
+ *rval = convertor.toval(&ret);
+ }
+ catch (const AssertionException& e) {
+ if (!JS_IsExceptionPending(context)) {
+ JS_ReportError(context, e.what());
+ }
+ return JS_FALSE;
+ }
+ catch (const SocketException& e) {
+ if (!JS_IsExceptionPending(context)) {
+ JS_ReportError(context, e.toString().c_str());
+ }
+ return JS_FALSE;
+ }
+ catch (const std::exception& e) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed(16396);
+ }
+
+ return JS_TRUE;
+ }
+
+ JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ 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] );
+
+ 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" );
+ verify( JS_SetPrivate( cx , mycursor , new CursorHolder( cursor, *connHolder ) ) );
+ *rval = OBJECT_TO_JSVAL( mycursor );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const SocketException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.toString().c_str() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16298 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool mongo_update(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ 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] );
+
+ conn->update( ns , c.toObject( argv[1] ) , c.toObject( argv[2] ) , upsert , multi );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const SocketException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.toString().c_str() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16299 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool mongo_insert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ 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] );
+
+ 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 );
+ }
+ else {
+ BSONObj o = c.toObject( argv[1] );
+ // TODO: add _id
+
+ conn->insert( ns , o );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const SocketException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.toString().c_str() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16300 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool mongo_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ 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] );
+
+ conn->remove( ns , o , justOne );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const SocketException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.toString().c_str() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16301 );
+ }
+ return JS_TRUE;
+ }
+
+ JSFunctionSpec mongo_functions[] = {
+ { "auth" , mongo_auth , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,
+ { "logout", mongo_logout, 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 ) {
+ try {
+ smuassert( cx , "db_collection_constructor wrong args" , argc == 4 );
+ Convertor c(cx);
+ if ( haveLocalShardingInfo( c.toString( argv[3] ) ) ) {
+ JS_ReportError( cx , "can't use sharded collection from db.eval" );
+ return JS_FALSE;
+ }
+
+ verify( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) );
+ verify( JS_SetProperty( cx , obj , "_db" , &(argv[1]) ) );
+ verify( JS_SetProperty( cx , obj , "_shortName" , &(argv[2]) ) );
+ verify( JS_SetProperty( cx , obj , "_fullName" , &(argv[3]) ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16302 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool db_collection_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) {
+ try {
+ 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;
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16303 );
+ }
+ return JS_TRUE;
+ }
+
+ JSClass db_collection_class = {
+ "DBCollection", // class name
+ JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE, // flags
+ JS_PropertyStub, // addProperty
+ JS_PropertyStub, // delProperty
+ JS_PropertyStub, // getProperty
+ JS_PropertyStub, // setProperty
+ JS_EnumerateStub, // enumerate
+ (JSResolveOp)db_collection_resolve, // resolve
+ JS_ConvertStub, // convert
+ JS_FinalizeStub, // finalize
+ JSCLASS_NO_OPTIONAL_MEMBERS // optional members
+ };
+
+
+ JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ) {
+ Convertor c(cx);
+
+ verify( c.hasProperty( db , "_mongo" ) );
+ verify( 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 ) {
+ try {
+ smuassert( cx, "wrong number of arguments to DB" , argc == 2 );
+
+ Convertor convertor( cx );
+ string dbName = convertor.toString( argv[1] );
+ string msg = str::stream() << "[" << dbName << "] is not a valid database name";
+ smuassert( cx, msg.c_str(), NamespaceString::validDBName( dbName ) );
+
+ verify( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) );
+ verify( JS_SetProperty( cx , obj , "_name" , &(argv[1]) ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16304 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool db_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) {
+ try {
+ 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;
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16305 );
+ }
+ 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 ) {
+ try {
+ 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] );
+
+ Scope::validateObjectIdString( s );
+ 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() );
+ verify( JS_SetProperty( cx , obj , "str" , &v ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16306 );
+ }
+ 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 ) {
+ try {
+ if ( argc != 2 ) {
+ JS_ReportError( cx, "DBPointer takes 2 arguments -- DBPointer(namespace,objectId)" );
+ return JS_FALSE;
+ }
+ if ( ! JSVAL_IS_OID( argv[1] ) ) {
+ JS_ReportError( cx , "2nd argument to DBPointer must be objectId" );
+ return JS_FALSE;
+ }
+ 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 );
+ }
+ verify( JS_SetProperty( cx , obj , "ns" , &(argv[0]) ) );
+ verify( JS_SetProperty( cx , obj , "id" , &(argv[1]) ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16307 );
+ }
+ return JS_TRUE;
+ }
+
+ 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 ) {
+ try {
+ if ( argc != 2 ) {
+ JS_ReportError( cx , "DBRef takes 2 arguments -- DBRef($ref,$id)" );
+ verify( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( BSONObj().getOwned() ) ) ) );
+ return JS_FALSE;
+ }
+ 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 );
+ }
+ JSObject * o = JS_NewObject( cx , NULL , NULL, NULL );
+ CHECKNEWOBJECT( o, cx, "dbref_constructor" );
+ verify( JS_SetProperty( cx, o , "$ref" , &argv[ 0 ] ) );
+ verify( JS_SetProperty( cx, o , "$id" , &argv[ 1 ] ) );
+ BSONObj bo = c.toObject( o );
+ verify( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( bo.getOwned() ) ) ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16308 );
+ }
+ return JS_TRUE;
+ }
+
+ 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
+
+ verify( 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 );
+ verify( 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;
+ verify( 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 ) {
+ try {
+ if ( argc != 2 ) {
+ JS_ReportError( cx , "BinData takes 2 arguments -- BinData(subtype,data)" );
+ return JS_FALSE;
+ }
+ Convertor c( cx );
+ int subtype = static_cast<int>( c.toNumber( argv[ 0 ] ) );
+ if ( subtype < 0 || subtype > 255 ) {
+ JS_ReportError( cx, "BinData subtype must be between 0 and 255" );
+ 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;
+ }
+ 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 );
+ }
+ verify( 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)subtype ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16309 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool bindata_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ Convertor c(cx);
+ int type = static_cast<int>( c.getNumber( obj, "type" ) );
+ int len = static_cast<int>(c.getNumber( obj, "len" ) );
+ void *holder = JS_GetPrivate( cx, obj );
+ verify( holder );
+ const char *data = ( ( BinDataHolder* )( holder ) )->c_;
+ stringstream ss;
+ ss << "BinData(" << type << ",\"";
+ base64::encode( ss, data, len );
+ ss << "\")";
+ string ret = ss.str();
+ *rval = c.toval( ret.c_str() );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16310 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool bindataBase64(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ Convertor c(cx);
+ int len = static_cast<int>( c.getNumber( obj, "len" ) );
+ void *holder = JS_GetPrivate( cx, obj );
+ verify( holder );
+ const char *data = ( ( BinDataHolder* )( holder ) )->c_;
+ stringstream ss;
+ base64::encode( ss, data, len );
+ string ret = ss.str();
+ *rval = c.toval( ret.c_str() );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16311 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool bindataAsHex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ Convertor c(cx);
+ int len = static_cast<int>( c.getNumber( obj, "len" ) );
+ void *holder = JS_GetPrivate( cx, obj );
+ verify( 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 = static_cast<unsigned char>( data[i] );
+ ss << setw(2) << v;
+ }
+ string ret = ss.str();
+ *rval = c.toval( ret.c_str() );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16312 );
+ }
+ return JS_TRUE;
+ }
+
+ void bindata_finalize( JSContext * cx , JSObject * obj ) {
+ try {
+ void *holder = JS_GetPrivate( cx, obj );
+ if ( holder ) {
+ delete reinterpret_cast<BinDataHolder*>( holder );
+ verify( JS_SetPrivate( cx , obj , 0 ) );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16313 );
+ }
+ }
+
+ 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 ) {
+ try {
+ if ( argc > 0 ) {
+ JS_ReportError( cx , "Map takes no arguments" );
+ return JS_FALSE;
+ }
+
+ JSObject * arrayObj = JS_NewObject( cx , 0 , 0 , 0 );
+ CHECKNEWOBJECT( arrayObj, cx, "map_constructor" );
+
+ jsval a = OBJECT_TO_JSVAL( arrayObj );
+ JS_SetProperty( cx , obj , "_data" , &a );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16314 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool map_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp ) {
+ try {
+ Convertor c(cx);
+ string str( c.toString( idval ) );
+ if ( ! specialMapString( str ) ) {
+ log() << "illegal prop access: " << str << endl;
+ JS_ReportError( cx , "can't use array access with Map" );
+ return JS_FALSE;
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16315 );
+ }
+ return JS_TRUE;
+ }
+
+ 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 ) {
+ try {
+ smuassert( cx,
+ "Timestamp takes 0 or 2 arguments -- Timestamp() or Timestamp(t,i)",
+ 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 ] );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16316 );
+ }
+ 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 ) {
+ try {
+ 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 );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16317 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool numberlong_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ Convertor c(cx);
+ *rval = c.toval( static_cast<double>( c.toNumberLongUnsafe( obj ) ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16318 );
+ }
+ return JS_TRUE;
+ }
+
+ 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) {
+ try {
+ 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();
+ *rval = c.toval( ret.c_str() );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16319 );
+ }
+ return JS_TRUE;
+ }
+
+ 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 ) {
+ try {
+ 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 = static_cast<int>( parseLL( numStr ) );
+ //PRINT(n);
+ }
+ catch ( const AssertionException & ) {
+ smuassert( cx , "could not convert string to integer" , false );
+ }
+ c.makeIntObj( n, obj );
+ }
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16320 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool numberint_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+ try {
+ Convertor c(cx);
+ *rval = c.toval( static_cast<double>( c.toNumberInt( obj ) ) );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16321 );
+ }
+ return JS_TRUE;
+ }
+
+ 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) {
+ try {
+ Convertor c(cx);
+ int val = c.toNumberInt( obj );
+ string ret = str::stream() << "NumberInt(" << val << ")";
+ *rval = c.toval( ret.c_str() );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16322 );
+ }
+ return JS_TRUE;
+ }
+
+ 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 ) {
+ try {
+ 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 );
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16323 );
+ }
+ return JS_TRUE;
+ }
+
+ JSBool dbquery_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) {
+ try {
+ if ( flags & JSRESOLVE_ASSIGNING )
+ return JS_TRUE;
+
+ if ( ! JSVAL_IS_NUMBER( id ) )
+ return JS_TRUE;
+
+ jsval val = JSVAL_VOID;
+ verify( JS_CallFunctionName( cx , obj , "arrayAccess" , 1 , &id , &val ) );
+ Convertor c(cx);
+ c.setProperty( obj , c.toString( id ).c_str() , val );
+ *objp = obj;
+ }
+ catch ( const AssertionException& e ) {
+ if ( ! JS_IsExceptionPending( cx ) ) {
+ JS_ReportError( cx, e.what() );
+ }
+ return JS_FALSE;
+ }
+ catch ( const std::exception& e ) {
+ log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;
+ fassertFailed( 16324 );
+ }
+ 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 ) {
+
+ verify( JS_InitClass( cx , global , 0 , &mongo_class , local ? mongo_local_constructor : mongo_external_constructor , 0 , 0 , mongo_functions , 0 , 0 ) );
+
+ verify( JS_InitClass( cx , global , 0 , &object_id_class , object_id_constructor , 0 , 0 , 0 , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &db_class , db_constructor , 2 , 0 , 0 , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &db_collection_class , db_collection_constructor , 4 , 0 , 0 , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &internal_cursor_class , internal_cursor_constructor , 0 , 0 , internal_cursor_functions , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &dbquery_class , dbquery_constructor , 0 , 0 , 0 , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &dbpointer_class , dbpointer_constructor , 0 , 0 , dbpointer_functions , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &bindata_class , bindata_constructor , 0 , 0 , bindata_functions , 0 , 0 ) );
+// verify( JS_InitClass( cx , global , 0 , &uuid_class , uuid_constructor , 0 , 0 , uuid_functions , 0 , 0 ) );
+
+ verify( JS_InitClass( cx , global , 0 , &timestamp_class , timestamp_constructor , 0 , 0 , 0 , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &numberlong_class , numberlong_constructor , 0 , 0 , numberlong_functions , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &numberint_class , numberint_constructor , 0 , 0 , numberint_functions , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &minkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &maxkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) );
+
+ verify( JS_InitClass( cx , global , 0 , &map_class , map_constructor , 0 , 0 , map_functions , 0 , 0 ) );
+
+ verify( JS_InitClass( cx , global , 0 , &bson_ro_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) );
+ verify( JS_InitClass( cx , global , 0 , &bson_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) );
+
+ static const char *dbrefName = "DBRef";
+ dbref_class.name = dbrefName;
+ verify( 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..99e33ac4600
--- /dev/null
+++ b/src/mongo/scripting/utils.cpp
@@ -0,0 +1,71 @@
+// 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 "mongo/scripting/engine.h"
+#include "mongo/util/md5.hpp"
+#include "mongo/util/version.h"
+
+namespace mongo {
+
+ void installBenchmarkSystem( Scope& scope );
+
+ static BSONObj native_hex_md5( const BSONObj& args, void* data ) {
+ uassert( 10261,
+ "hex_md5 takes a single string argument -- hex_md5(string)",
+ args.nFields() == 1 && args.firstElement().type() == String );
+ const char * s = args.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 ) );
+ }
+
+ static BSONObj native_version( const BSONObj& args, void* data ) {
+ cout << "version: " << versionString << endl;
+ if ( strstr( versionString , "+" ) )
+ printGitVersion();
+ return BSONObj();
+ }
+
+ static BSONObj native_sleep( const mongo::BSONObj& args, void* data ) {
+ uassert( 16259,
+ "sleep takes a single numeric argument -- sleep(milliseconds)",
+ args.nFields() == 1 && args.firstElement().isNumber() );
+ sleepmillis( static_cast<long long>( args.firstElement().number() ) );
+
+ BSONObjBuilder b;
+ b.appendUndefined( "" );
+ return b.obj();
+ }
+
+ // ---------------------------------
+ // ---- installer --------
+ // ---------------------------------
+
+ void installGlobalUtils( Scope& scope ) {
+ scope.injectNative( "hex_md5" , native_hex_md5 );
+ scope.injectNative( "version" , native_version );
+ scope.injectNative( "sleep" , native_sleep );
+
+ installBenchmarkSystem( scope );
+ }
+
+}
diff --git a/src/mongo/scripting/v8_db.cpp b/src/mongo/scripting/v8_db.cpp
new file mode 100644
index 00000000000..cc9b5ce000a
--- /dev/null
+++ b/src/mongo/scripting/v8_db.cpp
@@ -0,0 +1,1145 @@
+// v8_db.cpp
+
+/* Copyright 2009 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "v8_wrapper.h"
+#include "v8_utils.h"
+#include "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 "../db/namespacestring.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);
+ scope->injectV8Function("logout", mongoLogout, 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() ) {
+ verify( 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());
+ verify( 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() ) );
+ }
+
+ v8::Handle<v8::Value> mongoLogout(V8Scope* scope, const v8::Arguments& args) {
+ jsassert(args.Length() == 1, "update needs 1 arg");
+ DBClientBase* conn = getConnection(args);
+ const string db = toSTLString(args[0]);
+
+ BSONObj ret;
+ try {
+ conn->logout(db, ret);
+ }
+ catch (const std::exception& ex) {
+ return v8::ThrowException(v8::String::New(ex.what()));
+ }
+
+ return scope->mongoToLZV8(ret, false, false);
+ }
+
+// + 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) {
+ verify( 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++ )
+ verify( ! args[i]->IsUndefined() );
+
+ string dbName = toSTLString( args[1] );
+ if ( !NamespaceString::validDBName( dbName)) {
+ string msg = str::stream() << "[" << dbName << "] is not a "
+ << "valid database name";
+ return v8::ThrowException( v8::String::New( msg.c_str() ));
+ }
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> collectionInit( V8Scope* scope, const v8::Arguments& args ) {
+ verify( 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++ )
+ verify( ! args[i]->IsUndefined() );
+
+ return v8::Undefined();
+ }
+
+ v8::Handle<v8::Value> dbQueryInit( V8Scope* scope, const v8::Arguments& args ) {
+
+ v8::Handle<v8::Object> t = args.This();
+
+ verify( 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::Handle<v8::Value>();
+ }
+
+ // no hit, create new collection
+ v8::Handle<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "getCollection" ) );
+ verify( 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" ) );
+ verify( 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..a77115de35f
--- /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"
+
+namespace mongo {
+ class DBClientBase;
+ // 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> mongoLogout(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..dadea6bcbd3
--- /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) verify(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..b3bb7857ac3
--- /dev/null
+++ b/src/mongo/scripting/v8_wrapper.cpp
@@ -0,0 +1,91 @@
+// v8_wrapper.cpp
+
+/* Copyright 2009 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "v8_wrapper.h"
+#include "v8_utils.h"
+#include "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" ) );
+ verify( t->IsExternal() );
+ Local<External> c = External::Cast( *t );
+ WrapperHolder * w = (WrapperHolder*)(c->Value());
+ verify( 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 );
+
+}