1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #include "mongo/platform/basic.h"
32 
33 #include "mongo/db/query/query_request.h"
34 
35 #include "mongo/base/status.h"
36 #include "mongo/base/status_with.h"
37 #include "mongo/bson/simple_bsonobj_comparator.h"
38 #include "mongo/client/dbclientinterface.h"
39 #include "mongo/db/catalog/uuid_catalog.h"
40 #include "mongo/db/commands.h"
41 #include "mongo/db/dbmessage.h"
42 #include "mongo/db/namespace_string.h"
43 #include "mongo/db/repl/read_concern_args.h"
44 #include "mongo/stdx/memory.h"
45 #include "mongo/util/assert_util.h"
46 #include "mongo/util/mongoutils/str.h"
47 
48 namespace mongo {
49 
50 using std::string;
51 using std::unique_ptr;
52 
53 const std::string QueryRequest::kUnwrappedReadPrefField("$queryOptions");
54 const std::string QueryRequest::kWrappedReadPrefField("$readPreference");
55 
56 const char QueryRequest::cmdOptionMaxTimeMS[] = "maxTimeMS";
57 const char QueryRequest::queryOptionMaxTimeMS[] = "$maxTimeMS";
58 
59 const string QueryRequest::metaGeoNearDistance("geoNearDistance");
60 const string QueryRequest::metaGeoNearPoint("geoNearPoint");
61 const string QueryRequest::metaIndexKey("indexKey");
62 const string QueryRequest::metaRecordId("recordId");
63 const string QueryRequest::metaSortKey("sortKey");
64 const string QueryRequest::metaTextScore("textScore");
65 
66 const long long QueryRequest::kDefaultBatchSize = 101;
67 
68 namespace {
69 
checkFieldType(const BSONElement & el,BSONType type)70 Status checkFieldType(const BSONElement& el, BSONType type) {
71     if (type != el.type()) {
72         str::stream ss;
73         ss << "Failed to parse: " << el.toString() << ". "
74            << "'" << el.fieldName() << "' field must be of BSON type " << typeName(type) << ".";
75         return Status(ErrorCodes::FailedToParse, ss);
76     }
77 
78     return Status::OK();
79 }
80 
81 // Find command field names.
82 const char kFilterField[] = "filter";
83 const char kProjectionField[] = "projection";
84 const char kSortField[] = "sort";
85 const char kHintField[] = "hint";
86 const char kCollationField[] = "collation";
87 const char kSkipField[] = "skip";
88 const char kLimitField[] = "limit";
89 const char kBatchSizeField[] = "batchSize";
90 const char kNToReturnField[] = "ntoreturn";
91 const char kSingleBatchField[] = "singleBatch";
92 const char kCommentField[] = "comment";
93 const char kMaxScanField[] = "maxScan";
94 const char kMaxField[] = "max";
95 const char kMinField[] = "min";
96 const char kReturnKeyField[] = "returnKey";
97 const char kShowRecordIdField[] = "showRecordId";
98 const char kSnapshotField[] = "snapshot";
99 const char kTailableField[] = "tailable";
100 const char kOplogReplayField[] = "oplogReplay";
101 const char kNoCursorTimeoutField[] = "noCursorTimeout";
102 const char kAwaitDataField[] = "awaitData";
103 const char kPartialResultsField[] = "allowPartialResults";
104 const char kTermField[] = "term";
105 const char kOptionsField[] = "options";
106 
107 // Field names for sorting options.
108 const char kNaturalSortField[] = "$natural";
109 
110 }  // namespace
111 
112 const char QueryRequest::kFindCommandName[] = "find";
113 const char QueryRequest::kShardVersionField[] = "shardVersion";
114 
QueryRequest(NamespaceString nss)115 QueryRequest::QueryRequest(NamespaceString nss) : _nss(std::move(nss)) {}
QueryRequest(CollectionUUID uuid)116 QueryRequest::QueryRequest(CollectionUUID uuid) : _uuid(std::move(uuid)) {}
117 
refreshNSS(OperationContext * opCtx)118 void QueryRequest::refreshNSS(OperationContext* opCtx) {
119     if (_uuid) {
120         const UUIDCatalog& catalog = UUIDCatalog::get(opCtx);
121         auto foundColl = catalog.lookupCollectionByUUID(_uuid.get());
122         uassert(ErrorCodes::NamespaceNotFound,
123                 str::stream() << "UUID " << _uuid.get() << " specified in query request not found",
124                 foundColl);
125         dassert(opCtx->lockState()->isDbLockedForMode(foundColl->ns().db(), MODE_IS));
126         _nss = foundColl->ns();
127     }
128     invariant(!_nss.isEmpty());
129 }
130 
131 // static
parseFromFindCommand(unique_ptr<QueryRequest> qr,const BSONObj & cmdObj,bool isExplain)132 StatusWith<unique_ptr<QueryRequest>> QueryRequest::parseFromFindCommand(unique_ptr<QueryRequest> qr,
133                                                                         const BSONObj& cmdObj,
134                                                                         bool isExplain) {
135     qr->_explain = isExplain;
136     bool tailable = false;
137     bool awaitData = false;
138 
139     // Parse the command BSON by looping through one element at a time.
140     BSONObjIterator it(cmdObj);
141     while (it.more()) {
142         BSONElement el = it.next();
143         const auto fieldName = el.fieldNameStringData();
144         if (fieldName == kFindCommandName) {
145             // Check both UUID and String types for "find" field.
146             Status status = checkFieldType(el, BinData);
147             if (!status.isOK()) {
148                 status = checkFieldType(el, String);
149             }
150             if (!status.isOK()) {
151                 return status;
152             }
153         } else if (fieldName == kFilterField) {
154             Status status = checkFieldType(el, Object);
155             if (!status.isOK()) {
156                 return status;
157             }
158 
159             qr->_filter = el.Obj().getOwned();
160         } else if (fieldName == kProjectionField) {
161             Status status = checkFieldType(el, Object);
162             if (!status.isOK()) {
163                 return status;
164             }
165 
166             qr->_proj = el.Obj().getOwned();
167         } else if (fieldName == kSortField) {
168             Status status = checkFieldType(el, Object);
169             if (!status.isOK()) {
170                 return status;
171             }
172 
173             qr->_sort = el.Obj().getOwned();
174         } else if (fieldName == kHintField) {
175             BSONObj hintObj;
176             if (Object == el.type()) {
177                 hintObj = cmdObj["hint"].Obj().getOwned();
178             } else if (String == el.type()) {
179                 hintObj = el.wrap("$hint");
180             } else {
181                 return Status(ErrorCodes::FailedToParse,
182                               "hint must be either a string or nested object");
183             }
184 
185             qr->_hint = hintObj;
186         } else if (fieldName == repl::ReadConcernArgs::kReadConcernFieldName) {
187             // Read concern parsing is handled elsewhere, but we store a copy here.
188             Status status = checkFieldType(el, Object);
189             if (!status.isOK()) {
190                 return status;
191             }
192 
193             qr->_readConcern = el.Obj().getOwned();
194         } else if (fieldName == QueryRequest::kUnwrappedReadPrefField) {
195             // Read preference parsing is handled elsewhere, but we store a copy here.
196             Status status = checkFieldType(el, Object);
197             if (!status.isOK()) {
198                 return status;
199             }
200 
201             qr->setUnwrappedReadPref(el.Obj());
202         } else if (fieldName == kCollationField) {
203             // Collation parsing is handled elsewhere, but we store a copy here.
204             Status status = checkFieldType(el, Object);
205             if (!status.isOK()) {
206                 return status;
207             }
208 
209             qr->_collation = el.Obj().getOwned();
210         } else if (fieldName == kSkipField) {
211             if (!el.isNumber()) {
212                 str::stream ss;
213                 ss << "Failed to parse: " << cmdObj.toString() << ". "
214                    << "'skip' field must be numeric.";
215                 return Status(ErrorCodes::FailedToParse, ss);
216             }
217 
218             long long skip = el.numberLong();
219 
220             // A skip value of 0 means that there is no skip.
221             if (skip) {
222                 qr->_skip = skip;
223             }
224         } else if (fieldName == kLimitField) {
225             if (!el.isNumber()) {
226                 str::stream ss;
227                 ss << "Failed to parse: " << cmdObj.toString() << ". "
228                    << "'limit' field must be numeric.";
229                 return Status(ErrorCodes::FailedToParse, ss);
230             }
231 
232             long long limit = el.numberLong();
233 
234             // A limit value of 0 means that there is no limit.
235             if (limit) {
236                 qr->_limit = limit;
237             }
238         } else if (fieldName == kBatchSizeField) {
239             if (!el.isNumber()) {
240                 str::stream ss;
241                 ss << "Failed to parse: " << cmdObj.toString() << ". "
242                    << "'batchSize' field must be numeric.";
243                 return Status(ErrorCodes::FailedToParse, ss);
244             }
245 
246             qr->_batchSize = el.numberLong();
247         } else if (fieldName == kNToReturnField) {
248             if (!el.isNumber()) {
249                 str::stream ss;
250                 ss << "Failed to parse: " << cmdObj.toString() << ". "
251                    << "'ntoreturn' field must be numeric.";
252                 return Status(ErrorCodes::FailedToParse, ss);
253             }
254 
255             qr->_ntoreturn = el.numberLong();
256         } else if (fieldName == kSingleBatchField) {
257             Status status = checkFieldType(el, Bool);
258             if (!status.isOK()) {
259                 return status;
260             }
261 
262             qr->_wantMore = !el.boolean();
263         } else if (fieldName == kCommentField) {
264             Status status = checkFieldType(el, String);
265             if (!status.isOK()) {
266                 return status;
267             }
268 
269             qr->_comment = el.str();
270         } else if (fieldName == kMaxScanField) {
271             if (!el.isNumber()) {
272                 str::stream ss;
273                 ss << "Failed to parse: " << cmdObj.toString() << ". "
274                    << "'maxScan' field must be numeric.";
275                 return Status(ErrorCodes::FailedToParse, ss);
276             }
277 
278             qr->_maxScan = el.numberInt();
279         } else if (fieldName == cmdOptionMaxTimeMS) {
280             StatusWith<int> maxTimeMS = parseMaxTimeMS(el);
281             if (!maxTimeMS.isOK()) {
282                 return maxTimeMS.getStatus();
283             }
284 
285             qr->_maxTimeMS = maxTimeMS.getValue();
286         } else if (fieldName == kMinField) {
287             Status status = checkFieldType(el, Object);
288             if (!status.isOK()) {
289                 return status;
290             }
291 
292             qr->_min = el.Obj().getOwned();
293         } else if (fieldName == kMaxField) {
294             Status status = checkFieldType(el, Object);
295             if (!status.isOK()) {
296                 return status;
297             }
298 
299             qr->_max = el.Obj().getOwned();
300         } else if (fieldName == kReturnKeyField) {
301             Status status = checkFieldType(el, Bool);
302             if (!status.isOK()) {
303                 return status;
304             }
305 
306             qr->_returnKey = el.boolean();
307         } else if (fieldName == kShowRecordIdField) {
308             Status status = checkFieldType(el, Bool);
309             if (!status.isOK()) {
310                 return status;
311             }
312 
313             qr->_showRecordId = el.boolean();
314         } else if (fieldName == kSnapshotField) {
315             Status status = checkFieldType(el, Bool);
316             if (!status.isOK()) {
317                 return status;
318             }
319 
320             qr->_snapshot = el.boolean();
321         } else if (fieldName == kTailableField) {
322             Status status = checkFieldType(el, Bool);
323             if (!status.isOK()) {
324                 return status;
325             }
326 
327             tailable = el.boolean();
328         } else if (fieldName == kOplogReplayField) {
329             Status status = checkFieldType(el, Bool);
330             if (!status.isOK()) {
331                 return status;
332             }
333 
334             qr->_oplogReplay = el.boolean();
335         } else if (fieldName == kNoCursorTimeoutField) {
336             Status status = checkFieldType(el, Bool);
337             if (!status.isOK()) {
338                 return status;
339             }
340 
341             qr->_noCursorTimeout = el.boolean();
342         } else if (fieldName == kAwaitDataField) {
343             Status status = checkFieldType(el, Bool);
344             if (!status.isOK()) {
345                 return status;
346             }
347 
348             awaitData = el.boolean();
349         } else if (fieldName == kPartialResultsField) {
350             Status status = checkFieldType(el, Bool);
351             if (!status.isOK()) {
352                 return status;
353             }
354 
355             qr->_allowPartialResults = el.boolean();
356         } else if (fieldName == kOptionsField) {
357             // 3.0.x versions of the shell may generate an explain of a find command with an
358             // 'options' field. We accept this only if the 'options' field is empty so that
359             // the shell's explain implementation is forwards compatible.
360             //
361             // TODO: Remove for 3.4.
362             if (!qr->isExplain()) {
363                 return Status(ErrorCodes::FailedToParse,
364                               str::stream() << "Field '" << kOptionsField
365                                             << "' is only allowed for explain.");
366             }
367 
368             Status status = checkFieldType(el, Object);
369             if (!status.isOK()) {
370                 return status;
371             }
372 
373             BSONObj optionsObj = el.Obj();
374             if (!optionsObj.isEmpty()) {
375                 return Status(ErrorCodes::FailedToParse,
376                               str::stream() << "Failed to parse options: " << optionsObj.toString()
377                                             << ". You may need to update your shell or driver.");
378             }
379         } else if (fieldName == kShardVersionField) {
380             // Shard version parsing is handled elsewhere.
381         } else if (fieldName == kTermField) {
382             Status status = checkFieldType(el, NumberLong);
383             if (!status.isOK()) {
384                 return status;
385             }
386             qr->_replicationTerm = el._numberLong();
387         } else if (!Command::isGenericArgument(fieldName)) {
388             return Status(ErrorCodes::FailedToParse,
389                           str::stream() << "Failed to parse: " << cmdObj.toString() << ". "
390                                         << "Unrecognized field '"
391                                         << fieldName
392                                         << "'.");
393         }
394     }
395 
396     auto tailableMode = tailableModeFromBools(tailable, awaitData);
397     if (!tailableMode.isOK()) {
398         return tailableMode.getStatus();
399     }
400     qr->_tailableMode = tailableMode.getValue();
401     qr->addMetaProjection();
402 
403     Status validateStatus = qr->validate();
404     if (!validateStatus.isOK()) {
405         return validateStatus;
406     }
407 
408     return std::move(qr);
409 }
410 
makeFromFindCommand(NamespaceString nss,const BSONObj & cmdObj,bool isExplain)411 StatusWith<unique_ptr<QueryRequest>> QueryRequest::makeFromFindCommand(NamespaceString nss,
412                                                                        const BSONObj& cmdObj,
413                                                                        bool isExplain) {
414     BSONElement first = cmdObj.firstElement();
415     if (first.type() == BinData && first.binDataType() == BinDataType::newUUID) {
416         auto uuid = uassertStatusOK(UUID::parse(first));
417         auto qr = stdx::make_unique<QueryRequest>(uuid);
418         return parseFromFindCommand(std::move(qr), cmdObj, isExplain);
419     } else {
420         auto qr = stdx::make_unique<QueryRequest>(nss);
421         return parseFromFindCommand(std::move(qr), cmdObj, isExplain);
422     }
423 }
424 
asFindCommand() const425 BSONObj QueryRequest::asFindCommand() const {
426     BSONObjBuilder bob;
427     asFindCommand(&bob);
428     return bob.obj();
429 }
430 
asFindCommand(BSONObjBuilder * cmdBuilder) const431 void QueryRequest::asFindCommand(BSONObjBuilder* cmdBuilder) const {
432     cmdBuilder->append(kFindCommandName, _nss.coll());
433 
434     if (!_filter.isEmpty()) {
435         cmdBuilder->append(kFilterField, _filter);
436     }
437 
438     if (!_proj.isEmpty()) {
439         cmdBuilder->append(kProjectionField, _proj);
440     }
441 
442     if (!_sort.isEmpty()) {
443         cmdBuilder->append(kSortField, _sort);
444     }
445 
446     if (!_hint.isEmpty()) {
447         cmdBuilder->append(kHintField, _hint);
448     }
449 
450     if (!_readConcern.isEmpty()) {
451         cmdBuilder->append(repl::ReadConcernArgs::kReadConcernFieldName, _readConcern);
452     }
453 
454     if (!_collation.isEmpty()) {
455         cmdBuilder->append(kCollationField, _collation);
456     }
457 
458     if (_skip) {
459         cmdBuilder->append(kSkipField, *_skip);
460     }
461 
462     if (_ntoreturn) {
463         cmdBuilder->append(kNToReturnField, *_ntoreturn);
464     }
465 
466     if (_limit) {
467         cmdBuilder->append(kLimitField, *_limit);
468     }
469 
470     if (_batchSize) {
471         cmdBuilder->append(kBatchSizeField, *_batchSize);
472     }
473 
474     if (!_wantMore) {
475         cmdBuilder->append(kSingleBatchField, true);
476     }
477 
478     if (!_comment.empty()) {
479         cmdBuilder->append(kCommentField, _comment);
480     }
481 
482     if (_maxScan > 0) {
483         cmdBuilder->append(kMaxScanField, _maxScan);
484     }
485 
486     if (_maxTimeMS > 0) {
487         cmdBuilder->append(cmdOptionMaxTimeMS, _maxTimeMS);
488     }
489 
490     if (!_max.isEmpty()) {
491         cmdBuilder->append(kMaxField, _max);
492     }
493 
494     if (!_min.isEmpty()) {
495         cmdBuilder->append(kMinField, _min);
496     }
497 
498     if (_returnKey) {
499         cmdBuilder->append(kReturnKeyField, true);
500     }
501 
502     if (_showRecordId) {
503         cmdBuilder->append(kShowRecordIdField, true);
504     }
505 
506     if (_snapshot) {
507         cmdBuilder->append(kSnapshotField, true);
508     }
509 
510     switch (_tailableMode) {
511         case TailableMode::kTailable: {
512             cmdBuilder->append(kTailableField, true);
513             break;
514         }
515         case TailableMode::kTailableAndAwaitData: {
516             cmdBuilder->append(kTailableField, true);
517             cmdBuilder->append(kAwaitDataField, true);
518             break;
519         }
520         case TailableMode::kNormal: {
521             break;
522         }
523     }
524 
525     if (_oplogReplay) {
526         cmdBuilder->append(kOplogReplayField, true);
527     }
528 
529     if (_noCursorTimeout) {
530         cmdBuilder->append(kNoCursorTimeoutField, true);
531     }
532 
533     if (_allowPartialResults) {
534         cmdBuilder->append(kPartialResultsField, true);
535     }
536 
537     if (_replicationTerm) {
538         cmdBuilder->append(kTermField, *_replicationTerm);
539     }
540 }
541 
addReturnKeyMetaProj()542 void QueryRequest::addReturnKeyMetaProj() {
543     BSONObjBuilder projBob;
544     projBob.appendElements(_proj);
545     // We use $$ because it's never going to show up in a user's projection.
546     // The exact text doesn't matter.
547     BSONObj indexKey = BSON("$$" << BSON("$meta" << QueryRequest::metaIndexKey));
548     projBob.append(indexKey.firstElement());
549     _proj = projBob.obj();
550 }
551 
addShowRecordIdMetaProj()552 void QueryRequest::addShowRecordIdMetaProj() {
553     BSONObjBuilder projBob;
554     projBob.appendElements(_proj);
555     BSONObj metaRecordId = BSON("$recordId" << BSON("$meta" << QueryRequest::metaRecordId));
556     projBob.append(metaRecordId.firstElement());
557     _proj = projBob.obj();
558 }
559 
validate() const560 Status QueryRequest::validate() const {
561     // Min and Max objects must have the same fields.
562     if (!_min.isEmpty() && !_max.isEmpty()) {
563         if (!_min.isFieldNamePrefixOf(_max) || (_min.nFields() != _max.nFields())) {
564             return Status(ErrorCodes::BadValue, "min and max must have the same field names");
565         }
566     }
567 
568     // Can't combine a normal sort and a $meta projection on the same field.
569     BSONObjIterator projIt(_proj);
570     while (projIt.more()) {
571         BSONElement projElt = projIt.next();
572         if (isTextScoreMeta(projElt)) {
573             BSONElement sortElt = _sort[projElt.fieldName()];
574             if (!sortElt.eoo() && !isTextScoreMeta(sortElt)) {
575                 return Status(ErrorCodes::BadValue,
576                               "can't have a non-$meta sort on a $meta projection");
577             }
578         }
579     }
580 
581     if (!isValidSortOrder(_sort)) {
582         return Status(ErrorCodes::BadValue, "bad sort specification");
583     }
584 
585     // All fields with a $meta sort must have a corresponding $meta projection.
586     BSONObjIterator sortIt(_sort);
587     while (sortIt.more()) {
588         BSONElement sortElt = sortIt.next();
589         if (isTextScoreMeta(sortElt)) {
590             BSONElement projElt = _proj[sortElt.fieldName()];
591             if (projElt.eoo() || !isTextScoreMeta(projElt)) {
592                 return Status(ErrorCodes::BadValue,
593                               "must have $meta projection for all $meta sort keys");
594             }
595         }
596     }
597 
598     if (_snapshot) {
599         if (!_sort.isEmpty()) {
600             return Status(ErrorCodes::BadValue, "E12001 can't use sort with snapshot");
601         }
602         if (!_hint.isEmpty()) {
603             return Status(ErrorCodes::BadValue, "E12002 can't use hint with snapshot");
604         }
605     }
606 
607     if ((_limit || _batchSize) && _ntoreturn) {
608         return Status(ErrorCodes::BadValue,
609                       "'limit' or 'batchSize' fields can not be set with 'ntoreturn' field.");
610     }
611 
612 
613     if (_skip && *_skip < 0) {
614         return Status(ErrorCodes::BadValue,
615                       str::stream() << "Skip value must be non-negative, but received: " << *_skip);
616     }
617 
618     if (_limit && *_limit < 0) {
619         return Status(ErrorCodes::BadValue,
620                       str::stream() << "Limit value must be non-negative, but received: "
621                                     << *_limit);
622     }
623 
624     if (_batchSize && *_batchSize < 0) {
625         return Status(ErrorCodes::BadValue,
626                       str::stream() << "BatchSize value must be non-negative, but received: "
627                                     << *_batchSize);
628     }
629 
630     if (_ntoreturn && *_ntoreturn < 0) {
631         return Status(ErrorCodes::BadValue,
632                       str::stream() << "NToReturn value must be non-negative, but received: "
633                                     << *_ntoreturn);
634     }
635 
636     if (_maxScan < 0) {
637         return Status(ErrorCodes::BadValue,
638                       str::stream() << "MaxScan value must be non-negative, but received: "
639                                     << _maxScan);
640     }
641 
642     if (_maxTimeMS < 0) {
643         return Status(ErrorCodes::BadValue,
644                       str::stream() << "MaxTimeMS value must be non-negative, but received: "
645                                     << _maxTimeMS);
646     }
647 
648     if (_tailableMode != TailableMode::kNormal) {
649         // Tailable cursors cannot have any sort other than {$natural: 1}.
650         const BSONObj expectedSort = BSON(kNaturalSortField << 1);
651         if (!_sort.isEmpty() &&
652             SimpleBSONObjComparator::kInstance.evaluate(_sort != expectedSort)) {
653             return Status(ErrorCodes::BadValue,
654                           "cannot use tailable option with a sort other than {$natural: 1}");
655         }
656 
657         // Cannot indicate that you want a 'singleBatch' if the cursor is tailable.
658         if (!_wantMore) {
659             return Status(ErrorCodes::BadValue,
660                           "cannot use tailable option with the 'singleBatch' option");
661         }
662     }
663 
664     return Status::OK();
665 }
666 
667 // static
parseMaxTimeMS(BSONElement maxTimeMSElt)668 StatusWith<int> QueryRequest::parseMaxTimeMS(BSONElement maxTimeMSElt) {
669     if (!maxTimeMSElt.eoo() && !maxTimeMSElt.isNumber()) {
670         return StatusWith<int>(
671             ErrorCodes::BadValue,
672             (StringBuilder() << maxTimeMSElt.fieldNameStringData() << " must be a number").str());
673     }
674     long long maxTimeMSLongLong = maxTimeMSElt.safeNumberLong();  // returns 0 on EOO
675     if (maxTimeMSLongLong < 0 || maxTimeMSLongLong > INT_MAX) {
676         return StatusWith<int>(
677             ErrorCodes::BadValue,
678             (StringBuilder() << maxTimeMSElt.fieldNameStringData() << " is out of range").str());
679     }
680     double maxTimeMSDouble = maxTimeMSElt.numberDouble();
681     if (maxTimeMSElt.type() == mongo::NumberDouble && floor(maxTimeMSDouble) != maxTimeMSDouble) {
682         return StatusWith<int>(
683             ErrorCodes::BadValue,
684             (StringBuilder() << maxTimeMSElt.fieldNameStringData() << " has non-integral value")
685                 .str());
686     }
687     return StatusWith<int>(static_cast<int>(maxTimeMSLongLong));
688 }
689 
690 // static
isTextScoreMeta(BSONElement elt)691 bool QueryRequest::isTextScoreMeta(BSONElement elt) {
692     // elt must be foo: {$meta: "textScore"}
693     if (mongo::Object != elt.type()) {
694         return false;
695     }
696     BSONObj metaObj = elt.Obj();
697     BSONObjIterator metaIt(metaObj);
698     // must have exactly 1 element
699     if (!metaIt.more()) {
700         return false;
701     }
702     BSONElement metaElt = metaIt.next();
703     if (!str::equals("$meta", metaElt.fieldName())) {
704         return false;
705     }
706     if (mongo::String != metaElt.type()) {
707         return false;
708     }
709     if (QueryRequest::metaTextScore != metaElt.valuestr()) {
710         return false;
711     }
712     // must have exactly 1 element
713     if (metaIt.more()) {
714         return false;
715     }
716     return true;
717 }
718 
719 // static
isValidSortOrder(const BSONObj & sortObj)720 bool QueryRequest::isValidSortOrder(const BSONObj& sortObj) {
721     BSONObjIterator i(sortObj);
722     while (i.more()) {
723         BSONElement e = i.next();
724         // fieldNameSize() includes NULL terminator. For empty field name,
725         // we should be checking for 1 instead of 0.
726         if (1 == e.fieldNameSize()) {
727             return false;
728         }
729         if (isTextScoreMeta(e)) {
730             continue;
731         }
732         long long n = e.safeNumberLong();
733         if (!(e.isNumber() && (n == -1LL || n == 1LL))) {
734             return false;
735         }
736     }
737     return true;
738 }
739 
740 // static
isQueryIsolated(const BSONObj & query)741 bool QueryRequest::isQueryIsolated(const BSONObj& query) {
742     BSONObjIterator iter(query);
743     while (iter.more()) {
744         BSONElement elt = iter.next();
745         if (str::equals(elt.fieldName(), "$isolated") && elt.trueValue())
746             return true;
747         if (str::equals(elt.fieldName(), "$atomic") && elt.trueValue())
748             return true;
749     }
750     return false;
751 }
752 
753 //
754 // Old QueryRequest parsing code: SOON TO BE DEPRECATED.
755 //
756 
757 // static
fromLegacyQueryMessage(const QueryMessage & qm)758 StatusWith<unique_ptr<QueryRequest>> QueryRequest::fromLegacyQueryMessage(const QueryMessage& qm) {
759     auto qr = stdx::make_unique<QueryRequest>(NamespaceString(qm.ns));
760 
761     Status status = qr->init(qm.ntoskip, qm.ntoreturn, qm.queryOptions, qm.query, qm.fields, true);
762     if (!status.isOK()) {
763         return status;
764     }
765 
766     return std::move(qr);
767 }
768 
fromLegacyQuery(NamespaceString nss,const BSONObj & queryObj,const BSONObj & proj,int ntoskip,int ntoreturn,int queryOptions)769 StatusWith<unique_ptr<QueryRequest>> QueryRequest::fromLegacyQuery(NamespaceString nss,
770                                                                    const BSONObj& queryObj,
771                                                                    const BSONObj& proj,
772                                                                    int ntoskip,
773                                                                    int ntoreturn,
774                                                                    int queryOptions) {
775     auto qr = stdx::make_unique<QueryRequest>(nss);
776 
777     Status status = qr->init(ntoskip, ntoreturn, queryOptions, queryObj, proj, true);
778     if (!status.isOK()) {
779         return status;
780     }
781 
782     return std::move(qr);
783 }
784 
init(int ntoskip,int ntoreturn,int queryOptions,const BSONObj & queryObj,const BSONObj & proj,bool fromQueryMessage)785 Status QueryRequest::init(int ntoskip,
786                           int ntoreturn,
787                           int queryOptions,
788                           const BSONObj& queryObj,
789                           const BSONObj& proj,
790                           bool fromQueryMessage) {
791     _proj = proj.getOwned();
792 
793     if (ntoskip) {
794         _skip = ntoskip;
795     }
796 
797     if (ntoreturn) {
798         if (ntoreturn < 0) {
799             if (ntoreturn == std::numeric_limits<int>::min()) {
800                 // ntoreturn is negative but can't be negated.
801                 return Status(ErrorCodes::BadValue, "bad ntoreturn value in query");
802             }
803             _ntoreturn = -ntoreturn;
804             _wantMore = false;
805         } else {
806             _ntoreturn = ntoreturn;
807         }
808     }
809 
810     // An ntoreturn of 1 is special because it also means to return at most one batch.
811     if (_ntoreturn.value_or(0) == 1) {
812         _wantMore = false;
813     }
814 
815     // Initialize flags passed as 'queryOptions' bit vector.
816     initFromInt(queryOptions);
817 
818     if (fromQueryMessage) {
819         BSONElement queryField = queryObj["query"];
820         if (!queryField.isABSONObj()) {
821             queryField = queryObj["$query"];
822         }
823         if (queryField.isABSONObj()) {
824             _filter = queryField.embeddedObject().getOwned();
825             Status status = initFullQuery(queryObj);
826             if (!status.isOK()) {
827                 return status;
828             }
829         } else {
830             _filter = queryObj.getOwned();
831         }
832     } else {
833         // This is the debugging code path.
834         _filter = queryObj.getOwned();
835     }
836 
837     _hasReadPref = queryObj.hasField("$readPreference");
838 
839     return validate();
840 }
841 
initFullQuery(const BSONObj & top)842 Status QueryRequest::initFullQuery(const BSONObj& top) {
843     BSONObjIterator i(top);
844 
845     while (i.more()) {
846         BSONElement e = i.next();
847         const char* name = e.fieldName();
848 
849         if (0 == strcmp("$orderby", name) || 0 == strcmp("orderby", name)) {
850             if (Object == e.type()) {
851                 _sort = e.embeddedObject().getOwned();
852             } else if (Array == e.type()) {
853                 _sort = e.embeddedObject();
854 
855                 // TODO: Is this ever used?  I don't think so.
856                 // Quote:
857                 // This is for languages whose "objects" are not well ordered (JSON is well
858                 // ordered).
859                 // [ { a : ... } , { b : ... } ] -> { a : ..., b : ... }
860                 // note: this is slow, but that is ok as order will have very few pieces
861                 BSONObjBuilder b;
862                 char p[2] = "0";
863 
864                 while (1) {
865                     BSONObj j = _sort.getObjectField(p);
866                     if (j.isEmpty()) {
867                         break;
868                     }
869                     BSONElement e = j.firstElement();
870                     if (e.eoo()) {
871                         return Status(ErrorCodes::BadValue, "bad order array");
872                     }
873                     if (!e.isNumber()) {
874                         return Status(ErrorCodes::BadValue, "bad order array [2]");
875                     }
876                     b.append(e);
877                     (*p)++;
878                     if (!(*p <= '9')) {
879                         return Status(ErrorCodes::BadValue, "too many ordering elements");
880                     }
881                 }
882 
883                 _sort = b.obj();
884             } else {
885                 return Status(ErrorCodes::BadValue, "sort must be object or array");
886             }
887         } else if ('$' == *name) {
888             name++;
889             if (str::equals("explain", name)) {
890                 // Won't throw.
891                 _explain = e.trueValue();
892             } else if (str::equals("snapshot", name)) {
893                 // Won't throw.
894                 _snapshot = e.trueValue();
895             } else if (str::equals("min", name)) {
896                 if (!e.isABSONObj()) {
897                     return Status(ErrorCodes::BadValue, "$min must be a BSONObj");
898                 }
899                 _min = e.embeddedObject().getOwned();
900             } else if (str::equals("max", name)) {
901                 if (!e.isABSONObj()) {
902                     return Status(ErrorCodes::BadValue, "$max must be a BSONObj");
903                 }
904                 _max = e.embeddedObject().getOwned();
905             } else if (str::equals("hint", name)) {
906                 if (e.isABSONObj()) {
907                     _hint = e.embeddedObject().getOwned();
908                 } else if (String == e.type()) {
909                     _hint = e.wrap();
910                 } else {
911                     return Status(ErrorCodes::BadValue,
912                                   "$hint must be either a string or nested object");
913                 }
914             } else if (str::equals("returnKey", name)) {
915                 // Won't throw.
916                 if (e.trueValue()) {
917                     _returnKey = true;
918                     addReturnKeyMetaProj();
919                 }
920             } else if (str::equals("maxScan", name)) {
921                 // Won't throw.
922                 _maxScan = e.numberInt();
923             } else if (str::equals("showDiskLoc", name)) {
924                 // Won't throw.
925                 if (e.trueValue()) {
926                     _showRecordId = true;
927                     addShowRecordIdMetaProj();
928                 }
929             } else if (str::equals("maxTimeMS", name)) {
930                 StatusWith<int> maxTimeMS = parseMaxTimeMS(e);
931                 if (!maxTimeMS.isOK()) {
932                     return maxTimeMS.getStatus();
933                 }
934                 _maxTimeMS = maxTimeMS.getValue();
935             } else if (str::equals("comment", name)) {
936                 // Legacy $comment can be any BSON element. Convert to string if it isn't
937                 // already.
938                 if (e.type() == BSONType::String) {
939                     _comment = e.str();
940                 } else {
941                     _comment = e.toString(false);
942                 }
943             }
944         }
945     }
946 
947     return Status::OK();
948 }
949 
getOptions() const950 int QueryRequest::getOptions() const {
951     int options = 0;
952     if (_tailableMode == TailableMode::kTailable) {
953         options |= QueryOption_CursorTailable;
954     } else if (_tailableMode == TailableMode::kTailableAndAwaitData) {
955         options |= QueryOption_CursorTailable;
956         options |= QueryOption_AwaitData;
957     }
958     if (_slaveOk) {
959         options |= QueryOption_SlaveOk;
960     }
961     if (_oplogReplay) {
962         options |= QueryOption_OplogReplay;
963     }
964     if (_noCursorTimeout) {
965         options |= QueryOption_NoCursorTimeout;
966     }
967     if (_exhaust) {
968         options |= QueryOption_Exhaust;
969     }
970     if (_allowPartialResults) {
971         options |= QueryOption_PartialResults;
972     }
973     return options;
974 }
975 
initFromInt(int options)976 void QueryRequest::initFromInt(int options) {
977     bool tailable = (options & QueryOption_CursorTailable) != 0;
978     bool awaitData = (options & QueryOption_AwaitData) != 0;
979     _tailableMode = uassertStatusOK(tailableModeFromBools(tailable, awaitData));
980     _slaveOk = (options & QueryOption_SlaveOk) != 0;
981     _oplogReplay = (options & QueryOption_OplogReplay) != 0;
982     _noCursorTimeout = (options & QueryOption_NoCursorTimeout) != 0;
983     _exhaust = (options & QueryOption_Exhaust) != 0;
984     _allowPartialResults = (options & QueryOption_PartialResults) != 0;
985 }
986 
addMetaProjection()987 void QueryRequest::addMetaProjection() {
988     // We might need to update the projection object with a $meta projection.
989     if (returnKey()) {
990         addReturnKeyMetaProj();
991     }
992 
993     if (showRecordId()) {
994         addShowRecordIdMetaProj();
995     }
996 }
997 
getEffectiveBatchSize() const998 boost::optional<long long> QueryRequest::getEffectiveBatchSize() const {
999     return _batchSize ? _batchSize : _ntoreturn;
1000 }
1001 
asAggregationCommand() const1002 StatusWith<BSONObj> QueryRequest::asAggregationCommand() const {
1003     BSONObjBuilder aggregationBuilder;
1004 
1005     // First, check if this query has options that are not supported in aggregation.
1006     if (!_min.isEmpty()) {
1007         return {ErrorCodes::InvalidPipelineOperator,
1008                 str::stream() << "Option " << kMinField << " not supported in aggregation."};
1009     }
1010     if (!_max.isEmpty()) {
1011         return {ErrorCodes::InvalidPipelineOperator,
1012                 str::stream() << "Option " << kMaxField << " not supported in aggregation."};
1013     }
1014     if (_maxScan != 0) {
1015         return {ErrorCodes::InvalidPipelineOperator,
1016                 str::stream() << "Option " << kMaxScanField << " not supported in aggregation."};
1017     }
1018     if (_returnKey) {
1019         return {ErrorCodes::InvalidPipelineOperator,
1020                 str::stream() << "Option " << kReturnKeyField << " not supported in aggregation."};
1021     }
1022     if (_showRecordId) {
1023         return {ErrorCodes::InvalidPipelineOperator,
1024                 str::stream() << "Option " << kShowRecordIdField
1025                               << " not supported in aggregation."};
1026     }
1027     if (_snapshot) {
1028         return {ErrorCodes::InvalidPipelineOperator,
1029                 str::stream() << "Option " << kSnapshotField << " not supported in aggregation."};
1030     }
1031     if (isTailable()) {
1032         return {ErrorCodes::InvalidPipelineOperator,
1033                 "Tailable cursors are not supported in aggregation."};
1034     }
1035     if (_oplogReplay) {
1036         return {ErrorCodes::InvalidPipelineOperator,
1037                 str::stream() << "Option " << kOplogReplayField
1038                               << " not supported in aggregation."};
1039     }
1040     if (_noCursorTimeout) {
1041         return {ErrorCodes::InvalidPipelineOperator,
1042                 str::stream() << "Option " << kNoCursorTimeoutField
1043                               << " not supported in aggregation."};
1044     }
1045     if (_allowPartialResults) {
1046         return {ErrorCodes::InvalidPipelineOperator,
1047                 str::stream() << "Option " << kPartialResultsField
1048                               << " not supported in aggregation."};
1049     }
1050     if (_ntoreturn) {
1051         return {ErrorCodes::BadValue,
1052                 str::stream() << "Cannot convert to an aggregation if ntoreturn is set."};
1053     }
1054     if (_sort[kNaturalSortField]) {
1055         return {ErrorCodes::InvalidPipelineOperator,
1056                 str::stream() << "Sort option " << kNaturalSortField
1057                               << " not supported in aggregation."};
1058     }
1059     // The aggregation command normally does not support the 'singleBatch' option, but we make a
1060     // special exception if 'limit' is set to 1.
1061     if (!_wantMore && _limit.value_or(0) != 1LL) {
1062         return {ErrorCodes::InvalidPipelineOperator,
1063                 str::stream() << "Option " << kSingleBatchField
1064                               << " not supported in aggregation."};
1065     }
1066 
1067     // Now that we've successfully validated this QR, begin building the aggregation command.
1068     aggregationBuilder.append("aggregate", _nss.coll());
1069 
1070     // Construct an aggregation pipeline that finds the equivalent documents to this query request.
1071     BSONArrayBuilder pipelineBuilder(aggregationBuilder.subarrayStart("pipeline"));
1072     if (!_filter.isEmpty()) {
1073         BSONObjBuilder matchBuilder(pipelineBuilder.subobjStart());
1074         matchBuilder.append("$match", _filter);
1075         matchBuilder.doneFast();
1076     }
1077     if (!_sort.isEmpty()) {
1078         BSONObjBuilder sortBuilder(pipelineBuilder.subobjStart());
1079         sortBuilder.append("$sort", _sort);
1080         sortBuilder.doneFast();
1081     }
1082     if (_skip) {
1083         BSONObjBuilder skipBuilder(pipelineBuilder.subobjStart());
1084         skipBuilder.append("$skip", *_skip);
1085         skipBuilder.doneFast();
1086     }
1087     if (_limit) {
1088         BSONObjBuilder limitBuilder(pipelineBuilder.subobjStart());
1089         limitBuilder.append("$limit", *_limit);
1090         limitBuilder.doneFast();
1091     }
1092     if (!_proj.isEmpty()) {
1093         BSONObjBuilder projectBuilder(pipelineBuilder.subobjStart());
1094         projectBuilder.append("$project", _proj);
1095         projectBuilder.doneFast();
1096     }
1097     pipelineBuilder.doneFast();
1098 
1099     // The aggregation 'cursor' option is always set, regardless of the presence of batchSize.
1100     BSONObjBuilder batchSizeBuilder(aggregationBuilder.subobjStart("cursor"));
1101     if (_batchSize) {
1102         batchSizeBuilder.append(kBatchSizeField, *_batchSize);
1103     }
1104     batchSizeBuilder.doneFast();
1105 
1106     // Other options.
1107     aggregationBuilder.append("collation", _collation);
1108     if (_maxTimeMS > 0) {
1109         aggregationBuilder.append(cmdOptionMaxTimeMS, _maxTimeMS);
1110     }
1111     if (!_hint.isEmpty()) {
1112         aggregationBuilder.append("hint", _hint);
1113     }
1114     if (!_comment.empty()) {
1115         aggregationBuilder.append("comment", _comment);
1116     }
1117     if (!_readConcern.isEmpty()) {
1118         aggregationBuilder.append("readConcern", _readConcern);
1119     }
1120     if (!_unwrappedReadPref.isEmpty()) {
1121         aggregationBuilder.append(QueryRequest::kUnwrappedReadPrefField, _unwrappedReadPref);
1122     }
1123     return StatusWith<BSONObj>(aggregationBuilder.obj());
1124 }
1125 }  // namespace mongo
1126