サーバ側でのコードの実行

Based on v26

最初に

Mongoはデータベースプロセス内でのコードの実行をサポートしています。

クエリー中の $where 句とファンクション

find() オペレーション用の通常形式のドキュメントスタイルのクエリに加え、SQLスタイルのWHERE句や完全なJavascriptファンクションを使うこともできます。

このクエリーモードを使うと、データベースはコレクションの中の一つずつのオブジェクトに対して、ファンクションを実行するか、条件を評価します。

文字列を指定した場合、"this"でオブジェクトを表します(下記の例を参照)。 Javascriptファンクションの場合、通常のJavascriptのシンタックスを使う事ができます。

Mongoシェル 内で次の4つの文は等価です。

db.myCollection.find( { a : { $gt: 3 } } );
db.myCollection.find( { $where: "this.a > 3" });
db.myCollection.find( "this.a > 3" );
db.myCollection.find( { $where: function() { return this.a > 3;}});

最初の文が一番好ましいです。 これは、わずかに他と比べ速いです。これは、オプティマイザが簡単に理解し使うインデックスを選ぶことができるからです。

データスタイルのfind条件と、ファンクションを混ぜることもできます。これは、データスタイルの表現が先に評価され、もしマッチしない場合には、その後の評価をする必要がないので、パフェーマンス面で有利になることがあります。加えて、データベースは、与えられた条件のフィールドから、インデックスを使用するかどうかを検討します。二つの形式を組み合わせるには、 クエリーオブジェクトに $where フィールドとして評価したいファンクションを渡します。たとえば、

db.myCollection.find( { active: true, $where: function() { return obj.credits - obj.debits < 0; } } );
db.myCollection.find( { active: true, $where: "this.credits - this.debits < 0" } );

db.eval() の使用

db.eval() はデータベースサーバで(Javascriptで書かれた)ファンクションを実行するために使います。
これは、たくさんのデータを処理する必要がある場合に便利です。このようなケースでは、データのネットワーク転送がボトルネックになるからです。
db.eval() は、サーバ上で呼び出され、ファンクションの戻り値を返します。実行に失敗した場合には例外が投げられます。

簡単な例として、これは 3 + 3 をサーバで実行して取得します。

> db.eval( function() { return 3+3; } );
6
>

コレクション内のすべてのドキュメントで、指定したフィールド foo を削除したいとしましょう。クライアントサイドでやるとしたら以下のようになります。

function my_erase() {
db.things.find().forEach( function(obj) {
delete obj.foo;
db.things.save(obj);
} );
}

my_erase();

my_erase() をクライアント側で呼ぶと、コレクションのすべてのコンテンツがサーバからクライアントに転送され、また送り返されます。
この代わりに、 eval() へこのファンクションを渡すことができます。そしてサーバ側の実行環境で実行されます。 サーバでは、 db 変数は、現在のデータベースにセットされます。

db.eval(my_erase);

> myfunc = function(x){ return x; };

> db.eval( myfunc, {k:"asdf"} );
{ k : "asdf" }

> db.eval( myfunc, "asdf" );
"asdf"

> db.eval( function(x){ return x; }, 2 );
2.0

評価中にエラーが起きた場合 (サーバ側でのnull pointer exceptionとか)、次の形で例外が投げられます。

{ dbEvalException: { errno : -3.0 , errmsg : "invoke failed" , ok : 0.0 } }

Mongoの count() ファンクションと等価なことを eval() でする例です。

function mycount(collection) {
return db.eval( function(){return db[collection].find({},{_id:ObjId()}).length();} );
}

アトミックに、インクリメントといくつかの計算をするために、 db.eval() を使用する例です。

function inc( name , howMuch ){
return db.eval(
function(){
var t = db.things.findOne( { name : name } );
t = t || { name : name , num : 0 , total : 0 , avg : 0 };
t.num++;
t.total += howMuch;
t.avg = t.total / t.num;
db.things.save( t );
return t;
}
);
}

db.things.remove( {} );
print( tojson( inc( "eliot" , 2 )) );
print( tojson( inc( "eliot" , 3 )) );

サーバサイドへのファンクションの保存

バージョン1.1.1以上

system.js という特別なコレクションがあります。これに、Javascriptのファンクションを繰り返し使うために保存することができます。ファンクションを保存するためには、

db.system.js.save( { _id : "foo" , value : function( x , y ){ return x + y; } } );

_id はファンクションの名前です。データーベース毎にユニークです。

一度これをしたら、 foo をどのJavascriptからでも呼ぶことができます。 (db.eval, $where, map/reduce)

http://github.com/mongodb/mongo/tree/master/jstests/storefunc.js にもっと例があります。

Map/Reduce

MongoDBは、サーバ上でのJavascriptベースのmap/reduceをサポートしています。 map/reduce ドキュメント に情報があります。

並列性に関するノート

eval() は、実行中、mongodプロセス全体をブロックします。このため、このオペレーションはアトミックですが、他の処理中の操作は止まります。

並列性が必要なときには、eval()の代わりにmap/reduceを使ってください。


Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

IF YOU HAVE A QUESTION, POST IT TO THE USER GROUP.

These pages are fine for comments, but for questions, your best bet will always be the MongoDB User Group.

blog comments powered by Disqus