diff options
-rw-r--r-- | jstests/core/count2.js | 36 | ||||
-rw-r--r-- | jstests/core/crud_api.js | 4 | ||||
-rw-r--r-- | jstests/libs/override_methods/set_read_preference_secondary.js | 22 | ||||
-rw-r--r-- | jstests/noPassthrough/client_metadata_slowlog.js | 12 | ||||
-rw-r--r-- | jstests/noPassthrough/client_metadata_slowlog_rs.js | 2 | ||||
-rw-r--r-- | jstests/noPassthrough/log_find_getmore.js | 48 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/dbquery.cpp | 15 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/internedstring.defs | 6 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/mongo.cpp | 97 | ||||
-rw-r--r-- | src/mongo/shell/collection.js | 11 | ||||
-rw-r--r-- | src/mongo/shell/explain_query.js | 17 | ||||
-rw-r--r-- | src/mongo/shell/query.js | 354 |
12 files changed, 218 insertions, 406 deletions
diff --git a/jstests/core/count2.js b/jstests/core/count2.js index 9c8aaca7a32..21aa75aebd0 100644 --- a/jstests/core/count2.js +++ b/jstests/core/count2.js @@ -1,30 +1,32 @@ // @tags: [requires_getmore, requires_fastcount] - -t = db.count2; -t.drop(); +(function() { +"use strict"; +const coll = db.count2; +coll.drop(); for (var i = 0; i < 1000; i++) { - t.save({num: i, m: i % 20}); + assert.commandWorked(coll.insert({num: i, m: i % 20})); } -assert.eq(1000, t.count(), "A"); -assert.eq(1000, t.find().count(), "B"); -assert.eq(1000, t.find().toArray().length, "C"); +assert.eq(1000, coll.count()); +assert.eq(1000, coll.find().count()); +assert.eq(1000, coll.find().toArray().length); -assert.eq(50, t.find({m: 5}).toArray().length, "D"); -assert.eq(50, t.find({m: 5}).count(), "E"); +assert.eq(50, coll.find({m: 5}).toArray().length); +assert.eq(50, coll.find({m: 5}).count()); -assert.eq(40, t.find({m: 5}).skip(10).toArray().length, "F"); -assert.eq(50, t.find({m: 5}).skip(10).count(), "G"); -assert.eq(40, t.find({m: 5}).skip(10).countReturn(), "H"); +assert.eq(40, coll.find({m: 5}).skip(10).toArray().length); +assert.eq(50, coll.find({m: 5}).skip(10).count()); +assert.eq(40, coll.find({m: 5}).skip(10).count(true)); -assert.eq(20, t.find({m: 5}).skip(10).limit(20).toArray().length, "I"); -assert.eq(50, t.find({m: 5}).skip(10).limit(20).count(), "J"); -assert.eq(20, t.find({m: 5}).skip(10).limit(20).countReturn(), "K"); +assert.eq(20, coll.find({m: 5}).skip(10).limit(20).toArray().length); +assert.eq(50, coll.find({m: 5}).skip(10).limit(20).count()); +assert.eq(20, coll.find({m: 5}).skip(10).limit(20).count(true)); -assert.eq(5, t.find({m: 5}).skip(45).limit(20).countReturn(), "L"); +assert.eq(5, coll.find({m: 5}).skip(45).limit(20).count(true)); // Negative skip values should return error -var negSkipResult = db.runCommand({count: 't', skip: -2}); +var negSkipResult = db.runCommand({count: coll.getName(), skip: -2}); assert(!negSkipResult.ok, "negative skip value shouldn't work, n = " + negSkipResult.n); assert(negSkipResult.errmsg.length > 0, "no error msg for negative skip"); +}()); diff --git a/jstests/core/crud_api.js b/jstests/core/crud_api.js index 1f4ca21aac5..a6679cde794 100644 --- a/jstests/core/crud_api.js +++ b/jstests/core/crud_api.js @@ -667,10 +667,6 @@ var crudAPISpecTests = function crudAPISpecTests() { var cursor = coll.find({}).sort({a: 1}).tailable(false); assert.eq(2, (cursor._options & ~DBQuery.Option.slaveOk)); - // Check modifiers - var cursor = coll.find({}).modifiers({$hint: 'a_1'}); - assert.eq('a_1', cursor._query['$hint']); - // allowPartialResults var cursor = coll.find({}).allowPartialResults(); assert.eq(128, (cursor._options & ~DBQuery.Option.slaveOk)); diff --git a/jstests/libs/override_methods/set_read_preference_secondary.js b/jstests/libs/override_methods/set_read_preference_secondary.js index 2b38510d5ee..74554cc9289 100644 --- a/jstests/libs/override_methods/set_read_preference_secondary.js +++ b/jstests/libs/override_methods/set_read_preference_secondary.js @@ -138,19 +138,19 @@ function runCommandWithReadPreferenceSecondary( !bsonBinaryEqual({_: commandObj.$readPreference}, {_: kReadPreferenceSecondary})) { throw new Error("Cowardly refusing to override read preference of command: " + tojson(commandObj)); - } + } else if (!commandObj.hasOwnProperty("$readPreference")) { + if (commandObj === commandObjUnwrapped) { + // We wrap the command object using a "query" field rather than a "$query" field to + // match the implementation of DB.prototype._attachReadPreferenceToCommand(). + commandObj = {query: commandObj}; + } else { + // We create a copy of 'commandObj' to avoid mutating the parameter the caller + // specified. + commandObj = Object.assign({}, commandObj); + } - if (commandObj === commandObjUnwrapped) { - // We wrap the command object using a "query" field rather than a "$query" field to - // match the implementation of DB.prototype._attachReadPreferenceToCommand(). - commandObj = {query: commandObj}; - } else { - // We create a copy of 'commandObj' to avoid mutating the parameter the caller - // specified. - commandObj = Object.assign({}, commandObj); + commandObj.$readPreference = kReadPreferenceSecondary; } - - commandObj.$readPreference = kReadPreferenceSecondary; } const serverResponse = func.apply(conn, makeFuncArgs(commandObj)); diff --git a/jstests/noPassthrough/client_metadata_slowlog.js b/jstests/noPassthrough/client_metadata_slowlog.js index 1b10ff0a504..ef2c6a92d66 100644 --- a/jstests/noPassthrough/client_metadata_slowlog.js +++ b/jstests/noPassthrough/client_metadata_slowlog.js @@ -1,8 +1,6 @@ /** * Test that verifies client metadata is logged as part of slow query logging in MongoD. */ -load("jstests/libs/logv2_helpers.js"); - (function() { 'use strict'; @@ -23,14 +21,8 @@ assert.eq(count, 1, "expected 1 document"); print(`Checking ${conn.fullOptions.logFile} for client metadata message`); let log = cat(conn.fullOptions.logFile); -let predicate = null; -if (isJsonLog(conn)) { - predicate = - /Slow query.*test.foo.*"appName":"MongoDB Shell".*"command":{"count":"foo","query":{"\$where":{"\$code":"function\(\)/; -} else { - predicate = - /COMMAND .* command test.foo appName: "MongoDB Shell" command: count { count: "foo", query: { \$where: function\(\)/; -} +let predicate = + /Slow query.*test.foo.*"appName":"MongoDB Shell".*"command":{"count":"foo","query":{"\$where":{"\$code":"function\(\)/; // Dump the log line by line to avoid log truncation for (var a of log.split("\n")) { diff --git a/jstests/noPassthrough/client_metadata_slowlog_rs.js b/jstests/noPassthrough/client_metadata_slowlog_rs.js index d23b845a9bb..665a6453ba0 100644 --- a/jstests/noPassthrough/client_metadata_slowlog_rs.js +++ b/jstests/noPassthrough/client_metadata_slowlog_rs.js @@ -5,8 +5,6 @@ * requires_replication, * ] */ -load("jstests/libs/logv2_helpers.js"); - (function() { 'use strict'; diff --git a/jstests/noPassthrough/log_find_getmore.js b/jstests/noPassthrough/log_find_getmore.js index 8fc0e425d87..6bbe7ecb3fc 100644 --- a/jstests/noPassthrough/log_find_getmore.js +++ b/jstests/noPassthrough/log_find_getmore.js @@ -2,8 +2,6 @@ * Confirms that the log output for find and getMore are in the expected format. * @tags: [requires_profiling] */ -load("jstests/libs/logv2_helpers.js"); - (function() { "use strict"; @@ -55,17 +53,9 @@ cursor.next(); // Perform initial query and retrieve first document in batch. let cursorid = getLatestProfilerEntry(testDB).cursorid; let logLine = [ - 'command log_getmore.test appName: "MongoDB Shell" command: find { find: "test", filter:' + - ' { a: { $gt: 0.0 } }, skip: 1.0, batchSize: 5.0, limit: 10.0, singleBatch: false, sort:' + - ' { a: 1.0 }, hint: { a: 1.0 }', - 'queryHash:' + '"msg":"Slow query","attr":{"type":"command","ns":"log_getmore.test","appName":"MongoDB Shell","command":{"find":"test","filter":{"a":{"$gt":0}},"skip":1,"batchSize":5,"limit":10,"singleBatch":false,"sort":{"a":1},"hint":{"a":1}', + '"queryHash":' ]; -if (isJsonLog(conn)) { - logLine = [ - '"msg":"Slow query","attr":{"type":"command","ns":"log_getmore.test","appName":"MongoDB Shell","command":{"find":"test","filter":{"a":{"$gt":0}},"skip":1,"batchSize":5,"limit":10,"singleBatch":false,"sort":{"a":1},"hint":{"a":1}', - '"queryHash":' - ]; -} // Check the logs to verify that find appears as above. assertLogLineContains(conn, logLine); @@ -87,23 +77,12 @@ function cursorIdToString(cursorId) { } logLine = [ - 'command log_getmore.test appName: "MongoDB Shell" command: getMore { getMore: ' + - cursorIdToString(cursorid) + ', collection: "test", batchSize: 5.0', - 'originatingCommand: { find: "test", ' + - 'filter: { a: { $gt: 0.0 } }, skip: 1.0, batchSize: 5.0, limit: 10.0, singleBatch: ' + - 'false, sort: { a: 1.0 }, hint: { a: 1.0 }', - 'queryHash:' + `"msg":"Slow query","attr":{"type":"command","ns":"log_getmore.test","appName":"MongoDB Shell","command":{"getMore":${ + cursorIdToString(cursorid)},"collection":"test","batchSize":5,`, + '"originatingCommand":{"find":"test","filter":{"a":{"$gt":0}},"skip":1,"batchSize":5,"limit":10,"singleBatch":false,"sort":{"a":1},"hint":{"a":1}', + '"queryHash":' ]; -if (isJsonLog(conn)) { - logLine = [ - `"msg":"Slow query","attr":{"type":"command","ns":"log_getmore.test","appName":"MongoDB Shell","command":{"getMore":${ - cursorIdToString(cursorid)},"collection":"test","batchSize":5,`, - '"originatingCommand":{"find":"test","filter":{"a":{"$gt":0}},"skip":1,"batchSize":5,"limit":10,"singleBatch":false,"sort":{"a":1},"hint":{"a":1}', - '"queryHash":' - ]; -} - assertLogLineContains(conn, logLine); // TEST: Verify the log format of a getMore command following an aggregation. @@ -113,20 +92,11 @@ cursorid = getLatestProfilerEntry(testDB).cursorid; assert.eq(cursor.itcount(), 10); logLine = [ - 'command log_getmore.test appName: "MongoDB Shell" command: getMore { getMore: ' + - cursorIdToString(cursorid) + ', collection: "test"', - 'originatingCommand: { aggregate: "test", pipeline: ' + - '[ { $match: { a: { $gt: 0.0 } } } ], cursor: { batchSize: 0.0 }, hint: { a: 1.0 }' + `"msg":"Slow query","attr":{"type":"command","ns":"log_getmore.test","appName":"MongoDB Shell","command":{"getMore":${ + cursorIdToString(cursorid)},"collection":"test"`, + '"originatingCommand":{"aggregate":"test","pipeline":[{"$match":{"a":{"$gt":0}}}],"cursor":{"batchSize":0},"hint":{"a":1}' ]; -if (isJsonLog(conn)) { - logLine = [ - `"msg":"Slow query","attr":{"type":"command","ns":"log_getmore.test","appName":"MongoDB Shell","command":{"getMore":${ - cursorIdToString(cursorid)},"collection":"test"`, - '"originatingCommand":{"aggregate":"test","pipeline":[{"$match":{"a":{"$gt":0}}}],"cursor":{"batchSize":0},"hint":{"a":1}' - ]; -} - assertLogLineContains(conn, logLine); MongoRunner.stopMongod(conn); diff --git a/src/mongo/scripting/mozjs/dbquery.cpp b/src/mongo/scripting/mozjs/dbquery.cpp index e196c8d6a72..0e12f899609 100644 --- a/src/mongo/scripting/mozjs/dbquery.cpp +++ b/src/mongo/scripting/mozjs/dbquery.cpp @@ -59,7 +59,9 @@ void DBQueryInfo::construct(JSContext* cx, JS::CallArgs args) { o.setValue(InternedString::_collection, args.get(2)); o.setValue(InternedString::_ns, args.get(3)); - JS::RootedObject emptyObj(cx); + JSObject* newPlainObject = JS_NewPlainObject(cx); + uassert(6887104, "failed to create new JS object", newPlainObject); + JS::RootedObject emptyObj{cx, newPlainObject}; JS::RootedValue emptyObjVal(cx); emptyObjVal.setObjectOrNull(emptyObj); @@ -67,15 +69,15 @@ void DBQueryInfo::construct(JSContext* cx, JS::CallArgs args) { nullVal.setNull(); if (args.length() > 4 && args.get(4).isObject()) { - o.setValue(InternedString::_query, args.get(4)); + o.setValue(InternedString::_filter, args.get(4)); } else { - o.setValue(InternedString::_query, emptyObjVal); + o.setValue(InternedString::_filter, nullVal); } if (args.length() > 5 && args.get(5).isObject()) { - o.setValue(InternedString::_fields, args.get(5)); + o.setValue(InternedString::_projection, args.get(5)); } else { - o.setValue(InternedString::_fields, nullVal); + o.setValue(InternedString::_projection, nullVal); } if (args.length() > 6 && args.get(6).isNumber()) { @@ -102,9 +104,10 @@ void DBQueryInfo::construct(JSContext* cx, JS::CallArgs args) { o.setNumber(InternedString::_options, 0); } + o.setValue(InternedString::_additionalCmdParams, emptyObjVal); + o.setValue(InternedString::_cursor, nullVal); o.setNumber(InternedString::_numReturned, 0); - o.setBoolean(InternedString::_special, false); args.rval().setObjectOrNull(thisv); } diff --git a/src/mongo/scripting/mozjs/internedstring.defs b/src/mongo/scripting/mozjs/internedstring.defs index 2fc2a8e5eff..1b5d102f36e 100644 --- a/src/mongo/scripting/mozjs/internedstring.defs +++ b/src/mongo/scripting/mozjs/internedstring.defs @@ -4,6 +4,7 @@ * with different definitions of MONGO_MOZJS_INTERNED_STRING. */ +MONGO_MOZJS_INTERNED_STRING(_additionalCmdParams, "_additionalCmdParams") MONGO_MOZJS_INTERNED_STRING(arrayAccess, "arrayAccess") MONGO_MOZJS_INTERNED_STRING(authenticated, "authenticated") MONGO_MOZJS_INTERNED_STRING(_batchSize, "_batchSize") @@ -22,8 +23,8 @@ MONGO_MOZJS_INTERNED_STRING(dollar_db, "$db") MONGO_MOZJS_INTERNED_STRING(dollar_id, "$id") MONGO_MOZJS_INTERNED_STRING(dollar_ref, "$ref") MONGO_MOZJS_INTERNED_STRING(exactValueString, "exactValueString") -MONGO_MOZJS_INTERNED_STRING(_fields, "_fields") MONGO_MOZJS_INTERNED_STRING(fileName, "fileName") +MONGO_MOZJS_INTERNED_STRING(_filter, "_filter") MONGO_MOZJS_INTERNED_STRING(flags, "flags") MONGO_MOZJS_INTERNED_STRING(floatApprox, "floatApprox") MONGO_MOZJS_INTERNED_STRING(_fullName, "_fullName") @@ -48,8 +49,8 @@ MONGO_MOZJS_INTERNED_STRING(_numReturned, "_numReturned") MONGO_MOZJS_INTERNED_STRING(_options, "_options") MONGO_MOZJS_INTERNED_STRING(options, "options") MONGO_MOZJS_INTERNED_STRING(password, "password") +MONGO_MOZJS_INTERNED_STRING(_projection, "_projection") MONGO_MOZJS_INTERNED_STRING(prototype, "prototype") -MONGO_MOZJS_INTERNED_STRING(_query, "_query") MONGO_MOZJS_INTERNED_STRING(readOnly, "readOnly") MONGO_MOZJS_INTERNED_STRING(reason, "reason") MONGO_MOZJS_INTERNED_STRING(_retryWrites, "_retryWrites") @@ -62,7 +63,6 @@ MONGO_MOZJS_INTERNED_STRING(singleton, "singleton") MONGO_MOZJS_INTERNED_STRING(_skip, "_skip") MONGO_MOZJS_INTERNED_STRING(slaveOk, "slaveOk") MONGO_MOZJS_INTERNED_STRING(source, "source") -MONGO_MOZJS_INTERNED_STRING(_special, "_special") MONGO_MOZJS_INTERNED_STRING(stack, "stack") MONGO_MOZJS_INTERNED_STRING(str, "str") MONGO_MOZJS_INTERNED_STRING(top, "top") diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp index cf828514c1b..caf27e11b7d 100644 --- a/src/mongo/scripting/mozjs/mongo.cpp +++ b/src/mongo/scripting/mozjs/mongo.cpp @@ -34,7 +34,6 @@ #include "mongo/bson/simple_bsonelement_comparator.h" #include "mongo/client/client_api_version_parameters_gen.h" -#include "mongo/client/client_deprecated.h" #include "mongo/client/dbclient_base.h" #include "mongo/client/dbclient_rs.h" #include "mongo/client/global_conn_pool.h" @@ -320,95 +319,31 @@ void MongoBase::Functions::_runCommandImpl::call(JSContext* cx, JS::CallArgs arg }); } -namespace { -/** - * WARNING: Do not add new callers! This is a special-purpose function that exists only to - * accommodate the shell. - * - * Although OP_QUERY find is no longer supported by either the shell or the server, the shell's - * exhaust path still internally constructs a request that resembles on OP_QUERY find. This function - * converts this query to a 'FindCommandRequest'. - */ -FindCommandRequest upconvertLegacyOpQueryToFindCommandRequest(NamespaceString nss, - const BSONObj& opQueryFormattedBson, - const BSONObj& projection, - int limit, - int skip, - int batchSize, - int queryOptions) { - FindCommandRequest findCommand{std::move(nss)}; - - client_deprecated::initFindFromLegacyOptions(opQueryFormattedBson, queryOptions, &findCommand); - - if (!projection.isEmpty()) { - findCommand.setProjection(projection.getOwned()); - } - - if (limit) { - // To avoid changing the behavior of the shell API, we allow the caller of the JS code to - // use a negative limit to request at most a single batch. - if (limit < 0) { - findCommand.setLimit(-static_cast<int64_t>(limit)); - findCommand.setSingleBatch(true); - } else { - findCommand.setLimit(limit); - } - } - if (skip) { - findCommand.setSkip(skip); - } - if (batchSize) { - findCommand.setBatchSize(batchSize); - } - - return findCommand; -} -} // namespace - void MongoBase::Functions::find::call(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); - if (args.length() != 7) - uasserted(ErrorCodes::BadValue, "find needs 7 args"); - - if (!args.get(1).isObject()) - uasserted(ErrorCodes::BadValue, "needs to be an object"); + tassert(6887100, "wrong number of args for find operation", args.length() == 3); + tassert(6887101, "first arg must be an object", args.get(0).isObject()); + tassert(6887102, "second arg must be an object", args.get(1).isObject()); + tassert(6887103, "third arg must be a boolean", args.get(2).isBoolean()); auto conn = getConnection(args); - std::string ns = ValueWriter(cx, args.get(0)).toString(); - - BSONObj fields; - BSONObj q = ValueWriter(cx, args.get(1)).toBSON(); - - bool haveFields = false; + const BSONObj cmdObj = ValueWriter(cx, args.get(0)).toBSON(); + const BSONObj readPreference = ValueWriter(cx, args.get(1)).toBSON(); + const bool isExhaust = ValueWriter(cx, args.get(2)).toBoolean(); - if (args.get(2).isObject()) { - JS::RootedObject obj(cx, args.get(2).toObjectOrNull()); - - ObjectWrapper(cx, obj).enumerate([&](jsid) { - haveFields = true; - return false; - }); + FindCommandRequest findCmdRequest = + FindCommandRequest::parse(IDLParserContext("FindCommandRequest"), cmdObj); + ReadPreferenceSetting readPref; + if (!readPreference.isEmpty()) { + readPref = uassertStatusOK(ReadPreferenceSetting::fromInnerBSON(readPreference)); } + ExhaustMode exhaustMode = isExhaust ? ExhaustMode::kOn : ExhaustMode::kOff; - if (haveFields) - fields = ValueWriter(cx, args.get(2)).toBSON(); - - int limit = ValueWriter(cx, args.get(3)).toInt32(); - int nToSkip = ValueWriter(cx, args.get(4)).toInt32(); - int batchSize = ValueWriter(cx, args.get(5)).toInt32(); - int options = ValueWriter(cx, args.get(6)).toInt32(); - - auto findCmd = upconvertLegacyOpQueryToFindCommandRequest( - NamespaceString{ns}, q, fields, limit, nToSkip, batchSize, options); - auto readPref = uassertStatusOK(ReadPreferenceSetting::fromContainingBSON(q)); - ExhaustMode exhaustMode = - ((options & QueryOption_Exhaust) != 0) ? ExhaustMode::kOn : ExhaustMode::kOff; - std::unique_ptr<DBClientCursor> cursor = conn->find(std::move(findCmd), readPref, exhaustMode); - if (!cursor.get()) { - uasserted(ErrorCodes::InternalError, "error doing query: failed"); - } + std::unique_ptr<DBClientCursor> cursor = + conn->find(std::move(findCmdRequest), readPref, exhaustMode); + uassert(ErrorCodes::InternalError, "error doing query: failed", cursor); JS::RootedObject c(cx); scope->getProto<CursorInfo>().newObject(&c); diff --git a/src/mongo/shell/collection.js b/src/mongo/shell/collection.js index 85c6b92c09d..2afb52f04d8 100644 --- a/src/mongo/shell/collection.js +++ b/src/mongo/shell/collection.js @@ -219,7 +219,7 @@ DBCollection.prototype._massageObject = function(q) { throw Error("don't know how to massage : " + type); }; -DBCollection.prototype.find = function(query, fields, limit, skip, batchSize, options) { +DBCollection.prototype.find = function(filter, projection, limit, skip, batchSize, options) { // Verify that API version parameters are not supplied via the shell helper. assert.noAPIParams(options); @@ -227,8 +227,8 @@ DBCollection.prototype.find = function(query, fields, limit, skip, batchSize, op this._db, this, this._fullName, - this._massageObject(query), - fields, + this._massageObject(filter), + projection, limit, skip, batchSize, @@ -253,8 +253,9 @@ DBCollection.prototype.find = function(query, fields, limit, skip, batchSize, op return cursor; }; -DBCollection.prototype.findOne = function(query, fields, options, readConcern, collation) { - var cursor = this.find(query, fields, -1 /* limit */, 0 /* skip*/, 0 /* batchSize */, options); +DBCollection.prototype.findOne = function(filter, projection, options, readConcern, collation) { + var cursor = + this.find(filter, projection, -1 /* limit */, 0 /* skip*/, 0 /* batchSize */, options); if (readConcern) { cursor = cursor.readConcern(readConcern); diff --git a/src/mongo/shell/explain_query.js b/src/mongo/shell/explain_query.js index 453751d88de..f109ba3f72d 100644 --- a/src/mongo/shell/explain_query.js +++ b/src/mongo/shell/explain_query.js @@ -90,25 +90,22 @@ var DBExplainQuery = (function() { // True means to always apply the skip and limit values. innerCmd = this._query._convertToCountCmd(this._applySkipLimit); } else { - var canAttachReadPref = false; - innerCmd = this._query._convertToCommand(canAttachReadPref); + innerCmd = this._query._convertToCommand(); } var explainCmd = {explain: innerCmd}; explainCmd["verbosity"] = this._verbosity; - // If "maxTimeMS" is set on innerCmd, it needs to be propagated to the top-level - // of explainCmd so that it has the intended effect. + + // If "maxTimeMS" or "$readPreference" are set on 'innerCmd', they need to be + // propagated to the top-level of 'explainCmd' in order to have the intended effect. if (innerCmd.hasOwnProperty("maxTimeMS")) { explainCmd.maxTimeMS = innerCmd.maxTimeMS; } - - var explainDb = this._query._db; - - if ("$readPreference" in this._query._query) { - var prefObj = this._query._query.$readPreference; - explainCmd = explainDb._attachReadPreferenceToCommand(explainCmd, prefObj); + if (innerCmd.hasOwnProperty("$readPreference")) { + explainCmd["$readPreference"] = innerCmd["$readPreference"]; } + var explainDb = this._query._db; var explainResult = explainDb.runReadCommand(explainCmd, null, this._query._options); return Explainable.throwOrReturn(explainResult); diff --git a/src/mongo/shell/query.js b/src/mongo/shell/query.js index fc8dd6e8731..d5a34bb20d8 100644 --- a/src/mongo/shell/query.js +++ b/src/mongo/shell/query.js @@ -1,22 +1,27 @@ // query.js if (typeof DBQuery == "undefined") { - DBQuery = function(mongo, db, collection, ns, query, fields, limit, skip, batchSize, options) { - this._mongo = mongo; // 0 - this._db = db; // 1 - this._collection = collection; // 2 - this._ns = ns; // 3 - - this._query = query || {}; // 4 - this._fields = fields; // 5 - this._limit = limit || 0; // 6 - this._skip = skip || 0; // 7 + DBQuery = function( + mongo, db, collection, ns, filter, projection, limit, skip, batchSize, options) { + this._mongo = mongo; + this._db = db; + this._collection = collection; + this._ns = ns; + + this._filter = filter || {}; + this._projection = projection; + this._limit = limit || 0; + this._skip = skip || 0; this._batchSize = batchSize || 0; this._options = options || 0; + // This houses find command parameters which are not passed to this constructor function or + // held as properties of 'this'. When the find command represented by this 'DBQuery' is + // assembled, this object will be appended to the find command object verbatim. + this._additionalCmdParams = {}; + this._cursor = null; this._numReturned = 0; - this._special = false; this._prettyShell = false; }; print("DBQuery probably won't have array access "); @@ -63,27 +68,18 @@ DBQuery.prototype.help = function() { }; DBQuery.prototype.clone = function() { - var q = new DBQuery(this._mongo, - this._db, - this._collection, - this._ns, - this._query, - this._fields, - this._limit, - this._skip, - this._batchSize, - this._options); - q._special = this._special; - return q; -}; - -DBQuery.prototype._ensureSpecial = function() { - if (this._special) - return; - - var n = {query: this._query}; - this._query = n; - this._special = true; + const cloneResult = new DBQuery(this._mongo, + this._db, + this._collection, + this._ns, + this._filter, + this._projection, + this._limit, + this._skip, + this._batchSize, + this._options); + cloneResult._additionalCmdParams = this._additionalCmdParams; + return cloneResult; }; DBQuery.prototype._checkModify = function() { @@ -117,43 +113,43 @@ DBQuery.prototype._exec = function() { assert.eq(0, this._numReturned); this._cursorSeen = 0; + const findCmd = this._convertToCommand(); + // We forbid queries with the exhaust option from running as 'DBCommandCursor', because // 'DBCommandCursor' does not currently support exhaust. // // In the future, we could unify the shell's exhaust and non-exhaust code paths. if (!this._isExhaustCursor()) { - var canAttachReadPref = true; - var findCmd = this._convertToCommand(canAttachReadPref); - var cmdRes = this._db.runReadCommand(findCmd, null, this._options); + const cmdRes = this._db.runReadCommand(findCmd, null, this._options); this._cursor = new DBCommandCursor(this._db, cmdRes, this._batchSize); } else { // The exhaust cursor option is disallowed under a session because it doesn't work as - // expected, but all requests from the shell use implicit sessions, so to allow users - // to continue using exhaust cursors through the shell, they are only disallowed with + // expected, but all requests from the shell use implicit sessions, so to allow users to + // continue using exhaust cursors through the shell, they are only disallowed with // explicit sessions. if (this._db.getSession()._isExplicit) { throw new Error("Explicit session is not allowed for exhaust queries"); } - if (this._special && this._query.readConcern) { + if (findCmd["readConcern"]) { throw new Error("readConcern is not allowed for exhaust queries"); } - if (this._special && this._query.collation) { + if (findCmd["collation"]) { throw new Error("collation is not allowed for exhaust queries"); } - if (this._special && this._query._allowDiskUse) { + if (findCmd["allowDiskUse"]) { throw new Error("allowDiskUse is not allowed for exhaust queries"); } - this._cursor = this._mongo.find(this._ns, - this._query, - this._fields, - this._limit, - this._skip, - this._batchSize, - this._options); + let readPreference = {}; + if (findCmd["$readPreference"]) { + readPreference = findCmd["$readPreference"]; + } + + findCmd["$db"] = this._db.getName(); + this._cursor = this._mongo.find(findCmd, readPreference, true /*isExhaust*/); } } return this._cursor; @@ -161,21 +157,14 @@ DBQuery.prototype._exec = function() { /** * Internal helper used to convert this cursor into the format required by the find command. - * - * If canAttachReadPref is true, may attach a read preference to the resulting command using the - * "wrapped form": { $query: { <cmd>: ... }, $readPreference: { ... } }. */ -DBQuery.prototype._convertToCommand = function(canAttachReadPref) { - var cmd = {}; +DBQuery.prototype._convertToCommand = function() { + let cmd = {}; cmd["find"] = this._collection.getName(); - if (this._special) { - if (this._query.query) { - cmd["filter"] = this._query.query; - } - } else if (this._query) { - cmd["filter"] = this._query; + if (this._filter) { + cmd["filter"] = this._filter; } if (this._skip) { @@ -201,52 +190,8 @@ DBQuery.prototype._convertToCommand = function(canAttachReadPref) { } } - if ("orderby" in this._query) { - cmd["sort"] = this._query.orderby; - } - - if (this._fields) { - cmd["projection"] = this._fields; - } - - if ("$hint" in this._query) { - cmd["hint"] = this._query.$hint; - } - - if ("$comment" in this._query) { - cmd["comment"] = this._query.$comment; - } - - if ("$maxTimeMS" in this._query) { - cmd["maxTimeMS"] = this._query.$maxTimeMS; - } - - if ("$max" in this._query) { - cmd["max"] = this._query.$max; - } - - if ("$min" in this._query) { - cmd["min"] = this._query.$min; - } - - if ("$returnKey" in this._query) { - cmd["returnKey"] = this._query.$returnKey; - } - - if ("$showDiskLoc" in this._query) { - cmd["showRecordId"] = this._query.$showDiskLoc; - } - - if ("readConcern" in this._query) { - cmd["readConcern"] = this._query.readConcern; - } - - if ("collation" in this._query) { - cmd["collation"] = this._query.collation; - } - - if ("allowDiskUse" in this._query) { - cmd["allowDiskUse"] = this._query.allowDiskUse; + if (this._projection) { + cmd["projection"] = this._projection; } if ((this._options & DBQuery.Option.tailable) != 0) { @@ -265,13 +210,7 @@ DBQuery.prototype._convertToCommand = function(canAttachReadPref) { cmd["allowPartialResults"] = true; } - if (canAttachReadPref) { - // If there is a readPreference, use the wrapped command form. - if ("$readPreference" in this._query) { - var prefObj = this._query.$readPreference; - cmd = this._db._attachReadPreferenceToCommand(cmd, prefObj); - } - } + cmd = Object.merge(cmd, this._additionalCmdParams); return cmd; }; @@ -306,20 +245,19 @@ DBQuery.prototype.hasNext = function() { this._cursor.close(); return false; } - var o = this._cursor.hasNext(); - return o; + return this._cursor.hasNext(); }; DBQuery.prototype.next = function() { this._exec(); - var o = this._cursor.hasNext(); + let o = this._cursor.hasNext(); if (o) this._cursorSeen++; else throw Error("error hasNext: " + o); - var ret = this._cursor.next(); + let ret = this._cursor.next(); if (ret.$err) { throw _getErrorWithCode(ret, "error: " + tojson(ret)); } @@ -331,7 +269,7 @@ DBQuery.prototype.next = function() { DBQuery.prototype.objsLeftInBatch = function() { this._exec(); - var ret = this._cursor.objsLeftInBatch(); + let ret = this._cursor.objsLeftInBatch(); if (ret.$err) throw _getErrorWithCode(ret, "error: " + tojson(ret)); @@ -353,7 +291,7 @@ DBQuery.prototype.toArray = function() { if (this._arr) return this._arr; - var a = []; + let a = []; while (this.hasNext()) a.push(this.next()); this._arr = a; @@ -361,42 +299,41 @@ DBQuery.prototype.toArray = function() { }; DBQuery.prototype._convertToCountCmd = function(applySkipLimit) { - var cmd = {count: this._collection.getName()}; + let cmd = {count: this._collection.getName()}; - if (this._query) { - if (this._special) { - cmd.query = this._query.query; - if (this._query.$maxTimeMS) { - cmd.maxTimeMS = this._query.$maxTimeMS; - } - if (this._query.$hint) { - cmd.hint = this._query.$hint; - } - if (this._query.readConcern) { - cmd.readConcern = this._query.readConcern; - } - if (this._query.collation) { - cmd.collation = this._query.collation; - } - } else { - cmd.query = this._query; - } + if (this._filter) { + cmd["query"] = this._filter; + } + + if (this._additionalCmdParams["maxTimeMS"]) { + cmd["maxTimeMS"] = this._additionalCmdParams["maxTimeMS"]; + } + if (this._additionalCmdParams["hint"]) { + cmd["hint"] = this._additionalCmdParams["hint"]; + } + if (this._additionalCmdParams["readConcern"]) { + cmd["readConcern"] = this._additionalCmdParams["readConcern"]; + } + if (this._additionalCmdParams["collation"]) { + cmd["collation"] = this._additionalCmdParams["collation"]; } if (applySkipLimit) { - if (this._limit) - cmd.limit = this._limit; - if (this._skip) - cmd.skip = this._skip; + if (this._limit) { + cmd["limit"] = this._limit; + } + if (this._skip) { + cmd["skip"] = this._skip; + } } return cmd; }; DBQuery.prototype.count = function(applySkipLimit) { - var cmd = this._convertToCountCmd(applySkipLimit); + let cmd = this._convertToCountCmd(applySkipLimit); - var res = this._db.runReadCommand(cmd); + let res = this._db.runReadCommand(cmd); if (res && res.n != null) return res.n; throw _getErrorWithCode(res, "count failed: " + tojson(res)); @@ -406,34 +343,22 @@ DBQuery.prototype.size = function() { return this.count(true); }; -DBQuery.prototype.countReturn = function() { - var c = this.count(); - - if (this._skip) - c = c - this._skip; - - if (this._limit > 0 && this._limit < c) - return this._limit; - - return c; -}; - /** * iterative count - only for testing */ DBQuery.prototype.itcount = function() { - var num = 0; + let num = 0; // Track how many bytes we've used this cursor to iterate iterated. This function can be called // with some very large cursors. SpiderMonkey appears happy to allow these objects to // accumulate, so regular gc() avoids an overly large memory footprint. // // TODO: migrate this function into c++ - var bytesSinceGC = 0; + let bytesSinceGC = 0; while (this.hasNext()) { num++; - var nextDoc = this.next(); + let nextDoc = this.next(); bytesSinceGC += Object.bsonsize(nextDoc); // Garbage collect every 10 MB. @@ -449,26 +374,28 @@ DBQuery.prototype.length = function() { return this.toArray().length; }; -DBQuery.prototype._addSpecial = function(name, value) { - this._ensureSpecial(); - this._query[name] = value; - return this; -}; - DBQuery.prototype.sort = function(sortBy) { - return this._addSpecial("orderby", sortBy); + this._checkModify(); + this._additionalCmdParams["sort"] = sortBy; + return this; }; DBQuery.prototype.hint = function(hint) { - return this._addSpecial("$hint", hint); + this._checkModify(); + this._additionalCmdParams["hint"] = hint; + return this; }; DBQuery.prototype.min = function(min) { - return this._addSpecial("$min", min); + this._checkModify(); + this._additionalCmdParams["min"] = min; + return this; }; DBQuery.prototype.max = function(max) { - return this._addSpecial("$max", max); + this._checkModify(); + this._additionalCmdParams["max"] = max; + return this; }; /** @@ -479,39 +406,49 @@ DBQuery.prototype.showDiskLoc = function() { }; DBQuery.prototype.showRecordId = function() { - return this._addSpecial("$showDiskLoc", true); + this._checkModify(); + this._additionalCmdParams["showRecordId"] = true; + return this; }; DBQuery.prototype.maxTimeMS = function(maxTimeMS) { - return this._addSpecial("$maxTimeMS", maxTimeMS); + this._checkModify(); + this._additionalCmdParams["maxTimeMS"] = maxTimeMS; + return this; }; DBQuery.prototype.readConcern = function(level, atClusterTime = undefined) { - var readConcernObj = + this._checkModify(); + let readConcernObj = atClusterTime ? {level: level, atClusterTime: atClusterTime} : {level: level}; - - return this._addSpecial("readConcern", readConcernObj); + this._additionalCmdParams["readConcern"] = readConcernObj; + return this; }; DBQuery.prototype.collation = function(collationSpec) { - return this._addSpecial("collation", collationSpec); + this._checkModify(); + this._additionalCmdParams["collation"] = collationSpec; + return this; }; DBQuery.prototype.allowDiskUse = function(value) { - return this._addSpecial("allowDiskUse", (value === undefined ? true : value)); + this._checkModify(); + value = (value === undefined) ? true : value; + this._additionalCmdParams["allowDiskUse"] = value; + return this; }; /** * Sets the read preference for this cursor. * - * @param mode {string} read preference mode to use. - * @param tagSet {Array.<Object>} optional. The list of tags to use, order matters. - * @param hedgeOptions {<Object>} optional. The hedge options of the form {enabled: <bool>}. + * 'mode': A string indicating read preference mode to use. + * 'tagSet': An optional list of tags to use. Order matters. + * 'hedgeOptions': An optional object of the form {enabled: <bool>}. * - * @return this cursor + * Returns 'this'. */ DBQuery.prototype.readPref = function(mode, tagSet, hedgeOptions) { - var readPrefObj = {mode: mode}; + let readPrefObj = {mode: mode}; if (tagSet) { readPrefObj.tags = tagSet; @@ -521,7 +458,8 @@ DBQuery.prototype.readPref = function(mode, tagSet, hedgeOptions) { readPrefObj.hedge = hedgeOptions; } - return this._addSpecial("$readPreference", readPrefObj); + this._additionalCmdParams["$readPreference"] = readPrefObj; + return this; }; DBQuery.prototype.forEach = function(func) { @@ -530,7 +468,7 @@ DBQuery.prototype.forEach = function(func) { }; DBQuery.prototype.map = function(func) { - var a = []; + let a = []; while (this.hasNext()) a.push(func(this.next())); return a; @@ -541,16 +479,20 @@ DBQuery.prototype.arrayAccess = function(idx) { }; DBQuery.prototype.comment = function(comment) { - return this._addSpecial("$comment", comment); + this._checkModify(); + this._additionalCmdParams["comment"] = comment; + return this; }; DBQuery.prototype.explain = function(verbose) { - var explainQuery = new DBExplainQuery(this, verbose); + let explainQuery = new DBExplainQuery(this, verbose); return explainQuery.finish(); }; DBQuery.prototype.returnKey = function() { - return this._addSpecial("$returnKey", true); + this._checkModify(); + this._additionalCmdParams["returnKey"] = true; + return this; }; DBQuery.prototype.pretty = function() { @@ -560,15 +502,15 @@ DBQuery.prototype.pretty = function() { DBQuery.prototype.shellPrint = function() { try { - var start = new Date().getTime(); - var n = 0; + let start = new Date().getTime(); + let n = 0; while (this.hasNext() && n < DBQuery.shellBatchSize) { - var s = this._prettyShell ? tojson(this.next()) : tojson(this.next(), "", true); + let s = this._prettyShell ? tojson(this.next()) : tojson(this.next(), "", true); print(s); n++; } if (typeof _verboseShell !== 'undefined' && _verboseShell) { - var time = new Date().getTime() - start; + let time = new Date().getTime() - start; print("Fetched " + n + " record(s) in " + time + "ms"); } if (this.hasNext()) { @@ -583,7 +525,7 @@ DBQuery.prototype.shellPrint = function() { }; DBQuery.prototype.toString = function() { - return "DBQuery: " + this._ns + " -> " + tojson(this._query); + return "DBQuery: " + this._ns + " -> " + tojson(this._filter); }; // @@ -627,7 +569,7 @@ DBQuery.prototype.noCursorTimeout = function() { */ DBQuery.prototype.projection = function(document) { this._checkModify(); - this._fields = document; + this._projection = document; return this; }; @@ -652,30 +594,6 @@ DBQuery.prototype.tailable = function(awaitData) { return this; }; -/** - * Specify a document containing modifiers for the query. - * - * @method - * @see http://docs.mongodb.org/manual/reference/operator/query-modifier/ - * @param {object} document A document containing modifers to apply to the cursor. - * @return {DBQuery} - */ -DBQuery.prototype.modifiers = function(document) { - this._checkModify(); - - for (var name in document) { - if (name[0] != '$') { - throw new Error('All modifiers must start with a $ such as $returnKey'); - } - } - - for (var name in document) { - this._addSpecial(name, document[name]); - } - - return this; -}; - DBQuery.prototype.close = function() { if (this._cursor) { this._cursor.close(); @@ -772,11 +690,11 @@ DBCommandCursor.prototype.isExhausted = function() { DBCommandCursor.prototype.close = function() { if (bsonWoCompare({_: this._cursorid}, {_: NumberLong(0)}) !== 0) { - var killCursorCmd = { + let killCursorCmd = { killCursors: this._collName, cursors: [this._cursorid], }; - var cmdRes = this._db.runCommand(killCursorCmd); + let cmdRes = this._db.runCommand(killCursorCmd); if (cmdRes.ok != 1) { throw _getErrorWithCode(cmdRes, "killCursors command failed: " + tojson(cmdRes)); } @@ -806,7 +724,7 @@ DBCommandCursor.prototype._updatePostBatchResumeToken = function(cursorObj) { */ DBCommandCursor.prototype._runGetMoreCommand = function() { // Construct the getMore command. - var getMoreCmd = {getMore: this._cursorid, collection: this._collName}; + let getMoreCmd = {getMore: this._cursorid, collection: this._collName}; if (this._batchSize) { getMoreCmd["batchSize"] = this._batchSize; @@ -823,7 +741,7 @@ DBCommandCursor.prototype._runGetMoreCommand = function() { } // Deliver the getMore command, and check for errors in the response. - var cmdRes = this._db.runCommand(getMoreCmd); + let cmdRes = this._db.runCommand(getMoreCmd); assert.commandWorked(cmdRes, () => "getMore command failed: " + tojson(cmdRes)); if (this._ns !== cmdRes.cursor.ns) { |