diff options
Diffstat (limited to 'src/mongo/scripting')
-rw-r--r-- | src/mongo/scripting/bench.cpp | 785 | ||||
-rw-r--r-- | src/mongo/scripting/engine.cpp | 519 | ||||
-rw-r--r-- | src/mongo/scripting/engine.h | 235 | ||||
-rw-r--r-- | src/mongo/scripting/engine_java.cpp | 764 | ||||
-rw-r--r-- | src/mongo/scripting/engine_java.h | 223 | ||||
-rw-r--r-- | src/mongo/scripting/engine_none.cpp | 24 | ||||
-rw-r--r-- | src/mongo/scripting/engine_spidermonkey.cpp | 1766 | ||||
-rw-r--r-- | src/mongo/scripting/engine_spidermonkey.h | 105 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8.cpp | 1634 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8.h | 254 | ||||
-rw-r--r-- | src/mongo/scripting/sm_db.cpp | 1284 | ||||
-rw-r--r-- | src/mongo/scripting/utils.cpp | 77 | ||||
-rw-r--r-- | src/mongo/scripting/v8_db.cpp | 1128 | ||||
-rw-r--r-- | src/mongo/scripting/v8_db.h | 94 | ||||
-rw-r--r-- | src/mongo/scripting/v8_utils.cpp | 295 | ||||
-rw-r--r-- | src/mongo/scripting/v8_utils.h | 43 | ||||
-rw-r--r-- | src/mongo/scripting/v8_wrapper.cpp | 99 | ||||
-rw-r--r-- | src/mongo/scripting/v8_wrapper.h | 34 |
18 files changed, 9363 insertions, 0 deletions
diff --git a/src/mongo/scripting/bench.cpp b/src/mongo/scripting/bench.cpp new file mode 100644 index 00000000000..01291b1e1f0 --- /dev/null +++ b/src/mongo/scripting/bench.cpp @@ -0,0 +1,785 @@ +/** @file bench.cpp */ + +/* + * Copyright (C) 2010 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "pch.h" +#include "engine.h" +#include "../util/md5.hpp" +#include "../util/version.h" +#include "../client/dbclient.h" +#include "../client/connpool.h" +#include <pcrecpp.h> + +// --------------------------------- +// ---- benchmarking system -------- +// --------------------------------- + +// TODO: Maybe extract as library to avoid code duplication? +namespace { + inline pcrecpp::RE_Options flags2options(const char* flags) { + pcrecpp::RE_Options options; + options.set_utf8(true); + while ( flags && *flags ) { + if ( *flags == 'i' ) + options.set_caseless(true); + else if ( *flags == 'm' ) + options.set_multiline(true); + else if ( *flags == 'x' ) + options.set_extended(true); + flags++; + } + return options; + } +} + +namespace mongo { + + struct BenchRunConfig { + BenchRunConfig() : _mutex( "BenchRunConfig" ) { + host = "localhost"; + db = "test"; + username = ""; + password = ""; + + parallel = 1; + seconds = 1; + handleErrors = false; + hideErrors = false; + hideResults = true; + + active = true; + threadsReady = 0; + error = false; + errCount = 0; + throwGLE = false; + breakOnTrap = true; + } + + string host; + string db; + string username; + string password; + + unsigned parallel; + double seconds; + + bool hideResults; + bool handleErrors; + bool hideErrors; + + shared_ptr< pcrecpp::RE > trapPattern; + shared_ptr< pcrecpp::RE > noTrapPattern; + shared_ptr< pcrecpp::RE > watchPattern; + shared_ptr< pcrecpp::RE > noWatchPattern; + + BSONObj ops; + + volatile bool active; // true at starts, gets set to false when should stop + AtomicUInt threadsReady; + + bool error; + bool throwGLE; + bool breakOnTrap; + + AtomicUInt threadsActive; + + mongo::mutex _mutex; + long long errCount; + BSONArrayBuilder trapped; + }; + + static bool _hasSpecial( const BSONObj& obj ) { + BSONObjIterator i( obj ); + while ( i.more() ) { + BSONElement e = i.next(); + if ( e.fieldName()[0] == '#' ) + return true; + + if ( ! e.isABSONObj() ) + continue; + + if ( _hasSpecial( e.Obj() ) ) + return true; + } + return false; + } + + static void _fixField( BSONObjBuilder& b , const BSONElement& e ) { + assert( e.type() == Object ); + + BSONObj sub = e.Obj(); + assert( sub.nFields() == 1 ); + + BSONElement f = sub.firstElement(); + if ( str::equals( "#RAND_INT" , f.fieldName() ) ) { + BSONObjIterator i( f.Obj() ); + int min = i.next().numberInt(); + int max = i.next().numberInt(); + + int x = min + ( rand() % ( max - min ) ); + b.append( e.fieldName() , x ); + } + else { + uasserted( 14811 , str::stream() << "invalid bench dynamic piece: " << f.fieldName() ); + } + + } + + static void fixQuery( BSONObjBuilder& b , const BSONObj& obj ) { + BSONObjIterator i( obj ); + while ( i.more() ) { + BSONElement e = i.next(); + + if ( ! e.isABSONObj() ) { + b.append( e ); + continue; + } + + BSONObj sub = e.Obj(); + if ( sub.firstElement().fieldName()[0] == '#' ) { + _fixField( b , e ); + } + else { + BSONObjBuilder xx( e.type() == Object ? b.subobjStart( e.fieldName() ) : b.subarrayStart( e.fieldName() ) ); + fixQuery( xx , sub ); + xx.done(); + } + + } + } + + static BSONObj fixQuery( const BSONObj& obj ) { + + if ( ! _hasSpecial( obj ) ) + return obj; + + BSONObjBuilder b( obj.objsize() + 128 ); + fixQuery( b , obj ); + return b.obj(); + } + + + static void _benchThread( BenchRunConfig * config, ScopedDbConnection& conn ){ + + long long count = 0; + while ( config->active ) { + BSONObjIterator i( config->ops ); + while ( i.more() ) { + BSONElement e = i.next(); + + string ns = e["ns"].String(); + string op = e["op"].String(); + + int delay = e["delay"].eoo() ? 0 : e["delay"].Int(); + + auto_ptr<Scope> scope; + ScriptingFunction scopeFunc = 0; + BSONObj scopeObj; + + if (config->username != "") { + string errmsg; + if (!conn.get()->auth(config->db, config->username, config->password, errmsg)) { + uasserted(15931, "Authenticating to connection for _benchThread failed: " + errmsg); + } + } + + bool check = ! e["check"].eoo(); + if( check ){ + if ( e["check"].type() == CodeWScope || e["check"].type() == Code || e["check"].type() == String ) { + scope = globalScriptEngine->getPooledScope( ns ); + assert( scope.get() ); + + if ( e.type() == CodeWScope ) { + scopeFunc = scope->createFunction( e["check"].codeWScopeCode() ); + scopeObj = BSONObj( e.codeWScopeScopeData() ); + } + else { + scopeFunc = scope->createFunction( e["check"].valuestr() ); + } + + scope->init( &scopeObj ); + assert( scopeFunc ); + } + else { + warning() << "Invalid check type detected in benchRun op : " << e << endl; + check = false; + } + } + + try { + if ( op == "findOne" ) { + + BSONObj result = conn->findOne( ns , fixQuery( e["query"].Obj() ) ); + + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [findOne]" << causedBy( scope->getError() ) << endl; + return; + } + } + + if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [findOne] : " << result << endl; + + } + else if ( op == "command" ) { + + BSONObj result; + // TODO + /* bool ok = */ conn->runCommand( ns , fixQuery( e["command"].Obj() ), result, e["options"].numberInt() ); + + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [command]" << causedBy( scope->getError() ) << endl; + return; + } + } + + if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [command] : " << result << endl; + + } + else if( op == "find" || op == "query" ) { + + int limit = e["limit"].eoo() ? 0 : e["limit"].numberInt(); + int skip = e["skip"].eoo() ? 0 : e["skip"].Int(); + int options = e["options"].eoo() ? 0 : e["options"].Int(); + int batchSize = e["batchSize"].eoo() ? 0 : e["batchSize"].Int(); + BSONObj filter = e["filter"].eoo() ? BSONObj() : e["filter"].Obj(); + + auto_ptr<DBClientCursor> cursor = conn->query( ns, fixQuery( e["query"].Obj() ), limit, skip, &filter, options, batchSize ); + + int count = cursor->itcount(); + + if( check ){ + BSONObj thisValue = BSON( "count" << count ); + int err = scope->invoke( scopeFunc , 0 , &thisValue, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [find]" << causedBy( scope->getError() ) << endl; + return; + } + } + + if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [query] : " << count << endl; + + } + else if( op == "update" ) { + + bool multi = e["multi"].trueValue(); + bool upsert = e["upsert"].trueValue(); + BSONObj query = e["query"].eoo() ? BSONObj() : e["query"].Obj(); + BSONObj update = e["update"].Obj(); + + conn->update( ns, fixQuery( query ), update, upsert , multi ); + + bool safe = e["safe"].trueValue(); + if( safe ){ + BSONObj result = conn->getLastErrorDetailed(); + + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [update]" << causedBy( scope->getError() ) << endl; + return; + } + } + + if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe update] : " << result << endl; + + if( ! result["err"].eoo() && result["err"].type() == String && ( config->throwGLE || e["throwGLE"].trueValue() ) ) + throw DBException( (string)"From benchRun GLE" + causedBy( result["err"].String() ), + result["code"].eoo() ? 0 : result["code"].Int() ); + } + } + else if( op == "insert" ) { + + conn->insert( ns, fixQuery( e["doc"].Obj() ) ); + + bool safe = e["safe"].trueValue(); + if( safe ){ + BSONObj result = conn->getLastErrorDetailed(); + + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [insert]" << causedBy( scope->getError() ) << endl; + return; + } + } + + if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe insert] : " << result << endl; + + if( ! result["err"].eoo() && result["err"].type() == String && ( config->throwGLE || e["throwGLE"].trueValue() ) ) + throw DBException( (string)"From benchRun GLE" + causedBy( result["err"].String() ), + result["code"].eoo() ? 0 : result["code"].Int() ); + } + } + else if( op == "delete" || op == "remove" ) { + + bool multi = e["multi"].eoo() ? true : e["multi"].trueValue(); + BSONObj query = e["query"].eoo() ? BSONObj() : e["query"].Obj(); + + conn->remove( ns, fixQuery( query ), ! multi ); + + bool safe = e["safe"].trueValue(); + if( safe ){ + BSONObj result = conn->getLastErrorDetailed(); + + if( check ){ + int err = scope->invoke( scopeFunc , 0 , &result, 1000 * 60 , false ); + if( err ){ + log() << "Error checking in benchRun thread [delete]" << causedBy( scope->getError() ) << endl; + return; + } + } + + if( ! config->hideResults || e["showResult"].trueValue() ) log() << "Result from benchRun thread [safe remove] : " << result << endl; + + if( ! result["err"].eoo() && result["err"].type() == String && ( config->throwGLE || e["throwGLE"].trueValue() ) ) + throw DBException( (string)"From benchRun GLE " + causedBy( result["err"].String() ), + result["code"].eoo() ? 0 : result["code"].Int() ); + } + } + else if ( op == "createIndex" ) { + conn->ensureIndex( ns , e["key"].Obj() , false , "" , false ); + } + else if ( op == "dropIndex" ) { + conn->dropIndex( ns , e["key"].Obj() ); + } + else { + log() << "don't understand op: " << op << endl; + config->error = true; + return; + } + } + catch( DBException& ex ){ + if( ! config->hideErrors || e["showError"].trueValue() ){ + + bool yesWatch = ( config->watchPattern && config->watchPattern->FullMatch( ex.what() ) ); + bool noWatch = ( config->noWatchPattern && config->noWatchPattern->FullMatch( ex.what() ) ); + + if( ( ! config->watchPattern && config->noWatchPattern && ! noWatch ) || // If we're just ignoring things + ( ! config->noWatchPattern && config->watchPattern && yesWatch ) || // If we're just watching things + ( config->watchPattern && config->noWatchPattern && yesWatch && ! noWatch ) ) + log() << "Error in benchRun thread for op " << e << causedBy( ex ) << endl; + } + + bool yesTrap = ( config->trapPattern && config->trapPattern->FullMatch( ex.what() ) ); + bool noTrap = ( config->noTrapPattern && config->noTrapPattern->FullMatch( ex.what() ) ); + + if( ( ! config->trapPattern && config->noTrapPattern && ! noTrap ) || + ( ! config->noTrapPattern && config->trapPattern && yesTrap ) || + ( config->trapPattern && config->noTrapPattern && yesTrap && ! noTrap ) ){ + { + scoped_lock lock( config->_mutex ); + config->trapped.append( BSON( "error" << ex.what() << "op" << e << "count" << count ) ); + } + if( config->breakOnTrap ) return; + } + if( ! config->handleErrors && ! e["handleError"].trueValue() ) return; + + { + scoped_lock lock( config->_mutex ); + config->errCount++; + } + } + catch( ... ){ + if( ! config->hideErrors || e["showError"].trueValue() ) log() << "Error in benchRun thread caused by unknown error for op " << e << endl; + if( ! config->handleErrors && ! e["handleError"].trueValue() ) return; + + { + scoped_lock lock( config->_mutex ); + config->errCount++; + } + } + + if ( ++count % 100 == 0 ) { + conn->getLastError(); + } + + sleepmillis( delay ); + + } + } + } + + static void benchThread( BenchRunConfig * config ) { + + ScopedDbConnection conn( config->host ); + config->threadsReady++; + config->threadsActive++; + + try { + if (config->username != "") { + string errmsg; + if (!conn.get()->auth(config->db, config->username, config->password, errmsg)) { + uasserted(15932, "Authenticating to connection for benchThread failed: " + errmsg); + } + } + + _benchThread( config, conn ); + } + catch( DBException& e ){ + error() << "DBException not handled in benchRun thread" << causedBy( e ) << endl; + } + catch( std::exception& e ){ + error() << "Exception not handled in benchRun thread" << causedBy( e ) << endl; + } + catch( ... ){ + error() << "Exception not handled in benchRun thread." << endl; + } + conn->getLastError(); + config->threadsActive--; + conn.done(); + + } + + + class BenchRunner { + public: + + BenchRunner( ) { + } + + ~BenchRunner() { + } + + void init( BSONObj& args ){ + + oid.init(); + activeRuns[ oid ] = this; + + if ( args["host"].type() == String ) + config.host = args["host"].String(); + if ( args["db"].type() == String ) + config.db = args["db"].String(); + if ( args["username"].type() == String ) + config.username = args["username"].String(); + if ( args["password"].type() == String ) + config.db = args["password"].String(); + + if ( args["parallel"].isNumber() ) + config.parallel = args["parallel"].numberInt(); + if ( args["seconds"].isNumber() ) + config.seconds = args["seconds"].numberInt(); + if ( ! args["hideResults"].eoo() ) + config.hideResults = args["hideResults"].trueValue(); + if ( ! args["handleErrors"].eoo() ) + config.handleErrors = args["handleErrors"].trueValue(); + if ( ! args["hideErrors"].eoo() ) + config.hideErrors = args["hideErrors"].trueValue(); + if ( ! args["throwGLE"].eoo() ) + config.throwGLE = args["throwGLE"].trueValue(); + if ( ! args["breakOnTrap"].eoo() ) + config.breakOnTrap = args["breakOnTrap"].trueValue(); + + + if ( ! args["trapPattern"].eoo() ){ + const char* regex = args["trapPattern"].regex(); + const char* flags = args["trapPattern"].regexFlags(); + config.trapPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) ); + } + + if ( ! args["noTrapPattern"].eoo() ){ + const char* regex = args["noTrapPattern"].regex(); + const char* flags = args["noTrapPattern"].regexFlags(); + config.noTrapPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) ); + } + + if ( ! args["watchPattern"].eoo() ){ + const char* regex = args["watchPattern"].regex(); + const char* flags = args["watchPattern"].regexFlags(); + config.watchPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) ); + } + + if ( ! args["noWatchPattern"].eoo() ){ + const char* regex = args["noWatchPattern"].regex(); + const char* flags = args["noWatchPattern"].regexFlags(); + config.noWatchPattern = shared_ptr< pcrecpp::RE >( new pcrecpp::RE( regex, flags2options( flags ) ) ); + } + + config.ops = args["ops"].Obj().getOwned(); + conn = shared_ptr< ScopedDbConnection >( new ScopedDbConnection( config.host ) ); + + // Get initial stats + conn->get()->simpleCommand( "admin" , &before , "serverStatus" ); + + // Start threads + for ( unsigned i = 0; i < config.parallel; i++ ) + threads.push_back( shared_ptr< boost::thread >( new boost::thread( boost::bind( benchThread , &config ) ) ) ); + + // Give them time to init + while ( config.threadsReady < config.parallel ) sleepmillis( 1 ); + + } + + void done(){ + + log() << "Ending! (waiting for " << threads.size() << " threads)" << endl; + + { + scoped_lock lock( config._mutex ); + config.active = false; + } + + for ( unsigned i = 0; i < threads.size(); i++ ) threads[i]->join(); + + // Get final stats + conn->get()->simpleCommand( "admin" , &after , "serverStatus" ); + after.getOwned(); + + conn.get()->done(); + + activeRuns.erase( oid ); + + } + + BSONObj status(){ + scoped_lock lock( config._mutex ); + return BSON( "errCount" << config.errCount << + "trappedCount" << config.trapped.arrSize() << + "threadsActive" << config.threadsActive.get() ); + } + + static BenchRunner* get( BSONObj args ){ + BenchRunner* runner = new BenchRunner(); + runner->init( args ); + return runner; + } + + static BenchRunner* get( OID oid ){ + return activeRuns[ oid ]; + } + + static BSONObj finish( BenchRunner* runner ){ + + runner->done(); + + // vector<BSONOBj> errors = runner->config.errors; + bool error = runner->config.error; + + if ( error ) + return BSON( "err" << 1 ); + + // compute actual ops/sec + BSONObj before = runner->before["opcounters"].Obj(); + BSONObj after = runner->after["opcounters"].Obj(); + + BSONObjBuilder buf; + buf.append( "note" , "values per second" ); + buf.append( "errCount", (long long) runner->config.errCount ); + buf.append( "trapped", runner->config.trapped.arr() ); + { + BSONObjIterator i( after ); + while ( i.more() ) { + BSONElement e = i.next(); + double x = e.number(); + x = x - before[e.fieldName()].number(); + buf.append( e.fieldName() , x / runner->config.seconds ); + } + } + + BSONObj zoo = buf.obj(); + + delete runner; + return zoo; + } + + static map< OID, BenchRunner* > activeRuns; + + OID oid; + BenchRunConfig config; + vector< shared_ptr< boost::thread > > threads; + + shared_ptr< ScopedDbConnection > conn; + BSONObj before; + BSONObj after; + + }; + + map< OID, BenchRunner* > BenchRunner::activeRuns; + + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchRun( const BSONObj& argsFake, void* data ) { + assert( argsFake.firstElement().isABSONObj() ); + BSONObj args = argsFake.firstElement().Obj(); + + // setup + + BenchRunConfig config; + + if ( args["host"].type() == String ) + config.host = args["host"].String(); + if ( args["db"].type() == String ) + config.db = args["db"].String(); + if ( args["username"].type() == String ) + config.username = args["username"].String(); + if ( args["password"].type() == String ) + config.password = args["password"].String(); + + if ( args["parallel"].isNumber() ) + config.parallel = args["parallel"].numberInt(); + if ( args["seconds"].isNumber() ) + config.seconds = args["seconds"].number(); + + + config.ops = args["ops"].Obj(); + + // execute + + ScopedDbConnection conn( config.host ); + + if (config.username != "") { + string errmsg; + if (!conn.get()->auth(config.db, config.username, config.password, errmsg)) { + uasserted(15930, "Authenticating to connection for bench run failed: " + errmsg); + } + } + + + // start threads + vector<boost::thread*> all; + for ( unsigned i=0; i<config.parallel; i++ ) + all.push_back( new boost::thread( boost::bind( benchThread , &config ) ) ); + + // give them time to init + while ( config.threadsReady < config.parallel ) + sleepmillis( 1 ); + + BSONObj before; + conn->simpleCommand( "admin" , &before , "serverStatus" ); + + sleepmillis( (int)(1000.0 * config.seconds) ); + + BSONObj after; + conn->simpleCommand( "admin" , &after , "serverStatus" ); + + conn.done(); + + config.active = false; + + for ( unsigned i=0; i<all.size(); i++ ) + all[i]->join(); + + if ( config.error ) + return BSON( "err" << 1 ); + + // compute actual ops/sec + + before = before["opcounters"].Obj().copy(); + after = after["opcounters"].Obj().copy(); + + bool totals = args["totals"].trueValue(); + + BSONObjBuilder buf; + if ( ! totals ) + buf.append( "note" , "values per second" ); + + { + BSONObjIterator i( after ); + while ( i.more() ) { + BSONElement e = i.next(); + double x = e.number(); + x = x - before[e.fieldName()].number(); + if ( ! totals ) + x = x / config.seconds; + buf.append( e.fieldName() , x ); + } + } + BSONObj zoo = buf.obj(); + return BSON( "" << zoo ); + } + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchRunSync( const BSONObj& argsFake, void* data ) { + + assert( argsFake.firstElement().isABSONObj() ); + BSONObj args = argsFake.firstElement().Obj(); + + // Get new BenchRunner object + BenchRunner* runner = BenchRunner::get( args ); + + sleepsecs( static_cast<int>( runner->config.seconds ) ); + + return BenchRunner::finish( runner ); + + } + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchStart( const BSONObj& argsFake, void* data ) { + + assert( argsFake.firstElement().isABSONObj() ); + BSONObj args = argsFake.firstElement().Obj(); + + // Get new BenchRunner object + BenchRunner* runner = BenchRunner::get( args ); + + log() << "Starting benchRun test " << runner->oid << endl; + + return BSON( "" << runner->oid.toString() ); + } + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchStatus( const BSONObj& argsFake, void* data ) { + + OID oid = OID( argsFake.firstElement().String() ); + + log() << "Getting status for benchRun test " << oid << endl; + + // Get new BenchRunner object + BenchRunner* runner = BenchRunner::get( oid ); + + BSONObj statusObj = runner->status(); + + return BSON( "" << statusObj ); + } + + /** + * benchRun( { ops : [] , host : XXX , db : XXXX , parallel : 5 , seconds : 5 } + */ + BSONObj benchFinish( const BSONObj& argsFake, void* data ) { + + OID oid = OID( argsFake.firstElement().String() ); + + log() << "Finishing benchRun test " << oid << endl; + + // Get new BenchRunner object + BenchRunner* runner = BenchRunner::get( oid ); + + BSONObj finalObj = BenchRunner::finish( runner ); + + return BSON( "" << finalObj ); + } + + void installBenchmarkSystem( Scope& scope ) { + scope.injectNative( "benchRun" , benchRun ); + scope.injectNative( "benchRunSync" , benchRunSync ); + scope.injectNative( "benchStart" , benchStart ); + scope.injectNative( "benchStatus" , benchStatus ); + scope.injectNative( "benchFinish" , benchFinish ); + } + +} diff --git a/src/mongo/scripting/engine.cpp b/src/mongo/scripting/engine.cpp new file mode 100644 index 00000000000..13fe681ebe5 --- /dev/null +++ b/src/mongo/scripting/engine.cpp @@ -0,0 +1,519 @@ +// engine.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pch.h" +#include "engine.h" +#include "../util/file.h" +#include "../client/dbclient.h" + +namespace mongo { + + long long Scope::_lastVersion = 1; + + int Scope::_numScopes = 0; + + Scope::Scope() : _localDBName("") , _loadedVersion(0), _numTimeUsed(0) { + _numScopes++; + } + + Scope::~Scope() { + _numScopes--; + } + + ScriptEngine::ScriptEngine() : _scopeInitCallback() { + } + + ScriptEngine::~ScriptEngine() { + } + + void Scope::append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ) { + int t = type( scopeName ); + + switch ( t ) { + case Object: + builder.append( fieldName , getObject( scopeName ) ); + break; + case Array: + builder.appendArray( fieldName , getObject( scopeName ) ); + break; + case NumberDouble: + builder.append( fieldName , getNumber( scopeName ) ); + break; + case NumberInt: + builder.append( fieldName , getNumberInt( scopeName ) ); + break; + case NumberLong: + builder.append( fieldName , getNumberLongLong( scopeName ) ); + break; + case String: + builder.append( fieldName , getString( scopeName ).c_str() ); + break; + case Bool: + builder.appendBool( fieldName , getBoolean( scopeName ) ); + break; + case jstNULL: + case Undefined: + builder.appendNull( fieldName ); + break; + case Date: + // TODO: make signed + builder.appendDate( fieldName , Date_t((unsigned long long)getNumber( scopeName )) ); + break; + case Code: + builder.appendCode( fieldName , getString( scopeName ) ); + break; + default: + stringstream temp; + temp << "can't append type from:"; + temp << t; + uassert( 10206 , temp.str() , 0 ); + } + + } + + int Scope::invoke( const char* code , const BSONObj* args, const BSONObj* recv, int timeoutMs ) { + ScriptingFunction func = createFunction( code ); + uassert( 10207 , "compile failed" , func ); + return invoke( func , args, recv, timeoutMs ); + } + + bool Scope::execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs ) { + + path p( filename ); + + if ( ! exists( p ) ) { + log() << "file [" << filename << "] doesn't exist" << endl; + if ( assertOnError ) + assert( 0 ); + return false; + } + + // iterate directories and recurse using all *.js files in the directory + if ( is_directory( p ) ) { + directory_iterator end; + bool empty = true; + for (directory_iterator it (p); it != end; it++) { + empty = false; + path sub (*it); + if (!endsWith(sub.string().c_str(), ".js")) + continue; + if (!execFile(sub.string().c_str(), printResult, reportError, assertOnError, timeoutMs)) + return false; + } + + if (empty) { + log() << "directory [" << filename << "] doesn't have any *.js files" << endl; + if ( assertOnError ) + assert( 0 ); + return false; + } + + return true; + } + + File f; + f.open( filename.c_str() , true ); + + unsigned L; + { + fileofs fo = f.len(); + assert( fo <= 0x7ffffffe ); + L = (unsigned) fo; + } + boost::scoped_array<char> data (new char[L+1]); + data[L] = 0; + f.read( 0 , data.get() , L ); + + int offset = 0; + if (data[0] == '#' && data[1] == '!') { + const char* newline = strchr(data.get(), '\n'); + if (! newline) + return true; // file of just shebang treated same as empty file + offset = newline - data.get(); + } + + StringData code (data.get() + offset, L - offset); + + return exec( code , filename , printResult , reportError , assertOnError, timeoutMs ); + } + + void Scope::storedFuncMod() { + _lastVersion++; + } + + void Scope::validateObjectIdString( const string &str ) { + massert( 10448 , "invalid object id: length", str.size() == 24 ); + + for ( string::size_type i=0; i<str.size(); i++ ) { + char c = str[i]; + if ( ( c >= '0' && c <= '9' ) || + ( c >= 'a' && c <= 'f' ) || + ( c >= 'A' && c <= 'F' ) ) { + continue; + } + massert( 10430 , "invalid object id: not hex", false ); + } + } + + void Scope::loadStored( bool ignoreNotConnected ) { + if ( _localDBName.size() == 0 ) { + if ( ignoreNotConnected ) + return; + uassert( 10208 , "need to have locallyConnected already" , _localDBName.size() ); + } + if ( _loadedVersion == _lastVersion ) + return; + + _loadedVersion = _lastVersion; + + string coll = _localDBName + ".system.js"; + + static DBClientBase * db = createDirectClient(); + auto_ptr<DBClientCursor> c = db->query( coll , Query(), 0, 0, NULL, QueryOption_SlaveOk, 0 ); + assert( c.get() ); + + set<string> thisTime; + + while ( c->more() ) { + BSONObj o = c->nextSafe(); + + BSONElement n = o["_id"]; + BSONElement v = o["value"]; + + uassert( 10209 , str::stream() << "name has to be a string: " << n , n.type() == String ); + uassert( 10210 , "value has to be set" , v.type() != EOO ); + + setElement( n.valuestr() , v ); + + thisTime.insert( n.valuestr() ); + _storedNames.insert( n.valuestr() ); + + } + + // --- remove things from scope that were removed + + list<string> toremove; + + for ( set<string>::iterator i=_storedNames.begin(); i!=_storedNames.end(); i++ ) { + string n = *i; + if ( thisTime.count( n ) == 0 ) + toremove.push_back( n ); + } + + for ( list<string>::iterator i=toremove.begin(); i!=toremove.end(); i++ ) { + string n = *i; + _storedNames.erase( n ); + execSetup( (string)"delete " + n , "clean up scope" ); + } + + } + + ScriptingFunction Scope::createFunction( const char * code ) { + if ( code[0] == '/' && code [1] == '*' ) { + code += 2; + while ( code[0] && code[1] ) { + if ( code[0] == '*' && code[1] == '/' ) { + code += 2; + break; + } + code++; + } + } + map<string,ScriptingFunction>::iterator i = _cachedFunctions.find( code ); + if ( i != _cachedFunctions.end() ) + return i->second; + ScriptingFunction f = _createFunction( code ); + _cachedFunctions[code] = f; + return f; + } + + namespace JSFiles { + extern const JSFile collection; + extern const JSFile db; + extern const JSFile mongo; + extern const JSFile mr; + extern const JSFile query; + extern const JSFile utils; + extern const JSFile utils_sh; + } + + void Scope::execCoreFiles() { + // keeping same order as in SConstruct + execSetup(JSFiles::utils); + execSetup(JSFiles::utils_sh); + execSetup(JSFiles::db); + execSetup(JSFiles::mongo); + execSetup(JSFiles::mr); + execSetup(JSFiles::query); + execSetup(JSFiles::collection); + } + + typedef map< string , list<Scope*> > PoolToScopes; + + class ScopeCache { + public: + + ScopeCache() : _mutex("ScopeCache") { + _magic = 17; + } + + ~ScopeCache() { + assert( _magic == 17 ); + _magic = 1; + + if ( inShutdown() ) + return; + + clear(); + } + + void done( const string& pool , Scope * s ) { + scoped_lock lk( _mutex ); + list<Scope*> & l = _pools[pool]; + bool oom = s->hasOutOfMemoryException(); + + // do not keep too many contexts, or use them for too long + if ( l.size() > 10 || s->getTimeUsed() > 100 || oom ) { + delete s; + } + else { + l.push_back( s ); + s->reset(); + } + + if (oom) { + // out of mem, make some room + log() << "Clearing all idle JS contexts due to out of memory" << endl; + clear(); + } + } + + Scope * get( const string& pool ) { + scoped_lock lk( _mutex ); + list<Scope*> & l = _pools[pool]; + if ( l.size() == 0 ) + return 0; + + Scope * s = l.back(); + l.pop_back(); + s->reset(); + s->incTimeUsed(); + return s; + } + + void clear() { + set<Scope*> seen; + + for ( PoolToScopes::iterator i=_pools.begin() ; i != _pools.end(); i++ ) { + for ( list<Scope*>::iterator j=i->second.begin(); j != i->second.end(); j++ ) { + Scope * s = *j; + assert( ! seen.count( s ) ); + delete s; + seen.insert( s ); + } + } + + _pools.clear(); + } + + private: + PoolToScopes _pools; + mongo::mutex _mutex; + int _magic; + }; + + thread_specific_ptr<ScopeCache> scopeCache; + + class PooledScope : public Scope { + public: + PooledScope( const string pool , Scope * real ) : _pool( pool ) , _real( real ) { + _real->loadStored( true ); + }; + virtual ~PooledScope() { + ScopeCache * sc = scopeCache.get(); + if ( sc ) { + sc->done( _pool , _real ); + _real = 0; + } + else { + // this means that the Scope was killed from a different thread + // for example a cursor got timed out that has a $where clause + log(3) << "warning: scopeCache is empty!" << endl; + delete _real; + _real = 0; + } + } + + void reset() { + _real->reset(); + } + void init( const BSONObj * data ) { + _real->init( data ); + } + + void localConnect( const char * dbName ) { + _real->localConnect( dbName ); + } + void externalSetup() { + _real->externalSetup(); + } + + double getNumber( const char *field ) { + return _real->getNumber( field ); + } + string getString( const char *field ) { + return _real->getString( field ); + } + bool getBoolean( const char *field ) { + return _real->getBoolean( field ); + } + BSONObj getObject( const char *field ) { + return _real->getObject( field ); + } + + int type( const char *field ) { + return _real->type( field ); + } + + void setElement( const char *field , const BSONElement& val ) { + _real->setElement( field , val ); + } + void setNumber( const char *field , double val ) { + _real->setNumber( field , val ); + } + void setString( const char *field , const char * val ) { + _real->setString( field , val ); + } + void setObject( const char *field , const BSONObj& obj , bool readOnly=true ) { + _real->setObject( field , obj , readOnly ); + } + void setBoolean( const char *field , bool val ) { + _real->setBoolean( field , val ); + } +// void setThis( const BSONObj * obj ) { +// _real->setThis( obj ); +// } + + void setFunction( const char *field , const char * code ) { + _real->setFunction(field, code); + } + + ScriptingFunction createFunction( const char * code ) { + return _real->createFunction( code ); + } + + ScriptingFunction _createFunction( const char * code ) { + return _real->createFunction( code ); + } + + void rename( const char * from , const char * to ) { + _real->rename( from , to ); + } + + /** + * @return 0 on success + */ + int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) { + return _real->invoke( func , args , recv, timeoutMs , ignoreReturn, readOnlyArgs, readOnlyRecv ); + } + + string getError() { + return _real->getError(); + } + + bool hasOutOfMemoryException() { + return _real->hasOutOfMemoryException(); + } + + bool exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) { + return _real->exec( code , name , printResult , reportError , assertOnError , timeoutMs ); + } + bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) { + return _real->execFile( filename , printResult , reportError , assertOnError , timeoutMs ); + } + + void injectNative( const char *field, NativeFunction func, void* data ) { + _real->injectNative( field , func, data ); + } + + void gc() { + _real->gc(); + } + + void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ) { + _real->append(builder, fieldName, scopeName); + } + + private: + string _pool; + Scope * _real; + }; + + auto_ptr<Scope> ScriptEngine::getPooledScope( const string& pool ) { + if ( ! scopeCache.get() ) { + scopeCache.reset( new ScopeCache() ); + } + + Scope * s = scopeCache->get( pool ); + if ( ! s ) { + s = newScope(); + } + + auto_ptr<Scope> p; + p.reset( new PooledScope( pool , s ) ); + return p; + } + + void ScriptEngine::threadDone() { + ScopeCache * sc = scopeCache.get(); + if ( sc ) { + sc->clear(); + } + } + + void ( *ScriptEngine::_connectCallback )( DBClientWithCommands & ) = 0; + const char * ( *ScriptEngine::_checkInterruptCallback )() = 0; + unsigned ( *ScriptEngine::_getInterruptSpecCallback )() = 0; + + ScriptEngine * globalScriptEngine = 0; + + bool hasJSReturn( const string& code ) { + size_t x = code.find( "return" ); + if ( x == string::npos ) + return false; + + return + ( x == 0 || ! isalpha( code[x-1] ) ) && + ! isalpha( code[x+6] ); + } + + const char * jsSkipWhiteSpace( const char * raw ) { + while ( raw[0] ) { + while (isspace(*raw)) { + raw++; + } + + if ( raw[0] != '/' || raw[1] != '/' ) + break; + + while ( raw[0] && raw[0] != '\n' ) + raw++; + } + return raw; + } +} + diff --git a/src/mongo/scripting/engine.h b/src/mongo/scripting/engine.h new file mode 100644 index 00000000000..f4b39740001 --- /dev/null +++ b/src/mongo/scripting/engine.h @@ -0,0 +1,235 @@ +// engine.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../pch.h" +#include "../db/jsobj.h" + +namespace mongo { + + struct JSFile { + const char* name; + const StringData& source; + }; + + typedef unsigned long long ScriptingFunction; + typedef BSONObj (*NativeFunction) ( const BSONObj &args, void* data ); + + class Scope : boost::noncopyable { + public: + Scope(); + virtual ~Scope(); + + virtual void reset() = 0; + virtual void init( const BSONObj * data ) = 0; + void init( const char * data ) { + BSONObj o( data ); + init( &o ); + } + + virtual void localConnect( const char * dbName ) = 0; + virtual void externalSetup() = 0; + + class NoDBAccess { + Scope * _s; + public: + NoDBAccess( Scope * s ) { + _s = s; + } + ~NoDBAccess() { + _s->rename( "____db____" , "db" ); + } + }; + NoDBAccess disableDBAccess( const char * why ) { + rename( "db" , "____db____" ); + return NoDBAccess( this ); + } + + virtual double getNumber( const char *field ) = 0; + virtual int getNumberInt( const char *field ) { return (int)getNumber( field ); } + virtual long long getNumberLongLong( const char *field ) { return (long long)getNumber( field ); } + virtual string getString( const char *field ) = 0; + virtual bool getBoolean( const char *field ) = 0; + virtual BSONObj getObject( const char *field ) = 0; + + virtual int type( const char *field ) = 0; + + virtual void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ); + + virtual void setElement( const char *field , const BSONElement& e ) = 0; + virtual void setNumber( const char *field , double val ) = 0; + virtual void setString( const char *field , const char * val ) = 0; + virtual void setObject( const char *field , const BSONObj& obj , bool readOnly=true ) = 0; + virtual void setBoolean( const char *field , bool val ) = 0; + virtual void setFunction( const char *field , const char * code ) = 0; +// virtual void setThis( const BSONObj * obj ) = 0; + + virtual ScriptingFunction createFunction( const char * code ); + + virtual void rename( const char * from , const char * to ) = 0; + /** + * @return 0 on success + */ + virtual int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool readOnlyArgs = false, bool readOnlyRecv = false ) = 0; + void invokeSafe( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool readOnlyArgs = false, bool readOnlyRecv = false ) { + int res = invoke( func , args , recv, timeoutMs, ignoreReturn, readOnlyArgs, readOnlyRecv ); + if ( res == 0 ) + return; + throw UserException( 9004 , (string)"invoke failed: " + getError() ); + } + virtual string getError() = 0; + virtual bool hasOutOfMemoryException() = 0; + + int invoke( const char* code , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 ); + void invokeSafe( const char* code , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 ) { + if ( invoke( code , args , recv, timeoutMs ) == 0 ) + return; + throw UserException( 9005 , (string)"invoke failed: " + getError() ); + } + + virtual bool exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) = 0; + virtual void execSetup( const StringData& code , const string& name = "setup" ) { + exec( code , name , false , true , true , 0 ); + } + + void execSetup( const JSFile& file) { + execSetup(file.source, file.name); + } + + void execCoreFiles(); + + virtual bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ); + + virtual void injectNative( const char *field, NativeFunction func, void* data = 0 ) = 0; + + virtual void gc() = 0; + + void loadStored( bool ignoreNotConnected = false ); + + /** + if any changes are made to .system.js, call this + right now its just global - slightly inefficient, but a lot simpler + */ + static void storedFuncMod(); + + static int getNumScopes() { + return _numScopes; + } + + static void validateObjectIdString( const string &str ); + + /** increments the number of times a scope was used */ + void incTimeUsed() { ++_numTimeUsed; } + /** gets the number of times a scope was used */ + int getTimeUsed() { return _numTimeUsed; } + + protected: + + virtual ScriptingFunction _createFunction( const char * code ) = 0; + + string _localDBName; + long long _loadedVersion; + set<string> _storedNames; + static long long _lastVersion; + map<string,ScriptingFunction> _cachedFunctions; + int _numTimeUsed; + + static int _numScopes; + }; + + void installGlobalUtils( Scope& scope ); + + class DBClientWithCommands; + + class ScriptEngine : boost::noncopyable { + public: + ScriptEngine(); + virtual ~ScriptEngine(); + + virtual Scope * newScope() { + Scope *s = createScope(); + if ( s && _scopeInitCallback ) + _scopeInitCallback( *s ); + installGlobalUtils( *s ); + return s; + } + + virtual void runTest() = 0; + + virtual bool utf8Ok() const = 0; + + static void setup(); + + /** gets a scope from the pool or a new one if pool is empty + * @param pool An identifier for the pool, usually the db name + * @return the scope */ + auto_ptr<Scope> getPooledScope( const string& pool ); + + /** call this method to release some JS resources when a thread is done */ + void threadDone(); + + struct Unlocker { virtual ~Unlocker() {} }; + virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new Unlocker ); } + + void setScopeInitCallback( void ( *func )( Scope & ) ) { _scopeInitCallback = func; } + static void setConnectCallback( void ( *func )( DBClientWithCommands& ) ) { _connectCallback = func; } + static void runConnectCallback( DBClientWithCommands &c ) { + if ( _connectCallback ) + _connectCallback( c ); + } + + // engine implementation may either respond to interrupt events or + // poll for interrupts + + // the interrupt functions must not wait indefinitely on a lock + virtual void interrupt( unsigned opSpec ) {} + virtual void interruptAll() {} + + static void setGetInterruptSpecCallback( unsigned ( *func )() ) { _getInterruptSpecCallback = func; } + static bool haveGetInterruptSpecCallback() { return _getInterruptSpecCallback; } + static unsigned getInterruptSpec() { + massert( 13474, "no _getInterruptSpecCallback", _getInterruptSpecCallback ); + return _getInterruptSpecCallback(); + } + + static void setCheckInterruptCallback( const char * ( *func )() ) { _checkInterruptCallback = func; } + static bool haveCheckInterruptCallback() { return _checkInterruptCallback; } + static const char * checkInterrupt() { + return _checkInterruptCallback ? _checkInterruptCallback() : ""; + } + static bool interrupted() { + const char *r = checkInterrupt(); + return r && r[ 0 ]; + } + + protected: + virtual Scope * createScope() = 0; + + private: + void ( *_scopeInitCallback )( Scope & ); + static void ( *_connectCallback )( DBClientWithCommands & ); + static const char * ( *_checkInterruptCallback )(); + static unsigned ( *_getInterruptSpecCallback )(); + }; + + bool hasJSReturn( const string& s ); + + const char * jsSkipWhiteSpace( const char * raw ); + + extern ScriptEngine * globalScriptEngine; +} diff --git a/src/mongo/scripting/engine_java.cpp b/src/mongo/scripting/engine_java.cpp new file mode 100644 index 00000000000..57388166e98 --- /dev/null +++ b/src/mongo/scripting/engine_java.cpp @@ -0,0 +1,764 @@ +// java.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "pch.h" +#include "engine_java.h" +#include <iostream> +#include <map> +#include <list> + +#include "../db/jsobj.h" +#include "../db/db.h" + +using namespace boost::filesystem; + +namespace mongo { + +//#define JNI_DEBUG 1 + +#ifdef JNI_DEBUG +#undef JNI_DEBUG +#define JNI_DEBUG(x) cout << x << endl +#else +#undef JNI_DEBUG +#define JNI_DEBUG(x) +#endif + +} // namespace mongo + + + +#include "../util/net/message.h" +#include "../db/db.h" + +using namespace std; + +namespace mongo { + +#if defined(_WIN32) + /* [dm] this being undefined without us adding it here means there is + no tss cleanup on windows for boost lib? + we don't care for now esp on windows only + + the boost source says: + + This function's sole purpose is to cause a link error in cases where + automatic tss cleanup is not implemented by Boost.Threads as a + reminder that user code is responsible for calling the necessary + functions at the appropriate times (and for implementing an a + tss_cleanup_implemented() function to eliminate the linker's + missing symbol error). + + If Boost.Threads later implements automatic tss cleanup in cases + where it currently doesn't (which is the plan), the duplicate + symbol error will warn the user that their custom solution is no + longer needed and can be removed. + */ + extern "C" void tss_cleanup_implemented(void) { + //out() << "tss_cleanup_implemented called" << endl; + } +#endif + + JavaJSImpl * JavaJS = 0; + extern string dbExecCommand; + +#if !defined(NOJNI) + + void myJNIClean( JNIEnv * env ) { + JavaJS->detach( env ); + } + +#if defined(_WIN32) + const char SYSTEM_COLON = ';'; +#else + const char SYSTEM_COLON = ':'; +#endif + + + void _addClassPath( const char * ed , stringstream & ss , const char * subdir ) { + path includeDir(ed); + includeDir /= subdir; + directory_iterator end; + try { + directory_iterator i(includeDir); + while ( i != end ) { + path p = *i; + ss << SYSTEM_COLON << p.string(); + i++; + } + } + catch (...) { + problem() << "exception looking for ed class path includeDir: " << includeDir.string() << endl; + sleepsecs(3); + dbexit( EXIT_JAVA ); + } + } + + + JavaJSImpl::JavaJSImpl(const char *appserverPath) { + _jvm = 0; + _mainEnv = 0; + _dbhook = 0; + + stringstream ss; + string edTemp; + + const char * ed = 0; + ss << "-Djava.class.path=."; + + if ( appserverPath ) { + ed = findEd(appserverPath); + assert( ed ); + + ss << SYSTEM_COLON << ed << "/build/"; + + _addClassPath( ed , ss , "include" ); + _addClassPath( ed , ss , "include/jython/" ); + _addClassPath( ed , ss , "include/jython/javalib" ); + } + else { + const string jars = findJars(); + _addClassPath( jars.c_str() , ss , "jars" ); + + edTemp += (string)jars + "/jars/mongojs-js.jar"; + ed = edTemp.c_str(); + } + + + +#if defined(_WIN32) + ss << SYSTEM_COLON << "C:\\Program Files\\Java\\jdk\\lib\\tools.jar"; +#else + ss << SYSTEM_COLON << "/opt/java/lib/tools.jar"; +#endif + + if ( getenv( "CLASSPATH" ) ) + ss << SYSTEM_COLON << getenv( "CLASSPATH" ); + + string s = ss.str(); + char * p = (char *)malloc( s.size() * 4 ); + strcpy( p , s.c_str() ); + char *q = p; +#if defined(_WIN32) + while ( *p ) { + if ( *p == '/' ) *p = '\\'; + p++; + } +#endif + + log(1) << "classpath: " << q << endl; + + JavaVMOption * options = new JavaVMOption[4]; + options[0].optionString = q; + options[1].optionString = (char*)"-Djava.awt.headless=true"; + options[2].optionString = (char*)"-Xmx300m"; + + // Prevent JVM from using async signals internally, since on linux the pre-installed handlers for these + // signals don't seem to be respected by JNI. + options[3].optionString = (char*)"-Xrs"; + // -Xcheck:jni + + _vmArgs = new JavaVMInitArgs(); + _vmArgs->version = JNI_VERSION_1_4; + _vmArgs->options = options; + _vmArgs->nOptions = 4; + _vmArgs->ignoreUnrecognized = JNI_FALSE; + + log(1) << "loading JVM" << endl; + jint res = JNI_CreateJavaVM( &_jvm, (void**)&_mainEnv, _vmArgs ); + + if ( res ) { + log() << "using classpath: " << q << endl; + log() + << " res : " << (unsigned) res << " " + << "_jvm : " << _jvm << " " + << "_env : " << _mainEnv << " " + << endl; + problem() << "Couldn't create JVM res:" << (int) res << " terminating" << endl; + log() << "(try --nojni if you do not require that functionality)" << endl; + exit(22); + } + jassert( res == 0 ); + jassert( _jvm > 0 ); + jassert( _mainEnv > 0 ); + + _envs = new boost::thread_specific_ptr<JNIEnv>( myJNIClean ); + assert( ! _envs->get() ); + _envs->reset( _mainEnv ); + + _dbhook = findClass( "ed/db/JSHook" ); + if ( _dbhook == 0 ) { + log() << "using classpath: " << q << endl; + printException(); + } + jassert( _dbhook ); + + if ( ed ) { + jmethodID init = _mainEnv->GetStaticMethodID( _dbhook , "init" , "(Ljava/lang/String;)V" ); + jassert( init ); + _mainEnv->CallStaticVoidMethod( _dbhook , init , _getEnv()->NewStringUTF( ed ) ); + } + + _dbjni = findClass( "ed/db/DBJni" ); + jassert( _dbjni ); + + _scopeCreate = _mainEnv->GetStaticMethodID( _dbhook , "scopeCreate" , "()J" ); + _scopeInit = _mainEnv->GetStaticMethodID( _dbhook , "scopeInit" , "(JLjava/nio/ByteBuffer;)Z" ); + _scopeSetThis = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetThis" , "(JLjava/nio/ByteBuffer;)Z" ); + _scopeReset = _mainEnv->GetStaticMethodID( _dbhook , "scopeReset" , "(J)Z" ); + _scopeFree = _mainEnv->GetStaticMethodID( _dbhook , "scopeFree" , "(J)V" ); + + _scopeGetNumber = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetNumber" , "(JLjava/lang/String;)D" ); + _scopeGetString = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetString" , "(JLjava/lang/String;)Ljava/lang/String;" ); + _scopeGetBoolean = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetBoolean" , "(JLjava/lang/String;)Z" ); + _scopeGetType = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetType" , "(JLjava/lang/String;)B" ); + _scopeGetObject = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetObject" , "(JLjava/lang/String;Ljava/nio/ByteBuffer;)I" ); + _scopeGuessObjectSize = _mainEnv->GetStaticMethodID( _dbhook , "scopeGuessObjectSize" , "(JLjava/lang/String;)J" ); + + _scopeSetNumber = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetNumber" , "(JLjava/lang/String;D)Z" ); + _scopeSetBoolean = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetBoolean" , "(JLjava/lang/String;Z)Z" ); + _scopeSetString = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetString" , "(JLjava/lang/String;Ljava/lang/String;)Z" ); + _scopeSetObject = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetObject" , "(JLjava/lang/String;Ljava/nio/ByteBuffer;)Z" ); + + _functionCreate = _mainEnv->GetStaticMethodID( _dbhook , "functionCreate" , "(Ljava/lang/String;)J" ); + _invoke = _mainEnv->GetStaticMethodID( _dbhook , "invoke" , "(JJ)I" ); + + jassert( _scopeCreate ); + jassert( _scopeInit ); + jassert( _scopeSetThis ); + jassert( _scopeReset ); + jassert( _scopeFree ); + + jassert( _scopeGetNumber ); + jassert( _scopeGetString ); + jassert( _scopeGetObject ); + jassert( _scopeGetBoolean ); + jassert( _scopeGetType ); + jassert( _scopeGuessObjectSize ); + + jassert( _scopeSetNumber ); + jassert( _scopeSetBoolean ); + jassert( _scopeSetString ); + jassert( _scopeSetObject ); + + jassert( _functionCreate ); + jassert( _invoke ); + + JNINativeMethod * nativeSay = new JNINativeMethod(); + nativeSay->name = (char*)"native_say"; + nativeSay->signature = (char*)"(Ljava/nio/ByteBuffer;)V"; + nativeSay->fnPtr = (void*)java_native_say; + _mainEnv->RegisterNatives( _dbjni , nativeSay , 1 ); + + + JNINativeMethod * nativeCall = new JNINativeMethod(); + nativeCall->name = (char*)"native_call"; + nativeCall->signature = (char*)"(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I"; + nativeCall->fnPtr = (void*)java_native_call; + _mainEnv->RegisterNatives( _dbjni , nativeCall , 1 ); + + } + + JavaJSImpl::~JavaJSImpl() { + if ( _jvm ) { + _jvm->DestroyJavaVM(); + cout << "Destroying JVM" << endl; + } + } + +// scope + + jlong JavaJSImpl::scopeCreate() { + return _getEnv()->CallStaticLongMethod( _dbhook , _scopeCreate ); + } + + jboolean JavaJSImpl::scopeReset( jlong id ) { + return _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeReset ); + } + + void JavaJSImpl::scopeFree( jlong id ) { + _getEnv()->CallStaticVoidMethod( _dbhook , _scopeFree , id ); + } + +// scope setters + + int JavaJSImpl::scopeSetBoolean( jlong id , const char * field , jboolean val ) { + jstring fieldString = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetNumber , id , fieldString , val ); + _getEnv()->DeleteLocalRef( fieldString ); + return res; + } + + int JavaJSImpl::scopeSetNumber( jlong id , const char * field , double val ) { + jstring fieldString = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetNumber , id , fieldString , val ); + _getEnv()->DeleteLocalRef( fieldString ); + return res; + } + + int JavaJSImpl::scopeSetString( jlong id , const char * field , const char * val ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jstring s2 = _getEnv()->NewStringUTF( val ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetString , id , s1 , s2 ); + _getEnv()->DeleteLocalRef( s1 ); + _getEnv()->DeleteLocalRef( s2 ); + return res; + } + + int JavaJSImpl::scopeSetObject( jlong id , const char * field , const BSONObj * obj ) { + jobject bb = 0; + if ( obj ) { + bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + } + + jstring s1 = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetObject , id , s1 , bb ); + _getEnv()->DeleteLocalRef( s1 ); + if ( bb ) + _getEnv()->DeleteLocalRef( bb ); + + return res; + } + + int JavaJSImpl::scopeInit( jlong id , const BSONObj * obj ) { + if ( ! obj ) + return 0; + + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeInit , id , bb ); + _getEnv()->DeleteLocalRef( bb ); + return res; + } + + int JavaJSImpl::scopeSetThis( jlong id , const BSONObj * obj ) { + if ( ! obj ) + return 0; + + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetThis , id , bb ); + _getEnv()->DeleteLocalRef( bb ); + return res; + } + +// scope getters + + char JavaJSImpl::scopeGetType( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + int res =_getEnv()->CallStaticByteMethod( _dbhook , _scopeGetType , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + double JavaJSImpl::scopeGetNumber( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + double res = _getEnv()->CallStaticDoubleMethod( _dbhook , _scopeGetNumber , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + jboolean JavaJSImpl::scopeGetBoolean( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jboolean res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeGetBoolean , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + string JavaJSImpl::scopeGetString( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jstring s = (jstring)_getEnv()->CallStaticObjectMethod( _dbhook , _scopeGetString , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + + if ( ! s ) + return ""; + + const char * c = _getEnv()->GetStringUTFChars( s , 0 ); + string retStr(c); + _getEnv()->ReleaseStringUTFChars( s , c ); + return retStr; + } + + BSONObj JavaJSImpl::scopeGetObject( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + int guess = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGuessObjectSize , id , _getEnv()->NewStringUTF( field ) ); + _getEnv()->DeleteLocalRef( s1 ); + + if ( guess == 0 ) + return BSONObj(); + + BSONObj::Holder* holder = (BSONObj::Holder*) malloc(guess + sizeof(unsigned)); + holder->zero() + + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)holder->data , guess ); + jassert( bb ); + + int len = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGetObject , id , _getEnv()->NewStringUTF( field ) , bb ); + _getEnv()->DeleteLocalRef( bb ); + jassert( len > 0 && len < guess ); + + BSONObj obj(holder); + assert( obj.objsize() <= guess ); + return obj; + } + +// other + + jlong JavaJSImpl::functionCreate( const char * code ) { + jstring s = _getEnv()->NewStringUTF( code ); + jassert( s ); + jlong id = _getEnv()->CallStaticLongMethod( _dbhook , _functionCreate , s ); + _getEnv()->DeleteLocalRef( s ); + return id; + } + + int JavaJSImpl::invoke( jlong scope , jlong function ) { + return _getEnv()->CallStaticIntMethod( _dbhook , _invoke , scope , function ); + } + +// --- fun run method + + void JavaJSImpl::run( const char * js ) { + jclass c = findClass( "ed/js/JS" ); + jassert( c ); + + jmethodID m = _getEnv()->GetStaticMethodID( c , "eval" , "(Ljava/lang/String;)Ljava/lang/Object;" ); + jassert( m ); + + jstring s = _getEnv()->NewStringUTF( js ); + log() << _getEnv()->CallStaticObjectMethod( c , m , s ) << endl; + _getEnv()->DeleteLocalRef( s ); + } + + void JavaJSImpl::printException() { + jthrowable exc = _getEnv()->ExceptionOccurred(); + if ( exc ) { + _getEnv()->ExceptionDescribe(); + _getEnv()->ExceptionClear(); + } + + } + + JNIEnv * JavaJSImpl::_getEnv() { + JNIEnv * env = _envs->get(); + if ( env ) + return env; + + int res = _jvm->AttachCurrentThread( (void**)&env , (void*)&_vmArgs ); + if ( res ) { + out() << "ERROR javajs attachcurrentthread fails res:" << res << '\n'; + assert(false); + } + + _envs->reset( env ); + return env; + } + + Scope * JavaJSImpl::createScope() { + return new JavaScope(); + } + + void ScriptEngine::setup() { + if ( ! JavaJS ) { + JavaJS = new JavaJSImpl(); + globalScriptEngine = JavaJS; + } + } + + void jasserted(const char *msg, const char *file, unsigned line) { + log() << "jassert failed " << msg << " " << file << " " << line << endl; + if ( JavaJS ) JavaJS->printException(); + throw AssertionException(); + } + + + const char* findEd(const char *path) { + +#if defined(_WIN32) + + if (!path) { + path = findEd(); + } + + // @TODO check validity + + return path; +#else + + if (!path) { + return findEd(); + } + + log() << "Appserver location specified : " << path << endl; + + if (!path) { + log() << " invalid appserver location : " << path << " : terminating - prepare for bus error" << endl; + return 0; + } + + DIR *testDir = opendir(path); + + if (testDir) { + log(1) << " found directory for appserver : " << path << endl; + closedir(testDir); + return path; + } + else { + log() << " ERROR : not a directory for specified appserver location : " << path << " - prepare for bus error" << endl; + return null; + } +#endif + } + + const char * findEd() { + +#if defined(_WIN32) + log() << "Appserver location will be WIN32 default : c:/l/ed/" << endl; + return "c:/l/ed"; +#else + + static list<const char*> possibleEdDirs; + if ( ! possibleEdDirs.size() ) { + possibleEdDirs.push_back( "../../ed/ed/" ); // this one for dwight dev box + possibleEdDirs.push_back( "../ed/" ); + possibleEdDirs.push_back( "../../ed/" ); + possibleEdDirs.push_back( "../babble/" ); + possibleEdDirs.push_back( "../../babble/" ); + } + + for ( list<const char*>::iterator i = possibleEdDirs.begin() ; i != possibleEdDirs.end(); i++ ) { + const char * temp = *i; + DIR * test = opendir( temp ); + if ( ! test ) + continue; + + closedir( test ); + log(1) << "found directory for appserver : " << temp << endl; + return temp; + } + + return 0; +#endif + }; + + const string findJars() { + + static list<string> possible; + if ( ! possible.size() ) { + possible.push_back( "./" ); + possible.push_back( "../" ); + + log(2) << "dbExecCommand: " << dbExecCommand << endl; + + string dbDir = dbExecCommand; +#ifdef WIN32 + if ( dbDir.find( "\\" ) != string::npos ) { + dbDir = dbDir.substr( 0 , dbDir.find_last_of( "\\" ) ); + } + else { + dbDir = "."; + } +#else + if ( dbDir.find( "/" ) != string::npos ) { + dbDir = dbDir.substr( 0 , dbDir.find_last_of( "/" ) ); + } + else { + bool found = false; + + if ( getenv( "PATH" ) ) { + string s = getenv( "PATH" ); + s += ":"; + pcrecpp::StringPiece input( s ); + string dir; + pcrecpp::RE re("(.*?):"); + while ( re.Consume( &input, &dir ) ) { + string test = dir + "/" + dbExecCommand; + if ( boost::filesystem::exists( test ) ) { + while ( boost::filesystem::symbolic_link_exists( test ) ) { + char tmp[2048]; + int len = readlink( test.c_str() , tmp , 2048 ); + tmp[len] = 0; + log(5) << " symlink " << test << " -->> " << tmp << endl; + test = tmp; + + dir = test.substr( 0 , test.rfind( "/" ) ); + } + dbDir = dir; + found = true; + break; + } + } + } + + if ( ! found ) + dbDir = "."; + } +#endif + + log(2) << "dbDir [" << dbDir << "]" << endl; + possible.push_back( ( dbDir + "/../lib/mongo/" )); + possible.push_back( ( dbDir + "/../lib64/mongo/" )); + possible.push_back( ( dbDir + "/../lib32/mongo/" )); + possible.push_back( ( dbDir + "/" )); + possible.push_back( ( dbDir + "/lib64/mongo/" )); + possible.push_back( ( dbDir + "/lib32/mongo/" )); + } + + for ( list<string>::iterator i = possible.begin() ; i != possible.end(); i++ ) { + const string temp = *i; + const string jarDir = ((string)temp) + "jars/"; + + log(5) << "possible jarDir [" << jarDir << "]" << endl; + + path p(jarDir ); + if ( ! boost::filesystem::exists( p) ) + continue; + + log(1) << "found directory for jars : " << jarDir << endl; + return temp; + } + + problem() << "ERROR : can't find directory for jars - terminating" << endl; + exit(44); + return 0; + + }; + + +// --- + + JNIEXPORT void JNICALL java_native_say(JNIEnv * env , jclass, jobject outBuffer ) { + JNI_DEBUG( "native say called!" ); + + Message out( env->GetDirectBufferAddress( outBuffer ) , false ); + Message in; + + jniCallback( out , in ); + assert( ! out.doIFreeIt() ); + curNs = 0; + } + + JNIEXPORT jint JNICALL java_native_call(JNIEnv * env , jclass, jobject outBuffer , jobject inBuffer ) { + JNI_DEBUG( "native call called!" ); + + Message out( env->GetDirectBufferAddress( outBuffer ) , false ); + Message in; + + jniCallback( out , in ); + curNs = 0; + + JNI_DEBUG( "in.data : " << in.data ); + if ( in.data && in.data->len > 0 ) { + JNI_DEBUG( "copying data of len :" << in.data->len ); + assert( env->GetDirectBufferCapacity( inBuffer ) >= in.data->len ); + memcpy( env->GetDirectBufferAddress( inBuffer ) , in.data , in.data->len ); + + assert( ! out.doIFreeIt() ); + assert( in.doIFreeIt() ); + return in.data->len; + } + + return 0; + } + +// ---- + + void JavaJSImpl::runTest() { + + const int debug = 0; + + JavaJSImpl& JavaJS = *mongo::JavaJS; + + jlong scope = JavaJS.scopeCreate(); + jassert( scope ); + if ( debug ) out() << "got scope" << endl; + + + jlong func1 = JavaJS.functionCreate( "foo = 5.6; bar = \"eliot\"; abc = { foo : 517 }; " ); + jassert( ! JavaJS.invoke( scope , func1 ) ); + + + if ( debug ) out() << "func3 start" << endl; + jlong func3 = JavaJS.functionCreate( "function(){ z = true; } " ); + jassert( func3 ); + jassert( ! JavaJS.invoke( scope , func3 ) ); + jassert( JavaJS.scopeGetBoolean( scope , "z" ) ); + if ( debug ) out() << "func3 done" << endl; + + if ( debug ) out() << "going to get object" << endl; + BSONObj obj = JavaJS.scopeGetObject( scope , "abc" ); + if ( debug ) out() << "done getting object" << endl; + + if ( debug ) { + out() << "obj : " << obj.toString() << endl; + } + + { + time_t start = time(0); + for ( int i=0; i<5000; i++ ) { + JavaJS.scopeSetObject( scope , "obj" , &obj ); + } + time_t end = time(0); + + if ( debug ) + out() << "time : " << (unsigned) ( end - start ) << endl; + } + + if ( debug ) out() << "func4 start" << endl; + JavaJS.scopeSetObject( scope , "obj" , &obj ); + if ( debug ) out() << "\t here 1" << endl; + jlong func4 = JavaJS.functionCreate( "tojson( obj );" ); + if ( debug ) out() << "\t here 2" << endl; + jassert( ! JavaJS.invoke( scope , func4 ) ); + if ( debug ) out() << "func4 end" << endl; + + if ( debug ) out() << "func5 start" << endl; + jassert( JavaJS.scopeSetObject( scope , "c" , &obj ) ); + jlong func5 = JavaJS.functionCreate( "assert.eq( 517 , c.foo );" ); + jassert( func5 ); + jassert( ! JavaJS.invoke( scope , func5 ) ); + if ( debug ) out() << "func5 done" << endl; + + if ( debug ) out() << "func6 start" << endl; + for ( int i=0; i<100; i++ ) { + double val = i + 5; + JavaJS.scopeSetNumber( scope , "zzz" , val ); + jlong func6 = JavaJS.functionCreate( " xxx = zzz; " ); + jassert( ! JavaJS.invoke( scope , func6 ) ); + double n = JavaJS.scopeGetNumber( scope , "xxx" ); + jassert( val == n ); + } + if ( debug ) out() << "func6 done" << endl; + + jlong func7 = JavaJS.functionCreate( "return 11;" ); + jassert( ! JavaJS.invoke( scope , func7 ) ); + assert( 11 == JavaJS.scopeGetNumber( scope , "return" ) ); + + scope = JavaJS.scopeCreate(); + jlong func8 = JavaJS.functionCreate( "function(){ return 12; }" ); + jassert( ! JavaJS.invoke( scope , func8 ) ); + assert( 12 == JavaJS.scopeGetNumber( scope , "return" ) ); + + } + +#endif + +} // namespace mongo diff --git a/src/mongo/scripting/engine_java.h b/src/mongo/scripting/engine_java.h new file mode 100644 index 00000000000..b8245ba6f22 --- /dev/null +++ b/src/mongo/scripting/engine_java.h @@ -0,0 +1,223 @@ +// engine_java.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* this file contains code to call into java (into the 10gen sandbox) from inside the database */ + +#pragma once + +#include "../pch.h" + +#include <jni.h> +#include <errno.h> +#include <sys/types.h> + +#if !defined(_WIN32) +#include <dirent.h> +#endif + +#include "../db/jsobj.h" + +#include "engine.h" + +namespace mongo { + + void jasserted(const char *msg, const char *file, unsigned line); +#define jassert(_Expression) if ( ! ( _Expression ) ){ jasserted(#_Expression, __FILE__, __LINE__); } + + const char * findEd(); + const char * findEd(const char *); + const string findJars(); + + class BSONObj; + + class JavaJSImpl : public ScriptEngine { + public: + JavaJSImpl(const char * = 0); + ~JavaJSImpl(); + + jlong scopeCreate(); + int scopeInit( jlong id , const BSONObj * obj ); + int scopeSetThis( jlong id , const BSONObj * obj ); + jboolean scopeReset( jlong id ); + void scopeFree( jlong id ); + + double scopeGetNumber( jlong id , const char * field ); + string scopeGetString( jlong id , const char * field ); + jboolean scopeGetBoolean( jlong id , const char * field ); + BSONObj scopeGetObject( jlong id , const char * field ); + char scopeGetType( jlong id , const char * field ); + + int scopeSetNumber( jlong id , const char * field , double val ); + int scopeSetString( jlong id , const char * field , const char * val ); + int scopeSetObject( jlong id , const char * field , const BSONObj * obj ); + int scopeSetBoolean( jlong id , const char * field , jboolean val ); + + jlong functionCreate( const char * code ); + + /* return values: + public static final int NO_SCOPE = -1; + public static final int NO_FUNCTION = -2; + public static final int INVOKE_ERROR = -3; + public static final int INVOKE_SUCCESS = 0; + */ + int invoke( jlong scope , jlong function ); + + void printException(); + + void run( const char * js ); + + void detach( JNIEnv * env ) { + _jvm->DetachCurrentThread(); + } + + Scope * createScope(); + + void runTest(); + private: + + jobject create( const char * name ) { + jclass c = findClass( name ); + if ( ! c ) + return 0; + + jmethodID cons = _getEnv()->GetMethodID( c , "<init>" , "()V" ); + if ( ! cons ) + return 0; + + return _getEnv()->NewObject( c , cons ); + } + + jclass findClass( const char * name ) { + return _getEnv()->FindClass( name ); + } + + + private: + + JNIEnv * _getEnv(); + + JavaVM * _jvm; + JNIEnv * _mainEnv; + JavaVMInitArgs * _vmArgs; + + boost::thread_specific_ptr<JNIEnv> * _envs; + + jclass _dbhook; + jclass _dbjni; + + jmethodID _scopeCreate; + jmethodID _scopeInit; + jmethodID _scopeSetThis; + jmethodID _scopeReset; + jmethodID _scopeFree; + + jmethodID _scopeGetNumber; + jmethodID _scopeGetString; + jmethodID _scopeGetObject; + jmethodID _scopeGetBoolean; + jmethodID _scopeGuessObjectSize; + jmethodID _scopeGetType; + + jmethodID _scopeSetNumber; + jmethodID _scopeSetString; + jmethodID _scopeSetObject; + jmethodID _scopeSetBoolean; + + jmethodID _functionCreate; + + jmethodID _invoke; + + }; + + extern JavaJSImpl *JavaJS; + +// a javascript "scope" + class JavaScope : public Scope { + public: + JavaScope() { + s = JavaJS->scopeCreate(); + } + virtual ~JavaScope() { + JavaJS->scopeFree(s); + s = 0; + } + void reset() { + JavaJS->scopeReset(s); + } + + void init( BSONObj * o ) { + JavaJS->scopeInit( s , o ); + } + + void localConnect( const char * dbName ) { + setString("$client", dbName ); + } + + double getNumber(const char *field) { + return JavaJS->scopeGetNumber(s,field); + } + string getString(const char *field) { + return JavaJS->scopeGetString(s,field); + } + bool getBoolean(const char *field) { + return JavaJS->scopeGetBoolean(s,field); + } + BSONObj getObject(const char *field ) { + return JavaJS->scopeGetObject(s,field); + } + int type(const char *field ) { + return JavaJS->scopeGetType(s,field); + } + + void setThis( const BSONObj * obj ) { + JavaJS->scopeSetThis( s , obj ); + } + + void setNumber(const char *field, double val ) { + JavaJS->scopeSetNumber(s,field,val); + } + void setString(const char *field, const char * val ) { + JavaJS->scopeSetString(s,field,val); + } + void setObject(const char *field, const BSONObj& obj , bool readOnly ) { + uassert( 10211 , "only readOnly setObject supported in java" , readOnly ); + JavaJS->scopeSetObject(s,field,&obj); + } + void setBoolean(const char *field, bool val ) { + JavaJS->scopeSetBoolean(s,field,val); + } + + ScriptingFunction createFunction( const char * code ) { + return JavaJS->functionCreate( code ); + } + + int invoke( ScriptingFunction function , const BSONObj& args ) { + setObject( "args" , args , true ); + return JavaJS->invoke(s,function); + } + + string getError() { + return getString( "error" ); + } + + jlong s; + }; + + JNIEXPORT void JNICALL java_native_say(JNIEnv *, jclass, jobject outBuffer ); + JNIEXPORT jint JNICALL java_native_call(JNIEnv *, jclass, jobject outBuffer , jobject inBuffer ); + +} // namespace mongo diff --git a/src/mongo/scripting/engine_none.cpp b/src/mongo/scripting/engine_none.cpp new file mode 100644 index 00000000000..d13dbecc06e --- /dev/null +++ b/src/mongo/scripting/engine_none.cpp @@ -0,0 +1,24 @@ +// engine_none.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "engine.h" + +namespace mongo { + void ScriptEngine::setup() { + // noop + } +} diff --git a/src/mongo/scripting/engine_spidermonkey.cpp b/src/mongo/scripting/engine_spidermonkey.cpp new file mode 100644 index 00000000000..70b89cddbb5 --- /dev/null +++ b/src/mongo/scripting/engine_spidermonkey.cpp @@ -0,0 +1,1766 @@ +// engine_spidermonkey.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pch.h" +#include "engine_spidermonkey.h" +#include "../client/dbclient.h" + +#ifndef _WIN32 +#include <boost/date_time/posix_time/posix_time.hpp> +#undef assert +#define assert MONGO_assert +#endif + +#define smuassert( cx , msg , val ) \ + if ( ! ( val ) ){ \ + JS_ReportError( cx , msg ); \ + return JS_FALSE; \ + } + +#define CHECKNEWOBJECT(xx,ctx,w) \ + if ( ! xx ){ \ + massert(13072,(string)"JS_NewObject failed: " + w ,xx); \ + } + +#define CHECKJSALLOC( newthing ) \ + massert( 13615 , "JS allocation failed, either memory leak or using too much memory" , newthing ) + +namespace mongo { + + class InvalidUTF8Exception : public UserException { + public: + InvalidUTF8Exception() : UserException( 9006 , "invalid utf8" ) { + } + }; + + string trim( string s ) { + while ( s.size() && isspace( s[0] ) ) + s = s.substr( 1 ); + + while ( s.size() && isspace( s[s.size()-1] ) ) + s = s.substr( 0 , s.size() - 1 ); + + return s; + } + + boost::thread_specific_ptr<SMScope> currentScope( dontDeleteScope ); + boost::recursive_mutex &smmutex = *( new boost::recursive_mutex ); +#define smlock recursive_scoped_lock ___lk( smmutex ); + +#define GETHOLDER(x,o) ((BSONHolder*)JS_GetPrivate( x , o )) + + class BSONFieldIterator; + + class BSONHolder { + public: + + BSONHolder( BSONObj obj ) { + _obj = obj.getOwned(); + _inResolve = false; + _modified = false; + _magic = 17; + } + + ~BSONHolder() { + _magic = 18; + } + + void check() { + uassert( 10212 , "holder magic value is wrong" , _magic == 17 && _obj.isValid() ); + } + + BSONFieldIterator * it(); + + BSONObj _obj; + bool _inResolve; + char _magic; + list<string> _extra; + set<string> _removed; + bool _modified; + }; + + class BSONFieldIterator { + public: + + BSONFieldIterator( BSONHolder * holder ) { + + set<string> added; + + BSONObjIterator it( holder->_obj ); + while ( it.more() ) { + BSONElement e = it.next(); + if ( holder->_removed.count( e.fieldName() ) ) + continue; + _names.push_back( e.fieldName() ); + added.insert( e.fieldName() ); + } + + for ( list<string>::iterator i = holder->_extra.begin(); i != holder->_extra.end(); i++ ) { + if ( ! added.count( *i ) ) + _names.push_back( *i ); + } + + _it = _names.begin(); + } + + bool more() { + return _it != _names.end(); + } + + string next() { + string s = *_it; + _it++; + return s; + } + + private: + list<string> _names; + list<string>::iterator _it; + }; + + BSONFieldIterator * BSONHolder::it() { + return new BSONFieldIterator( this ); + } + + class TraverseStack { + public: + TraverseStack() { + _o = 0; + _parent = 0; + } + + TraverseStack( JSObject * o , const TraverseStack * parent ) { + _o = o; + _parent = parent; + } + + TraverseStack dive( JSObject * o ) const { + if ( o ) { + uassert( 13076 , (string)"recursive toObject" , ! has( o ) ); + } + return TraverseStack( o , this ); + } + + int depth() const { + int d = 0; + const TraverseStack * s = _parent; + while ( s ) { + s = s->_parent; + d++; + } + return d; + } + + bool isTop() const { + return _parent == 0; + } + + bool has( JSObject * o ) const { + if ( ! o ) + return false; + const TraverseStack * s = this; + while ( s ) { + if ( s->_o == o ) + return true; + s = s->_parent; + } + return false; + } + + JSObject * _o; + const TraverseStack * _parent; + }; + + class Convertor : boost::noncopyable { + public: + Convertor( JSContext * cx ) { + _context = cx; + } + + string toString( JSString * so ) { + jschar * s = JS_GetStringChars( so ); + size_t srclen = JS_GetStringLength( so ); + if( srclen == 0 ) + return ""; + + size_t len = srclen * 6; // we only need *3, but see note on len below + char * dst = (char*)malloc( len ); + + len /= 2; + // doc re weird JS_EncodeCharacters api claims len expected in 16bit + // units, but experiments suggest 8bit units expected. We allocate + // enough memory that either will work. + + if ( !JS_EncodeCharacters( _context , s , srclen , dst , &len) ) { + StringBuilder temp; + temp << "Not proper UTF-16: "; + for ( size_t i=0; i<srclen; i++ ) { + if ( i > 0 ) + temp << ","; + temp << s[i]; + } + uasserted( 13498 , temp.str() ); + } + + string ss( dst , len ); + free( dst ); + if ( !JS_CStringsAreUTF8() ) + for( string::const_iterator i = ss.begin(); i != ss.end(); ++i ) + uassert( 10213 , "non ascii character detected", (unsigned char)(*i) <= 127 ); + return ss; + } + + string toString( jsval v ) { + return toString( JS_ValueToString( _context , v ) ); + } + + // NOTE No validation of passed in object + long long toNumberLongUnsafe( JSObject *o ) { + boost::uint64_t val; + if ( hasProperty( o, "top" ) ) { + val = + ( (boost::uint64_t)(boost::uint32_t)getNumber( o , "top" ) << 32 ) + + ( boost::uint32_t)( getNumber( o , "bottom" ) ); + } + else { + val = (boost::uint64_t)(boost::int64_t) getNumber( o, "floatApprox" ); + } + return val; + } + + int toNumberInt( JSObject *o ) { + return (boost::uint32_t)(boost::int32_t) getNumber( o, "floatApprox" ); + } + + double toNumber( jsval v ) { + double d; + uassert( 10214 , "not a number" , JS_ValueToNumber( _context , v , &d ) ); + return d; + } + + bool toBoolean( jsval v ) { + JSBool b; + assert( JS_ValueToBoolean( _context, v , &b ) ); + return b; + } + + OID toOID( jsval v ) { + JSContext * cx = _context; + assert( JSVAL_IS_OID( v ) ); + + JSObject * o = JSVAL_TO_OBJECT( v ); + OID oid; + oid.init( getString( o , "str" ) ); + return oid; + } + + BSONObj toObject( JSObject * o , const TraverseStack& stack=TraverseStack() ) { + if ( ! o ) + return BSONObj(); + + if ( JS_InstanceOf( _context , o , &bson_ro_class , 0 ) ) { + BSONHolder * holder = GETHOLDER( _context , o ); + assert( holder ); + return holder->_obj.getOwned(); + } + + BSONObj orig; + if ( JS_InstanceOf( _context , o , &bson_class , 0 ) ) { + BSONHolder * holder = GETHOLDER(_context,o); + assert( holder ); + if ( ! holder->_modified ) { + return holder->_obj; + } + orig = holder->_obj; + } + + BSONObjBuilder b; + + if ( ! appendSpecialDBObject( this , b , "value" , OBJECT_TO_JSVAL( o ) , o ) ) { + + if ( stack.isTop() ) { + jsval theid = getProperty( o , "_id" ); + if ( ! JSVAL_IS_VOID( theid ) ) { + append( b , "_id" , theid , EOO , stack.dive( o ) ); + } + } + + JSIdArray * properties = JS_Enumerate( _context , o ); + assert( properties ); + + for ( jsint i=0; i<properties->length; i++ ) { + jsid id = properties->vector[i]; + jsval nameval; + assert( JS_IdToValue( _context ,id , &nameval ) ); + string name = toString( nameval ); + if ( stack.isTop() && name == "_id" ) + continue; + + append( b , name , getProperty( o , name.c_str() ) , orig[name].type() , stack.dive( o ) ); + } + + JS_DestroyIdArray( _context , properties ); + } + + return b.obj(); + } + + BSONObj toObject( jsval v ) { + if ( JSVAL_IS_NULL( v ) || + JSVAL_IS_VOID( v ) ) + return BSONObj(); + + uassert( 10215 , "not an object" , JSVAL_IS_OBJECT( v ) ); + return toObject( JSVAL_TO_OBJECT( v ) ); + } + + string getFunctionCode( JSFunction * func ) { + return toString( JS_DecompileFunction( _context , func , 0 ) ); + } + + string getFunctionCode( jsval v ) { + uassert( 10216 , "not a function" , JS_TypeOfValue( _context , v ) == JSTYPE_FUNCTION ); + return getFunctionCode( JS_ValueToFunction( _context , v ) ); + } + + void appendRegex( BSONObjBuilder& b , const string& name , string s ) { + assert( s[0] == '/' ); + s = s.substr(1); + string::size_type end = s.rfind( '/' ); + b.appendRegex( name , s.substr( 0 , end ) , s.substr( end + 1 ) ); + } + + void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO , const TraverseStack& stack=TraverseStack() ) { + //cout << "name: " << name << "\t" << typeString( val ) << " oldType: " << oldType << endl; + switch ( JS_TypeOfValue( _context , val ) ) { + + case JSTYPE_VOID: b.appendUndefined( name ); break; + case JSTYPE_NULL: b.appendNull( name ); break; + + case JSTYPE_NUMBER: { + double d = toNumber( val ); + if ( oldType == NumberInt && ((int)d) == d ) + b.append( name , (int)d ); + else + b.append( name , d ); + break; + } + case JSTYPE_STRING: b.append( name , toString( val ) ); break; + case JSTYPE_BOOLEAN: b.appendBool( name , toBoolean( val ) ); break; + + case JSTYPE_OBJECT: { + JSObject * o = JSVAL_TO_OBJECT( val ); + if ( ! o || o == JSVAL_NULL ) { + b.appendNull( name ); + } + else if ( ! appendSpecialDBObject( this , b , name , val , o ) ) { + BSONObj sub = toObject( o , stack ); + if ( JS_IsArrayObject( _context , o ) ) { + b.appendArray( name , sub ); + } + else { + b.append( name , sub ); + } + } + break; + } + + case JSTYPE_FUNCTION: { + string s = toString(val); + if ( s[0] == '/' ) { + appendRegex( b , name , s ); + } + else { + b.appendCode( name , getFunctionCode( val ) ); + } + break; + } + + default: uassert( 10217 , (string)"can't append field. name:" + name + " type: " + typeString( val ) , 0 ); + } + } + + // ---------- to spider monkey --------- + + bool hasFunctionIdentifier( const string& code ) { + if ( code.size() < 9 || code.find( "function" ) != 0 ) + return false; + + return code[8] == ' ' || code[8] == '('; + } + + bool isSimpleStatement( const string& code ) { + if ( hasJSReturn( code ) ) + return false; + + if ( code.find( ';' ) != string::npos && + code.find( ';' ) != code.rfind( ';' ) ) + return false; + + if ( code.find( '\n') != string::npos ) + return false; + + if ( code.find( "for(" ) != string::npos || + code.find( "for (" ) != string::npos || + code.find( "while (" ) != string::npos || + code.find( "while(" ) != string::npos ) + return false; + + return true; + } + + void addRoot( JSFunction * f , const char * name ); + + JSFunction * compileFunction( const char * code, JSObject * assoc = 0 ) { + const char * gcName = "unknown"; + JSFunction * f = _compileFunction( code , assoc , gcName ); + //addRoot( f , gcName ); + return f; + } + + JSFunction * _compileFunction( const char * raw , JSObject * assoc , const char *& gcName ) { + if ( ! assoc ) + assoc = JS_GetGlobalObject( _context ); + + raw = jsSkipWhiteSpace( raw ); + + //cout << "RAW\n---\n" << raw << "\n---" << endl; + + static int fnum = 1; + stringstream fname; + fname << "__cf__" << fnum++ << "__"; + + if ( ! hasFunctionIdentifier( raw ) ) { + string s = raw; + if ( isSimpleStatement( s ) ) { + s = "return " + s; + } + gcName = "cf anon"; + fname << "anon"; + return JS_CompileFunction( _context , assoc , fname.str().c_str() , 0 , 0 , s.c_str() , s.size() , "nofile_a" , 0 ); + } + + string code = raw; + + size_t start = code.find( '(' ); + assert( start != string::npos ); + + string fbase; + if ( start > 9 ) { + fbase = trim( code.substr( 9 , start - 9 ) ); + } + if ( fbase.length() == 0 ) { + fbase = "anonymous_function"; + } + fname << "f__" << fbase; + + code = code.substr( start + 1 ); + size_t end = code.find( ')' ); + assert( end != string::npos ); + + string paramString = trim( code.substr( 0 , end ) ); + code = code.substr( end + 1 ); + + vector<string> params; + while ( paramString.size() ) { + size_t c = paramString.find( ',' ); + if ( c == string::npos ) { + params.push_back( paramString ); + break; + } + params.push_back( trim( paramString.substr( 0 , c ) ) ); + paramString = trim( paramString.substr( c + 1 ) ); + paramString = trim( paramString ); + } + + boost::scoped_array<const char *> paramArray (new const char*[params.size()]); + for ( size_t i=0; i<params.size(); i++ ) + paramArray[i] = params[i].c_str(); + + // avoid munging previously munged name (kludge; switching to v8 fixes underlying issue) + if ( fbase.find("__cf__") != 0 && fbase.find("__f__") == string::npos ) { + fbase = fname.str(); + } + + JSFunction * func = JS_CompileFunction( _context , assoc , fbase.c_str() , params.size() , paramArray.get() , code.c_str() , code.size() , "nofile_b" , 0 ); + + if ( ! func ) { + log() << "compile failed for: " << raw << endl; + return 0; + } + gcName = "cf normal"; + return func; + } + + jsval toval( double d ) { + jsval val; + assert( JS_NewNumberValue( _context, d , &val ) ); + return val; + } + + jsval toval( const char * c ) { + JSString * s = JS_NewStringCopyZ( _context , c ); + if ( s ) + return STRING_TO_JSVAL( s ); + + // possibly unicode, try manual + + size_t len = strlen( c ); + size_t dstlen = len * 4; + jschar * dst = (jschar*)malloc( dstlen ); + + JSBool res = JS_DecodeBytes( _context , c , len , dst, &dstlen ); + if ( res ) { + s = JS_NewUCStringCopyN( _context , dst , dstlen ); + } + + free( dst ); + + if ( ! res ) { + tlog() << "decode failed. probably invalid utf-8 string [" << c << "]" << endl; + jsval v; + if ( JS_GetPendingException( _context , &v ) ) + tlog() << "\t why: " << toString( v ) << endl; + throw InvalidUTF8Exception(); + } + + CHECKJSALLOC( s ); + return STRING_TO_JSVAL( s ); + } + + JSObject * toJSObject( const BSONObj * obj , bool readOnly=false ) { + static string ref = "$ref"; + if ( ref == obj->firstElementFieldName() ) { + JSObject * o = JS_NewObject( _context , &dbref_class , NULL, NULL); + CHECKNEWOBJECT(o,_context,"toJSObject1"); + assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); + return o; + } + JSObject * o = JS_NewObject( _context , readOnly ? &bson_ro_class : &bson_class , NULL, NULL); + CHECKNEWOBJECT(o,_context,"toJSObject2"); + assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); + return o; + } + + jsval toval( const BSONObj* obj , bool readOnly=false ) { + JSObject * o = toJSObject( obj , readOnly ); + return OBJECT_TO_JSVAL( o ); + } + + void makeLongObj( long long n, JSObject * o ) { + boost::uint64_t val = (boost::uint64_t)n; + CHECKNEWOBJECT(o,_context,"NumberLong1"); + double floatApprox = (double)(boost::int64_t)val; + setProperty( o , "floatApprox" , toval( floatApprox ) ); + if ( (boost::int64_t)val != (boost::int64_t)floatApprox ) { + // using 2 doubles here instead of a single double because certain double + // bit patterns represent undefined values and sm might trash them + setProperty( o , "top" , toval( (double)(boost::uint32_t)( val >> 32 ) ) ); + setProperty( o , "bottom" , toval( (double)(boost::uint32_t)( val & 0x00000000ffffffff ) ) ); + } + } + + jsval toval( long long n ) { + JSObject * o = JS_NewObject( _context , &numberlong_class , 0 , 0 ); + makeLongObj( n, o ); + return OBJECT_TO_JSVAL( o ); + } + + void makeIntObj( int n, JSObject * o ) { + boost::uint32_t val = (boost::uint32_t)n; + CHECKNEWOBJECT(o,_context,"NumberInt1"); + double floatApprox = (double)(boost::int32_t)val; + setProperty( o , "floatApprox" , toval( floatApprox ) ); + } + + jsval toval( int n ) { + JSObject * o = JS_NewObject( _context , &numberint_class , 0 , 0 ); + makeIntObj( n, o ); + return OBJECT_TO_JSVAL( o ); + } + + jsval toval( const BSONElement& e ) { + + switch( e.type() ) { + case EOO: + case jstNULL: + case Undefined: + return JSVAL_NULL; + case NumberDouble: + case NumberInt: + return toval( e.number() ); +// case NumberInt: +// return toval( e.numberInt() ); + case Symbol: // TODO: should we make a special class for this + case String: + return toval( e.valuestr() ); + case Bool: + return e.boolean() ? JSVAL_TRUE : JSVAL_FALSE; + case Object: { + BSONObj embed = e.embeddedObject().getOwned(); + return toval( &embed ); + } + case Array: { + + BSONObj embed = e.embeddedObject().getOwned(); + + if ( embed.isEmpty() ) { + return OBJECT_TO_JSVAL( JS_NewArrayObject( _context , 0 , 0 ) ); + } + + JSObject * array = JS_NewArrayObject( _context , 1 , 0 ); + CHECKJSALLOC( array ); + + jsval myarray = OBJECT_TO_JSVAL( array ); + + BSONObjIterator i( embed ); + while ( i.more() ){ + const BSONElement& e = i.next(); + jsval v = toval( e ); + assert( JS_SetElement( _context , array , atoi(e.fieldName()) , &v ) ); + } + + return myarray; + } + case jstOID: { + OID oid = e.__oid(); + JSObject * o = JS_NewObject( _context , &object_id_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"jstOID"); + setProperty( o , "str" , toval( oid.str().c_str() ) ); + return OBJECT_TO_JSVAL( o ); + } + case RegEx: { + const char * flags = e.regexFlags(); + uintN flagNumber = 0; + while ( *flags ) { + switch ( *flags ) { + case 'g': flagNumber |= JSREG_GLOB; break; + case 'i': flagNumber |= JSREG_FOLD; break; + case 'm': flagNumber |= JSREG_MULTILINE; break; + //case 'y': flagNumber |= JSREG_STICKY; break; + + default: + log() << "warning: unknown regex flag:" << *flags << endl; + } + flags++; + } + + JSObject * r = JS_NewRegExpObject( _context , (char*)e.regex() , strlen( e.regex() ) , flagNumber ); + assert( r ); + return OBJECT_TO_JSVAL( r ); + } + case Code: { + JSFunction * func = compileFunction( e.valuestr() ); + if ( func ) + return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); + return JSVAL_NULL; + } + case CodeWScope: { + JSFunction * func = compileFunction( e.codeWScopeCode() ); + if ( !func ) + return JSVAL_NULL; + + BSONObj extraScope = e.codeWScopeObject(); + if ( ! extraScope.isEmpty() ) { + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + } + + return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); + } + case Date: + return OBJECT_TO_JSVAL( js_NewDateObjectMsec( _context , (jsdouble) ((long long)e.date().millis) ) ); + + case MinKey: + return OBJECT_TO_JSVAL( JS_NewObject( _context , &minkey_class , 0 , 0 ) ); + + case MaxKey: + return OBJECT_TO_JSVAL( JS_NewObject( _context , &maxkey_class , 0 , 0 ) ); + + case Timestamp: { + JSObject * o = JS_NewObject( _context , ×tamp_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"Timestamp1"); + setProperty( o , "t" , toval( (double)(e.timestampTime()) ) ); + setProperty( o , "i" , toval( (double)(e.timestampInc()) ) ); + return OBJECT_TO_JSVAL( o ); + } + case NumberLong: { + return toval( e.numberLong() ); + } + case DBRef: { + JSObject * o = JS_NewObject( _context , &dbpointer_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"DBRef1"); + setProperty( o , "ns" , toval( e.dbrefNS() ) ); + + JSObject * oid = JS_NewObject( _context , &object_id_class , 0 , 0 ); + CHECKNEWOBJECT(oid,_context,"DBRef2"); + setProperty( oid , "str" , toval( e.dbrefOID().str().c_str() ) ); + + setProperty( o , "id" , OBJECT_TO_JSVAL( oid ) ); + return OBJECT_TO_JSVAL( o ); + } + case BinData: { + JSObject * o = JS_NewObject( _context , &bindata_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"Bindata_BinData1"); + int len; + const char * data = e.binData( len ); + assert( data ); + assert( JS_SetPrivate( _context , o , new BinDataHolder( data , len ) ) ); + + setProperty( o , "len" , toval( (double)len ) ); + setProperty( o , "type" , toval( (double)e.binDataType() ) ); + return OBJECT_TO_JSVAL( o ); + } + } + + log() << "toval: unknown type: " << (int) e.type() << endl; + uassert( 10218 , "not done: toval" , 0 ); + return 0; + } + + // ------- object helpers ------ + + JSObject * getJSObject( JSObject * o , const char * name ) { + jsval v; + assert( JS_GetProperty( _context , o , name , &v ) ); + return JSVAL_TO_OBJECT( v ); + } + + JSObject * getGlobalObject( const char * name ) { + return getJSObject( JS_GetGlobalObject( _context ) , name ); + } + + JSObject * getGlobalPrototype( const char * name ) { + return getJSObject( getGlobalObject( name ) , "prototype" ); + } + + bool hasProperty( JSObject * o , const char * name ) { + JSBool res; + assert( JS_HasProperty( _context , o , name , & res ) ); + return res; + } + + jsval getProperty( JSObject * o , const char * field ) { + uassert( 10219 , "object passed to getPropery is null" , o ); + jsval v; + assert( JS_GetProperty( _context , o , field , &v ) ); + return v; + } + + void setProperty( JSObject * o , const char * field , jsval v ) { + assert( JS_SetProperty( _context , o , field , &v ) ); + } + + string typeString( jsval v ) { + JSType t = JS_TypeOfValue( _context , v ); + return JS_GetTypeName( _context , t ); + } + + bool getBoolean( JSObject * o , const char * field ) { + return toBoolean( getProperty( o , field ) ); + } + + double getNumber( JSObject * o , const char * field ) { + return toNumber( getProperty( o , field ) ); + } + + string getString( JSObject * o , const char * field ) { + return toString( getProperty( o , field ) ); + } + + JSClass * getClass( JSObject * o , const char * field ) { + jsval v; + assert( JS_GetProperty( _context , o , field , &v ) ); + if ( ! JSVAL_IS_OBJECT( v ) ) + return 0; + return JS_GET_CLASS( _context , JSVAL_TO_OBJECT( v ) ); + } + + JSContext * _context; + + + }; + + + void bson_finalize( JSContext * cx , JSObject * obj ) { + BSONHolder * o = GETHOLDER( cx , obj ); + if ( o ) { + delete o; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSBool bson_enumerate( JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp ) { + + BSONHolder * o = GETHOLDER( cx , obj ); + + if ( enum_op == JSENUMERATE_INIT ) { + if ( o ) { + BSONFieldIterator * it = o->it(); + *statep = PRIVATE_TO_JSVAL( it ); + } + else { + *statep = 0; + } + if ( idp ) + *idp = JSVAL_ZERO; + return JS_TRUE; + } + + BSONFieldIterator * it = (BSONFieldIterator*)JSVAL_TO_PRIVATE( *statep ); + if ( ! it ) { + *statep = 0; + return JS_TRUE; + } + + if ( enum_op == JSENUMERATE_NEXT ) { + if ( it->more() ) { + string name = it->next(); + Convertor c(cx); + assert( JS_ValueToId( cx , c.toval( name.c_str() ) , idp ) ); + } + else { + delete it; + *statep = 0; + } + return JS_TRUE; + } + + if ( enum_op == JSENUMERATE_DESTROY ) { + if ( it ) + delete it; + return JS_TRUE; + } + + uassert( 10220 , "don't know what to do with this op" , 0 ); + return JS_FALSE; + } + + JSBool noaccess( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ) { + // in init code still + return JS_TRUE; + } + if ( holder->_inResolve ) + return JS_TRUE; + JS_ReportError( cx , "doing write op on read only operation" ); + return JS_FALSE; + } + + JSClass bson_ro_class = { + "bson_ro_object" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE , + noaccess, noaccess, JS_PropertyStub, noaccess, + (JSEnumerateOp)bson_enumerate, (JSResolveOp)(&resolveBSONField) , JS_ConvertStub, bson_finalize , + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool bson_cons( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + cerr << "bson_cons : shouldn't be here!" << endl; + JS_ReportError( cx , "can't construct bson object" ); + return JS_FALSE; + } + + JSFunctionSpec bson_functions[] = { + { 0 } + }; + + JSBool bson_add_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ) { + // static init + return JS_TRUE; + } + if ( ! holder->_inResolve ) { + Convertor c(cx); + string name = c.toString( idval ); + if ( holder->_obj[name].eoo() ) { + holder->_extra.push_back( name ); + } + holder->_modified = true; + } + return JS_TRUE; + } + + + JSBool mark_modified( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { + Convertor c(cx); + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( !holder ) // needed when we're messing with DBRef.prototype + return JS_TRUE; + if ( holder->_inResolve ) + return JS_TRUE; + holder->_modified = true; + holder->_removed.erase( c.toString( idval ) ); + return JS_TRUE; + } + + JSBool mark_modified_remove( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { + Convertor c(cx); + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( holder->_inResolve ) + return JS_TRUE; + holder->_modified = true; + holder->_removed.insert( c.toString( idval ) ); + return JS_TRUE; + } + + JSClass bson_class = { + "bson_object" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE , + bson_add_prop, mark_modified_remove, JS_PropertyStub, mark_modified, + (JSEnumerateOp)bson_enumerate, (JSResolveOp)(&resolveBSONField) , JS_ConvertStub, bson_finalize , + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + static JSClass global_class = { + "global", JSCLASS_GLOBAL_FLAGS, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // --- global helpers --- + + JSBool hexToBinData(JSContext * cx, jsval *rval, int subtype, string s) { + JSObject * o = JS_NewObject( cx , &bindata_class , 0 , 0 ); + CHECKNEWOBJECT(o,_context,"Bindata_BinData1"); + int len = s.size() / 2; + char * data = new char[len]; + char *p = data; + const char *src = s.c_str(); + for( size_t i = 0; i+1 < s.size(); i += 2 ) { + *p++ = fromHex(src + i); + } + assert( JS_SetPrivate( cx , o , new BinDataHolder( data , len ) ) ); + Convertor c(cx); + c.setProperty( o, "len", c.toval((double)len) ); + c.setProperty( o, "type", c.toval((double)subtype) ); + *rval = OBJECT_TO_JSVAL( o ); + delete data; + return JS_TRUE; + } + + JSBool _HexData( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( argc != 2 ) { + JS_ReportError( cx , "HexData needs 2 arguments -- HexData(subtype,hexstring)" ); + return JS_FALSE; + } + int type = (int)c.toNumber( argv[ 0 ] ); + if ( type == 2 ) { + JS_ReportError( cx , "BinData subtype 2 is deprecated" ); + return JS_FALSE; + } + string s = c.toString(argv[1]); + return hexToBinData(cx, rval, type, s); + } + + JSBool _UUID( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( argc != 1 ) { + JS_ReportError( cx , "UUID needs argument -- UUID(hexstring)" ); + return JS_FALSE; + } + string s = c.toString(argv[0]); + if( s.size() != 32 ) { + JS_ReportError( cx , "bad UUID hex string len" ); + return JS_FALSE; + } + return hexToBinData(cx, rval, 3, s); + } + + JSBool _MD5( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( argc != 1 ) { + JS_ReportError( cx , "MD5 needs argument -- MD5(hexstring)" ); + return JS_FALSE; + } + string s = c.toString(argv[0]); + if( s.size() != 32 ) { + JS_ReportError( cx , "bad MD5 hex string len" ); + return JS_FALSE; + } + return hexToBinData(cx, rval, 5, s); + } + + JSBool native_print( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { + stringstream ss; + Convertor c( cx ); + for ( uintN i=0; i<argc; i++ ) { + if ( i > 0 ) + ss << " "; + ss << c.toString( argv[i] ); + } + ss << "\n"; + Logstream::logLockless( ss.str() ); + return JS_TRUE; + } + + JSBool native_helper( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { + Convertor c(cx); + + NativeFunction func = (NativeFunction)((long long)c.getNumber( obj , "x" ) ); + void* data = (void*)((long long)c.getNumber( obj , "y" ) ); + assert( func ); + + BSONObj a; + if ( argc > 0 ) { + BSONObjBuilder args; + for ( uintN i=0; i<argc; i++ ) { + c.append( args , args.numStr( i ) , argv[i] ); + } + + a = args.obj(); + } + + BSONObj out; + try { + out = func( a, data ); + } + catch ( std::exception& e ) { + JS_ReportError( cx , e.what() ); + return JS_FALSE; + } + + if ( out.isEmpty() ) { + *rval = JSVAL_VOID; + } + else { + *rval = c.toval( out.firstElement() ); + } + + return JS_TRUE; + } + + JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ); + + JSBool native_gc( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { + JS_GC( cx ); + return JS_TRUE; + } + + JSFunctionSpec globalHelpers[] = { + { "print" , &native_print , 0 , 0 , 0 } , + { "nativeHelper" , &native_helper , 1 , 0 , 0 } , + { "load" , &native_load , 1 , 0 , 0 } , + { "gc" , &native_gc , 1 , 0 , 0 } , + { "UUID", &_UUID, 0, 0, 0 } , + { "MD5", &_MD5, 0, 0, 0 } , + { "HexData", &_HexData, 0, 0, 0 } , + { 0 , 0 , 0 , 0 , 0 } + }; + + // ----END global helpers ---- + + // Object helpers + + JSBool bson_get_size(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + if ( argc != 1 || !JSVAL_IS_OBJECT( argv[ 0 ] ) ) { + JS_ReportError( cx , "bsonsize requires one valid object" ); + return JS_FALSE; + } + + Convertor c(cx); + + if ( argv[0] == JSVAL_VOID || argv[0] == JSVAL_NULL ) { + *rval = c.toval( 0.0 ); + return JS_TRUE; + } + + JSObject * o = JSVAL_TO_OBJECT( argv[0] ); + + double size = 0; + + if ( JS_InstanceOf( cx , o , &bson_ro_class , 0 ) || + JS_InstanceOf( cx , o , &bson_class , 0 ) ) { + BSONHolder * h = GETHOLDER( cx , o ); + if ( h ) { + size = h->_obj.objsize(); + } + } + else { + BSONObj temp = c.toObject( o ); + size = temp.objsize(); + } + + *rval = c.toval( size ); + return JS_TRUE; + } + + JSFunctionSpec objectHelpers[] = { + { "bsonsize" , &bson_get_size , 1 , 0 , 0 } , + { 0 , 0 , 0 , 0 , 0 } + }; + + // end Object helpers + + JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { + assert( JS_EnterLocalRootScope( cx ) ); + Convertor c( cx ); + + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ) { + // static init + *objp = 0; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + holder->check(); + + string s = c.toString( id ); + + BSONElement e = holder->_obj[ s.c_str() ]; + + if ( e.type() == EOO || holder->_removed.count( s ) ) { + *objp = 0; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + + jsval val; + try { + val = c.toval( e ); + } + catch ( InvalidUTF8Exception& ) { + JS_LeaveLocalRootScope( cx ); + JS_ReportError( cx , "invalid utf8" ); + return JS_FALSE; + } + + assert( ! holder->_inResolve ); + holder->_inResolve = true; + assert( JS_SetProperty( cx , obj , s.c_str() , &val ) ); + holder->_inResolve = false; + + if ( val != JSVAL_NULL && val != JSVAL_VOID && JSVAL_IS_OBJECT( val ) ) { + // TODO: this is a hack to get around sub objects being modified + // basically right now whenever a sub object is read we mark whole obj as possibly modified + JSObject * oo = JSVAL_TO_OBJECT( val ); + if ( JS_InstanceOf( cx , oo , &bson_class , 0 ) || + JS_IsArrayObject( cx , oo ) ) { + holder->_modified = true; + } + } + + *objp = obj; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + + + class SMScope; + + class SMEngine : public ScriptEngine { + public: + + SMEngine() { +#ifdef SM18 + JS_SetCStringsAreUTF8(); +#endif + + _runtime = JS_NewRuntime(64L * 1024L * 1024L); + uassert( 10221 , "JS_NewRuntime failed" , _runtime ); + + if ( ! utf8Ok() ) { + log() << "*** warning: spider monkey build without utf8 support. consider rebuilding with utf8 support" << endl; + } + + int x = 0; + assert( x = 1 ); + uassert( 10222 , "assert not being executed" , x == 1 ); + } + + ~SMEngine() { + JS_DestroyRuntime( _runtime ); + JS_ShutDown(); + } + + Scope * createScope(); + + void runTest(); + + virtual bool utf8Ok() const { return JS_CStringsAreUTF8(); } + +#ifdef XULRUNNER + JSClass * _dateClass; + JSClass * _regexClass; +#endif + + + private: + JSRuntime * _runtime; + friend class SMScope; + }; + + SMEngine * globalSMEngine; + + + void ScriptEngine::setup() { + globalSMEngine = new SMEngine(); + globalScriptEngine = globalSMEngine; + } + + + // ------ scope ------ + + + JSBool no_gc(JSContext *cx, JSGCStatus status) { + return JS_FALSE; + } + + JSBool yes_gc(JSContext *cx, JSGCStatus status) { + return JS_TRUE; + } + + class SMScope : public Scope { + public: + SMScope() : _this( 0 ) , _externalSetup( false ) , _localConnect( false ) { + smlock; + _context = JS_NewContext( globalSMEngine->_runtime , 8192 ); + _convertor = new Convertor( _context ); + massert( 10431 , "JS_NewContext failed" , _context ); + + JS_SetOptions( _context , JSOPTION_VAROBJFIX); + //JS_SetVersion( _context , JSVERSION_LATEST); TODO + JS_SetErrorReporter( _context , errorReporter ); + + _global = JS_NewObject( _context , &global_class, NULL, NULL); + massert( 10432 , "JS_NewObject failed for global" , _global ); + JS_SetGlobalObject( _context , _global ); + massert( 10433 , "js init failed" , JS_InitStandardClasses( _context , _global ) ); + + JS_SetOptions( _context , JS_GetOptions( _context ) | JSOPTION_VAROBJFIX ); + + JS_DefineFunctions( _context , _global , globalHelpers ); + + JS_DefineFunctions( _context , _convertor->getGlobalObject( "Object" ), objectHelpers ); + + //JS_SetGCCallback( _context , no_gc ); // this is useful for seeing if something is a gc problem + + _postCreateHacks(); + } + + ~SMScope() { + smlock; + uassert( 10223 , "deleted SMScope twice?" , _convertor ); + + for ( list<void*>::iterator i=_roots.begin(); i != _roots.end(); i++ ) { + JS_RemoveRoot( _context , *i ); + } + _roots.clear(); + + if ( _this ) { + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + + if ( _convertor ) { + delete _convertor; + _convertor = 0; + } + + if ( _context ) { + // This is expected to reclaim _global as well. + JS_DestroyContext( _context ); + _context = 0; + } + + } + + void reset() { + smlock; + assert( _convertor ); + return; + if ( _this ) { + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + currentScope.reset( this ); + _error = ""; + } + + void addRoot( void * root , const char * name ) { + JS_AddNamedRoot( _context , root , name ); + _roots.push_back( root ); + } + + void init( const BSONObj * data ) { + smlock; + if ( ! data ) + return; + + BSONObjIterator i( *data ); + while ( i.more() ) { + BSONElement e = i.next(); + _convertor->setProperty( _global , e.fieldName() , _convertor->toval( e ) ); + _initFieldNames.insert( e.fieldName() ); + } + + } + + bool hasOutOfMemoryException() { + string err = getError(); + return err.find("out of memory") != string::npos; + } + + void externalSetup() { + smlock; + uassert( 10224 , "already local connected" , ! _localConnect ); + if ( _externalSetup ) + return; + initMongoJS( this , _context , _global , false ); + _externalSetup = true; + } + + void localConnect( const char * dbName ) { + { + smlock; + uassert( 10225 , "already setup for external db" , ! _externalSetup ); + if ( _localConnect ) { + uassert( 10226 , "connected to different db" , _localDBName == dbName ); + return; + } + + initMongoJS( this , _context , _global , true ); + + exec( "_mongo = new Mongo();" ); + exec( ((string)"db = _mongo.getDB( \"" + dbName + "\" ); ").c_str() ); + + _localConnect = true; + _localDBName = dbName; + } + loadStored(); + } + + // ----- getters ------ + double getNumber( const char *field ) { + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + return _convertor->toNumber( val ); + } + + string getString( const char *field ) { + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + JSString * s = JS_ValueToString( _context , val ); + return _convertor->toString( s ); + } + + bool getBoolean( const char *field ) { + smlock; + return _convertor->getBoolean( _global , field ); + } + + BSONObj getObject( const char *field ) { + smlock; + return _convertor->toObject( _convertor->getProperty( _global , field ) ); + } + + JSObject * getJSObject( const char * field ) { + smlock; + return _convertor->getJSObject( _global , field ); + } + + int type( const char *field ) { + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + + switch ( JS_TypeOfValue( _context , val ) ) { + case JSTYPE_VOID: return Undefined; + case JSTYPE_NULL: return jstNULL; + case JSTYPE_OBJECT: { + if ( val == JSVAL_NULL ) + return jstNULL; + JSObject * o = JSVAL_TO_OBJECT( val ); + if ( JS_IsArrayObject( _context , o ) ) + return Array; + if ( isDate( _context , o ) ) + return Date; + return Object; + } + case JSTYPE_FUNCTION: return Code; + case JSTYPE_STRING: return String; + case JSTYPE_NUMBER: return NumberDouble; + case JSTYPE_BOOLEAN: return Bool; + default: + uassert( 10227 , "unknown type" , 0 ); + } + return 0; + } + + // ----- setters ------ + + void setElement( const char *field , const BSONElement& val ) { + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setNumber( const char *field , double val ) { + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setString( const char *field , const char * val ) { + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setObject( const char *field , const BSONObj& obj , bool readOnly ) { + smlock; + jsval v = _convertor->toval( &obj , readOnly ); + JS_SetProperty( _context , _global , field , &v ); + } + + void setBoolean( const char *field , bool val ) { + smlock; + jsval v = BOOLEAN_TO_JSVAL( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setThis( const BSONObj * obj ) { + smlock; + if ( _this ) { + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + + if ( obj ) { + _this = _convertor->toJSObject( obj ); + JS_AddNamedRoot( _context , &_this , "scope this" ); + } + } + + void setFunction( const char *field , const char * code ) { + smlock; + jsval v = OBJECT_TO_JSVAL(JS_GetFunctionObject(_convertor->compileFunction(code))); + JS_SetProperty( _context , _global , field , &v ); + } + + void rename( const char * from , const char * to ) { + smlock; + jsval v; + assert( JS_GetProperty( _context , _global , from , &v ) ); + assert( JS_SetProperty( _context , _global , to , &v ) ); + v = JSVAL_VOID; + assert( JS_SetProperty( _context , _global , from , &v ) ); + } + + // ---- functions ----- + + ScriptingFunction _createFunction( const char * code ) { + smlock; + precall(); + return (ScriptingFunction)_convertor->compileFunction( code ); + } + + struct TimeoutSpec { + boost::posix_time::ptime start; + boost::posix_time::time_duration timeout; + int count; + }; + + // should not generate exceptions, as those can be caught in + // javascript code; returning false without an exception exits + // immediately + static JSBool _interrupt( JSContext *cx ) { + TimeoutSpec &spec = *(TimeoutSpec *)( JS_GetContextPrivate( cx ) ); + if ( ++spec.count % 1000 != 0 ) + return JS_TRUE; + const char * interrupt = ScriptEngine::checkInterrupt(); + if ( interrupt && interrupt[ 0 ] ) { + return JS_FALSE; + } + if ( spec.timeout.ticks() == 0 ) { + return JS_TRUE; + } + boost::posix_time::time_duration elapsed = ( boost::posix_time::microsec_clock::local_time() - spec.start ); + if ( elapsed < spec.timeout ) { + return JS_TRUE; + } + return JS_FALSE; + + } + + static JSBool interrupt( JSContext *cx, JSScript *script ) { + return _interrupt( cx ); + } + + void installInterrupt( int timeoutMs ) { + if ( timeoutMs != 0 || ScriptEngine::haveCheckInterruptCallback() ) { + TimeoutSpec *spec = new TimeoutSpec; + spec->timeout = boost::posix_time::millisec( timeoutMs ); + spec->start = boost::posix_time::microsec_clock::local_time(); + spec->count = 0; + JS_SetContextPrivate( _context, (void*)spec ); +#if defined(SM181) && !defined(XULRUNNER190) + JS_SetOperationCallback( _context, _interrupt ); +#else + JS_SetBranchCallback( _context, interrupt ); +#endif + } + } + + void uninstallInterrupt( int timeoutMs ) { + if ( timeoutMs != 0 || ScriptEngine::haveCheckInterruptCallback() ) { +#if defined(SM181) && !defined(XULRUNNER190) + JS_SetOperationCallback( _context , 0 ); +#else + JS_SetBranchCallback( _context, 0 ); +#endif + delete (TimeoutSpec *)JS_GetContextPrivate( _context ); + JS_SetContextPrivate( _context, 0 ); + } + } + + void precall() { + _error = ""; + currentScope.reset( this ); + } + + bool exec( const StringData& code , const string& name = "(anon)" , bool printResult = false , bool reportError = true , bool assertOnError = true, int timeoutMs = 0 ) { + smlock; + precall(); + + jsval ret = JSVAL_VOID; + + installInterrupt( timeoutMs ); + JSBool worked = JS_EvaluateScript( _context , _global , code.data() , code.size() , name.c_str() , 1 , &ret ); + uninstallInterrupt( timeoutMs ); + + if ( ! worked && _error.size() == 0 ) { + jsval v; + if ( JS_GetPendingException( _context , &v ) ) { + _error = _convertor->toString( v ); + if ( reportError ) + cout << _error << endl; + } + } + + uassert( 10228 , str::stream() << name + " exec failed: " << _error , worked || ! assertOnError ); + + if ( reportError && ! _error.empty() ) { + // cout << "exec error: " << _error << endl; + // already printed in reportError, so... TODO + } + + if ( worked ) + _convertor->setProperty( _global , "__lastres__" , ret ); + + if ( worked && printResult && ! JSVAL_IS_VOID( ret ) ) + cout << _convertor->toString( ret ) << endl; + + return worked; + } + + int invoke( JSFunction * func , const BSONObj* args, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) { + smlock; + precall(); + + assert( JS_EnterLocalRootScope( _context ) ); + + int nargs = args ? args->nFields() : 0; + scoped_array<jsval> smargsPtr( new jsval[nargs] ); + if ( nargs ) { + BSONObjIterator it( *args ); + for ( int i=0; i<nargs; i++ ) { + smargsPtr[i] = _convertor->toval( it.next() ); + } + } + + if ( !args ) { + _convertor->setProperty( _global , "args" , JSVAL_NULL ); + } + else { + setObject( "args" , *args , true ); // this is for backwards compatability + } + + JS_LeaveLocalRootScope( _context ); + + installInterrupt( timeoutMs ); + jsval rval; + setThis(recv); + JSBool ret = JS_CallFunction( _context , _this ? _this : _global , func , nargs , smargsPtr.get() , &rval ); + setThis(0); + uninstallInterrupt( timeoutMs ); + + if ( !ret ) { + return -3; + } + + if ( ! ignoreReturn ) { + assert( JS_SetProperty( _context , _global , "return" , &rval ) ); + } + + return 0; + } + + int invoke( ScriptingFunction funcAddr , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = 0, bool readOnlyArgs = false, bool readOnlyRecv = false ) { + return invoke( (JSFunction*)funcAddr , args , recv, timeoutMs , ignoreReturn, readOnlyArgs, readOnlyRecv); + } + + void gotError( string s ) { + _error = s; + } + + string getError() { + return _error; + } + + void injectNative( const char *field, NativeFunction func, void* data ) { + smlock; + string name = field; + _convertor->setProperty( _global , (name + "_").c_str() , _convertor->toval( (double)(long long)func ) ); + + stringstream code; + if (data) { + _convertor->setProperty( _global , (name + "_data_").c_str() , _convertor->toval( (double)(long long)data ) ); + code << field << "_" << " = { x : " << field << "_ , y: " << field << "_data_ }; "; + } else { + code << field << "_" << " = { x : " << field << "_ }; "; + } + code << field << " = function(){ return nativeHelper.apply( " << field << "_ , arguments ); }"; + exec( code.str() ); + } + + virtual void gc() { + smlock; + JS_GC( _context ); + } + + JSContext *SavedContext() const { return _context; } + + private: + + void _postCreateHacks() { +#ifdef XULRUNNER + exec( "__x__ = new Date(1);" ); + globalSMEngine->_dateClass = _convertor->getClass( _global , "__x__" ); + exec( "__x__ = /abc/i" ); + globalSMEngine->_regexClass = _convertor->getClass( _global , "__x__" ); +#endif + } + + JSContext * _context; + Convertor * _convertor; + + JSObject * _global; + JSObject * _this; + + string _error; + list<void*> _roots; + + bool _externalSetup; + bool _localConnect; + + set<string> _initFieldNames; + + }; + + /* used to make the logging not overly chatty in the mongo shell. */ + extern bool isShell; + + void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ) { + stringstream ss; + if( !isShell ) + ss << "JS Error: "; + ss << message; + + if ( report && report->filename ) { + ss << " " << report->filename << ":" << report->lineno; + } + + tlog() << ss.str() << endl; + + if ( currentScope.get() ) { + currentScope->gotError( ss.str() ); + } + } + + JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { + Convertor c(cx); + + Scope * s = currentScope.get(); + + for ( uintN i=0; i<argc; i++ ) { + string filename = c.toString( argv[i] ); + //cout << "load [" << filename << "]" << endl; + + if ( ! s->execFile( filename , false , true , false ) ) { + JS_ReportError( cx , ((string)"error loading js file: " + filename ).c_str() ); + return JS_FALSE; + } + } + + return JS_TRUE; + } + + + + void SMEngine::runTest() { + SMScope s; + + s.localConnect( "foo" ); + + s.exec( "assert( db.getMongo() )" ); + s.exec( "assert( db.bar , 'collection getting does not work' ); " ); + s.exec( "assert.eq( db._name , 'foo' );" ); + s.exec( "assert( _mongo == db.getMongo() ); " ); + s.exec( "assert( _mongo == db._mongo ); " ); + s.exec( "assert( typeof DB.bar == 'undefined' ); " ); + s.exec( "assert( typeof DB.prototype.bar == 'undefined' , 'resolution is happening on prototype, not object' ); " ); + + s.exec( "assert( db.bar ); " ); + s.exec( "assert( typeof db.addUser == 'function' )" ); + s.exec( "assert( db.addUser == DB.prototype.addUser )" ); + s.exec( "assert.eq( 'foo.bar' , db.bar._fullName ); " ); + s.exec( "db.bar.verify();" ); + + s.exec( "db.bar.silly.verify();" ); + s.exec( "assert.eq( 'foo.bar.silly' , db.bar.silly._fullName )" ); + s.exec( "assert.eq( 'function' , typeof _mongo.find , 'mongo.find is not a function' )" ); + + assert( (string)"abc" == trim( "abc" ) ); + assert( (string)"abc" == trim( " abc" ) ); + assert( (string)"abc" == trim( "abc " ) ); + assert( (string)"abc" == trim( " abc " ) ); + + } + + Scope * SMEngine::createScope() { + return new SMScope(); + } + + void Convertor::addRoot( JSFunction * f , const char * name ) { + if ( ! f ) + return; + + SMScope * scope = currentScope.get(); + uassert( 10229 , "need a scope" , scope ); + + JSObject * o = JS_GetFunctionObject( f ); + assert( o ); + scope->addRoot( &o , name ); + } + +} + +#include "sm_db.cpp" diff --git a/src/mongo/scripting/engine_spidermonkey.h b/src/mongo/scripting/engine_spidermonkey.h new file mode 100644 index 00000000000..9fd430d853d --- /dev/null +++ b/src/mongo/scripting/engine_spidermonkey.h @@ -0,0 +1,105 @@ +// engine_spidermonkey.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "engine.h" + +// START inc hacking + +#ifdef WIN32 +#include "jstypes.h" +#undef JS_PUBLIC_API +#undef JS_PUBLIC_DATA +#define JS_PUBLIC_API(t) t __cdecl +#define JS_PUBLIC_DATA(t) t +#endif + +#include "jsapi.h" +#include "jsobj.h" +#include "jsdate.h" +#include "jsregexp.h" + +// END inc hacking + +// -- SM 1.6 hacks --- +#ifndef JSCLASS_GLOBAL_FLAGS +#error old version of spider monkey ( probably 1.6 ) you should upgrade to at least 1.7 +#endif +// -- END SM 1.6 hacks --- + +#ifdef JSVAL_IS_TRACEABLE +#define SM18 +#endif + +#ifdef XULRUNNER +#define SM181 +#endif + +namespace mongo { + + class SMScope; + class Convertor; + + extern JSClass bson_class; + extern JSClass bson_ro_class; + + extern JSClass object_id_class; + extern JSClass dbpointer_class; + extern JSClass dbref_class; + extern JSClass bindata_class; + extern JSClass timestamp_class; + extern JSClass numberlong_class; + extern JSClass numberint_class; + extern JSClass minkey_class; + extern JSClass maxkey_class; + + // internal things + void dontDeleteScope( SMScope * s ) {} + void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ); + extern boost::thread_specific_ptr<SMScope> currentScope; + + // bson + JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ); + + + // mongo + void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ); + bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ); + +#define JSVAL_IS_OID(v) ( JSVAL_IS_OBJECT( v ) && JS_InstanceOf( cx , JSVAL_TO_OBJECT( v ) , &object_id_class , 0 ) ) + + bool isDate( JSContext * cx , JSObject * o ); + + // JS private data must be 2byte aligned, so we use a holder to refer to an unaligned pointer. + struct BinDataHolder { + BinDataHolder( const char *c, int copyLen = -1 ) : + c_( const_cast< char * >( c ) ), + iFree_( copyLen != -1 ) { + if ( copyLen != -1 ) { + c_ = (char*)malloc( copyLen ); + memcpy( c_, c, copyLen ); + } + } + ~BinDataHolder() { + if ( iFree_ ) + free( c_ ); + } + char *c_; + bool iFree_; + }; +} diff --git a/src/mongo/scripting/engine_v8.cpp b/src/mongo/scripting/engine_v8.cpp new file mode 100644 index 00000000000..53539c2f75c --- /dev/null +++ b/src/mongo/scripting/engine_v8.cpp @@ -0,0 +1,1634 @@ +//engine_v8.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(_WIN32) +/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with + our usage of boost */ +#include "boost/cstdint.hpp" +using namespace boost; +#define V8STDINT_H_ +#endif + +#include "engine_v8.h" + +#include "v8_wrapper.h" +#include "v8_utils.h" +#include "v8_db.h" + +#define V8_SIMPLE_HEADER v8::Isolate::Scope iscope(_isolate); v8::Locker l(_isolate); HandleScope handle_scope; Context::Scope context_scope( _context ); + +namespace mongo { + + // guarded by v8 mutex + map< unsigned, int > __interruptSpecToThreadId; + map< unsigned, v8::Isolate* > __interruptSpecToIsolate; + + /** + * Unwraps a BSONObj from the JS wrapper + */ + static BSONObj* unwrapBSONObj(const Handle<v8::Object>& obj) { + Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); + if (field.IsEmpty() || !field->IsExternal()) + return 0; + void* ptr = field->Value(); + return (BSONObj*)ptr; + } + + static void weakRefBSONCallback(v8::Persistent<v8::Value> p, void* scope) { + // should we lock here? no idea, and no doc from v8 of course + HandleScope handle_scope; + if (!p.IsNearDeath()) + return; + Handle<External> field = Handle<External>::Cast(p->ToObject()->GetInternalField(0)); + BSONObj* data = (BSONObj*) field->Value(); + delete data; + p.Dispose(); + } + + Persistent<v8::Object> V8Scope::wrapBSONObject(Local<v8::Object> obj, BSONObj* data) { + obj->SetInternalField(0, v8::External::New(data)); + Persistent<v8::Object> p = Persistent<v8::Object>::New(obj); + p.MakeWeak(this, weakRefBSONCallback); + return p; + } + + static void weakRefArrayCallback(v8::Persistent<v8::Value> p, void* scope) { + // should we lock here? no idea, and no doc from v8 of course + HandleScope handle_scope; + if (!p.IsNearDeath()) + return; + Handle<External> field = Handle<External>::Cast(p->ToObject()->GetInternalField(0)); + char* data = (char*) field->Value(); + delete [] data; + p.Dispose(); + } + + Persistent<v8::Object> V8Scope::wrapArrayObject(Local<v8::Object> obj, char* data) { + obj->SetInternalField(0, v8::External::New(data)); + Persistent<v8::Object> p = Persistent<v8::Object>::New(obj); + p.MakeWeak(this, weakRefArrayCallback); + return p; + } + + static Handle<v8::Value> namedGet(Local<v8::String> name, const v8::AccessorInfo &info) { + // all properties should be set, otherwise means builtin or deleted + if (!(info.This()->HasRealNamedProperty(name))) + return v8::Handle<v8::Value>(); + + Handle<v8::Value> val = info.This()->GetRealNamedProperty(name); + if (!val->IsUndefined()) { + // value already cached + return val; + } + + string key = toSTLString(name); + BSONObj *obj = unwrapBSONObj(info.Holder()); + BSONElement elmt = obj->getField(key.c_str()); + if (elmt.eoo()) + return Handle<Value>(); + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + val = scope->mongoToV8Element(elmt, false); + info.This()->ForceSet(name, val); + + if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) { + // if accessing a subobject, it may get modified and base obj would not know + // have to set base as modified, which means some optim is lost + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + } + return val; + } + + static Handle<v8::Value> namedGetRO(Local<v8::String> name, const v8::AccessorInfo &info) { + string key = toSTLString(name); + BSONObj *obj = unwrapBSONObj(info.Holder()); + BSONElement elmt = obj->getField(key.c_str()); + if (elmt.eoo()) + return Handle<Value>(); + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + Handle<v8::Value> val = scope->mongoToV8Element(elmt, true); + return val; + } + + static Handle<v8::Value> namedSet(Local<v8::String> name, Local<v8::Value> value_obj, const v8::AccessorInfo& info) { + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + return Handle<Value>(); + } + + static Handle<v8::Array> namedEnumerator(const AccessorInfo &info) { + BSONObj *obj = unwrapBSONObj(info.Holder()); + Handle<v8::Array> arr = Handle<v8::Array>(v8::Array::New(obj->nFields())); + int i = 0; + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + // note here that if keys are parseable number, v8 will access them using index + for ( BSONObjIterator it(*obj); it.more(); ++i) { + const BSONElement& f = it.next(); +// arr->Set(i, v8::String::NewExternal(new ExternalString(f.fieldName()))); + Handle<v8::String> name = scope->getV8Str(f.fieldName()); + arr->Set(i, name); + } + return arr; + } + + Handle<Boolean> namedDelete( Local<v8::String> property, const AccessorInfo& info ) { + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + return Handle<Boolean>(); + } + +// v8::Handle<v8::Integer> namedQuery(Local<v8::String> property, const AccessorInfo& info) { +// string key = ToString(property); +// return v8::Integer::New(None); +// } + + static Handle<v8::Value> indexedGet(uint32_t index, const v8::AccessorInfo &info) { + // all properties should be set, otherwise means builtin or deleted + if (!(info.This()->HasRealIndexedProperty(index))) + return v8::Handle<v8::Value>(); + + StringBuilder ss; + ss << index; + string key = ss.str(); + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + // cannot get v8 to properly cache the indexed val in the js object +// Handle<v8::String> name = scope->getV8Str(key); +// // v8 API really confusing here, must check existence on index, but then fetch with name +// if (info.This()->HasRealIndexedProperty(index)) { +// Handle<v8::Value> val = info.This()->GetRealNamedProperty(name); +// if (!val.IsEmpty() && !val->IsNull()) +// return val; +// } + BSONObj *obj = unwrapBSONObj(info.Holder()); + BSONElement elmt = obj->getField(key); + if (elmt.eoo()) + return Handle<Value>(); + Handle<Value> val = scope->mongoToV8Element(elmt, false); +// info.This()->ForceSet(name, val); + + if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) { + // if accessing a subobject, it may get modified and base obj would not know + // have to set base as modified, which means some optim is lost + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + } + return val; + } + + Handle<Boolean> indexedDelete( uint32_t index, const AccessorInfo& info ) { + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + return Handle<Boolean>(); + } + + static Handle<v8::Value> indexedGetRO(uint32_t index, const v8::AccessorInfo &info) { + StringBuilder ss; + ss << index; + string key = ss.str(); + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + // cannot get v8 to properly cache the indexed val in the js object +// Handle<v8::String> name = scope->getV8Str(key); +// // v8 API really confusing here, must check existence on index, but then fetch with name +// if (info.This()->HasRealIndexedProperty(index)) { +// Handle<v8::Value> val = info.This()->GetRealNamedProperty(name); +// if (!val.IsEmpty() && !val->IsNull()) +// return val; +// } + BSONObj *obj = unwrapBSONObj(info.Holder()); + BSONElement elmt = obj->getField(key); + if (elmt.eoo()) + return Handle<Value>(); + Handle<Value> val = scope->mongoToV8Element(elmt, true); +// info.This()->ForceSet(name, val); + return val; + } + + static Handle<v8::Value> indexedSet(uint32_t index, Local<v8::Value> value_obj, const v8::AccessorInfo& info) { + Local< External > scp = External::Cast( *info.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + info.This()->SetHiddenValue(scope->V8STR_MODIFIED, v8::Boolean::New(true)); + return Handle<Value>(); + } + +// static Handle<v8::Array> indexedEnumerator(const AccessorInfo &info) { +// BSONObj *obj = unwrapBSONObj(info.Holder()); +// Handle<v8::Array> arr = Handle<v8::Array>(v8::Array::New(obj->nFields())); +// Local< External > scp = External::Cast( *info.Data() ); +// V8Scope* scope = (V8Scope*)(scp->Value()); +// int i = 0; +// for ( BSONObjIterator it(*obj); it.more(); ++i) { +// const BSONElement& f = it.next(); +//// arr->Set(i, v8::String::NewExternal(new ExternalString(f.fieldName()))); +// arr->Set(i, scope->getV8Str(f.fieldName())); +// } +// return arr; +// } + + Handle<Value> NamedReadOnlySet( Local<v8::String> property, Local<Value> value, const AccessorInfo& info ) { + string key = toSTLString(property); + cout << "cannot write property " << key << " to read-only object" << endl; + return value; + } + + Handle<Boolean> NamedReadOnlyDelete( Local<v8::String> property, const AccessorInfo& info ) { + string key = toSTLString(property); + cout << "cannot delete property " << key << " from read-only object" << endl; + return Boolean::New( false ); + } + + Handle<Value> IndexedReadOnlySet( uint32_t index, Local<Value> value, const AccessorInfo& info ) { + cout << "cannot write property " << index << " to read-only array" << endl; + return value; + } + + Handle<Boolean> IndexedReadOnlyDelete( uint32_t index, const AccessorInfo& info ) { + cout << "cannot delete property " << index << " from read-only array" << endl; + return Boolean::New( false ); + } + + // --- engine --- + +// void fatalHandler(const char* s1, const char* s2) { +// cout << "Fatal handler " << s1 << " " << s2; +// } + + V8ScriptEngine::V8ScriptEngine() { + v8::V8::Initialize(); + v8::Locker l; +// v8::Locker::StartPreemption( 10 ); + + int K = 1024; + v8::ResourceConstraints rc; + rc.set_max_young_space_size(4 * K * K); + rc.set_max_old_space_size(64 * K * K); + v8::SetResourceConstraints(&rc); +// v8::V8::IgnoreOutOfMemoryException(); +// v8::V8::SetFatalErrorHandler(fatalHandler); + } + + V8ScriptEngine::~V8ScriptEngine() { + } + + void ScriptEngine::setup() { + if ( !globalScriptEngine ) { + globalScriptEngine = new V8ScriptEngine(); + } + } + + void V8ScriptEngine::interrupt( unsigned opSpec ) { + v8::Locker l; + v8Locks::InterruptLock il; + if ( __interruptSpecToThreadId.count( opSpec ) ) { + int thread = __interruptSpecToThreadId[ opSpec ]; + if ( thread == -2 || thread == -3) { + // just mark as interrupted + __interruptSpecToThreadId[ opSpec ] = -3; + return; + } + + V8::TerminateExecution( __interruptSpecToIsolate[ opSpec ] ); + } + } + + void V8ScriptEngine::interruptAll() { + v8::Locker l; + v8Locks::InterruptLock il; + vector< Isolate* > toKill; // v8 mutex could potentially be yielded during the termination call + + for( map< unsigned, Isolate* >::const_iterator i = __interruptSpecToIsolate.begin(); i != __interruptSpecToIsolate.end(); ++i ) { + toKill.push_back( i->second ); + } + for( vector< Isolate* >::const_iterator i = toKill.begin(); i != toKill.end(); ++i ) { + V8::TerminateExecution( *i ); + } + } + + // --- scope --- + + V8Scope::V8Scope( V8ScriptEngine * engine ) + : _engine( engine ) , + _connectState( NOT ) { + + _isolate = v8::Isolate::New(); + v8::Isolate::Scope iscope(_isolate); + v8::Locker l(_isolate); + + HandleScope handleScope; + _context = Context::New(); + Context::Scope context_scope( _context ); + _global = Persistent< v8::Object >::New( _context->Global() ); + _emptyObj = Persistent< v8::Object >::New( v8::Object::New() ); + + // initialize lazy object template + lzObjectTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New()); + lzObjectTemplate->SetInternalFieldCount( 1 ); + lzObjectTemplate->SetNamedPropertyHandler(namedGet, namedSet, 0, namedDelete, 0, v8::External::New(this)); + lzObjectTemplate->SetIndexedPropertyHandler(indexedGet, indexedSet, 0, indexedDelete, 0, v8::External::New(this)); + + roObjectTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New()); + roObjectTemplate->SetInternalFieldCount( 1 ); + roObjectTemplate->SetNamedPropertyHandler(namedGetRO, NamedReadOnlySet, 0, NamedReadOnlyDelete, namedEnumerator, v8::External::New(this)); + roObjectTemplate->SetIndexedPropertyHandler(indexedGetRO, IndexedReadOnlySet, 0, IndexedReadOnlyDelete, 0, v8::External::New(this)); + + // initialize lazy array template + // unfortunately it is not possible to create true v8 array from a template + // this means we use an object template and copy methods over + // this it creates issues when calling certain methods that check array type + lzArrayTemplate = Persistent<ObjectTemplate>::New(ObjectTemplate::New()); + lzArrayTemplate->SetInternalFieldCount( 1 ); + lzArrayTemplate->SetIndexedPropertyHandler(indexedGet, 0, 0, 0, 0, v8::External::New(this)); + + internalFieldObjects = Persistent<ObjectTemplate>::New(ObjectTemplate::New()); + internalFieldObjects->SetInternalFieldCount( 1 ); + + V8STR_CONN = getV8Str( "_conn" ); + V8STR_ID = getV8Str( "_id" ); + V8STR_LENGTH = getV8Str( "length" ); + V8STR_LEN = getV8Str( "len" ); + V8STR_TYPE = getV8Str( "type" ); + V8STR_ISOBJECTID = getV8Str( "isObjectId" ); + V8STR_RETURN = getV8Str( "return" ); + V8STR_ARGS = getV8Str( "args" ); + V8STR_T = getV8Str( "t" ); + V8STR_I = getV8Str( "i" ); + V8STR_EMPTY = getV8Str( "" ); + V8STR_MINKEY = getV8Str( "$MinKey" ); + V8STR_MAXKEY = getV8Str( "$MaxKey" ); + V8STR_NUMBERLONG = getV8Str( "__NumberLong" ); + V8STR_NUMBERINT = getV8Str( "__NumberInt" ); + V8STR_DBPTR = getV8Str( "__DBPointer" ); + V8STR_BINDATA = getV8Str( "__BinData" ); + V8STR_NATIVE_FUNC = getV8Str( "_native_function" ); + V8STR_NATIVE_DATA = getV8Str( "_native_data" ); + V8STR_V8_FUNC = getV8Str( "_v8_function" ); + V8STR_RO = getV8Str( "_ro" ); + V8STR_MODIFIED = getV8Str( "_mod" ); + V8STR_FULLNAME = getV8Str( "_fullName" ); + + injectV8Function("print", Print); + injectV8Function("version", Version); + injectV8Function("load", load); + + _wrapper = Persistent< v8::Function >::New( getObjectWrapperTemplate(this)->GetFunction() ); + + injectV8Function("gc", GCV8); + + installDBTypes( this, _global ); + } + + V8Scope::~V8Scope() { + // make sure to disable interrupt, otherwise can get segfault on race condition + disableV8Interrupt(); + + { + V8_SIMPLE_HEADER + _wrapper.Dispose(); + _emptyObj.Dispose(); + for( unsigned i = 0; i < _funcs.size(); ++i ) + _funcs[ i ].Dispose(); + _funcs.clear(); + _global.Dispose(); + std::map <string, v8::Persistent <v8::String> >::iterator it = _strCache.begin(); + std::map <string, v8::Persistent <v8::String> >::iterator end = _strCache.end(); + while (it != end) { + it->second.Dispose(); + ++it; + } + lzObjectTemplate.Dispose(); + lzArrayTemplate.Dispose(); + roObjectTemplate.Dispose(); + internalFieldObjects.Dispose(); + _context.Dispose(); + } + + _isolate->Dispose(); + } + + bool V8Scope::hasOutOfMemoryException() { + if (!_context.IsEmpty()) + return _context->HasOutOfMemoryException(); + return false; + } + + /** + * JS Callback that will call a c++ function with BSON arguments. + */ + Handle< Value > V8Scope::nativeCallback( V8Scope* scope, const Arguments &args ) { + V8Lock l; + HandleScope handle_scope; + Local< External > f = External::Cast( *args.Callee()->Get( scope->V8STR_NATIVE_FUNC ) ); + NativeFunction function = (NativeFunction)(f->Value()); + Local< External > data = External::Cast( *args.Callee()->Get( scope->V8STR_NATIVE_DATA ) ); + BSONObjBuilder b; + for( int i = 0; i < args.Length(); ++i ) { + stringstream ss; + ss << i; + scope->v8ToMongoElement( b, ss.str(), args[ i ] ); + } + BSONObj nativeArgs = b.obj(); + BSONObj ret; + try { + ret = function( nativeArgs, data->Value() ); + } + catch( const std::exception &e ) { + return v8::ThrowException(v8::String::New(e.what())); + } + catch( ... ) { + return v8::ThrowException(v8::String::New("unknown exception")); + } + return handle_scope.Close( scope->mongoToV8Element( ret.firstElement() ) ); + } + + Handle< Value > V8Scope::load( V8Scope* scope, const Arguments &args ) { + Context::Scope context_scope(scope->_context); + for (int i = 0; i < args.Length(); ++i) { + std::string filename(toSTLString(args[i])); + if (!scope->execFile(filename, false , true , false)) { + return v8::ThrowException(v8::String::New((std::string("error loading file: ") + filename).c_str())); + } + } + return v8::True(); + } + + /** + * JS Callback that will call a c++ function with the v8 scope and v8 arguments. + * Handles interrupts, exception handling, etc + * + * The implementation below assumes that SERVER-1816 has been fixed - in + * particular, interrupted() must return true if an interrupt was ever + * sent; currently that is not the case if a new killop overwrites the data + * for an old one + */ + v8::Handle< v8::Value > V8Scope::v8Callback( const v8::Arguments &args ) { + Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_v8_function" ) ) ); + v8Function function = (v8Function)(f->Value()); + Local< External > scp = External::Cast( *args.Data() ); + V8Scope* scope = (V8Scope*)(scp->Value()); + + // originally v8 interrupt where disabled here cause: don't want to have to audit all v8 calls for termination exceptions + // but we do need to keep interrupt because much time may be spent here (e.g. sleep) + bool paused = scope->pauseV8Interrupt(); + + v8::Handle< v8::Value > ret; + string exception; + try { + ret = function( scope, args ); + } + catch( const std::exception &e ) { + exception = e.what(); + } + catch( ... ) { + exception = "unknown exception"; + } + if (paused) { + bool resume = scope->resumeV8Interrupt(); + if ( !resume || globalScriptEngine->interrupted() ) { + v8::V8::TerminateExecution(scope->_isolate); + return v8::ThrowException( v8::String::New( "Interruption in V8 native callback" ) ); + } + } + if ( !exception.empty() ) { + return v8::ThrowException( v8::String::New( exception.c_str() ) ); + } + return ret; + } + + // ---- global stuff ---- + + void V8Scope::init( const BSONObj * data ) { + V8Lock l; + if ( ! data ) + return; + + BSONObjIterator i( *data ); + while ( i.more() ) { + BSONElement e = i.next(); + setElement( e.fieldName() , e ); + } + } + + void V8Scope::setNumber( const char * field , double val ) { + V8_SIMPLE_HEADER + _global->Set( getV8Str( field ) , v8::Number::New( val ) ); + } + + void V8Scope::setString( const char * field , const char * val ) { + V8_SIMPLE_HEADER + _global->Set( getV8Str( field ) , v8::String::New( val ) ); + } + + void V8Scope::setBoolean( const char * field , bool val ) { + V8_SIMPLE_HEADER + _global->Set( getV8Str( field ) , v8::Boolean::New( val ) ); + } + + void V8Scope::setElement( const char *field , const BSONElement& e ) { + V8_SIMPLE_HEADER + _global->Set( getV8Str( field ) , mongoToV8Element( e ) ); + } + + void V8Scope::setObject( const char *field , const BSONObj& obj , bool readOnly) { + V8_SIMPLE_HEADER + // Set() accepts a ReadOnly parameter, but this just prevents the field itself + // from being overwritten and doesn't protect the object stored in 'field'. + _global->Set( getV8Str( field ) , mongoToLZV8( obj, false, readOnly) ); + } + + int V8Scope::type( const char *field ) { + V8_SIMPLE_HEADER + Handle<Value> v = get( field ); + if ( v->IsNull() ) + return jstNULL; + if ( v->IsUndefined() ) + return Undefined; + if ( v->IsString() ) + return String; + if ( v->IsFunction() ) + return Code; + if ( v->IsArray() ) + return Array; + if ( v->IsBoolean() ) + return Bool; + // needs to be explicit NumberInt to use integer +// if ( v->IsInt32() ) +// return NumberInt; + if ( v->IsNumber() ) + return NumberDouble; + if ( v->IsExternal() ) { + uassert( 10230 , "can't handle external yet" , 0 ); + return -1; + } + if ( v->IsDate() ) + return Date; + if ( v->IsObject() ) + return Object; + + throw UserException( 12509, (string)"don't know what this is: " + field ); + } + + v8::Handle<v8::Value> V8Scope::get( const char * field ) { + return _global->Get( getV8Str( field ) ); + } + + double V8Scope::getNumber( const char *field ) { + V8_SIMPLE_HEADER + return get( field )->ToNumber()->Value(); + } + + int V8Scope::getNumberInt( const char *field ) { + V8_SIMPLE_HEADER + return get( field )->ToInt32()->Value(); + } + + long long V8Scope::getNumberLongLong( const char *field ) { + V8_SIMPLE_HEADER + return get( field )->ToInteger()->Value(); + } + + string V8Scope::getString( const char *field ) { + V8_SIMPLE_HEADER + return toSTLString( get( field ) ); + } + + bool V8Scope::getBoolean( const char *field ) { + V8_SIMPLE_HEADER + return get( field )->ToBoolean()->Value(); + } + + BSONObj V8Scope::getObject( const char * field ) { + V8_SIMPLE_HEADER + Handle<Value> v = get( field ); + if ( v->IsNull() || v->IsUndefined() ) + return BSONObj(); + uassert( 10231 , "not an object" , v->IsObject() ); + return v8ToMongo( v->ToObject() ); + } + + // --- functions ----- + + bool hasFunctionIdentifier( const string& code ) { + if ( code.size() < 9 || code.find( "function" ) != 0 ) + return false; + + return code[8] == ' ' || code[8] == '('; + } + + Local< v8::Function > V8Scope::__createFunction( const char * raw ) { + raw = jsSkipWhiteSpace( raw ); + string code = raw; + if ( !hasFunctionIdentifier( code ) ) { + if ( code.find( "\n" ) == string::npos && + ! hasJSReturn( code ) && + ( code.find( ";" ) == string::npos || code.find( ";" ) == code.size() - 1 ) ) { + code = "return " + code; + } + code = "function(){ " + code + "}"; + } + + int num = _funcs.size() + 1; + + string fn; + { + stringstream ss; + ss << "_funcs" << num; + fn = ss.str(); + } + + code = fn + " = " + code; + + TryCatch try_catch; + // this might be time consuming, consider allowing an interrupt + Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) , + v8::String::New( fn.c_str() ) ); + if ( script.IsEmpty() ) { + _error = (string)"compile error: " + toSTLString( &try_catch ); + log() << _error << endl; + return Local< v8::Function >(); + } + + Local<Value> result = script->Run(); + if ( result.IsEmpty() ) { + _error = (string)"compile error: " + toSTLString( &try_catch ); + log() << _error << endl; + return Local< v8::Function >(); + } + + return v8::Function::Cast( *_global->Get( v8::String::New( fn.c_str() ) ) ); + } + + ScriptingFunction V8Scope::_createFunction( const char * raw ) { + V8_SIMPLE_HEADER + Local< Value > ret = __createFunction( raw ); + if ( ret.IsEmpty() ) + return 0; + Persistent<Value> f = Persistent< Value >::New( ret ); + uassert( 10232, "not a func" , f->IsFunction() ); + int num = _funcs.size() + 1; + _funcs.push_back( f ); + return num; + } + + void V8Scope::setFunction( const char *field , const char * code ) { + V8_SIMPLE_HEADER + _global->Set( getV8Str( field ) , __createFunction(code) ); + } + +// void V8Scope::setThis( const BSONObj * obj ) { +// V8_SIMPLE_HEADER +// if ( ! obj ) { +// _this = Persistent< v8::Object >::New( v8::Object::New() ); +// return; +// } +// +// //_this = mongoToV8( *obj ); +// v8::Handle<v8::Value> argv[1]; +// argv[0] = v8::External::New( createWrapperHolder( this, obj , true , false ) ); +// _this = Persistent< v8::Object >::New( _wrapper->NewInstance( 1, argv ) ); +// } + + void V8Scope::rename( const char * from , const char * to ) { + V8_SIMPLE_HEADER; + Handle<v8::String> f = getV8Str( from ); + Handle<v8::String> t = getV8Str( to ); + _global->Set( t , _global->Get( f ) ); + _global->Set( f , v8::Undefined() ); + } + + int V8Scope::invoke( ScriptingFunction func , const BSONObj* argsObject, const BSONObj* recv, int timeoutMs , bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) { + V8_SIMPLE_HEADER + Handle<Value> funcValue = _funcs[func-1]; + + TryCatch try_catch; + int nargs = argsObject ? argsObject->nFields() : 0; + scoped_array< Handle<Value> > args; + if ( nargs ) { + args.reset( new Handle<Value>[nargs] ); + BSONObjIterator it( *argsObject ); + for ( int i=0; i<nargs; i++ ) { + BSONElement next = it.next(); + args[i] = mongoToV8Element( next, readOnlyArgs ); + } + setObject( "args", *argsObject, readOnlyArgs); // for backwards compatibility + } + else { + _global->Set( V8STR_ARGS, v8::Undefined() ); + } + if ( globalScriptEngine->interrupted() ) { + stringstream ss; + ss << "error in invoke: " << globalScriptEngine->checkInterrupt(); + _error = ss.str(); + log() << _error << endl; + return 1; + } + Handle<v8::Object> v8recv; + if (recv != 0) + v8recv = mongoToLZV8(*recv, false, readOnlyRecv); + else + v8recv = _global; + + enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable + Local<Value> result = ((v8::Function*)(*funcValue))->Call( v8recv , nargs , nargs ? args.get() : 0 ); + disableV8Interrupt(); + + if ( result.IsEmpty() ) { + stringstream ss; + if ( try_catch.HasCaught() && !try_catch.CanContinue() ) { + ss << "error in invoke: " << globalScriptEngine->checkInterrupt(); + } + else { + ss << "error in invoke: " << toSTLString( &try_catch ); + } + _error = ss.str(); + log() << _error << endl; + return 1; + } + + if ( ! ignoreReturn ) { + _global->Set( V8STR_RETURN , result ); + } + + return 0; + } + + bool V8Scope::exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ) { + if ( timeoutMs ) { + static bool t = 1; + if ( t ) { + log() << "timeoutMs not support for v8 yet code: " << code << endl; + t = 0; + } + } + + V8_SIMPLE_HEADER + + TryCatch try_catch; + + Handle<Script> script = v8::Script::Compile( v8::String::New( code.data() ) , + v8::String::New( name.c_str() ) ); + if (script.IsEmpty()) { + stringstream ss; + ss << "compile error: " << toSTLString( &try_catch ); + _error = ss.str(); + if (reportError) + log() << _error << endl; + if ( assertOnError ) + uassert( 10233 , _error , 0 ); + return false; + } + + if ( globalScriptEngine->interrupted() ) { + _error = (string)"exec error: " + globalScriptEngine->checkInterrupt(); + if ( reportError ) { + log() << _error << endl; + } + if ( assertOnError ) { + uassert( 13475 , _error , 0 ); + } + return false; + } + enableV8Interrupt(); // because of v8 locker we can check interrupted, then enable + Handle<v8::Value> result = script->Run(); + disableV8Interrupt(); + if ( result.IsEmpty() ) { + if ( try_catch.HasCaught() && !try_catch.CanContinue() ) { + _error = (string)"exec error: " + globalScriptEngine->checkInterrupt(); + } + else { + _error = (string)"exec error: " + toSTLString( &try_catch ); + } + if ( reportError ) + log() << _error << endl; + if ( assertOnError ) + uassert( 10234 , _error , 0 ); + return false; + } + + _global->Set( getV8Str( "__lastres__" ) , result ); + + if ( printResult && ! result->IsUndefined() ) { + cout << toSTLString( result ) << endl; + } + + return true; + } + + void V8Scope::injectNative( const char *field, NativeFunction func, void* data ) { + injectNative(field, func, _global, data); + } + + void V8Scope::injectNative( const char *field, NativeFunction func, Handle<v8::Object>& obj, void* data ) { + V8_SIMPLE_HEADER + + Handle< FunctionTemplate > ft = createV8Function(nativeCallback); + ft->Set( this->V8STR_NATIVE_FUNC, External::New( (void*)func ) ); + ft->Set( this->V8STR_NATIVE_DATA, External::New( data ) ); + obj->Set( getV8Str( field ), ft->GetFunction() ); + } + + void V8Scope::injectV8Function( const char *field, v8Function func ) { + injectV8Function(field, func, _global); + } + + void V8Scope::injectV8Function( const char *field, v8Function func, Handle<v8::Object>& obj ) { + V8_SIMPLE_HEADER + + Handle< FunctionTemplate > ft = createV8Function(func); + Handle<v8::Function> f = ft->GetFunction(); + obj->Set( getV8Str( field ), f ); + } + + void V8Scope::injectV8Function( const char *field, v8Function func, Handle<v8::Template>& t ) { + V8_SIMPLE_HEADER + + Handle< FunctionTemplate > ft = createV8Function(func); + Handle<v8::Function> f = ft->GetFunction(); + t->Set( getV8Str( field ), f ); + } + + Handle<FunctionTemplate> V8Scope::createV8Function( v8Function func ) { + Handle< FunctionTemplate > ft = v8::FunctionTemplate::New(v8Callback, External::New( this )); + ft->Set( this->V8STR_V8_FUNC, External::New( (void*)func ) ); + return ft; + } + + void V8Scope::gc() { + cout << "in gc" << endl; + V8Lock l; + V8::LowMemoryNotification(); + } + + // ----- db access ----- + + void V8Scope::localConnect( const char * dbName ) { + { + V8_SIMPLE_HEADER + + if ( _connectState == EXTERNAL ) + throw UserException( 12510, "externalSetup already called, can't call externalSetup" ); + if ( _connectState == LOCAL ) { + if ( _localDBName == dbName ) + return; + throw UserException( 12511, "localConnect called with a different name previously" ); + } + + //_global->Set( v8::String::New( "Mongo" ) , _engine->_externalTemplate->GetFunction() ); + _global->Set( getV8Str( "Mongo" ) , getMongoFunctionTemplate( this, true )->GetFunction() ); + execCoreFiles(); + exec( "_mongo = new Mongo();" , "local connect 2" , false , true , true , 0 ); + exec( (string)"db = _mongo.getDB(\"" + dbName + "\");" , "local connect 3" , false , true , true , 0 ); + _connectState = LOCAL; + _localDBName = dbName; + } + loadStored(); + } + + void V8Scope::externalSetup() { + V8_SIMPLE_HEADER + if ( _connectState == EXTERNAL ) + return; + if ( _connectState == LOCAL ) + throw UserException( 12512, "localConnect already called, can't call externalSetup" ); + + installFork( this, _global, _context ); + _global->Set( getV8Str( "Mongo" ) , getMongoFunctionTemplate( this, false )->GetFunction() ); + execCoreFiles(); + _connectState = EXTERNAL; + } + + // ----- internal ----- + + void V8Scope::reset() { + _startCall(); + } + + void V8Scope::_startCall() { + _error = ""; + } + + Local< v8::Value > newFunction( const char *code ) { + stringstream codeSS; + codeSS << "____MontoToV8_newFunction_temp = " << code; + string codeStr = codeSS.str(); + Local< Script > compiled = Script::New( v8::String::New( codeStr.c_str() ) ); + Local< Value > ret = compiled->Run(); + return ret; + } + + Local< v8::Value > V8Scope::newId( const OID &id ) { + v8::Function * idCons = this->getObjectIdCons(); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::String::New( id.str().c_str() ); + return idCons->NewInstance( 1 , argv ); + } + + Local<v8::Object> V8Scope::mongoToV8( const BSONObj& m , bool array, bool readOnly ) { + + Local<v8::Object> o; + + // handle DBRef. needs to come first. isn't it? (metagoto) + static string ref = "$ref"; + if ( ref == m.firstElement().fieldName() ) { + const BSONElement& id = m["$id"]; + if (!id.eoo()) { // there's no check on $id exitence in sm implementation. risky ? + v8::Function* dbRef = getNamedCons( "DBRef" ); + o = dbRef->NewInstance(); + } + } + + Local< v8::ObjectTemplate > readOnlyObjects; + + if ( !o.IsEmpty() ) { + readOnly = false; + } + else if ( array ) { + // NOTE Looks like it's impossible to add interceptors to v8 arrays. + // so array itself will never be read only, but its values can be + o = v8::Array::New(); + } + else if ( !readOnly ) { + o = v8::Object::New(); + } + else { + // NOTE Our readOnly implemention relies on undocumented ObjectTemplate + // functionality that may be fragile, but it still seems like the best option + // for now -- fwiw, the v8 docs are pretty sparse. I've determined experimentally + // that when property handlers are set for an object template, they will attach + // to objects previously created by that template. To get this to work, though, + // it is necessary to initialize the template's property handlers before + // creating objects from the template (as I have in the following few lines + // of code). + // NOTE In my first attempt, I configured the permanent property handlers before + // constructiong the object and replaced the Set() calls below with ForceSet(). + // However, it turns out that ForceSet() only bypasses handlers for named + // properties and not for indexed properties. + readOnlyObjects = v8::ObjectTemplate::New(); + // NOTE This internal field will store type info for special db types. For + // regular objects the field is unnecessary - for simplicity I'm creating just + // one readOnlyObjects template for objects where the field is & isn't necessary, + // assuming that the overhead of an internal field is slight. + readOnlyObjects->SetInternalFieldCount( 1 ); + readOnlyObjects->SetNamedPropertyHandler( 0 ); + readOnlyObjects->SetIndexedPropertyHandler( 0 ); + o = readOnlyObjects->NewInstance(); + } + + mongo::BSONObj sub; + + for ( BSONObjIterator i(m); i.more(); ) { + const BSONElement& f = i.next(); + + Local<Value> v; + Handle<v8::String> name = getV8Str(f.fieldName()); + + switch ( f.type() ) { + + case mongo::Code: + o->Set( name, newFunction( f.valuestr() ) ); + break; + + case CodeWScope: + if ( !f.codeWScopeObject().isEmpty() ) + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + o->Set( name, newFunction( f.codeWScopeCode() ) ); + break; + + case mongo::String: + o->Set( name , v8::String::New( f.valuestr() ) ); + break; + + case mongo::jstOID: { + v8::Function * idCons = getObjectIdCons(); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::String::New( f.__oid().str().c_str() ); + o->Set( name , + idCons->NewInstance( 1 , argv ) ); + break; + } + + case mongo::NumberDouble: + case mongo::NumberInt: + o->Set( name , v8::Number::New( f.number() ) ); + break; + +// case mongo::NumberInt: { +// Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); +// int val = f.numberInt(); +// v8::Function* numberInt = getNamedCons( "NumberInt" ); +// v8::Handle<v8::Value> argv[1]; +// argv[0] = v8::Int32::New( val ); +// o->Set( name, numberInt->NewInstance( 1, argv ) ); +// break; +// } + + case mongo::Array: + sub = f.embeddedObject(); + o->Set( name , mongoToV8( sub , true, readOnly ) ); + break; + case mongo::Object: + sub = f.embeddedObject(); + o->Set( name , mongoToLZV8( sub , false, readOnly ) ); + break; + + case mongo::Date: + o->Set( name , v8::Date::New( (double) ((long long)f.date().millis) )); + break; + + case mongo::Bool: + o->Set( name , v8::Boolean::New( f.boolean() ) ); + break; + + case mongo::jstNULL: + case mongo::Undefined: // duplicate sm behavior + o->Set( name , v8::Null() ); + break; + + case mongo::RegEx: { + v8::Function * regex = getNamedCons( "RegExp" ); + + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.regex() ); + argv[1] = v8::String::New( f.regexFlags() ); + + o->Set( name , regex->NewInstance( 2 , argv ) ); + break; + } + + case mongo::BinData: { + int len; + const char *data = f.binData( len ); + + v8::Function* binData = getNamedCons( "BinData" ); + v8::Handle<v8::Value> argv[3]; + argv[0] = v8::Number::New( len ); + argv[1] = v8::Number::New( f.binDataType() ); + argv[2] = v8::String::New( data, len ); + o->Set( name, binData->NewInstance(3, argv) ); + break; + } + + case mongo::Timestamp: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + + sub->Set( V8STR_T , v8::Number::New( f.timestampTime() ) ); + sub->Set( V8STR_I , v8::Number::New( f.timestampInc() ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + + o->Set( name , sub ); + break; + } + + case mongo::NumberLong: { + unsigned long long val = f.numberLong(); + v8::Function* numberLong = getNamedCons( "NumberLong" ); + double floatApprox = (double)(long long)val; + // values above 2^53 are not accurately represented in JS + if ( (long long)val == (long long)floatApprox && val < 9007199254740992ULL ) { + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::Number::New( floatApprox ); + o->Set( name, numberLong->NewInstance( 1, argv ) ); + } + else { + v8::Handle<v8::Value> argv[3]; + argv[0] = v8::Number::New( floatApprox ); + argv[1] = v8::Integer::New( val >> 32 ); + argv[2] = v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ); + o->Set( name, numberLong->NewInstance(3, argv) ); + } + break; + } + + case mongo::MinKey: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + sub->Set( V8STR_MINKEY, v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + o->Set( name , sub ); + break; + } + + case mongo::MaxKey: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + sub->Set( V8STR_MAXKEY, v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + o->Set( name , sub ); + break; + } + + case mongo::DBRef: { + v8::Function* dbPointer = getNamedCons( "DBPointer" ); + v8::Handle<v8::Value> argv[2]; + argv[0] = getV8Str( f.dbrefNS() ); + argv[1] = newId( f.dbrefOID() ); + o->Set( name, dbPointer->NewInstance(2, argv) ); + break; + } + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + } + + if ( !array && readOnly ) { + readOnlyObjects->SetNamedPropertyHandler( 0, NamedReadOnlySet, 0, NamedReadOnlyDelete ); + readOnlyObjects->SetIndexedPropertyHandler( 0, IndexedReadOnlySet, 0, IndexedReadOnlyDelete ); + } + + return o; + } + + /** + * converts a BSONObj to a Lazy V8 object + */ + Handle<v8::Object> V8Scope::mongoToLZV8( const BSONObj& m , bool array, bool readOnly ) { + Local<v8::Object> o; + + if (readOnly) { + o = roObjectTemplate->NewInstance(); + o->SetHiddenValue(V8STR_RO, v8::Boolean::New(true)); + } else { + if (array) { + o = lzArrayTemplate->NewInstance(); + o->SetPrototype(v8::Array::New(1)->GetPrototype()); + o->Set(V8STR_LENGTH, v8::Integer::New(m.nFields()), DontEnum); + // o->Set(ARRAY_STRING, v8::Boolean::New(true), DontEnum); + } else { + o = lzObjectTemplate->NewInstance(); + + static string ref = "$ref"; + if ( ref == m.firstElement().fieldName() ) { + const BSONElement& id = m["$id"]; + if (!id.eoo()) { + v8::Function* dbRef = getNamedCons( "DBRef" ); + o->SetPrototype(dbRef->NewInstance()->GetPrototype()); + } + } + } + + // need to set all keys with dummy values, so that order of keys is correct during enumeration + // otherwise v8 will list any newly set property in JS before the ones of underlying BSON obj. + for (BSONObjIterator it(m); it.more();) { + const BSONElement& f = it.next(); + o->ForceSet(getV8Str(f.fieldName()), v8::Undefined()); + } + } + + BSONObj* own = new BSONObj(m.getOwned()); +// BSONObj* own = new BSONObj(m); + Persistent<v8::Object> p = wrapBSONObject(o, own); + return p; + } + + Handle<v8::Value> V8Scope::mongoToV8Element( const BSONElement &f, bool readOnly ) { +// Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); +// internalFieldObjects->SetInternalFieldCount( 1 ); + + switch ( f.type() ) { + + case mongo::Code: + return newFunction( f.valuestr() ); + + case CodeWScope: + if ( !f.codeWScopeObject().isEmpty() ) + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + return newFunction( f.codeWScopeCode() ); + + case mongo::String: +// return v8::String::NewExternal( new ExternalString( f.valuestr() )); + return v8::String::New( f.valuestr() ); +// return getV8Str( f.valuestr() ); + + case mongo::jstOID: + return newId( f.__oid() ); + + case mongo::NumberDouble: + case mongo::NumberInt: + return v8::Number::New( f.number() ); + + case mongo::Array: + // for arrays it's better to use non lazy object because: + // - the lazy array is not a true v8 array and requires some v8 src change for all methods to work + // - it made several tests about 1.5x slower + // - most times when an array is accessed, all its values will be used + return mongoToV8( f.embeddedObject() , true, readOnly ); + case mongo::Object: + return mongoToLZV8( f.embeddedObject() , false, readOnly); + + case mongo::Date: + return v8::Date::New( (double) ((long long)f.date().millis) ); + + case mongo::Bool: + return v8::Boolean::New( f.boolean() ); + + case mongo::EOO: + case mongo::jstNULL: + case mongo::Undefined: // duplicate sm behavior + return v8::Null(); + + case mongo::RegEx: { + v8::Function * regex = getNamedCons( "RegExp" ); + + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.regex() ); + argv[1] = v8::String::New( f.regexFlags() ); + + return regex->NewInstance( 2 , argv ); + break; + } + + case mongo::BinData: { + int len; + const char *data = f.binData( len ); + + v8::Function* binData = getNamedCons( "BinData" ); + v8::Handle<v8::Value> argv[3]; + argv[0] = v8::Number::New( len ); + argv[1] = v8::Number::New( f.binDataType() ); + argv[2] = v8::String::New( data, len ); + return binData->NewInstance( 3, argv ); + }; + + case mongo::Timestamp: { + Local<v8::Object> sub = internalFieldObjects->NewInstance(); + + sub->Set( V8STR_T , v8::Number::New( f.timestampTime() ) ); + sub->Set( V8STR_I , v8::Number::New( f.timestampInc() ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + + return sub; + } + + case mongo::NumberLong: { + unsigned long long val = f.numberLong(); + v8::Function* numberLong = getNamedCons( "NumberLong" ); + // values above 2^53 are not accurately represented in JS + if ( (long long)val == (long long)(double)(long long)(val) && val < 9007199254740992ULL ) { + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::Number::New( (double)(long long)( val ) ); + return numberLong->NewInstance( 1, argv ); + } + else { + v8::Handle<v8::Value> argv[3]; + argv[0] = v8::Number::New( (double)(long long)( val ) ); + argv[1] = v8::Integer::New( val >> 32 ); + argv[2] = v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ); + return numberLong->NewInstance( 3, argv ); + } + } + +// case mongo::NumberInt: { +// Local<v8::Object> sub = internalFieldObjects->NewInstance(); +// int val = f.numberInt(); +// v8::Function* numberInt = getNamedCons( "NumberInt" ); +// v8::Handle<v8::Value> argv[1]; +// argv[0] = v8::Int32::New(val); +// return numberInt->NewInstance( 1, argv ); +// } + + case mongo::MinKey: { + Local<v8::Object> sub = internalFieldObjects->NewInstance(); + sub->Set( V8STR_MINKEY, v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + return sub; + } + + case mongo::MaxKey: { + Local<v8::Object> sub = internalFieldObjects->NewInstance(); + sub->Set( V8STR_MAXKEY, v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + return sub; + } + + case mongo::DBRef: { + v8::Function* dbPointer = getNamedCons( "DBPointer" ); + v8::Handle<v8::Value> argv[2]; + argv[0] = getV8Str( f.dbrefNS() ); + argv[1] = newId( f.dbrefOID() ); + return dbPointer->NewInstance(2, argv); + } + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + return v8::Undefined(); + } + + void V8Scope::append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ) { + V8_SIMPLE_HEADER + Handle<v8::String> v8name = getV8Str(scopeName); + Handle<Value> value = _global->Get( v8name ); + v8ToMongoElement(builder, fieldName, value); + } + + void V8Scope::v8ToMongoElement( BSONObjBuilder & b , const string sname , v8::Handle<v8::Value> value , int depth, BSONObj* originalParent ) { + + if ( value->IsString() ) { +// Handle<v8::String> str = Handle<v8::String>::Cast(value); +// ExternalString* es = (ExternalString*) (str->GetExternalAsciiStringResource()); +// b.append( sname , es->data() ); + b.append( sname , toSTLString( value ).c_str() ); + return; + } + + if ( value->IsFunction() ) { + b.appendCode( sname , toSTLString( value ) ); + return; + } + + if ( value->IsNumber() ) { + double val = value->ToNumber()->Value(); + // if previous type was integer, keep it + int intval = (int)val; + if (val == intval && originalParent) { + BSONElement elmt = originalParent->getField(sname); + if (elmt.type() == mongo::NumberInt) { + b.append( sname , intval ); + return; + } + } + + b.append( sname , val ); + return; + } + + if ( value->IsArray() ) { + BSONObj sub = v8ToMongo( value->ToObject() , depth ); + b.appendArray( sname , sub ); + return; + } + + if ( value->IsDate() ) { + long long dateval = (long long)(v8::Date::Cast( *value )->NumberValue()); + b.appendDate( sname , Date_t( (unsigned long long) dateval ) ); + return; + } + + if ( value->IsExternal() ) + return; + + if ( value->IsObject() ) { + // The user could potentially modify the fields of these special objects, + // wreaking havoc when we attempt to reinterpret them. Not doing any validation + // for now... + Local< v8::Object > obj = value->ToObject(); + if ( obj->InternalFieldCount() && obj->GetInternalField( 0 )->IsNumber() ) { + switch( obj->GetInternalField( 0 )->ToInt32()->Value() ) { // NOTE Uint32's Value() gave me a linking error, so going with this instead + case Timestamp: + b.appendTimestamp( sname, + Date_t( (unsigned long long)(obj->Get( V8STR_T )->ToNumber()->Value() )), + obj->Get( V8STR_I )->ToInt32()->Value() ); + return; + case MinKey: + b.appendMinKey( sname ); + return; + case MaxKey: + b.appendMaxKey( sname ); + return; + default: + assert( "invalid internal field" == 0 ); + } + } + string s = toSTLString( value ); + if ( s.size() && s[0] == '/' ) { + s = s.substr( 1 ); + string r = s.substr( 0 , s.rfind( "/" ) ); + string o = s.substr( s.rfind( "/" ) + 1 ); + b.appendRegex( sname , r , o ); + } + else if ( value->ToObject()->GetPrototype()->IsObject() && + value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( V8STR_ISOBJECTID ) ) { + OID oid; + oid.init( toSTLString( value->ToObject()->Get(getV8Str("str")) ) ); + b.appendOID( sname , &oid ); + } + else if ( !value->ToObject()->GetHiddenValue( V8STR_NUMBERLONG ).IsEmpty() ) { + // TODO might be nice to potentially speed this up with an indexed internal + // field, but I don't yet know how to use an ObjectTemplate with a + // constructor. + v8::Handle< v8::Object > it = value->ToObject(); + long long val; + if ( !it->Has( getV8Str( "top" ) ) ) { + val = (long long)( it->Get( getV8Str( "floatApprox" ) )->NumberValue() ); + } + else { + val = (long long) + ( (unsigned long long)( it->Get( getV8Str( "top" ) )->ToInt32()->Value() ) << 32 ) + + (unsigned)( it->Get( getV8Str( "bottom" ) )->ToInt32()->Value() ); + } + + b.append( sname, val ); + } + else if ( !value->ToObject()->GetHiddenValue( V8STR_NUMBERINT ).IsEmpty() ) { + v8::Handle< v8::Object > it = value->ToObject(); + b.append(sname, it->GetHiddenValue(V8STR_NUMBERINT)->Int32Value()); + } + else if ( !value->ToObject()->GetHiddenValue( V8STR_DBPTR ).IsEmpty() ) { + OID oid; + Local<Value> theid = value->ToObject()->Get( getV8Str( "id" ) ); + oid.init( toSTLString( theid->ToObject()->Get(getV8Str("str")) ) ); + string ns = toSTLString( value->ToObject()->Get( getV8Str( "ns" ) ) ); + b.appendDBRef( sname, ns, oid ); + } + else if ( !value->ToObject()->GetHiddenValue( V8STR_BINDATA ).IsEmpty() ) { + int len = obj->Get( getV8Str( "len" ) )->ToInt32()->Value(); + Local<External> c = External::Cast( *(obj->GetInternalField( 0 )) ); + const char* dataArray = (char*)(c->Value());; + b.appendBinData( sname, + len, + mongo::BinDataType( obj->Get( getV8Str( "type" ) )->ToInt32()->Value() ), + dataArray ); + } + else { + BSONObj sub = v8ToMongo( value->ToObject() , depth ); + b.append( sname , sub ); + } + return; + } + + if ( value->IsBoolean() ) { + b.appendBool( sname , value->ToBoolean()->Value() ); + return; + } + + else if ( value->IsUndefined() ) { + b.appendUndefined( sname ); + return; + } + + else if ( value->IsNull() ) { + b.appendNull( sname ); + return; + } + + cout << "don't know how to convert to mongo field [" << sname << "]\t" << value << endl; + } + + BSONObj V8Scope::v8ToMongo( v8::Handle<v8::Object> o , int depth ) { + BSONObj* originalBSON = 0; + if (o->InternalFieldCount() > 0) { + originalBSON = unwrapBSONObj(o); + + if ( !o->GetHiddenValue( V8STR_RO ).IsEmpty() || + (o->HasNamedLookupInterceptor() && o->GetHiddenValue( V8STR_MODIFIED ).IsEmpty()) ) { + // object was readonly, use bson as is + return *originalBSON; + } + } + + BSONObjBuilder b; + + if ( depth == 0 ) { + if ( o->HasRealNamedProperty( V8STR_ID ) ) { + v8ToMongoElement( b , "_id" , o->Get( V8STR_ID ), 0, originalBSON ); + } + } + + Local<v8::Array> names = o->GetPropertyNames(); + for ( unsigned int i=0; i<names->Length(); i++ ) { + v8::Local<v8::String> name = names->Get( i )->ToString(); + +// if ( o->GetPrototype()->IsObject() && +// o->GetPrototype()->ToObject()->HasRealNamedProperty( name ) ) +// continue; + + v8::Local<v8::Value> value = o->Get( name ); + + const string sname = toSTLString( name ); + if ( depth == 0 && sname == "_id" ) + continue; + + v8ToMongoElement( b , sname , value , depth + 1, originalBSON ); + } + return b.obj(); + } + + // --- random utils ---- + + v8::Function * V8Scope::getNamedCons( const char * name ) { + return v8::Function::Cast( *(v8::Context::GetCurrent()->Global()->Get( getV8Str( name ) ) ) ); + } + + v8::Function * V8Scope::getObjectIdCons() { + return getNamedCons( "ObjectId" ); + } + + Handle<v8::Value> V8Scope::Print(V8Scope* scope, const Arguments& args) { + bool first = true; + for (int i = 0; i < args.Length(); i++) { + HandleScope handle_scope; + if (first) { + first = false; + } + else { + printf(" "); + } + v8::String::Utf8Value str(args[i]); + printf("%s", *str); + } + printf("\n"); + return v8::Undefined(); + } + + Handle<v8::Value> V8Scope::Version(V8Scope* scope, const Arguments& args) { + HandleScope handle_scope; + return handle_scope.Close( v8::String::New(v8::V8::GetVersion()) ); + } + + Handle<v8::Value> V8Scope::GCV8(V8Scope* scope, const Arguments& args) { + V8Lock l; + v8::V8::LowMemoryNotification(); + return v8::Undefined(); + } + + /** + * Gets a V8 strings from the scope's cache, creating one if needed + */ + v8::Handle<v8::String> V8Scope::getV8Str(string str) { + Persistent<v8::String> ptr = _strCache[str]; + if (ptr.IsEmpty()) { + ptr = Persistent<v8::String>::New(v8::String::New(str.c_str())); + _strCache[str] = ptr; +// cout << "Adding str " + str << endl; + } +// cout << "Returning str " + str << endl; + return ptr; + } + + // to be called with v8 mutex + void V8Scope::enableV8Interrupt() { + v8Locks::InterruptLock l; + if ( globalScriptEngine->haveGetInterruptSpecCallback() ) { + unsigned op = globalScriptEngine->getInterruptSpec(); + __interruptSpecToThreadId[ op ] = v8::V8::GetCurrentThreadId(); + __interruptSpecToIsolate[ op ] = _isolate; + } + } + + // to be called with v8 mutex + void V8Scope::disableV8Interrupt() { + v8Locks::InterruptLock l; + if ( globalScriptEngine->haveGetInterruptSpecCallback() ) { + unsigned op = globalScriptEngine->getInterruptSpec(); + __interruptSpecToIsolate.erase( op ); + __interruptSpecToThreadId.erase( op ); + } + } + + // to be called with v8 mutex + bool V8Scope::pauseV8Interrupt() { + v8Locks::InterruptLock l; + if ( globalScriptEngine->haveGetInterruptSpecCallback() ) { + unsigned op = globalScriptEngine->getInterruptSpec(); + int thread = __interruptSpecToThreadId[ op ]; + if ( thread == -2 || thread == -3) { + // already paused + return false; + } + __interruptSpecToThreadId[ op ] = -2; + } + return true; + } + + // to be called with v8 mutex + bool V8Scope::resumeV8Interrupt() { + v8Locks::InterruptLock l; + if ( globalScriptEngine->haveGetInterruptSpecCallback() ) { + unsigned op = globalScriptEngine->getInterruptSpec(); + if (__interruptSpecToThreadId[ op ] == -3) { + // was interrupted + return false; + } + __interruptSpecToThreadId[ op ] = v8::V8::GetCurrentThreadId(); + } + return true; + } + +} // namespace mongo diff --git a/src/mongo/scripting/engine_v8.h b/src/mongo/scripting/engine_v8.h new file mode 100644 index 00000000000..48a9858c63b --- /dev/null +++ b/src/mongo/scripting/engine_v8.h @@ -0,0 +1,254 @@ +//engine_v8.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <vector> +#include "engine.h" +#include <v8.h> + +using namespace v8; + +namespace mongo { + + class V8ScriptEngine; + class V8Scope; + + typedef Handle< Value > (*v8Function) ( V8Scope* scope, const v8::Arguments& args ); + + // Preemption is going to be allowed for the v8 mutex, and some of our v8 + // usage is not preemption safe. So we are using an additional mutex that + // will not be preempted. The V8Lock should be used in place of v8::Locker + // except in certain special cases involving interrupts. + namespace v8Locks { + struct InterruptLock { + InterruptLock(); + ~InterruptLock(); + }; + + // the implementations are quite simple - objects must be destroyed in + // reverse of the order created, and should not be shared between threads + struct RecursiveLock { + RecursiveLock(); + ~RecursiveLock(); + bool _unlock; + }; + struct RecursiveUnlock { + RecursiveUnlock(); + ~RecursiveUnlock(); + bool _lock; + }; + } // namespace v8Locks + class V8Lock { + public: + V8Lock() : _preemptionLock(Isolate::GetCurrent()){} + + private: + v8Locks::RecursiveLock _noPreemptionLock; + v8::Locker _preemptionLock; + }; + struct V8Unlock { + public: + V8Unlock() : _preemptionUnlock(Isolate::GetCurrent()){} + + private: + v8::Unlocker _preemptionUnlock; + v8Locks::RecursiveUnlock _noPreemptionUnlock; + }; + + class V8Scope : public Scope { + public: + + V8Scope( V8ScriptEngine * engine ); + ~V8Scope(); + + virtual void reset(); + virtual void init( const BSONObj * data ); + + virtual void localConnect( const char * dbName ); + virtual void externalSetup(); + + v8::Handle<v8::Value> get( const char * field ); // caller must create context and handle scopes + virtual double getNumber( const char *field ); + virtual int getNumberInt( const char *field ); + virtual long long getNumberLongLong( const char *field ); + virtual string getString( const char *field ); + virtual bool getBoolean( const char *field ); + virtual BSONObj getObject( const char *field ); + Handle<v8::Object> getGlobalObject() { return _global; }; + + virtual int type( const char *field ); + + virtual void setNumber( const char *field , double val ); + virtual void setString( const char *field , const char * val ); + virtual void setBoolean( const char *field , bool val ); + virtual void setElement( const char *field , const BSONElement& e ); + virtual void setObject( const char *field , const BSONObj& obj , bool readOnly); + virtual void setFunction( const char *field , const char * code ); +// virtual void setThis( const BSONObj * obj ); + + virtual void rename( const char * from , const char * to ); + + virtual ScriptingFunction _createFunction( const char * code ); + Local< v8::Function > __createFunction( const char * code ); + virtual int invoke( ScriptingFunction func , const BSONObj* args, const BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool readOnlyArgs = false, bool readOnlyRecv = false ); + virtual bool exec( const StringData& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ); + virtual string getError() { return _error; } + virtual bool hasOutOfMemoryException(); + + virtual void injectNative( const char *field, NativeFunction func, void* data = 0 ); + void injectNative( const char *field, NativeFunction func, Handle<v8::Object>& obj, void* data = 0 ); + void injectV8Function( const char *field, v8Function func ); + void injectV8Function( const char *field, v8Function func, Handle<v8::Object>& obj ); + void injectV8Function( const char *field, v8Function func, Handle<v8::Template>& t ); + Handle<v8::FunctionTemplate> createV8Function( v8Function func ); + + void gc(); + + Handle< Context > context() const { return _context; } + + v8::Local<v8::Object> mongoToV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false ); + v8::Handle<v8::Object> mongoToLZV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false ); + mongo::BSONObj v8ToMongo( v8::Handle<v8::Object> o , int depth = 0 ); + + void v8ToMongoElement( BSONObjBuilder & b , const string sname , v8::Handle<v8::Value> value , int depth = 0, BSONObj* originalParent=0 ); + v8::Handle<v8::Value> mongoToV8Element( const BSONElement &f, bool readOnly = false ); + virtual void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ); + + v8::Function * getNamedCons( const char * name ); + v8::Function * getObjectIdCons(); + Local< v8::Value > newId( const OID &id ); + + Persistent<v8::Object> wrapBSONObject(Local<v8::Object> obj, BSONObj* data); + Persistent<v8::Object> wrapArrayObject(Local<v8::Object> obj, char* data); + + v8::Handle<v8::String> getV8Str(string str); +// inline v8::Handle<v8::String> getV8Str(string str) { return v8::String::New(str.c_str()); } + inline v8::Handle<v8::String> getLocalV8Str(string str) { return v8::String::New(str.c_str()); } + + v8::Isolate* getIsolate() { return _isolate; } + Persistent<Context> getContext() { return _context; } + + // call with v8 mutex: + void enableV8Interrupt(); + void disableV8Interrupt(); + bool pauseV8Interrupt(); + bool resumeV8Interrupt(); + + Handle<v8::String> V8STR_CONN; + Handle<v8::String> V8STR_ID; + Handle<v8::String> V8STR_LENGTH; + Handle<v8::String> V8STR_LEN; + Handle<v8::String> V8STR_TYPE; + Handle<v8::String> V8STR_ISOBJECTID; + Handle<v8::String> V8STR_NATIVE_FUNC; + Handle<v8::String> V8STR_NATIVE_DATA; + Handle<v8::String> V8STR_V8_FUNC; + Handle<v8::String> V8STR_RETURN; + Handle<v8::String> V8STR_ARGS; + Handle<v8::String> V8STR_T; + Handle<v8::String> V8STR_I; + Handle<v8::String> V8STR_EMPTY; + Handle<v8::String> V8STR_MINKEY; + Handle<v8::String> V8STR_MAXKEY; + Handle<v8::String> V8STR_NUMBERLONG; + Handle<v8::String> V8STR_NUMBERINT; + Handle<v8::String> V8STR_DBPTR; + Handle<v8::String> V8STR_BINDATA; + Handle<v8::String> V8STR_WRAPPER; + Handle<v8::String> V8STR_RO; + Handle<v8::String> V8STR_MODIFIED; + Handle<v8::String> V8STR_FULLNAME; + + private: + void _startCall(); + + static Handle< Value > nativeCallback( V8Scope* scope, const Arguments &args ); + static v8::Handle< v8::Value > v8Callback( const v8::Arguments &args ); + static Handle< Value > load( V8Scope* scope, const Arguments &args ); + static Handle< Value > Print(V8Scope* scope, const v8::Arguments& args); + static Handle< Value > Version(V8Scope* scope, const v8::Arguments& args); + static Handle< Value > GCV8(V8Scope* scope, const v8::Arguments& args); + + + V8ScriptEngine * _engine; + + Persistent<Context> _context; + Persistent<v8::Object> _global; + + string _error; + vector< Persistent<Value> > _funcs; + v8::Persistent<v8::Object> _emptyObj; + + v8::Persistent<v8::Function> _wrapper; + + enum ConnectState { NOT , LOCAL , EXTERNAL }; + ConnectState _connectState; + + std::map <string, v8::Persistent <v8::String> > _strCache; + + Persistent<v8::ObjectTemplate> lzObjectTemplate; + Persistent<v8::ObjectTemplate> roObjectTemplate; + Persistent<v8::ObjectTemplate> lzArrayTemplate; + Persistent<v8::ObjectTemplate> internalFieldObjects; + v8::Isolate* _isolate; + }; + + class V8ScriptEngine : public ScriptEngine { + public: + V8ScriptEngine(); + virtual ~V8ScriptEngine(); + + virtual Scope * createScope() { return new V8Scope( this ); } + + virtual void runTest() {} + + bool utf8Ok() const { return true; } + + class V8UnlockForClient : public Unlocker { + V8Unlock u_; + }; + + virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new V8UnlockForClient ); } + + virtual void interrupt( unsigned opSpec ); + virtual void interruptAll(); + + private: + friend class V8Scope; + }; + + class ExternalString : public v8::String::ExternalAsciiStringResource { + public: + ExternalString(std::string str) : _data(str) { + } + + ~ExternalString() { + } + + const char* data () const { return _data.c_str(); } + size_t length () const { return _data.length(); } + private: +// string _str; +// const char* _data; + std::string _data; +// size_t _len; + }; + + extern ScriptEngine * globalScriptEngine; + +} diff --git a/src/mongo/scripting/sm_db.cpp b/src/mongo/scripting/sm_db.cpp new file mode 100644 index 00000000000..ea8780fa7c0 --- /dev/null +++ b/src/mongo/scripting/sm_db.cpp @@ -0,0 +1,1284 @@ +// sm_db.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// hacked in right now from engine_spidermonkey.cpp + +#include "../client/syncclusterconnection.h" +#include "../util/base64.h" +#include "../util/text.h" +#include "../util/hex.h" + +#if( BOOST_VERSION >= 104200 ) +//#include <boost/uuid/uuid.hpp> +#define HAVE_UUID 1 +#else +; +#endif + +namespace mongo { + + bool haveLocalShardingInfo( const string& ns ); + + // ------------ some defs needed --------------- + + JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ); + + // ------------ utils ------------------ + + + bool isSpecialName( const string& name ) { + static set<string> names; + if ( names.size() == 0 ) { + names.insert( "tojson" ); + names.insert( "toJson" ); + names.insert( "toString" ); + } + + if ( name.length() == 0 ) + return false; + + if ( name[0] == '_' ) + return true; + + return names.count( name ) > 0; + } + + + // ------ cursor ------ + + class CursorHolder { + public: + CursorHolder( auto_ptr< DBClientCursor > &cursor, const shared_ptr< DBClientWithCommands > &connection ) : + connection_( connection ), + cursor_( cursor ) { + assert( cursor_.get() ); + } + DBClientCursor *get() const { return cursor_.get(); } + private: + shared_ptr< DBClientWithCommands > connection_; + auto_ptr< DBClientCursor > cursor_; + }; + + DBClientCursor *getCursor( JSContext *cx, JSObject *obj ) { + CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj ); + uassert( 10235 , "no cursor!" , holder ); + return holder->get(); + } + + JSBool internal_cursor_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + uassert( 10236 , "no args to internal_cursor_constructor" , argc == 0 ); + assert( JS_SetPrivate( cx , obj , 0 ) ); // just for safety + return JS_TRUE; + } + + void internal_cursor_finalize( JSContext * cx , JSObject * obj ) { + CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj ); + if ( holder ) { + delete holder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSBool internal_cursor_hasNext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + DBClientCursor *cursor = getCursor( cx, obj ); + try { + *rval = cursor->more() ? JSVAL_TRUE : JSVAL_FALSE; + } + catch ( std::exception& e ) { + JS_ReportError( cx , e.what() ); + return JS_FALSE; + } + return JS_TRUE; + } + + JSBool internal_cursor_objsLeftInBatch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + DBClientCursor *cursor = getCursor( cx, obj ); + Convertor c(cx); + *rval = c.toval((double) cursor->objsLeftInBatch() ); + return JS_TRUE; + } + + JSBool internal_cursor_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + DBClientCursor *cursor = getCursor( cx, obj ); + + BSONObj n; + + try { + if ( ! cursor->more() ) { + JS_ReportError( cx , "cursor at the end" ); + return JS_FALSE; + } + + n = cursor->next(); + } + catch ( std::exception& e ) { + JS_ReportError( cx , e.what() ); + return JS_FALSE; + } + + Convertor c(cx); + *rval = c.toval( &n ); + return JS_TRUE; + } + + JSFunctionSpec internal_cursor_functions[] = { + { "hasNext" , internal_cursor_hasNext , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "objsLeftInBatch" , internal_cursor_objsLeftInBatch , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "next" , internal_cursor_next , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + JSClass internal_cursor_class = { + "InternalCursor" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, internal_cursor_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + // ------ mongo stuff ------ + + JSBool mongo_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + uassert( 10237 , "mongo_constructor not implemented yet" , 0 ); + throw -1; + } + + JSBool mongo_local_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + + shared_ptr< DBClientWithCommands > client( createDirectClient() ); + assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( client ) ) ) ); + + jsval host = c.toval( "EMBEDDED" ); + assert( JS_SetProperty( cx , obj , "host" , &host ) ); + + return JS_TRUE; + } + + JSBool mongo_external_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + + smuassert( cx , "0 or 1 args to Mongo" , argc <= 1 ); + + string host = "127.0.0.1"; + if ( argc > 0 ) + host = c.toString( argv[0] ); + + string errmsg; + + ConnectionString cs = ConnectionString::parse( host , errmsg ); + if ( ! cs.isValid() ) { + JS_ReportError( cx , errmsg.c_str() ); + return JS_FALSE; + } + + shared_ptr< DBClientWithCommands > conn( cs.connect( errmsg ) ); + if ( ! conn ) { + JS_ReportError( cx , errmsg.c_str() ); + return JS_FALSE; + } + + try{ + ScriptEngine::runConnectCallback( *conn ); + } + catch( std::exception& e ){ + // Can happen if connection goes down while we're starting up here + // Catch so that we don't get a hard-to-trace segfault from SM + JS_ReportError( cx, ((string)( str::stream() << "Error during mongo startup." << causedBy( e ) )).c_str() ); + return JS_FALSE; + } + + assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( conn ) ) ) ); + jsval host_val = c.toval( host.c_str() ); + assert( JS_SetProperty( cx , obj , "host" , &host_val ) ); + return JS_TRUE; + + } + + DBClientWithCommands *getConnection( JSContext *cx, JSObject *obj ) { + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + uassert( 10239 , "no connection!" , connHolder && connHolder->get() ); + return connHolder->get(); + } + + void mongo_finalize( JSContext * cx , JSObject * obj ) { + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + if ( connHolder ) { + delete connHolder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSClass mongo_class = { + "Mongo" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, mongo_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool mongo_auth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + smuassert( cx , "mongo_auth needs 3 args" , argc == 3 ); + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + smuassert( cx , "no connection!" , connHolder && connHolder->get() ); + DBClientWithCommands *conn = connHolder->get(); + + Convertor c( cx ); + + string db = c.toString( argv[0] ); + string username = c.toString( argv[1] ); + string password = c.toString( argv[2] ); + string errmsg = ""; + + try { + if (conn->auth(db, username, password, errmsg)) { + return JS_TRUE; + } + JS_ReportError( cx, errmsg.c_str() ); + } + catch ( ... ) { + JS_ReportError( cx , "error doing query: unknown" ); + } + return JS_FALSE; + } + + JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + smuassert( cx , "mongo_find needs 7 args" , argc == 7 ); + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + smuassert( cx , "no connection!" , connHolder && connHolder->get() ); + DBClientWithCommands *conn = connHolder->get(); + + Convertor c( cx ); + + string ns = c.toString( argv[0] ); + + BSONObj q = c.toObject( argv[1] ); + BSONObj f = c.toObject( argv[2] ); + + int nToReturn = (int) c.toNumber( argv[3] ); + int nToSkip = (int) c.toNumber( argv[4] ); + int batchSize = (int) c.toNumber( argv[5] ); + int options = (int)c.toNumber( argv[6] ); + + try { + + auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , options , batchSize ); + if ( ! cursor.get() ) { + log() << "query failed : " << ns << " " << q << " to: " << conn->toString() << endl; + JS_ReportError( cx , "error doing query: failed" ); + return JS_FALSE; + } + JSObject * mycursor = JS_NewObject( cx , &internal_cursor_class , 0 , 0 ); + CHECKNEWOBJECT( mycursor, cx, "internal_cursor_class" ); + assert( JS_SetPrivate( cx , mycursor , new CursorHolder( cursor, *connHolder ) ) ); + *rval = OBJECT_TO_JSVAL( mycursor ); + return JS_TRUE; + } + catch ( ... ) { + JS_ReportError( cx , "error doing query: unknown" ); + return JS_FALSE; + } + } + + JSBool mongo_update(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + smuassert( cx , "mongo_update needs at least 3 args" , argc >= 3 ); + smuassert( cx , "2nd param to update has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + smuassert( cx , "3rd param to update has to be an object" , JSVAL_IS_OBJECT( argv[2] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ) { + JS_ReportError( cx , "js db in read only mode - mongo_update" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10245 , "no connection!" , conn ); + + string ns = c.toString( argv[0] ); + + bool upsert = argc > 3 && c.toBoolean( argv[3] ); + bool multi = argc > 4 && c.toBoolean( argv[4] ); + + try { + conn->update( ns , c.toObject( argv[1] ) , c.toObject( argv[2] ) , upsert , multi ); + return JS_TRUE; + } + catch ( ... ) { + JS_ReportError( cx , "error doing update" ); + return JS_FALSE; + } + } + + JSBool mongo_insert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + smuassert( cx , "mongo_insert needs 2 args" , argc == 2 ); + smuassert( cx , "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ) { + JS_ReportError( cx , "js db in read only mode - mongo_insert" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10248 , "no connection!" , conn ); + + string ns = c.toString( argv[0] ); + + try { + JSObject * insertObj = JSVAL_TO_OBJECT( argv[1] ); + + if( JS_IsArrayObject( cx, insertObj ) ){ + vector<BSONObj> bos; + + jsuint len; + JSBool gotLen = JS_GetArrayLength( cx, insertObj, &len ); + smuassert( cx, "could not get length of array", gotLen ); + + for( jsuint i = 0; i < len; i++ ){ + + jsval el; + JSBool inserted = JS_GetElement( cx, insertObj, i, &el); + smuassert( cx, "could not find element in array object", inserted ); + + bos.push_back( c.toObject( el ) ); + } + + conn->insert( ns, bos ); + + return JS_TRUE; + } + else { + BSONObj o = c.toObject( argv[1] ); + // TODO: add _id + + conn->insert( ns , o ); + return JS_TRUE; + } + } + catch ( std::exception& e ) { + stringstream ss; + ss << "error doing insert:" << e.what(); + string s = ss.str(); + JS_ReportError( cx , s.c_str() ); + return JS_FALSE; + } + catch ( ... ) { + JS_ReportError( cx , "error doing insert" ); + return JS_FALSE; + } + } + + JSBool mongo_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + smuassert( cx , "mongo_remove needs 2 or 3 arguments" , argc == 2 || argc == 3 ); + smuassert( cx , "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ) { + JS_ReportError( cx , "js db in read only mode - mongo_remove" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10251 , "no connection!" , conn ); + + string ns = c.toString( argv[0] ); + BSONObj o = c.toObject( argv[1] ); + bool justOne = false; + if ( argc > 2 ) + justOne = c.toBoolean( argv[2] ); + + try { + conn->remove( ns , o , justOne ); + return JS_TRUE; + } + catch ( std::exception& e ) { + JS_ReportError( cx , e.what() ); + return JS_FALSE; + } + + catch ( ... ) { + JS_ReportError( cx , "error doing remove" ); + return JS_FALSE; + } + + } + + JSFunctionSpec mongo_functions[] = { + { "auth" , mongo_auth , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "find" , mongo_find , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "update" , mongo_update , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "insert" , mongo_insert , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "remove" , mongo_remove , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + // ------------- db_collection ------------- + + JSBool db_collection_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx , "db_collection_constructor wrong args" , argc == 4 ); + assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "_db" , &(argv[1]) ) ); + assert( JS_SetProperty( cx , obj , "_shortName" , &(argv[2]) ) ); + assert( JS_SetProperty( cx , obj , "_fullName" , &(argv[3]) ) ); + + Convertor c(cx); + if ( haveLocalShardingInfo( c.toString( argv[3] ) ) ) { + JS_ReportError( cx , "can't use sharded collection from db.eval" ); + return JS_FALSE; + } + + return JS_TRUE; + } + + JSBool db_collection_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + Convertor c( cx ); + string collname = c.toString( id ); + + if ( isSpecialName( collname ) ) + return JS_TRUE; + + if ( obj == c.getGlobalPrototype( "DBCollection" ) ) + return JS_TRUE; + + JSObject * proto = JS_GetPrototype( cx , obj ); + if ( c.hasProperty( obj , collname.c_str() ) || ( proto && c.hasProperty( proto , collname.c_str() ) ) ) + return JS_TRUE; + + string name = c.toString( c.getProperty( obj , "_shortName" ) ); + name += "."; + name += collname; + + jsval db = c.getProperty( obj , "_db" ); + if ( ! JSVAL_IS_OBJECT( db ) ) + return JS_TRUE; + + JSObject * coll = doCreateCollection( cx , JSVAL_TO_OBJECT( db ) , name ); + if ( ! coll ) + return JS_FALSE; + c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); + *objp = obj; + return JS_TRUE; + } + + JSClass db_collection_class = { + "DBCollection" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&db_collection_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ) { + Convertor c(cx); + + assert( c.hasProperty( db , "_mongo" ) ); + assert( c.hasProperty( db , "_name" ) ); + + JSObject * coll = JS_NewObject( cx , &db_collection_class , 0 , 0 ); + CHECKNEWOBJECT( coll, cx, "doCreateCollection" ); + c.setProperty( coll , "_mongo" , c.getProperty( db , "_mongo" ) ); + c.setProperty( coll , "_db" , OBJECT_TO_JSVAL( db ) ); + c.setProperty( coll , "_shortName" , c.toval( shortName.c_str() ) ); + + string name = c.toString( c.getProperty( db , "_name" ) ); + name += "." + shortName; + c.setProperty( coll , "_fullName" , c.toval( name.c_str() ) ); + + if ( haveLocalShardingInfo( name ) ) { + JS_ReportError( cx , "can't use sharded collection from db.eval" ); + return 0; + } + + return coll; + } + + // -------------- DB --------------- + + + JSBool db_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx, "wrong number of arguments to DB" , argc == 2 ); + assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "_name" , &(argv[1]) ) ); + + return JS_TRUE; + } + + JSBool db_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + Convertor c( cx ); + + if ( obj == c.getGlobalPrototype( "DB" ) ) + return JS_TRUE; + + string collname = c.toString( id ); + + if ( isSpecialName( collname ) ) + return JS_TRUE; + + JSObject * proto = JS_GetPrototype( cx , obj ); + if ( proto && c.hasProperty( proto , collname.c_str() ) ) + return JS_TRUE; + + JSObject * coll = doCreateCollection( cx , obj , collname ); + if ( ! coll ) + return JS_FALSE; + c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); + + *objp = obj; + return JS_TRUE; + } + + JSClass db_class = { + "DB" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&db_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + // -------------- object id ------------- + + JSBool object_id_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + + OID oid; + if ( argc == 0 ) { + oid.init(); + } + else { + smuassert( cx , "object_id_constructor can't take more than 1 param" , argc == 1 ); + string s = c.toString( argv[0] ); + + try { + Scope::validateObjectIdString( s ); + } + catch ( const MsgAssertionException &m ) { + static string error = m.toString(); + JS_ReportError( cx, error.c_str() ); + return JS_FALSE; + } + oid.init( s ); + } + + if ( ! JS_InstanceOf( cx , obj , &object_id_class , 0 ) ) { + obj = JS_NewObject( cx , &object_id_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "object_id_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + jsval v = c.toval( oid.str().c_str() ); + assert( JS_SetProperty( cx , obj , "str" , &v ) ); + + return JS_TRUE; + } + + JSClass object_id_class = { + "ObjectId" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // dbpointer + + JSBool dbpointer_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( ! JS_InstanceOf( cx , obj , &dbpointer_class , 0 ) ) { + obj = JS_NewObject( cx , &dbpointer_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "dbpointer_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + if ( argc == 2 ) { + + if ( ! JSVAL_IS_OID( argv[1] ) ) { + JS_ReportError( cx , "2nd arg to DBPointer needs to be oid" ); + return JS_FALSE; + } + + assert( JS_SetProperty( cx , obj , "ns" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "id" , &(argv[1]) ) ); + return JS_TRUE; + } + else { + JS_ReportError( cx , "DBPointer needs 2 arguments" ); + return JS_FALSE; + } + } + + JSClass dbpointer_class = { + "DBPointer" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec dbpointer_functions[] = { + { 0 } + }; + + + JSBool dbref_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( ! JS_InstanceOf( cx , obj , &dbref_class , 0 ) ) { + obj = JS_NewObject( cx , &dbref_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "dbref_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + if ( argc == 2 ) { + JSObject * o = JS_NewObject( cx , NULL , NULL, NULL ); + CHECKNEWOBJECT( o, cx, "dbref_constructor" ); + assert( JS_SetProperty( cx, o , "$ref" , &argv[ 0 ] ) ); + assert( JS_SetProperty( cx, o , "$id" , &argv[ 1 ] ) ); + BSONObj bo = c.toObject( o ); + assert( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( bo.getOwned() ) ) ) ); + return JS_TRUE; + } + else { + JS_ReportError( cx , "DBRef needs 2 arguments" ); + assert( JS_SetPrivate( cx , obj , (void*)(new BSONHolder( BSONObj().getOwned() ) ) ) ); + return JS_FALSE; + } + } + + JSClass dbref_class = bson_class; // name will be fixed later + + // UUID ************************** + +#if 0 + JSBool uuid_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + + if( argc == 0 ) { +#if defined(HAVE_UUID) + //uuids::uuid +#else +#endif + JS_ReportError( cx , "UUID needs 1 argument -- UUID(hexstr)" ); + return JS_FALSE; + } + else if ( argc == 1 ) { + + string encoded = c.toString( argv[ 0 ] ); + if( encoded.size() != 32 ) { + JS_ReportError( cx, "expect 32 char hex string to UUID()" ); + return JS_FALSE; + } + + char buf[16]; + for( int i = 0; i < 16; i++ ) { + buf[i] = fromHex(encoded.c_str() + i * 2); + } + +zzz + + assert( JS_SetPrivate( cx, obj, new BinDataHolder( buf, 16 ) ) ); + c.setProperty( obj, "len", c.toval( (double)16 ) ); + c.setProperty( obj, "type", c.toval( (double)3 ) ); + + return JS_TRUE; + } + else { + JS_ReportError( cx , "UUID needs 1 argument -- UUID(hexstr)" ); + return JS_FALSE; + } + } + + JSBool uuid_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + void *holder = JS_GetPrivate( cx, obj ); + assert( holder ); + const char *data = ( ( BinDataHolder* )( holder ) )->c_; + stringstream ss; + ss << "UUID(\"" << toHex(data, 16); + ss << "\")"; + string ret = ss.str(); + return *rval = c.toval( ret.c_str() ); + } + + void uuid_finalize( JSContext * cx , JSObject * obj ) { + Convertor c(cx); + void *holder = JS_GetPrivate( cx, obj ); + if ( holder ) { + delete ( BinDataHolder* )holder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSClass uuid_class = { + "UUID" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, uuid_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec uuid_functions[] = { + { "toString" , uuid_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + +#endif + + // BinData ************************** + + JSBool bindata_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + Convertor c( cx ); + if ( ! JS_InstanceOf( cx , obj , &bindata_class , 0 ) ) { + obj = JS_NewObject( cx , &bindata_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "bindata_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + if ( argc == 2 ) { + + int type = (int)c.toNumber( argv[ 0 ] ); + if( type < 0 || type > 255 ) { + JS_ReportError( cx , "invalid BinData subtype -- range is 0..255 see bsonspec.org" ); + return JS_FALSE; + } + string encoded = c.toString( argv[ 1 ] ); + string decoded; + try { + decoded = base64::decode( encoded ); + } + catch(...) { + JS_ReportError(cx, "BinData could not decode base64 parameter"); + return JS_FALSE; + } + + assert( JS_SetPrivate( cx, obj, new BinDataHolder( decoded.data(), decoded.length() ) ) ); + c.setProperty( obj, "len", c.toval( (double)decoded.length() ) ); + c.setProperty( obj, "type", c.toval( (double)type ) ); + + return JS_TRUE; + } + else { + JS_ReportError( cx , "BinData needs 2 arguments -- BinData(subtype,data)" ); + return JS_FALSE; + } + } + + JSBool bindata_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + int type = (int)c.getNumber( obj , "type" ); + int len = (int)c.getNumber( obj, "len" ); + void *holder = JS_GetPrivate( cx, obj ); + assert( holder ); + const char *data = ( ( BinDataHolder* )( holder ) )->c_; + stringstream ss; + ss << "BinData(" << type << ",\""; + base64::encode( ss, (const char *)data, len ); + ss << "\")"; + string ret = ss.str(); + return *rval = c.toval( ret.c_str() ); + } + + JSBool bindataBase64(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + int len = (int)c.getNumber( obj, "len" ); + void *holder = JS_GetPrivate( cx, obj ); + assert( holder ); + const char *data = ( ( BinDataHolder* )( holder ) )->c_; + stringstream ss; + base64::encode( ss, (const char *)data, len ); + string ret = ss.str(); + return *rval = c.toval( ret.c_str() ); + } + + JSBool bindataAsHex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + int len = (int)c.getNumber( obj, "len" ); + void *holder = JS_GetPrivate( cx, obj ); + assert( holder ); + const char *data = ( ( BinDataHolder* )( holder ) )->c_; + stringstream ss; + ss.setf (ios_base::hex , ios_base::basefield); + ss.fill ('0'); + ss.setf (ios_base::right , ios_base::adjustfield); + for( int i = 0; i < len; i++ ) { + unsigned v = (unsigned char) data[i]; + ss << setw(2) << v; + } + string ret = ss.str(); + return *rval = c.toval( ret.c_str() ); + } + + void bindata_finalize( JSContext * cx , JSObject * obj ) { + Convertor c(cx); + void *holder = JS_GetPrivate( cx, obj ); + if ( holder ) { + delete ( BinDataHolder* )holder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSClass bindata_class = { + "BinData" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, bindata_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec bindata_functions[] = { + { "toString" , bindata_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "hex", bindataAsHex, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "base64", bindataBase64, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + // Map + + bool specialMapString( const string& s ) { + return s == "put" || s == "get" || s == "_get" || s == "values" || s == "_data" || s == "constructor" ; + } + + JSBool map_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + if ( argc > 0 ) { + JS_ReportError( cx , "Map takes no arguments" ); + return JS_FALSE; + } + + JSObject * array = JS_NewObject( cx , 0 , 0 , 0 ); + CHECKNEWOBJECT( array, cx, "map_constructor" ); + + jsval a = OBJECT_TO_JSVAL( array ); + JS_SetProperty( cx , obj , "_data" , &a ); + + return JS_TRUE; + } + + JSBool map_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp ) { + Convertor c(cx); + if ( specialMapString( c.toString( idval ) ) ) + return JS_TRUE; + + log() << "illegal prop access: " << c.toString( idval ) << endl; + JS_ReportError( cx , "can't use array access with Map" ); + return JS_FALSE; + } + + JSClass map_class = { + "Map" , JSCLASS_HAS_PRIVATE , + map_prop, JS_PropertyStub, map_prop, map_prop, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec map_functions[] = { + { 0 } + }; + + + // ----- + + JSClass timestamp_class = { + "Timestamp" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool timestamp_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx , "Timestamp needs 0 or 2 args" , argc == 0 || argc == 2 ); + + if ( ! JS_InstanceOf( cx , obj , ×tamp_class , 0 ) ) { + obj = JS_NewObject( cx , ×tamp_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "timestamp_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + Convertor c( cx ); + if ( argc == 0 ) { + c.setProperty( obj, "t", c.toval( 0.0 ) ); + c.setProperty( obj, "i", c.toval( 0.0 ) ); + } + else { + c.setProperty( obj, "t", argv[ 0 ] ); + c.setProperty( obj, "i", argv[ 1 ] ); + } + + return JS_TRUE; + } + + JSClass numberlong_class = { + "NumberLong" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool numberlong_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx , "NumberLong needs 0 or 1 args" , argc == 0 || argc == 1 ); + + if ( ! JS_InstanceOf( cx , obj , &numberlong_class , 0 ) ) { + obj = JS_NewObject( cx , &numberlong_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "numberlong_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + Convertor c( cx ); + if ( argc == 0 ) { + c.setProperty( obj, "floatApprox", c.toval( 0.0 ) ); + } + else if ( JSVAL_IS_NUMBER( argv[ 0 ] ) ) { + c.setProperty( obj, "floatApprox", argv[ 0 ] ); + } + else { + string num = c.toString( argv[ 0 ] ); + //PRINT(num); + const char *numStr = num.c_str(); + long long n; + try { + n = parseLL( numStr ); + //PRINT(n); + } + catch ( const AssertionException & ) { + smuassert( cx , "could not convert string to long long" , false ); + } + c.makeLongObj( n, obj ); + } + + return JS_TRUE; + } + + JSBool numberlong_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + return *rval = c.toval( double( c.toNumberLongUnsafe( obj ) ) ); + } + + JSBool numberlong_tonumber(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + return numberlong_valueof( cx, obj, argc, argv, rval ); + } + + JSBool numberlong_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + stringstream ss; + long long val = c.toNumberLongUnsafe( obj ); + const long long limit = 2LL << 30; + + if ( val <= -limit || limit <= val ) + ss << "NumberLong(\"" << val << "\")"; + else + ss << "NumberLong(" << val << ")"; + + string ret = ss.str(); + return *rval = c.toval( ret.c_str() ); + } + + JSFunctionSpec numberlong_functions[] = { + { "valueOf" , numberlong_valueof , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toNumber" , numberlong_tonumber , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toString" , numberlong_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + JSClass numberint_class = { + "NumberInt" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool numberint_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx , "NumberInt needs 0 or 1 args" , argc == 0 || argc == 1 ); + + if ( ! JS_InstanceOf( cx , obj , &numberint_class , 0 ) ) { + obj = JS_NewObject( cx , &numberint_class , 0 , 0 ); + CHECKNEWOBJECT( obj, cx, "numberint_constructor" ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + Convertor c( cx ); + if ( argc == 0 ) { + c.setProperty( obj, "floatApprox", c.toval( 0.0 ) ); + } + else if ( JSVAL_IS_NUMBER( argv[ 0 ] ) ) { + c.setProperty( obj, "floatApprox", argv[ 0 ] ); + } + else { + string num = c.toString( argv[ 0 ] ); + //PRINT(num); + const char *numStr = num.c_str(); + int n; + try { + n = (int) parseLL( numStr ); + //PRINT(n); + } + catch ( const AssertionException & ) { + smuassert( cx , "could not convert string to integer" , false ); + } + c.makeIntObj( n, obj ); + } + + return JS_TRUE; + } + + JSBool numberint_valueof(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + return *rval = c.toval( double( c.toNumberInt( obj ) ) ); + } + + JSBool numberint_tonumber(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + return numberint_valueof( cx, obj, argc, argv, rval ); + } + + JSBool numberint_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Convertor c(cx); + int val = c.toNumberInt( obj ); + string ret = str::stream() << "NumberInt(" << val << ")"; + return *rval = c.toval( ret.c_str() ); + } + + JSFunctionSpec numberint_functions[] = { + { "valueOf" , numberint_valueof , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toNumber" , numberint_tonumber , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "toString" , numberint_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + JSClass minkey_class = { + "MinKey" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSClass maxkey_class = { + "MaxKey" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // dbquery + + JSBool dbquery_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { + smuassert( cx , "DDQuery needs at least 4 args" , argc >= 4 ); + + Convertor c(cx); + c.setProperty( obj , "_mongo" , argv[0] ); + c.setProperty( obj , "_db" , argv[1] ); + c.setProperty( obj , "_collection" , argv[2] ); + c.setProperty( obj , "_ns" , argv[3] ); + + if ( argc > 4 && JSVAL_IS_OBJECT( argv[4] ) ) + c.setProperty( obj , "_query" , argv[4] ); + else { + JSObject * temp = JS_NewObject( cx , 0 , 0 , 0 ); + CHECKNEWOBJECT( temp, cx, "dbquery_constructor" ); + c.setProperty( obj , "_query" , OBJECT_TO_JSVAL( temp ) ); + } + + if ( argc > 5 && JSVAL_IS_OBJECT( argv[5] ) ) + c.setProperty( obj , "_fields" , argv[5] ); + else + c.setProperty( obj , "_fields" , JSVAL_NULL ); + + + if ( argc > 6 && JSVAL_IS_NUMBER( argv[6] ) ) + c.setProperty( obj , "_limit" , argv[6] ); + else + c.setProperty( obj , "_limit" , JSVAL_ZERO ); + + if ( argc > 7 && JSVAL_IS_NUMBER( argv[7] ) ) + c.setProperty( obj , "_skip" , argv[7] ); + else + c.setProperty( obj , "_skip" , JSVAL_ZERO ); + + if ( argc > 8 && JSVAL_IS_NUMBER( argv[8] ) ) + c.setProperty( obj , "_batchSize" , argv[8] ); + else + c.setProperty( obj , "_batchSize" , JSVAL_ZERO ); + + if ( argc > 9 && JSVAL_IS_NUMBER( argv[9] ) ) + c.setProperty( obj , "_options" , argv[9] ); + else + c.setProperty( obj , "_options" , JSVAL_ZERO ); + + + c.setProperty( obj , "_cursor" , JSVAL_NULL ); + c.setProperty( obj , "_numReturned" , JSVAL_ZERO ); + c.setProperty( obj , "_special" , JSVAL_FALSE ); + + return JS_TRUE; + } + + JSBool dbquery_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + if ( ! JSVAL_IS_NUMBER( id ) ) + return JS_TRUE; + + jsval val = JSVAL_VOID; + assert( JS_CallFunctionName( cx , obj , "arrayAccess" , 1 , &id , &val ) ); + Convertor c(cx); + c.setProperty( obj , c.toString( id ).c_str() , val ); + *objp = obj; + return JS_TRUE; + } + + JSClass dbquery_class = { + "DBQuery" , JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&dbquery_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // ---- other stuff ---- + + void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ) { + + assert( JS_InitClass( cx , global , 0 , &mongo_class , local ? mongo_local_constructor : mongo_external_constructor , 0 , 0 , mongo_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &object_id_class , object_id_constructor , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &db_class , db_constructor , 2 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &db_collection_class , db_collection_constructor , 4 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &internal_cursor_class , internal_cursor_constructor , 0 , 0 , internal_cursor_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &dbquery_class , dbquery_constructor , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &dbpointer_class , dbpointer_constructor , 0 , 0 , dbpointer_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &bindata_class , bindata_constructor , 0 , 0 , bindata_functions , 0 , 0 ) ); +// assert( JS_InitClass( cx , global , 0 , &uuid_class , uuid_constructor , 0 , 0 , uuid_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , ×tamp_class , timestamp_constructor , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &numberlong_class , numberlong_constructor , 0 , 0 , numberlong_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &numberint_class , numberint_constructor , 0 , 0 , numberint_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &minkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &maxkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &map_class , map_constructor , 0 , 0 , map_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &bson_ro_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &bson_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); + + static const char *dbrefName = "DBRef"; + dbref_class.name = dbrefName; + assert( JS_InitClass( cx , global , 0 , &dbref_class , dbref_constructor , 2 , 0 , bson_functions , 0 , 0 ) ); + + scope->execCoreFiles(); + } + + bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ) { + + if ( JS_InstanceOf( c->_context , o , &object_id_class , 0 ) ) { + OID oid; + oid.init( c->getString( o , "str" ) ); + b.append( name , oid ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &minkey_class , 0 ) ) { + b.appendMinKey( name ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &maxkey_class , 0 ) ) { + b.appendMaxKey( name ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , ×tamp_class , 0 ) ) { + b.appendTimestamp( name , (unsigned long long)c->getNumber( o , "t" ) , (unsigned int )c->getNumber( o , "i" ) ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &numberlong_class , 0 ) ) { + b.append( name , c->toNumberLongUnsafe( o ) ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &numberint_class , 0 ) ) { + b.append( name , c->toNumberInt( o ) ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &dbpointer_class , 0 ) ) { + b.appendDBRef( name , c->getString( o , "ns" ) , c->toOID( c->getProperty( o , "id" ) ) ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &bindata_class , 0 ) ) { + void *holder = JS_GetPrivate( c->_context , o ); + const char *data = ( ( BinDataHolder * )( holder ) )->c_; + b.appendBinData( name , + (int)(c->getNumber( o , "len" )) , (BinDataType)((char)(c->getNumber( o , "type" ) ) ) , + data + ); + return true; + } + +#if defined( SM16 ) || defined( MOZJS ) +#warning dates do not work in your version of spider monkey + { + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + if ( d ) { + b.appendDate( name , Date_t(d) ); + return true; + } + } +#elif defined( XULRUNNER ) + if ( JS_InstanceOf( c->_context , o, globalSMEngine->_dateClass , 0 ) ) { + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + b.appendDate( name , Date_t(d) ); + return true; + } +#else + if ( JS_InstanceOf( c->_context , o, &js_DateClass , 0 ) ) { + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + long long d2 = (long long)d; + b.appendDate( name , Date_t((unsigned long long)d2) ); + return true; + } +#endif + + + if ( JS_InstanceOf( c->_context , o , &dbquery_class , 0 ) || + JS_InstanceOf( c->_context , o , &mongo_class , 0 ) || + JS_InstanceOf( c->_context , o , &db_collection_class , 0 ) ) { + b.append( name , c->toString( val ) ); + return true; + } + +#if defined( XULRUNNER ) + if ( JS_InstanceOf( c->_context , o , globalSMEngine->_regexClass , 0 ) ) { + c->appendRegex( b , name , c->toString( val ) ); + return true; + } +#elif defined( SM18 ) + if ( JS_InstanceOf( c->_context , o , &js_RegExpClass , 0 ) ) { + c->appendRegex( b , name , c->toString( val ) ); + return true; + } +#endif + + return false; + } + + bool isDate( JSContext * cx , JSObject * o ) { +#if defined( SM16 ) || defined( MOZJS ) || defined( XULRUNNER ) + return js_DateGetMsecSinceEpoch( cx , o ) != 0; +#else + return JS_InstanceOf( cx , o, &js_DateClass, 0 ); +#endif + } + +} diff --git a/src/mongo/scripting/utils.cpp b/src/mongo/scripting/utils.cpp new file mode 100644 index 00000000000..612b173fdf8 --- /dev/null +++ b/src/mongo/scripting/utils.cpp @@ -0,0 +1,77 @@ +// utils.cpp +/* + * Copyright (C) 2010 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "pch.h" +#include "engine.h" +#include "../util/md5.hpp" +#include "../util/version.h" + +namespace mongo { + + void installBenchmarkSystem( Scope& scope ); + + BSONObj jsmd5( const BSONObj &a, void* data ) { + uassert( 10261 , "js md5 needs a string" , a.firstElement().type() == String ); + const char * s = a.firstElement().valuestrsafe(); + + md5digest d; + md5_state_t st; + md5_init(&st); + md5_append( &st , (const md5_byte_t*)s , strlen( s ) ); + md5_finish(&st, d); + + return BSON( "" << digestToString( d ) ); + } + + BSONObj JSVersion( const BSONObj& args, void* data ) { + cout << "version: " << versionString << endl; + if ( strstr( versionString , "+" ) ) + printGitVersion(); + return BSONObj(); + } + + + BSONObj JSSleep(const mongo::BSONObj &args, void* data) { + assert( args.nFields() == 1 ); + assert( args.firstElement().isNumber() ); + int ms = int( args.firstElement().number() ); + { + auto_ptr< ScriptEngine::Unlocker > u = globalScriptEngine->newThreadUnlocker(); + sleepmillis( ms ); + } + + BSONObjBuilder b; + b.appendUndefined( "" ); + return b.obj(); + } + + // --------------------------------- + // ---- installer -------- + // --------------------------------- + + void installGlobalUtils( Scope& scope ) { + scope.injectNative( "hex_md5" , jsmd5 ); + scope.injectNative( "version" , JSVersion ); + scope.injectNative( "sleep" , JSSleep ); + + installBenchmarkSystem( scope ); + } + +} + + diff --git a/src/mongo/scripting/v8_db.cpp b/src/mongo/scripting/v8_db.cpp new file mode 100644 index 00000000000..de419b368d9 --- /dev/null +++ b/src/mongo/scripting/v8_db.cpp @@ -0,0 +1,1128 @@ +// v8_db.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(_WIN32) +/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with + our usage of boost */ +#include "boost/cstdint.hpp" +using namespace boost; +#define V8STDINT_H_ +#endif + +#include "v8_wrapper.h" +#include "v8_utils.h" +#include "engine_v8.h" +#include "v8_db.h" +#include "util/base64.h" +#include "util/text.h" +#include "../client/syncclusterconnection.h" +#include "../s/d_logic.h" +#include <iostream> + +using namespace std; +using namespace v8; + +namespace mongo { + +#define DDD(x) + + static v8::Handle<v8::Value> newInstance( v8::Function* f, const v8::Arguments& args ) { + // need to translate arguments into an array + int argc = args.Length(); + scoped_array< Handle<Value> > argv( new Handle<Value>[argc] ); + for (int i = 0; i < argc; ++i) { + argv[i] = args[i]; + } + return f->NewInstance(argc, argv.get()); + } + + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( V8Scope* scope, bool local ) { + v8::Handle<v8::FunctionTemplate> mongo; + if ( local ) { + mongo = scope->createV8Function(mongoConsLocal); + } + else { + mongo = scope->createV8Function(mongoConsExternal); + } + mongo->InstanceTemplate()->SetInternalFieldCount( 1 ); + v8::Handle<v8::Template> proto = mongo->PrototypeTemplate(); + scope->injectV8Function("find", mongoFind, proto); + scope->injectV8Function("insert", mongoInsert, proto); + scope->injectV8Function("remove", mongoRemove, proto); + scope->injectV8Function("update", mongoUpdate, proto); + scope->injectV8Function("auth", mongoAuth, proto); + + v8::Handle<FunctionTemplate> ic = scope->createV8Function(internalCursorCons); + ic->InstanceTemplate()->SetInternalFieldCount( 1 ); + v8::Handle<v8::Template> icproto = ic->PrototypeTemplate(); + scope->injectV8Function("next", internalCursorNext, icproto); + scope->injectV8Function("hasNext", internalCursorHasNext, icproto); + scope->injectV8Function("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto); + scope->injectV8Function("readOnly", internalCursorReadOnly, icproto); + proto->Set( scope->getV8Str( "internalCursor" ) , ic ); + + return mongo; + } + + v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> numberLong = scope->createV8Function(numberLongInit); + v8::Local<v8::Template> proto = numberLong->PrototypeTemplate(); + scope->injectV8Function("valueOf", numberLongValueOf, proto); + scope->injectV8Function("toNumber", numberLongToNumber, proto); + scope->injectV8Function("toString", numberLongToString, proto); + + return numberLong; + } + + v8::Handle<v8::FunctionTemplate> getNumberIntFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> numberInt = scope->createV8Function(numberIntInit); + v8::Local<v8::Template> proto = numberInt->PrototypeTemplate(); + scope->injectV8Function("valueOf", numberIntValueOf, proto); + scope->injectV8Function("toNumber", numberIntToNumber, proto); + scope->injectV8Function("toString", numberIntToString, proto); + + return numberInt; + } + + v8::Handle<v8::FunctionTemplate> getBinDataFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> binData = scope->createV8Function(binDataInit); + binData->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local<v8::Template> proto = binData->PrototypeTemplate(); + scope->injectV8Function("toString", binDataToString, proto); + scope->injectV8Function("base64", binDataToBase64, proto); + scope->injectV8Function("hex", binDataToHex, proto); + return binData; + } + + v8::Handle<v8::FunctionTemplate> getUUIDFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(uuidInit); + return templ; + } + + v8::Handle<v8::FunctionTemplate> getMD5FunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(md5Init); + return templ; + } + + v8::Handle<v8::FunctionTemplate> getHexDataFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> templ = scope->createV8Function(hexDataInit); + return templ; + } + + v8::Handle<v8::FunctionTemplate> getTimestampFunctionTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> ts = scope->createV8Function(dbTimestampInit); + ts->InstanceTemplate()->SetInternalFieldCount( 1 ); + return ts; + } + +// void installDBTypes( V8Scope* scope, Handle<ObjectTemplate>& global ) { +// v8::Handle<v8::FunctionTemplate> db = scope->createV8Function(dbInit); +// db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); +// global->Set(v8::String::New("DB") , db ); +// +// v8::Handle<v8::FunctionTemplate> dbCollection = scope->createV8Function(collectionInit); +// dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); +// global->Set(v8::String::New("DBCollection") , dbCollection ); +// +// +// v8::Handle<v8::FunctionTemplate> dbQuery = scope->createV8Function(dbQueryInit); +// dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); +// global->Set(v8::String::New("DBQuery") , dbQuery ); +// +// global->Set( v8::String::New("ObjectId") , newV8Function< objectIdInit >(scope) ); +// +// global->Set( v8::String::New("DBRef") , newV8Function< dbRefInit >(scope) ); +// +// global->Set( v8::String::New("DBPointer") , newV8Function< dbPointerInit >(scope) ); +// +// global->Set( v8::String::New("BinData") , getBinDataFunctionTemplate(scope) ); +// +// global->Set( v8::String::New("NumberLong") , getNumberLongFunctionTemplate(scope) ); +// +// global->Set( v8::String::New("Timestamp") , getTimestampFunctionTemplate(scope) ); +// } + + void installDBTypes( V8Scope* scope, v8::Handle<v8::Object>& global ) { + v8::Handle<v8::FunctionTemplate> db = scope->createV8Function(dbInit); + db->InstanceTemplate()->SetNamedPropertyHandler( collectionGetter, collectionSetter ); + global->Set(scope->getV8Str("DB") , db->GetFunction() ); + v8::Handle<v8::FunctionTemplate> dbCollection = scope->createV8Function(collectionInit); + dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionGetter, collectionSetter ); + global->Set(scope->getV8Str("DBCollection") , dbCollection->GetFunction() ); + + + v8::Handle<v8::FunctionTemplate> dbQuery = scope->createV8Function(dbQueryInit); + dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); + global->Set(scope->getV8Str("DBQuery") , dbQuery->GetFunction() ); + + scope->injectV8Function("ObjectId", objectIdInit, global); + scope->injectV8Function("DBRef", dbRefInit, global); + scope->injectV8Function("DBPointer", dbPointerInit, global); + + global->Set( scope->getV8Str("BinData") , getBinDataFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("UUID") , getUUIDFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("MD5") , getMD5FunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("HexData") , getHexDataFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("NumberLong") , getNumberLongFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("NumberInt") , getNumberIntFunctionTemplate(scope)->GetFunction() ); + global->Set( scope->getV8Str("Timestamp") , getTimestampFunctionTemplate(scope)->GetFunction() ); + + BSONObjBuilder b; + b.appendMaxKey( "" ); + b.appendMinKey( "" ); + BSONObj o = b.obj(); + BSONObjIterator i( o ); + global->Set( scope->getV8Str("MaxKey"), scope->mongoToV8Element( i.next() ) ); + global->Set( scope->getV8Str("MinKey"), scope->mongoToV8Element( i.next() ) ); + + global->Get( scope->getV8Str( "Object" ) )->ToObject()->Set( scope->getV8Str("bsonsize") , scope->createV8Function(bsonsize)->GetFunction() ); + } + + void destroyConnection( Persistent<Value> self, void* parameter) { + delete static_cast<DBClientBase*>(parameter); + self.Dispose(); + self.Clear(); + } + + Handle<Value> mongoConsExternal(V8Scope* scope, const Arguments& args) { + + char host[255]; + + if ( args.Length() > 0 && args[0]->IsString() ) { + assert( args[0]->ToString()->Utf8Length() < 250 ); + args[0]->ToString()->WriteAscii( host ); + } + else { + strcpy( host , "127.0.0.1" ); + } + + string errmsg; + ConnectionString cs = ConnectionString::parse( host , errmsg ); + if ( ! cs.isValid() ) + return v8::ThrowException( v8::String::New( errmsg.c_str() ) ); + + + DBClientWithCommands * conn; + { + V8Unlock ul; + conn = cs.connect( errmsg ); + } + if ( ! conn ) + return v8::ThrowException( v8::String::New( errmsg.c_str() ) ); + + Persistent<v8::Object> self = Persistent<v8::Object>::New( args.Holder() ); + self.MakeWeak( conn , destroyConnection ); + + { + V8Unlock ul; + ScriptEngine::runConnectCallback( *conn ); + } + + args.This()->SetInternalField( 0 , External::New( conn ) ); + args.This()->Set( scope->getV8Str( "slaveOk" ) , Boolean::New( false ) ); + args.This()->Set( scope->getV8Str( "host" ) , scope->getV8Str( host ) ); + + return v8::Undefined(); + } + + Handle<Value> mongoConsLocal(V8Scope* scope, const Arguments& args) { + + if ( args.Length() > 0 ) + return v8::ThrowException( v8::String::New( "local Mongo constructor takes no args" ) ); + + DBClientBase * conn; + { + V8Unlock ul; + conn = createDirectClient(); + } + + Persistent<v8::Object> self = Persistent<v8::Object>::New( args.This() ); + self.MakeWeak( conn , destroyConnection ); + + // NOTE I don't believe the conn object will ever be freed. + args.This()->SetInternalField( 0 , External::New( conn ) ); + args.This()->Set( scope->getV8Str( "slaveOk" ) , Boolean::New( false ) ); + args.This()->Set( scope->getV8Str( "host" ) , scope->getV8Str( "EMBEDDED" ) ); + + return v8::Undefined(); + } + + + // --- + +#ifdef _WIN32 +#define GETNS char * ns = new char[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); +#else +#define GETNS char ns[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); +#endif + + DBClientBase * getConnection( const Arguments& args ) { + Local<External> c = External::Cast( *(args.This()->GetInternalField( 0 )) ); + DBClientBase * conn = (DBClientBase*)(c->Value()); + assert( conn ); + return conn; + } + + // ---- real methods + + void destroyCursor( Persistent<Value> self, void* parameter) { + delete static_cast<mongo::DBClientCursor*>(parameter); + self.Dispose(); + self.Clear(); + } + + /** + 0 - namespace + 1 - query + 2 - fields + 3 - limit + 4 - skip + */ + Handle<Value> mongoFind(V8Scope* scope, const Arguments& args) { + HandleScope handle_scope; + + jsassert( args.Length() == 7 , "find needs 7 args" ); + jsassert( args[1]->IsObject() , "needs to be an object" ); + DBClientBase * conn = getConnection( args ); + GETNS; + + BSONObj q = scope->v8ToMongo( args[1]->ToObject() ); + DDD( "query:" << q ); + + BSONObj fields; + bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0; + if ( haveFields ) + fields = scope->v8ToMongo( args[2]->ToObject() ); + + Local<v8::Object> mongo = args.This(); + + try { + auto_ptr<mongo::DBClientCursor> cursor; + int nToReturn = (int)(args[3]->ToNumber()->Value()); + int nToSkip = (int)(args[4]->ToNumber()->Value()); + int batchSize = (int)(args[5]->ToNumber()->Value()); + int options = (int)(args[6]->ToNumber()->Value()); + { + V8Unlock u; + cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, options , batchSize ); + if ( ! cursor.get() ) + return v8::ThrowException( v8::String::New( "error doing query: failed" ) ); + } + v8::Function * cons = (v8::Function*)( *( mongo->Get( scope->getV8Str( "internalCursor" ) ) ) ); + if ( !cons ) { + // may get here in case of thread termination + return v8::ThrowException( v8::String::New( "Could not create a cursor" ) ); + } + + Persistent<v8::Object> c = Persistent<v8::Object>::New( cons->NewInstance() ); + c.MakeWeak( cursor.get() , destroyCursor ); + c->SetInternalField( 0 , External::New( cursor.release() ) ); + return handle_scope.Close(c); + } + catch ( ... ) { + return v8::ThrowException( v8::String::New( "socket error on query" ) ); + } + } + + v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args) { + jsassert( args.Length() == 2 , "insert needs 2 args" ); + jsassert( args[1]->IsObject() , "have to insert an object" ); + + if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() ) + return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); + + DBClientBase * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + + if( args[1]->IsArray() ){ + + v8::Local<v8::Array> arr = v8::Array::Cast( *args[1] ); + vector<BSONObj> bos; + uint32_t len = arr->Length(); + + for( uint32_t i = 0; i < len; i++ ){ + + v8::Local<v8::Object> el = arr->CloneElementAt( i ); + + // Set ID on the element if necessary + if ( ! el->Has( scope->getV8Str( "_id" ) ) ) { + v8::Handle<v8::Value> argv[1]; + el->Set( scope->getV8Str( "_id" ) , scope->getObjectIdCons()->NewInstance( 0 , argv ) ); + } + + bos.push_back( scope->v8ToMongo( arr->CloneElementAt( i ) ) ); + } + + DDD( "want to save batch : " << bos.length ); + try { + V8Unlock u; + conn->insert( ns , bos ); + } + catch ( ... ) { + return v8::ThrowException( v8::String::New( "socket error on bulk insert" ) ); + } + + } + else { + + if ( ! in->Has( scope->getV8Str( "_id" ) ) ) { + v8::Handle<v8::Value> argv[1]; + in->Set( scope->getV8Str( "_id" ) , scope->getObjectIdCons()->NewInstance( 0 , argv ) ); + } + + BSONObj o = scope->v8ToMongo( in ); + + DDD( "want to save : " << o.jsonString() ); + try { + V8Unlock u; + conn->insert( ns , o ); + } + catch ( ... ) { + return v8::ThrowException( v8::String::New( "socket error on insert" ) ); + } + + } + + return v8::Undefined(); + } + + v8::Handle<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args) { + jsassert( args.Length() == 2 || args.Length() == 3 , "remove needs 2 args" ); + jsassert( args[1]->IsObject() , "have to remove an object template" ); + + if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() ) + return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); + + DBClientBase * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + BSONObj o = scope->v8ToMongo( in ); + + bool justOne = false; + if ( args.Length() > 2 ) { + justOne = args[2]->BooleanValue(); + } + + DDD( "want to remove : " << o.jsonString() ); + try { + V8Unlock u; + conn->remove( ns , o , justOne ); + } + catch ( ... ) { + return v8::ThrowException( v8::String::New( "socket error on remove" ) ); + } + + return v8::Undefined(); + } + + v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, const v8::Arguments& args) { + jsassert( args.Length() >= 3 , "update needs at least 3 args" ); + jsassert( args[1]->IsObject() , "1st param to update has to be an object" ); + jsassert( args[2]->IsObject() , "2nd param to update has to be an object" ); + + if ( args.This()->Get( scope->getV8Str( "readOnly" ) )->BooleanValue() ) + return v8::ThrowException( v8::String::New( "js db in read only mode" ) ); + + DBClientBase * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> q = args[1]->ToObject(); + v8::Handle<v8::Object> o = args[2]->ToObject(); + + bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value(); + bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); + + try { + BSONObj q1 = scope->v8ToMongo( q ); + BSONObj o1 = scope->v8ToMongo( o ); + V8Unlock u; + conn->update( ns , q1 , o1 , upsert, multi ); + } + catch ( ... ) { + return v8::ThrowException( v8::String::New( "socket error on remove" ) ); + } + + return v8::Undefined(); + } + + v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args) { + jsassert( args.Length() >= 3 , "update needs at least 3 args" ); + DBClientBase * conn = getConnection( args ); + string db = toSTLString(args[0]); + string username = toSTLString(args[1]); + string password = toSTLString(args[2]); + string errmsg = ""; + + try { + if (conn->auth(db, username, password, errmsg)) { + return v8::Boolean::New(true); + } + } catch ( ... ) { + } + return v8::ThrowException( v8::String::New( errmsg.c_str() ) ); + } + +// + JSBool mongo_auth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { +// + smuassert( cx , "mongo_auth needs 3 args" , argc == 3 ); +// + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); +// + smuassert( cx , "no connection!" , connHolder && connHolder->get() ); +// + DBClientWithCommands *conn = connHolder->get(); +// + +// + Convertor c( cx ); +// + +// + string db = c.toString( argv[0] ); +// + string username = c.toString( argv[1] ); +// + string password = c.toString( argv[2] ); +// + string errmsg = ""; +// + +// + try { +// + if (conn->auth(db, username, password, errmsg)) { +// + return JS_TRUE; +// + } +// + JS_ReportError( cx, errmsg.c_str() ); +// + } +// + catch ( ... ) { +// + JS_ReportError( cx , "error doing query: unknown" ); +// + } +// + return JS_FALSE; +// + } + + + // --- cursor --- + + mongo::DBClientCursor * getCursor( const Arguments& args ) { + Local<External> c = External::Cast( *(args.This()->GetInternalField( 0 ) ) ); + + mongo::DBClientCursor * cursor = (mongo::DBClientCursor*)(c->Value()); + return cursor; + } + + v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args) { + return v8::Undefined(); + } + + v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args) { + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return v8::Undefined(); + BSONObj o; + { + V8Unlock u; + o = cursor->next(); + } + bool ro = false; + if (args.This()->Has(scope->V8STR_RO)) + ro = args.This()->Get(scope->V8STR_RO)->BooleanValue(); + return scope->mongoToLZV8( o, false, ro ); + } + + v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args) { + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return Boolean::New( false ); + bool ret; + { + V8Unlock u; + ret = cursor->more(); + } + return Boolean::New( ret ); + } + + v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, const v8::Arguments& args) { + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return v8::Number::New( (double) 0 ); + int ret; + { + V8Unlock u; + ret = cursor->objsLeftInBatch(); + } + return v8::Number::New( (double) ret ); + } + + v8::Handle<v8::Value> internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args) { + Local<v8::Object> cursor = args.This(); + cursor->Set(scope->V8STR_RO, v8::Boolean::New(true)); + return cursor; + } + + // --- DB ---- + + v8::Handle<v8::Value> dbInit(V8Scope* scope, const v8::Arguments& args) { + assert( args.Length() == 2 ); + + args.This()->Set( scope->getV8Str( "_mongo" ) , args[0] ); + args.This()->Set( scope->getV8Str( "_name" ) , args[1] ); + + for ( int i=0; i<args.Length(); i++ ) + assert( ! args[i]->IsUndefined() ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> collectionInit( V8Scope* scope, const v8::Arguments& args ) { + assert( args.Length() == 4 ); + + args.This()->Set( scope->getV8Str( "_mongo" ) , args[0] ); + args.This()->Set( scope->getV8Str( "_db" ) , args[1] ); + args.This()->Set( scope->getV8Str( "_shortName" ) , args[2] ); + args.This()->Set( scope->V8STR_FULLNAME , args[3] ); + + if ( haveLocalShardingInfo( toSTLString( args[3] ) ) ) + return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) ); + + for ( int i=0; i<args.Length(); i++ ) + assert( ! args[i]->IsUndefined() ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> dbQueryInit( V8Scope* scope, const v8::Arguments& args ) { + + v8::Handle<v8::Object> t = args.This(); + + assert( args.Length() >= 4 ); + + t->Set( scope->getV8Str( "_mongo" ) , args[0] ); + t->Set( scope->getV8Str( "_db" ) , args[1] ); + t->Set( scope->getV8Str( "_collection" ) , args[2] ); + t->Set( scope->getV8Str( "_ns" ) , args[3] ); + + if ( args.Length() > 4 && args[4]->IsObject() ) + t->Set( scope->getV8Str( "_query" ) , args[4] ); + else + t->Set( scope->getV8Str( "_query" ) , v8::Object::New() ); + + if ( args.Length() > 5 && args[5]->IsObject() ) + t->Set( scope->getV8Str( "_fields" ) , args[5] ); + else + t->Set( scope->getV8Str( "_fields" ) , v8::Null() ); + + + if ( args.Length() > 6 && args[6]->IsNumber() ) + t->Set( scope->getV8Str( "_limit" ) , args[6] ); + else + t->Set( scope->getV8Str( "_limit" ) , Number::New( 0 ) ); + + if ( args.Length() > 7 && args[7]->IsNumber() ) + t->Set( scope->getV8Str( "_skip" ) , args[7] ); + else + t->Set( scope->getV8Str( "_skip" ) , Number::New( 0 ) ); + + if ( args.Length() > 8 && args[8]->IsNumber() ) + t->Set( scope->getV8Str( "_batchSize" ) , args[8] ); + else + t->Set( scope->getV8Str( "_batchSize" ) , Number::New( 0 ) ); + + if ( args.Length() > 9 && args[9]->IsNumber() ) + t->Set( scope->getV8Str( "_options" ) , args[9] ); + else + t->Set( scope->getV8Str( "_options" ) , Number::New( 0 ) ); + + + t->Set( scope->getV8Str( "_cursor" ) , v8::Null() ); + t->Set( scope->getV8Str( "_numReturned" ) , v8::Number::New(0) ); + t->Set( scope->getV8Str( "_special" ) , Boolean::New(false) ); + + return v8::Undefined(); + } + + Handle<Value> collectionSetter( Local<v8::String> name, Local<Value> value, const AccessorInfo& info ) { + // a collection name cannot be overwritten by a variable + string sname = toSTLString( name ); + if ( sname.length() == 0 || sname[0] == '_' ) { + // if starts with '_' we allow overwrite + return Handle<Value>(); + } + // dont set + return value; + } + + v8::Handle<v8::Value> collectionGetter( v8::Local<v8::String> name, const v8::AccessorInfo &info) { + DDD( "collectionFallback [" << name << "]" ); + + // first look in prototype, may be a function + v8::Handle<v8::Value> real = info.This()->GetPrototype()->ToObject()->Get( name ); + if ( !real->IsUndefined() ) + return real; + + // 2nd look into real values, may be cached collection object + string sname = toSTLString( name ); + if (info.This()->HasRealNamedProperty(name)) { + v8::Local<v8::Value> prop = info.This()->GetRealNamedProperty( name ); + if (prop->IsObject() && prop->ToObject()->HasRealNamedProperty(v8::String::New("_fullName"))) { + // need to check every time that the collection did not get sharded + if ( haveLocalShardingInfo( toSTLString( prop->ToObject()->GetRealNamedProperty(v8::String::New("_fullName")) ) ) ) + return v8::ThrowException( v8::String::New( "can't use sharded collection from db.eval" ) ); + } + return prop; + } else if ( sname.length() == 0 || sname[0] == '_' ) { + // if starts with '_' we dont return collection, one must use getCollection() + return v8::Undefined(); + } + + // no hit, create new collection + v8::Handle<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "getCollection" ) ); + assert( getCollection->IsFunction() ); + + TryCatch tryCatch; + v8::Function * f = (v8::Function*)(*getCollection); + v8::Handle<v8::Value> argv[1]; + argv[0] = name; + v8::Local<v8::Value> coll = f->Call( info.This() , 1 , argv ); + if (coll.IsEmpty()) { + if (tryCatch.HasCaught()) { + return v8::ThrowException( tryCatch.Exception() ); + } + return Handle<Value>(); + } + + // cache collection for reuse, dont enumerate + info.This()->ForceSet(name, coll, v8::DontEnum); + return coll; + } + + v8::Handle<v8::Value> dbQueryIndexAccess( unsigned int index , const v8::AccessorInfo& info ) { + v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "arrayAccess" ) ); + assert( arrayAccess->IsFunction() ); + + v8::Function * f = (v8::Function*)(*arrayAccess); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::Number::New( index ); + + return f->Call( info.This() , 1 , argv ); + } + + v8::Handle<v8::Value> objectIdInit( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { + v8::Function * f = scope->getObjectIdCons(); + return newInstance(f, args); + } + + OID oid; + + if ( args.Length() == 0 ) { + oid.init(); + } + else { + string s = toSTLString( args[0] ); + try { + Scope::validateObjectIdString( s ); + } + catch ( const MsgAssertionException &m ) { + string error = m.toString(); + return v8::ThrowException( v8::String::New( error.c_str() ) ); + } + oid.init( s ); + } + + it->Set( scope->getV8Str( "str" ) , v8::String::New( oid.str().c_str() ) ); + + return it; + } + + v8::Handle<v8::Value> dbRefInit( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { + v8::Function * f = scope->getNamedCons( "DBRef" ); + return newInstance(f, args); + } + + if (args.Length() != 2 && args.Length() != 0) { + return v8::ThrowException( v8::String::New( "DBRef needs 2 arguments" ) ); + } + + if ( args.Length() == 2 ) { + it->Set( scope->getV8Str( "$ref" ) , args[0] ); + it->Set( scope->getV8Str( "$id" ) , args[1] ); + } + + return it; + } + + v8::Handle<v8::Value> dbPointerInit( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { + v8::Function * f = scope->getNamedCons( "DBPointer" ); + return newInstance(f, args); + } + + if (args.Length() != 2) { + return v8::ThrowException( v8::String::New( "DBPointer needs 2 arguments" ) ); + } + + it->Set( scope->getV8Str( "ns" ) , args[0] ); + it->Set( scope->getV8Str( "id" ) , args[1] ); + it->SetHiddenValue( scope->getV8Str( "__DBPointer" ), v8::Number::New( 1 ) ); + + return it; + } + + v8::Handle<v8::Value> dbTimestampInit( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { + v8::Function * f = scope->getNamedCons( "Timestamp" ); + return newInstance(f, args); + } + + if ( args.Length() == 0 ) { + it->Set( scope->getV8Str( "t" ) , v8::Number::New( 0 ) ); + it->Set( scope->getV8Str( "i" ) , v8::Number::New( 0 ) ); + } + else if ( args.Length() == 2 ) { + it->Set( scope->getV8Str( "t" ) , args[0] ); + it->Set( scope->getV8Str( "i" ) , args[1] ); + } + else { + return v8::ThrowException( v8::String::New( "Timestamp needs 0 or 2 arguments" ) ); + } + + it->SetInternalField( 0, v8::Uint32::New( Timestamp ) ); + + return it; + } + + + v8::Handle<v8::Value> binDataInit( V8Scope* scope, const v8::Arguments& args ) { + v8::Local<v8::Object> it = args.This(); + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { + v8::Function* f = scope->getNamedCons( "BinData" ); + return newInstance(f, args); + } + + Handle<Value> type; + Handle<Value> len; + int rlen; + char* data; + if (args.Length() == 3) { + // 3 args: len, type, data + len = args[0]; + rlen = len->IntegerValue(); + type = args[1]; + v8::String::Utf8Value utf( args[ 2 ] ); + char* tmp = *utf; + data = new char[rlen]; + memcpy(data, tmp, rlen); + } + else if ( args.Length() == 2 ) { + // 2 args: type, base64 string + type = args[0]; + v8::String::Utf8Value utf( args[ 1 ] ); + string decoded = base64::decode( *utf ); + const char* tmp = decoded.data(); + rlen = decoded.length(); + data = new char[rlen]; + memcpy(data, tmp, rlen); + len = v8::Number::New(rlen); +// it->Set( scope->getV8Str( "data" ), v8::String::New( decoded.data(), decoded.length() ) ); + } else if (args.Length() == 0) { + // this is called by subclasses that will fill properties + return it; + } else { + return v8::ThrowException( v8::String::New( "BinData needs 2 or 3 arguments" ) ); + } + + it->Set( scope->getV8Str( "len" ) , len ); + it->Set( scope->getV8Str( "type" ) , type ); + it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) ); + Persistent<v8::Object> res = scope->wrapArrayObject(it, data); + return res; + } + + v8::Handle<v8::Value> binDataToString( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + int len = it->Get( scope->V8STR_LEN )->Int32Value(); + int type = it->Get( scope->V8STR_TYPE )->Int32Value(); + Local<External> c = External::Cast( *(it->GetInternalField( 0 )) ); + char* data = (char*)(c->Value()); + + stringstream ss; + ss << "BinData(" << type << ",\""; + base64::encode( ss, data, len ); + ss << "\")"; + string ret = ss.str(); + return v8::String::New( ret.c_str() ); + } + + v8::Handle<v8::Value> binDataToBase64( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + int len = Handle<v8::Number>::Cast(it->Get(scope->V8STR_LEN))->Int32Value(); + Local<External> c = External::Cast( *(it->GetInternalField( 0 )) ); + char* data = (char*)(c->Value()); + stringstream ss; + base64::encode( ss, (const char *)data, len ); + return v8::String::New(ss.str().c_str()); + } + + v8::Handle<v8::Value> binDataToHex( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + int len = Handle<v8::Number>::Cast(it->Get(scope->V8STR_LEN))->Int32Value(); + Local<External> c = External::Cast( *(it->GetInternalField( 0 )) ); + char* data = (char*)(c->Value()); + stringstream ss; + ss.setf (ios_base::hex , ios_base::basefield); + ss.fill ('0'); + ss.setf (ios_base::right , ios_base::adjustfield); + for( int i = 0; i < len; i++ ) { + unsigned v = (unsigned char) data[i]; + ss << setw(2) << v; + } + return v8::String::New(ss.str().c_str()); + } + + static v8::Handle<v8::Value> hexToBinData( V8Scope* scope, v8::Local<v8::Object> it, int type, string hexstr ) { + int len = hexstr.length() / 2; + char* data = new char[len]; + const char* src = hexstr.c_str(); + for( int i = 0; i < 16; i++ ) { + data[i] = fromHex(src + i * 2); + } + + it->Set( scope->V8STR_LEN , v8::Number::New(len) ); + it->Set( scope->V8STR_TYPE , v8::Number::New(type) ); + it->SetHiddenValue( scope->V8STR_BINDATA, v8::Number::New( 1 ) ); + Persistent<v8::Object> res = scope->wrapArrayObject(it, data); + return res; + } + + v8::Handle<v8::Value> uuidInit( V8Scope* scope, const v8::Arguments& args ) { + if (args.Length() != 1) { + return v8::ThrowException( v8::String::New( "UUIS needs 1 argument" ) ); + } + v8::String::Utf8Value utf( args[ 0 ] ); + if( utf.length() != 32 ) { + return v8::ThrowException( v8::String::New( "UUIS string must have 32 characters" ) ); + } + + v8::Function * f = scope->getNamedCons("BinData"); + Local<v8::Object> it = f->NewInstance(); + return hexToBinData(scope, it, bdtUUID, *utf); + } + + v8::Handle<v8::Value> md5Init( V8Scope* scope, const v8::Arguments& args ) { + if (args.Length() != 1) { + return v8::ThrowException( v8::String::New( "MD5 needs 1 argument" ) ); + } + v8::String::Utf8Value utf( args[ 0 ] ); + if( utf.length() != 32 ) { + return v8::ThrowException( v8::String::New( "MD5 string must have 32 characters" ) ); + } + + v8::Function * f = scope->getNamedCons("BinData"); + Local<v8::Object> it = f->NewInstance(); + return hexToBinData(scope, it, MD5Type, *utf); + } + + v8::Handle<v8::Value> hexDataInit( V8Scope* scope, const v8::Arguments& args ) { + if (args.Length() != 2) { + return v8::ThrowException( v8::String::New( "HexData needs 2 arguments" ) ); + } + v8::String::Utf8Value utf( args[ 1 ] ); + v8::Function * f = scope->getNamedCons("BinData"); + Local<v8::Object> it = f->NewInstance(); + return hexToBinData(scope, it, args[0]->IntegerValue(), *utf); + } + + v8::Handle<v8::Value> numberLongInit( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { + v8::Function * f = scope->getNamedCons( "NumberLong" ); + return newInstance(f, args); + } + + if (args.Length() != 0 && args.Length() != 1 && args.Length() != 3) { + return v8::ThrowException( v8::String::New( "NumberLong needs 0, 1 or 3 arguments" ) ); + } + + if ( args.Length() == 0 ) { + it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( 0 ) ); + } + else if ( args.Length() == 1 ) { + if ( args[ 0 ]->IsNumber() ) { + it->Set( scope->getV8Str( "floatApprox" ), args[ 0 ] ); + } + else { + v8::String::Utf8Value data( args[ 0 ] ); + string num = *data; + const char *numStr = num.c_str(); + long long n; + try { + n = parseLL( numStr ); + } + catch ( const AssertionException & ) { + return v8::ThrowException( v8::String::New( "could not convert string to long long" ) ); + } + unsigned long long val = n; + // values above 2^53 are not accurately represented in JS + if ( (long long)val == (long long)(double)(long long)(val) && val < 9007199254740992ULL ) { + it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) ); + } + else { + it->Set( scope->getV8Str( "floatApprox" ), v8::Number::New( (double)(long long)( val ) ) ); + it->Set( scope->getV8Str( "top" ), v8::Integer::New( val >> 32 ) ); + it->Set( scope->getV8Str( "bottom" ), v8::Integer::New( (unsigned long)(val & 0x00000000ffffffff) ) ); + } + } + } + else { + it->Set( scope->getV8Str( "floatApprox" ) , args[0] ); + it->Set( scope->getV8Str( "top" ) , args[1] ); + it->Set( scope->getV8Str( "bottom" ) , args[2] ); + } + it->SetHiddenValue( scope->V8STR_NUMBERLONG, v8::Number::New( 1 ) ); + + return it; + } + + long long numberLongVal( const v8::Handle< v8::Object > &it ) { + if ( !it->Has( v8::String::New( "top" ) ) ) + return (long long)( it->Get( v8::String::New( "floatApprox" ) )->NumberValue() ); + return + (long long) + ( (unsigned long long)( it->Get( v8::String::New( "top" ) )->ToInt32()->Value() ) << 32 ) + + (unsigned)( it->Get( v8::String::New( "bottom" ) )->ToInt32()->Value() ); + } + + v8::Handle<v8::Value> numberLongValueOf( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + long long val = numberLongVal( it ); + return v8::Number::New( double( val ) ); + } + + v8::Handle<v8::Value> numberLongToNumber( V8Scope* scope, const v8::Arguments& args ) { + return numberLongValueOf( scope, args ); + } + + v8::Handle<v8::Value> numberLongToString( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + + stringstream ss; + long long val = numberLongVal( it ); + const long long limit = 2LL << 30; + + if ( val <= -limit || limit <= val ) + ss << "NumberLong(\"" << val << "\")"; + else + ss << "NumberLong(" << val << ")"; + + string ret = ss.str(); + return v8::String::New( ret.c_str() ); + } + + v8::Handle<v8::Value> numberIntInit( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ) { + v8::Function * f = scope->getNamedCons( "NumberInt" ); + return newInstance(f, args); + } + + if (args.Length() != 0 && args.Length() != 1) { + return v8::ThrowException( v8::String::New( "NumberInt needs 0, 1 argument" ) ); + } + + if ( args.Length() == 0 ) { + it->SetHiddenValue( scope->V8STR_NUMBERINT, v8::Number::New( 0 ) ); + } + else if ( args.Length() == 1 ) { + it->SetHiddenValue( scope->V8STR_NUMBERINT, args[0]->ToInt32() ); + } + + return it; + } + + v8::Handle<v8::Value> numberIntValueOf( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value(); + return v8::Number::New( double( val ) ); + } + + v8::Handle<v8::Value> numberIntToNumber( V8Scope* scope, const v8::Arguments& args ) { + return numberIntValueOf( scope, args ); + } + + v8::Handle<v8::Value> numberIntToString( V8Scope* scope, const v8::Arguments& args ) { + v8::Handle<v8::Object> it = args.This(); + + stringstream ss; + int val = it->GetHiddenValue( scope->V8STR_NUMBERINT )->Int32Value(); + ss << "NumberInt(" << val << ")"; + + string ret = ss.str(); + return v8::String::New( ret.c_str() ); + } + + v8::Handle<v8::Value> bsonsize( V8Scope* scope, const v8::Arguments& args ) { + + if ( args.Length() != 1 ) + return v8::ThrowException( v8::String::New( "bsonsize needs 1 argument" ) ); + + if ( args[0]->IsNull() ) + return v8::Number::New(0); + + if ( ! args[ 0 ]->IsObject() ) + return v8::ThrowException( v8::String::New( "argument to bsonsize has to be an object" ) ); + + return v8::Number::New( scope->v8ToMongo( args[ 0 ]->ToObject() ).objsize() ); + } + + namespace v8Locks { + boost::mutex& __interruptMutex = *( new boost::mutex ); + + InterruptLock::InterruptLock() { + __interruptMutex.lock(); + } + + InterruptLock::~InterruptLock() { + __interruptMutex.unlock(); + } + + boost::mutex& __v8Mutex = *( new boost::mutex ); + ThreadLocalValue< bool > __locked; + + RecursiveLock::RecursiveLock() : _unlock() { + if ( !__locked.get() ) { + __v8Mutex.lock(); + __locked.set( true ); + _unlock = true; + } + } + RecursiveLock::~RecursiveLock() { + if ( _unlock ) { + __v8Mutex.unlock(); + __locked.set( false ); + } + } + + RecursiveUnlock::RecursiveUnlock() : _lock() { + if ( __locked.get() ) { + __v8Mutex.unlock(); + __locked.set( false ); + _lock = true; + } + } + RecursiveUnlock::~RecursiveUnlock() { + if ( _lock ) { + __v8Mutex.lock(); + __locked.set( true ); + } + } + } // namespace v8Locks +} diff --git a/src/mongo/scripting/v8_db.h b/src/mongo/scripting/v8_db.h new file mode 100644 index 00000000000..68946e0ed06 --- /dev/null +++ b/src/mongo/scripting/v8_db.h @@ -0,0 +1,94 @@ +// v8_db.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <v8.h> +#include <cstring> +#include <cstdio> +#include <cstdlib> + +#include "engine_v8.h" +#include "../client/dbclient.h" + +namespace mongo { + + // These functions may depend on the caller creating a handle scope and context scope. + + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( V8Scope * scope, bool local ); +// void installDBTypes( V8Scope * scope, v8::Handle<v8::ObjectTemplate>& global ); + void installDBTypes( V8Scope * scope, v8::Handle<v8::Object>& global ); + + // the actual globals + + mongo::DBClientBase * getConnection( const v8::Arguments& args ); + + // Mongo members + v8::Handle<v8::Value> mongoConsLocal(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoConsExternal(V8Scope* scope, const v8::Arguments& args); + + v8::Handle<v8::Value> mongoFind(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args); + + v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args); + + // DB members + + v8::Handle<v8::Value> dbInit(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> collectionInit(V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> objectIdInit( V8Scope* scope, const v8::Arguments& args ); + + v8::Handle<v8::Value> dbRefInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> dbPointerInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> dbTimestampInit( V8Scope* scope, const v8::Arguments& args ); + + v8::Handle<v8::Value> binDataInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> binDataToString( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> binDataToBase64( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> binDataToHex( V8Scope* scope, const v8::Arguments& args ); + + v8::Handle<v8::Value> uuidInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> md5Init( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> hexDataInit( V8Scope* scope, const v8::Arguments& args ); + + v8::Handle<v8::Value> numberLongInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> numberLongToNumber(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> numberLongValueOf(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> numberLongToString(V8Scope* scope, const v8::Arguments& args); + + v8::Handle<v8::Value> numberIntInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> numberIntToNumber(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> numberIntValueOf(V8Scope* scope, const v8::Arguments& args); + v8::Handle<v8::Value> numberIntToString(V8Scope* scope, const v8::Arguments& args); + + v8::Handle<v8::Value> dbQueryInit( V8Scope* scope, const v8::Arguments& args ); + v8::Handle<v8::Value> dbQueryIndexAccess( uint32_t index , const v8::AccessorInfo& info ); + + v8::Handle<v8::Value> collectionGetter( v8::Local<v8::String> name, const v8::AccessorInfo &info); + v8::Handle<v8::Value> collectionSetter( Local<v8::String> name, Local<Value> value, const AccessorInfo& info ); + + v8::Handle<v8::Value> bsonsize( V8Scope* scope, const v8::Arguments& args ); + +} + diff --git a/src/mongo/scripting/v8_utils.cpp b/src/mongo/scripting/v8_utils.cpp new file mode 100644 index 00000000000..9e7e8072220 --- /dev/null +++ b/src/mongo/scripting/v8_utils.cpp @@ -0,0 +1,295 @@ +// v8_utils.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(_WIN32) +/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with + our usage of boost */ +#include "boost/cstdint.hpp" +using namespace boost; +#define V8STDINT_H_ +#endif + +#include "v8_utils.h" +#include "v8_db.h" +#include <iostream> +#include <map> +#include <sstream> +#include <vector> +#include <boost/smart_ptr.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/xtime.hpp> +#include "engine_v8.h" + +using namespace std; +using namespace v8; + +namespace mongo { + + std::string toSTLString( const Handle<v8::Value> & o ) { + v8::String::Utf8Value str(o); + const char * foo = *str; + std::string s(foo); + return s; + } + + std::string toSTLString( const v8::TryCatch * try_catch ) { + + stringstream ss; + + //while ( try_catch ){ // disabled for v8 bleeding edge + + v8::String::Utf8Value exception(try_catch->Exception()); + Handle<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + ss << *exception << endl; + } + else { + + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + ss << *filename << ":" << linenum << " " << *exception << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + ss << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + ss << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + ss << "^"; + + ss << endl; + } + + //try_catch = try_catch->next_; + //} + + return ss.str(); + } + + + std::ostream& operator<<( std::ostream &s, const Handle<v8::Value> & o ) { + v8::String::Utf8Value str(o); + s << *str; + return s; + } + + std::ostream& operator<<( std::ostream &s, const v8::TryCatch * try_catch ) { + HandleScope handle_scope; + v8::String::Utf8Value exception(try_catch->Exception()); + Handle<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + s << *exception << endl; + } + else { + + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + cout << *filename << ":" << linenum << " " << *exception << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + cout << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + cout << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + cout << "^"; + + cout << endl; + } + + //if ( try_catch->next_ ) // disabled for v8 bleeding edge + // s << try_catch->next_; + + return s; + } + + void ReportException(v8::TryCatch* try_catch) { + cout << try_catch << endl; + } + + Handle< Context > baseContext_; + + class JSThreadConfig { + public: + JSThreadConfig( V8Scope* scope, const Arguments &args, bool newScope = false ) : started_(), done_(), newScope_( newScope ) { + jsassert( args.Length() > 0, "need at least one argument" ); + jsassert( args[ 0 ]->IsFunction(), "first argument must be a function" ); + + // arguments need to be copied into the isolate, go through bson + BSONObjBuilder b; + for( int i = 0; i < args.Length(); ++i ) { + scope->v8ToMongoElement(b, "arg" + i, args[i]); + } + args_ = b.obj(); + } + + ~JSThreadConfig() { + } + + void start() { + jsassert( !started_, "Thread already started" ); + // obtain own scope for execution + // do it here, not in constructor, otherwise it creates an infinite recursion from ScopedThread + _scope.reset( dynamic_cast< V8Scope * >( globalScriptEngine->newScope() ) ); + + JSThread jt( *this ); + thread_.reset( new boost::thread( jt ) ); + started_ = true; + } + void join() { + jsassert( started_ && !done_, "Thread not running" ); + thread_->join(); + done_ = true; + } + + BSONObj returnData() { + if ( !done_ ) + join(); + return returnData_; + } + + private: + class JSThread { + public: + JSThread( JSThreadConfig &config ) : config_( config ) {} + + void operator()() { + V8Scope* scope = config_._scope.get(); + v8::Isolate::Scope iscope(scope->getIsolate()); + v8::Locker l(scope->getIsolate()); + HandleScope handle_scope; + Context::Scope context_scope( scope->getContext() ); + + BSONObj args = config_.args_; + Local< v8::Function > f = v8::Function::Cast( *(scope->mongoToV8Element(args.firstElement(), true)) ); + int argc = args.nFields() - 1; + + boost::scoped_array< Local< Value > > argv( new Local< Value >[ argc ] ); + BSONObjIterator it(args); + it.next(); + for( int i = 0; i < argc; ++i ) { + argv[ i ] = Local< Value >::New( scope->mongoToV8Element(*it, true) ); + it.next(); + } + TryCatch try_catch; + Handle< Value > ret = f->Call( scope->getContext()->Global(), argc, argv.get() ); + if ( ret.IsEmpty() ) { + string e = toSTLString( &try_catch ); + log() << "js thread raised exception: " << e << endl; + // v8 probably does something sane if ret is empty, but not going to assume that for now + ret = v8::Undefined(); + } + // ret is translated to BSON to switch isolate + BSONObjBuilder b; + scope->v8ToMongoElement(b, "ret", ret); + config_.returnData_ = b.obj(); + } + + private: + JSThreadConfig &config_; + }; + + bool started_; + bool done_; + bool newScope_; + BSONObj args_; + scoped_ptr< boost::thread > thread_; + scoped_ptr< V8Scope > _scope; + BSONObj returnData_; + }; + + Handle< Value > ThreadInit( V8Scope* scope, const Arguments &args ) { + Handle<v8::Object> it = args.This(); + // NOTE I believe the passed JSThreadConfig will never be freed. If this + // policy is changed, JSThread may no longer be able to store JSThreadConfig + // by reference. + it->SetHiddenValue( v8::String::New( "_JSThreadConfig" ), External::New( new JSThreadConfig( scope, args ) ) ); + return v8::Undefined(); + } + + Handle< Value > ScopedThreadInit( V8Scope* scope, const Arguments &args ) { + Handle<v8::Object> it = args.This(); + // NOTE I believe the passed JSThreadConfig will never be freed. If this + // policy is changed, JSThread may no longer be able to store JSThreadConfig + // by reference. + it->SetHiddenValue( v8::String::New( "_JSThreadConfig" ), External::New( new JSThreadConfig( scope, args, true ) ) ); + return v8::Undefined(); + } + + JSThreadConfig *thisConfig( V8Scope* scope, const Arguments &args ) { + Local< External > c = External::Cast( *(args.This()->GetHiddenValue( v8::String::New( "_JSThreadConfig" ) ) ) ); + JSThreadConfig *config = (JSThreadConfig *)( c->Value() ); + return config; + } + + Handle< Value > ThreadStart( V8Scope* scope, const Arguments &args ) { + thisConfig( scope, args )->start(); + return v8::Undefined(); + } + + Handle< Value > ThreadJoin( V8Scope* scope, const Arguments &args ) { + thisConfig( scope, args )->join(); + return v8::Undefined(); + } + + Handle< Value > ThreadReturnData( V8Scope* scope, const Arguments &args ) { + BSONObj data = thisConfig( scope, args )->returnData(); + return scope->mongoToV8Element(data.firstElement(), true); + } + + Handle< Value > ThreadInject( V8Scope* scope, const Arguments &args ) { + jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" ); + jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" ); + + Local<v8::Object> o = args[0]->ToObject(); + + // install method on the Thread object + scope->injectV8Function("init", ThreadInit, o); + scope->injectV8Function("start", ThreadStart, o); + scope->injectV8Function("join", ThreadJoin, o); + scope->injectV8Function("returnData", ThreadReturnData, o); + return v8::Undefined(); + } + + Handle< Value > ScopedThreadInject( V8Scope* scope, const Arguments &args ) { + jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" ); + jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" ); + + Local<v8::Object> o = args[0]->ToObject(); + + scope->injectV8Function("init", ScopedThreadInit, o); + // inheritance takes care of other member functions + + return v8::Undefined(); + } + + void installFork( V8Scope* scope, v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ) { + if ( baseContext_.IsEmpty() ) // if this is the shell, first call will be with shell context, otherwise don't expect to use fork() anyway + baseContext_ = context; + scope->injectV8Function("_threadInject", ThreadInject, global); + scope->injectV8Function("_scopedThreadInject", ScopedThreadInject, global); + } + +} diff --git a/src/mongo/scripting/v8_utils.h b/src/mongo/scripting/v8_utils.h new file mode 100644 index 00000000000..ca5d317885f --- /dev/null +++ b/src/mongo/scripting/v8_utils.h @@ -0,0 +1,43 @@ +// v8_utils.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <v8.h> + +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include <assert.h> +#include <iostream> + +namespace mongo { + + void ReportException(v8::TryCatch* handler); + +#define jsassert(x,msg) assert(x) + + std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::Value> & o ); + std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::TryCatch> * try_catch ); + + std::string toSTLString( const v8::Handle<v8::Value> & o ); + std::string toSTLString( const v8::TryCatch * try_catch ); + + class V8Scope; + void installFork( V8Scope* scope, v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ); +} + diff --git a/src/mongo/scripting/v8_wrapper.cpp b/src/mongo/scripting/v8_wrapper.cpp new file mode 100644 index 00000000000..7c28a39cceb --- /dev/null +++ b/src/mongo/scripting/v8_wrapper.cpp @@ -0,0 +1,99 @@ +// v8_wrapper.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(_WIN32) +/** this is a hack - v8stdint.h defined uint16_t etc. on _WIN32 only, and that collides with + our usage of boost */ +#include "boost/cstdint.hpp" +using namespace boost; +#define V8STDINT_H_ +#endif + +#include "v8_wrapper.h" +#include "v8_utils.h" +#include "v8_db.h" +#include "engine_v8.h" + +#include <iostream> + +using namespace std; +using namespace v8; + +namespace mongo { + +#define DDD(x) + + // --- object wrapper --- + + class WrapperHolder { + public: + WrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ) + : _scope(scope), _o(o), _readOnly( readOnly ), _iDelete( iDelete ) { + } + + ~WrapperHolder() { + if ( _o && _iDelete ) { + delete _o; + } + _o = 0; + } + + v8::Handle<v8::Value> get( v8::Local<v8::String> name ) { + const string& s = toSTLString( name ); + const BSONElement& e = _o->getField( s ); + return _scope->mongoToV8Element(e); + } + + V8Scope* _scope; + const BSONObj * _o; + bool _readOnly; + bool _iDelete; + }; + + WrapperHolder * createWrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ) { + return new WrapperHolder( scope, o , readOnly , iDelete ); + } + + WrapperHolder * getWrapper( v8::Handle<v8::Object> o ) { + Handle<v8::Value> t = o->GetRealNamedProperty( v8::String::New( "_wrapper" ) ); + assert( t->IsExternal() ); + Local<External> c = External::Cast( *t ); + WrapperHolder * w = (WrapperHolder*)(c->Value()); + assert( w ); + return w; + } + + + Handle<Value> wrapperCons(V8Scope* scope, const Arguments& args) { + if ( ! ( args.Length() == 1 && args[0]->IsExternal() ) ) + return v8::ThrowException( v8::String::New( "wrapperCons needs 1 External arg" ) ); + + args.This()->Set( v8::String::New( "_wrapper" ) , args[0] ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> wrapperGetHandler( v8::Local<v8::String> name, const v8::AccessorInfo &info) { + return getWrapper( info.This() )->get( name ); + } + + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(V8Scope* scope) { + v8::Handle<v8::FunctionTemplate> t = scope->createV8Function(wrapperCons); + t->InstanceTemplate()->SetNamedPropertyHandler( wrapperGetHandler ); + return t; + } +} diff --git a/src/mongo/scripting/v8_wrapper.h b/src/mongo/scripting/v8_wrapper.h new file mode 100644 index 00000000000..22f14e6ae94 --- /dev/null +++ b/src/mongo/scripting/v8_wrapper.h @@ -0,0 +1,34 @@ +// v8_wrapper.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <v8.h> +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include "../db/jsobj.h" +#include "engine_v8.h" + +namespace mongo { + + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(V8Scope* scope); + + class WrapperHolder; + WrapperHolder * createWrapperHolder( V8Scope* scope, const BSONObj * o , bool readOnly , bool iDelete ); + +} |