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