mongodbのmapReduceのscopeで変数は渡せるけれど、関数を渡せない問題の回避策
下記のようにmongodbのmapReduceのmap/reduce関数内でちょっとした関数を呼びたい場合があります。(sampleです)
- sampleMR.js
var getCareer = function(ua){ if(ua.indexOf('DoCoMo') === 0){ return 'DoCoMo'; }else if(ua.indexOf('KDDI') === 0){ return 'KDDI'; }else if(ua.indexOf('SoftBank') === 0){ return 'SoftBank'; }else{ return null; } }; var map = function(){ if(this.ua){ emit(getCareer(this.ua), 1); } }; var reduce = function(k, vs){ var sum = 0; vs.forEach(function(v){ sum += v; }); return sum; }; db.logs.mapReduce(map, reduce, {out: 'logs.career'});
これを実行すると、下のようなエラーがでて終わってしまいます。
$ mongo access sampleMR.js MongoDB shell version: 1.8.0-rc0 connecting to: access Sat Mar 26 02:16:17 uncaught exception: map reduce failed:{ "assertion" : "map invoke failed: JS Error: ReferenceError: getCareer is not defined nofile_b:1", "assertionCode" : 9014, "errmsg" : "db assertion failure", "ok" : 0 } failed to load: sampleMR.js
scopeを使う
これに対して、scopeというoptionを追加するとglobalに登録するので変数が利用出来るようです
MapReduce - MongoDB
var getCareer = function(ua){ /* 略 */ }; var map = function(){ /* 略 */ }; var reduce = function(k, vs){ /* 略 */ }; db.logs.mapReduce(map, reduce, {scope: {x: 0, getCareer: getCareer}, out: 'logs.career'});
mongodbのdocumentではscopeでglobalにくっつけてmap/reduce/finalizeから使えるよーっとあるのですが(サンプル mongo/jstests/mr4.js at master · mongodb/mongo · GitHub) 、どうにも使えません。
関数以外を渡すとたしかに動作しているのですが、関数を渡すとあいかわらず使えません。
map関数内で定義する
var map = function(){ var getCareer = function(ua){ /* 略 */ }; if(this.ua){ emit(getCareer(this.ua), 1); } }; /* 略 */
これは動作しますが、map関数はおそらくdocの数だけ起動される=関数オブジェクトがdocの数だけ生成されては破棄される、というのは避けたいです。
クロージャを使う
クロージャを使って関数を渡してみます。
var map = function(){ var getCareer = function(ua){ /* 略 */ }; return function(){ if(this.ua){ emit(getCareer(this.ua), 1); } }; }();
これにしても
$ mongo access sampleMR.js MongoDB shell version: 1.8.0-rc0 connecting to: access Sat Mar 26 02:51:39 uncaught exception: map reduce failed:{ "assertion" : "map invoke failed: JS Error: ReferenceError: getCareer is not defined nofile_b:2", "assertionCode" : 9014, "errmsg" : "db assertion failure", "ok" : 0 } failed to load: sampleMR.js
冷たい応答です…><
db.system.jsで渡す
@doryokujinさんにtweeterでもらったのですがWorking With Stored JavaScript in MongoDB - Mike Dirolfを見ると「collection db.system.jsに関数を登録するとstored JSとして使えるよー」な感じです。
db.system.js.save({"_id": "getCareer", "value": function(ua){ /* 略 */ } }); var map = function(){ /* 略 */ }; /* 略 */ db.logs.mapReduce(map, reduce, {out: 'logs.career'});
これを実行してみると、うまくいきます!!
$ mongo access --eval 'db.logs.career.find().forEach(function(o){printjson(o)})' { "_id" : null, "value" : 122453 } { "_id" : "DoCoMo", "value" : 47024 } { "_id" : "KDDI", "value" : 21720 } { "_id" : "SoftBank", "value" : 7971 }
別ファイルに書いておいて保存
毎回、関数を定義するのもやなので、util.jsみたいな名前をつけておいて
var getCareer = function(ua){ /* 略 */ }; var hoge = function(args){}; var fuga = function(args){}; db.system.js.save({"_id": "getCareer", "value": getCareer}); db.system.js.save({"_id": "hoge", "value": hoge}); db.system.js.save({"_id": "fuga", "value": fuga});
これらに変更があったときに
$ mongo access util.js
として登録するといいと思います(system.jsはdbのcollectionの一つで、stored JSなのでmongodを再起動しても普通に使えます)
これで、mapReduceやgroupも怖くない!
ただ、結局、map/reduce/finalizeの環境はなんなんだろう・・・mapはthisにbindされてのはわかるんだけど・・・