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/index/sort_key_generator.h"
34
35 #include "mongo/bson/bsonobj_comparator.h"
36
37 namespace mongo {
38
SortKeyGenerator(const BSONObj & sortSpec,const CollatorInterface * collator)39 SortKeyGenerator::SortKeyGenerator(const BSONObj& sortSpec, const CollatorInterface* collator)
40 : _collator(collator) {
41 BSONObjBuilder btreeBob;
42
43 for (auto&& elt : sortSpec) {
44 if (elt.isNumber()) {
45 btreeBob.append(elt);
46 _patternPartTypes.push_back(SortPatternPartType::kFieldPath);
47 } else {
48 // If this field of the sort pattern is non-numeric, we expect it to be a text-score
49 // meta sort.
50 invariant(elt.type() == BSONType::Object);
51 invariant(elt.embeddedObject().nFields() == 1);
52 auto metaElem = elt.embeddedObject().firstElement();
53 invariant(metaElem.fieldNameStringData() == "$meta"_sd);
54 if (metaElem.valueStringData() == "textScore"_sd) {
55 _patternPartTypes.push_back(SortPatternPartType::kMetaTextScore);
56 } else {
57 invariant(metaElem.valueStringData() == "randVal"_sd);
58 _patternPartTypes.push_back(SortPatternPartType::kMetaRandVal);
59 }
60 _sortHasMeta = true;
61 }
62 }
63
64 // The fake index key pattern used to generate Btree keys.
65 _sortSpecWithoutMeta = btreeBob.obj();
66
67 // If we're just sorting by meta, don't bother with all the key stuff.
68 if (_sortSpecWithoutMeta.isEmpty()) {
69 return;
70 }
71
72 // We'll need to treat arrays as if we were to create an index over them. that is, we may need
73 // to unnest the first level and consider each array element to decide the sort order. In order
74 // to do this, we make a BtreeKeyGenerator.
75 std::vector<const char*> fieldNames;
76 std::vector<BSONElement> fixed;
77 for (auto&& patternElt : _sortSpecWithoutMeta) {
78 fieldNames.push_back(patternElt.fieldName());
79 fixed.push_back(BSONElement());
80 }
81
82 constexpr bool isSparse = false;
83 _indexKeyGen = stdx::make_unique<BtreeKeyGeneratorV1>(fieldNames, fixed, isSparse, _collator);
84 }
85
getSortKey(const BSONObj & obj,const Metadata * metadata) const86 StatusWith<BSONObj> SortKeyGenerator::getSortKey(const BSONObj& obj,
87 const Metadata* metadata) const {
88 if (_sortHasMeta) {
89 invariant(metadata);
90 }
91
92 auto indexKey = getIndexKey(obj);
93 if (!indexKey.isOK()) {
94 return indexKey;
95 }
96
97 if (!_sortHasMeta) {
98 // We don't have to worry about $meta sort, so the index key becomes the sort key.
99 return indexKey;
100 }
101
102 BSONObjBuilder mergedKeyBob;
103
104 // Merge metadata into the key.
105 BSONObjIterator sortKeyIt(indexKey.getValue());
106 for (auto type : _patternPartTypes) {
107 switch (type) {
108 case SortPatternPartType::kFieldPath: {
109 invariant(sortKeyIt.more());
110 mergedKeyBob.append(sortKeyIt.next());
111 continue;
112 }
113 case SortPatternPartType::kMetaTextScore: {
114 mergedKeyBob.append("", metadata->textScore);
115 continue;
116 }
117 case SortPatternPartType::kMetaRandVal: {
118 mergedKeyBob.append("", metadata->randVal);
119 continue;
120 }
121 default: { MONGO_UNREACHABLE; }
122 }
123 }
124
125 // We should have added a key component for each part of the index key pattern.
126 invariant(!sortKeyIt.more());
127
128 return mergedKeyBob.obj();
129 }
130
getIndexKey(const BSONObj & obj) const131 StatusWith<BSONObj> SortKeyGenerator::getIndexKey(const BSONObj& obj) const {
132 // Not sorting by anything in the key, just bail out early.
133 if (_sortSpecWithoutMeta.isEmpty()) {
134 return BSONObj();
135 }
136
137 // We will sort 'obj' in the same order an index over '_sortSpecWithoutMeta' would have. This is
138 // tricky. Consider the sort pattern {a:1} and the document {a: [1, 10]}. We have potentially
139 // two keys we could use to sort on. Here we extract these keys.
140 //
141 // The keys themselves will incorporate the collation, with strings translated to their
142 // corresponding collation keys. Therefore, we use the simple string comparator when comparing
143 // the keys themselves.
144 const StringData::ComparatorInterface* stringComparator = nullptr;
145 BSONObjComparator patternCmp(
146 _sortSpecWithoutMeta, BSONObjComparator::FieldNamesMode::kConsider, stringComparator);
147 BSONObjSet keys = patternCmp.makeBSONObjSet();
148
149 try {
150 // There's no need to compute the prefixes of the indexed fields that cause the index to be
151 // multikey when getting the index keys for sorting.
152 MultikeyPaths* multikeyPaths = nullptr;
153 _indexKeyGen->getKeys(obj, &keys, multikeyPaths);
154 } catch (const AssertionException& e) {
155 // Probably a parallel array.
156 if (ErrorCodes::CannotIndexParallelArrays == e.code()) {
157 return Status(ErrorCodes::BadValue, "cannot sort with keys that are parallel arrays");
158 } else {
159 return e.toStatus();
160 }
161 } catch (...) {
162 return Status(ErrorCodes::InternalError, "unknown error during sort key generation");
163 }
164
165 // Key generator isn't sparse so we should at least get an all-null key.
166 invariant(!keys.empty());
167
168 // The sort key is the first index key, ordered according to the pattern '_sortSpecWithoutMeta'.
169 return *keys.begin();
170 }
171
172 } // namespace mongo
173