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