Nie wspomniałeś, jakiego wydania używasz MongoDB, ale rozwiązanie będzie podobne do tego, co i tak jest tutaj prezentowane. Będę demonstrował na 2.2.4, co jest związane z Ubuntu 13.04.
Problem w tym, polega na wstrzyknięciu opcji do kursora. To miejsce, gdzie addOption
życie:
> var cursor = db.test.find()
> cursor.addOption
function (option) {
this._options |= option;
return this;
}
przyjrzyjmy się jak mapReduce
jest zdefiniowana:
> db.test.mapReduce
function (map, reduce, optionsOrOutString) {
var c = {mapreduce:this._shortName, map:map, reduce:reduce};
...
var raw = this._db.runCommand(c);
...
return new MapReduceResult(this._db, raw);
}
Tak buduje dokument uruchomić polecenie poprzez runCommand
. Spójrzmy dalej do niego:
> db.runCommand
function (obj) {
if (typeof obj == "string") {
var n = {};
n[obj] = 1;
obj = n;
}
return this.getCollection("$cmd").findOne(obj);
}
więc polecenie jest uruchamiane poprzez findOne
. Spójrzmy na to:
> db.test.findOne
function (query, fields, options) {
var cursor = this._mongo.find(this._fullName, this._massageObject(query) || {}, fields, -1, 0, 0, options || this.getQueryOptions());
if (!cursor.hasNext()) {
return null;
}
var ret = cursor.next();
...
return ret;
}
Ah, jest tu coś interesującego. Kursor jest inicjowany z flagami pochodzącymi z parametru options
, co niestety nie pomaga w przypadku, ponieważ runCommand
pozostawia to niezaznaczone, ale ORs to z getQueryOptions()
, które pochodzi z kolekcji. Spójrzmy na to:
> db.collection.getQueryOptions
function() {
var options = 0;
if (this.getSlaveOk()) {
options |= 4;
}
return options;
}
Ups ... to nie jest dobre. Tak więc nie mamy dostępu do kursora ani żadnego sposobu wprowadzania opcji zapytania do wykonanego polecenia za pomocą niehackowskich metod.
Cóż, ale dowiedzieliśmy się wiele o tym, jak polecenia redukcji mapy są w rzeczywistości dostarczane do serwera za pośrednictwem tego procesu. To tylko dokument, który jest sprawdzany w odniesieniu do określonej kolekcji w bazie danych. Oznacza to, że możemy zbudować to samo zapytanie i uruchomić je samodzielnie, ale zapewniając niezbędne flagi.
Nie będę się kłopotać budowaniem całego polecenia MongoDB i ustawianiem dla ciebie wyniku, ale pokażę, że faktycznie działa, wykonując polecenie za pomocą polecenia isMaster
.
To polecenie działa bez żadnych flag:
> db.getCollection("$cmd").findOne({isMaster: 1}).ismaster
true
Aby zobaczyć różnicę w efekcie będziemy tcpdump komunikacji z bazą danych.Widzimy w the wire protocol documentation że odpowiednie flagi żyć tuż przed nazwą zbiórki w 32 bitach całkowitą, więc jest to łatwe do wykrycia odpowiedniego kawałka wysypisko:
. vvvvvvvvv
0x0040: d407 0000 0000 0000 7465 7374 2e24 636d ........test.$cm
0x0050: 6400 0000 0000 ffff ffff 1700 0000 0169 d..............i
dobre. Możemy zobaczyć cztery bajty wyzerowane, tuż przed nazwą kolekcji.
Teraz zróbmy to samo, zapewniając trochę flag. Nauczyliśmy się od powyższej sekcji debugowania że flagi zapytań mogą być dostarczone jako trzecią opcją findOne
, więc zróbmy to:
> db.getCollection("$cmd").findOne({isMaster: 1}, undefined, 0xBEEF).ismaster
true
i patrz zrzut:
. vvvvvvvvv
0x0040: d407 0000 efbe 0000 7465 7374 2e24 636d ........test.$cm
0x0050: 6400 0000 0000 ffff ffff 1700 0000 0169 d..............i
Hej, nasz flagi zostały dostarczone tam, gdzie powinny, a my możemy zobaczyć, że zostały odwrócone, co oznacza, że bajty są zakodowane jako little-endian, pasujące do the docs.
Tak, to oznacza, że można dostarczyć flagę DBQuery.Option.noTimeout
jako trzecią opcją findOne
i ręcznie kod polecenie map-redukować jak opisano in the documentation w sposób podobny do tego, co zrobiliśmy z isMaster
, a dostaniesz co chcesz.