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