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 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kIndex
32 
33 #include "mongo/platform/basic.h"
34 
35 #include "mongo/db/index/expression_keys_private.h"
36 
37 #include <algorithm>
38 
39 #include "mongo/bson/bsonobjbuilder.h"
40 #include "mongo/bson/simple_bsonobj_comparator.h"
41 #include "mongo/db/hasher.h"
42 #include "mongo/db/json.h"
43 #include "mongo/db/query/collation/collator_interface_mock.h"
44 #include "mongo/unittest/unittest.h"
45 #include "mongo/util/log.h"
46 
47 using namespace mongo;
48 
49 namespace {
50 
51 const HashSeed kHashSeed = 0;
52 const int kHashVersion = 0;
53 
dumpKeyset(const BSONObjSet & objs)54 std::string dumpKeyset(const BSONObjSet& objs) {
55     std::stringstream ss;
56     ss << "[ ";
57     for (BSONObjSet::iterator i = objs.begin(); i != objs.end(); ++i) {
58         ss << i->toString() << " ";
59     }
60     ss << "]";
61 
62     return ss.str();
63 }
64 
assertKeysetsEqual(const BSONObjSet & expectedKeys,const BSONObjSet & actualKeys)65 bool assertKeysetsEqual(const BSONObjSet& expectedKeys, const BSONObjSet& actualKeys) {
66     if (expectedKeys.size() != actualKeys.size()) {
67         log() << "Expected: " << dumpKeyset(expectedKeys) << ", "
68               << "Actual: " << dumpKeyset(actualKeys);
69         return false;
70     }
71 
72     if (!std::equal(expectedKeys.begin(),
73                     expectedKeys.end(),
74                     actualKeys.begin(),
75                     SimpleBSONObjComparator::kInstance.makeEqualTo())) {
76         log() << "Expected: " << dumpKeyset(expectedKeys) << ", "
77               << "Actual: " << dumpKeyset(actualKeys);
78         return false;
79     }
80 
81     return true;
82 }
83 
makeHashKey(BSONElement elt)84 BSONObj makeHashKey(BSONElement elt) {
85     return BSON("" << BSONElementHasher::hash64(elt, kHashSeed));
86 }
87 
TEST(HashKeyGeneratorTest,CollationAppliedBeforeHashing)88 TEST(HashKeyGeneratorTest, CollationAppliedBeforeHashing) {
89     BSONObj obj = fromjson("{a: 'string'}");
90     BSONObjSet actualKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
91     CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
92     ExpressionKeysPrivate::getHashKeys(
93         obj, "a", kHashSeed, kHashVersion, false, &collator, &actualKeys, false);
94 
95     BSONObj backwardsObj = fromjson("{a: 'gnirts'}");
96     BSONObjSet expectedKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
97     expectedKeys.insert(makeHashKey(backwardsObj["a"]));
98 
99     ASSERT(assertKeysetsEqual(expectedKeys, actualKeys));
100 }
101 
TEST(HashKeyGeneratorTest,CollationDoesNotAffectNonStringFields)102 TEST(HashKeyGeneratorTest, CollationDoesNotAffectNonStringFields) {
103     BSONObj obj = fromjson("{a: 5}");
104     BSONObjSet actualKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
105     CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
106     ExpressionKeysPrivate::getHashKeys(
107         obj, "a", kHashSeed, kHashVersion, false, &collator, &actualKeys, false);
108 
109     BSONObjSet expectedKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
110     expectedKeys.insert(makeHashKey(obj["a"]));
111 
112     ASSERT(assertKeysetsEqual(expectedKeys, actualKeys));
113 }
114 
TEST(HashKeyGeneratorTest,CollatorAppliedBeforeHashingNestedObject)115 TEST(HashKeyGeneratorTest, CollatorAppliedBeforeHashingNestedObject) {
116     BSONObj obj = fromjson("{a: {b: 'string'}}");
117     BSONObj backwardsObj = fromjson("{a: {b: 'gnirts'}}");
118     BSONObjSet actualKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
119     CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
120     ExpressionKeysPrivate::getHashKeys(
121         obj, "a", kHashSeed, kHashVersion, false, &collator, &actualKeys, false);
122 
123     BSONObjSet expectedKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
124     expectedKeys.insert(makeHashKey(backwardsObj["a"]));
125 
126     ASSERT(assertKeysetsEqual(expectedKeys, actualKeys));
127 }
128 
TEST(HashKeyGeneratorTest,NoCollation)129 TEST(HashKeyGeneratorTest, NoCollation) {
130     BSONObj obj = fromjson("{a: 'string'}");
131     BSONObjSet actualKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
132     ExpressionKeysPrivate::getHashKeys(
133         obj, "a", kHashSeed, kHashVersion, false, nullptr, &actualKeys, false);
134 
135     BSONObjSet expectedKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
136     expectedKeys.insert(makeHashKey(obj["a"]));
137 
138     ASSERT(assertKeysetsEqual(expectedKeys, actualKeys));
139 }
140 
TEST(HashKeyGeneratorTest,ArrayAlongIndexFieldPathFails)141 TEST(HashKeyGeneratorTest, ArrayAlongIndexFieldPathFails) {
142     BSONObj obj = fromjson("{a: []}");
143     BSONObjSet actualKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
144     ASSERT_THROWS_CODE(
145         ExpressionKeysPrivate::getHashKeys(
146             obj, "a.b.c", kHashSeed, kHashVersion, false, nullptr, &actualKeys, false),
147         DBException,
148         16766);
149 }
150 
TEST(HashKeyGeneratorTest,ArrayAlongIndexFieldPathDoesNotFailWhenIgnoreFlagIsSet)151 TEST(HashKeyGeneratorTest, ArrayAlongIndexFieldPathDoesNotFailWhenIgnoreFlagIsSet) {
152     BSONObj obj = fromjson("{a: []}");
153     BSONObjSet actualKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
154     ExpressionKeysPrivate::getHashKeys(obj,
155                                        "a.b.c",
156                                        kHashSeed,
157                                        kHashVersion,
158                                        false,
159                                        nullptr,
160                                        &actualKeys,
161                                        true  // ignoreArraysAlongPath
162                                        );
163 
164     BSONObjSet expectedKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
165     expectedKeys.insert(makeHashKey(BSON("" << BSONNULL).firstElement()));
166     ASSERT(assertKeysetsEqual(expectedKeys, actualKeys));
167 }
168 
TEST(HashKeyGeneratorTest,ArrayAtTerminalPathAlwaysFails)169 TEST(HashKeyGeneratorTest, ArrayAtTerminalPathAlwaysFails) {
170     BSONObj obj = fromjson("{a : {b: {c: [1]}}}");
171     BSONObjSet actualKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
172     ASSERT_THROWS_CODE(ExpressionKeysPrivate::getHashKeys(obj,
173                                                           "a.b.c",
174                                                           kHashSeed,
175                                                           kHashVersion,
176                                                           true,  // isSparse
177                                                           nullptr,
178                                                           &actualKeys,
179                                                           true  // ignoreArraysAlongPath
180                                                           ),
181                        DBException,
182                        16766);
183 }
184 
185 }  // namespace
186