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::kQuery
32
33 #include "mongo/db/pipeline/resume_token.h"
34
35 #include <boost/optional/optional_io.hpp>
36 #include <random>
37
38 #include "mongo/db/pipeline/document.h"
39 #include "mongo/db/pipeline/document_source_change_stream.h"
40 #include "mongo/unittest/unittest.h"
41 #include "mongo/util/log.h"
42
43 namespace mongo {
44
45 namespace {
46
TEST(ResumeToken,EncodesFullTokenFromData)47 TEST(ResumeToken, EncodesFullTokenFromData) {
48 Timestamp ts(1000, 2);
49 UUID testUuid = UUID::gen();
50 Document documentKey{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
51
52 ResumeTokenData resumeTokenDataIn(ts, Value(documentKey), testUuid);
53 ResumeToken token(resumeTokenDataIn);
54 ResumeTokenData tokenData = token.getData();
55 ASSERT_EQ(resumeTokenDataIn, tokenData);
56 }
57
TEST(ResumeToken,EncodesTimestampOnlyTokenFromData)58 TEST(ResumeToken, EncodesTimestampOnlyTokenFromData) {
59 Timestamp ts(1001, 3);
60
61 ResumeTokenData resumeTokenDataIn;
62 resumeTokenDataIn.clusterTime = ts;
63 ResumeToken token(resumeTokenDataIn);
64 ResumeTokenData tokenData = token.getData();
65 ASSERT_EQ(resumeTokenDataIn, tokenData);
66 }
67
TEST(ResumeToken,RoundTripThroughBsonFullToken)68 TEST(ResumeToken, RoundTripThroughBsonFullToken) {
69 Timestamp ts(1000, 2);
70 UUID testUuid = UUID::gen();
71 Document documentKey{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
72
73 ResumeTokenData resumeTokenDataIn(ts, Value(documentKey), testUuid);
74 auto rtToken = ResumeToken::parse(ResumeToken(resumeTokenDataIn).toBSON());
75 ResumeTokenData tokenData = rtToken.getData();
76 ASSERT_EQ(resumeTokenDataIn, tokenData);
77 }
78
TEST(ResumeToken,RoundTripThroughBsonTimestampOnlyToken)79 TEST(ResumeToken, RoundTripThroughBsonTimestampOnlyToken) {
80 Timestamp ts(1001, 3);
81
82 ResumeTokenData resumeTokenDataIn;
83 resumeTokenDataIn.clusterTime = ts;
84 auto rtToken = ResumeToken::parse(ResumeToken(resumeTokenDataIn).toBSON());
85 ResumeTokenData tokenData = rtToken.getData();
86 ASSERT_EQ(resumeTokenDataIn, tokenData);
87 }
88
TEST(ResumeToken,RoundTripThroughDocumentFullToken)89 TEST(ResumeToken, RoundTripThroughDocumentFullToken) {
90 Timestamp ts(1000, 2);
91 UUID testUuid = UUID::gen();
92 Document documentKey{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
93
94 ResumeTokenData resumeTokenDataIn(ts, Value(documentKey), testUuid);
95 auto rtToken = ResumeToken::parse(ResumeToken(resumeTokenDataIn).toDocument());
96 ResumeTokenData tokenData = rtToken.getData();
97 ASSERT_EQ(resumeTokenDataIn, tokenData);
98 }
99
TEST(ResumeToken,RoundTripThroughDocumentTimestampOnlyToken)100 TEST(ResumeToken, RoundTripThroughDocumentTimestampOnlyToken) {
101 Timestamp ts(1001, 3);
102
103 ResumeTokenData resumeTokenDataIn;
104 resumeTokenDataIn.clusterTime = ts;
105 auto rtToken = ResumeToken::parse(ResumeToken(resumeTokenDataIn).toDocument());
106 ResumeTokenData tokenData = rtToken.getData();
107 ASSERT_EQ(resumeTokenDataIn, tokenData);
108 }
109
TEST(ResumeToken,TestMissingTypebitsOptimization)110 TEST(ResumeToken, TestMissingTypebitsOptimization) {
111 Timestamp ts(1000, 1);
112 UUID testUuid = UUID::gen();
113
114 ResumeTokenData hasTypeBitsData(ts, Value(Document{{"_id", 1.0}}), testUuid);
115 ResumeTokenData noTypeBitsData(ResumeTokenData(ts, Value(Document{{"_id", 1}}), testUuid));
116 ResumeToken hasTypeBitsToken(hasTypeBitsData);
117 ResumeToken noTypeBitsToken(noTypeBitsData);
118 ASSERT_EQ(noTypeBitsToken, hasTypeBitsToken);
119 auto hasTypeBitsDoc = hasTypeBitsToken.toDocument();
120 auto noTypeBitsDoc = noTypeBitsToken.toDocument();
121 ASSERT_FALSE(hasTypeBitsDoc["_typeBits"].missing());
122 ASSERT_TRUE(noTypeBitsDoc["_typeBits"].missing()) << noTypeBitsDoc["_typeBits"];
123 auto rtHasTypeBitsData = ResumeToken::parse(hasTypeBitsDoc).getData();
124 auto rtNoTypeBitsData = ResumeToken::parse(noTypeBitsDoc).getData();
125 ASSERT_EQ(hasTypeBitsData, rtHasTypeBitsData);
126 ASSERT_EQ(noTypeBitsData, rtNoTypeBitsData);
127 ASSERT_EQ(BSONType::NumberDouble, rtHasTypeBitsData.documentKey["_id"].getType());
128 ASSERT_EQ(BSONType::NumberInt, rtNoTypeBitsData.documentKey["_id"].getType());
129 }
130
131 // Tests comparison functions for tokens constructed from oplog data.
TEST(ResumeToken,CompareFromData)132 TEST(ResumeToken, CompareFromData) {
133 Timestamp ts1(1000, 1);
134 Timestamp ts2(1000, 2);
135 Timestamp ts3(1001, 1);
136 UUID testUuid = UUID::gen();
137 UUID testUuid2 = UUID::gen();
138 Document documentKey1a{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
139 Document documentKey1b{{"_id"_sd, "stuff"_sd},
140 {"otherkey"_sd, Document{{"otherstuff"_sd, 2.0}}}};
141 Document documentKey2{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 3}}}};
142 Document documentKey3{{"_id"_sd, "ztuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 0}}}};
143
144 ResumeToken token1a(ResumeTokenData(ts1, Value(documentKey1a), testUuid));
145 ResumeToken token1b(ResumeTokenData(ts1, Value(documentKey1b), testUuid));
146
147 // Equivalent types don't matter.
148 ASSERT_EQ(token1a, token1b);
149 ASSERT_LTE(token1a, token1b);
150 ASSERT_GTE(token1a, token1b);
151
152 // UUIDs matter, but all that really matters is they compare unequal.
153 ResumeToken tokenOtherCollection(ResumeTokenData(ts1, Value(documentKey1a), testUuid2));
154 ASSERT_NE(token1a, tokenOtherCollection);
155
156 ResumeToken token2(ResumeTokenData(ts1, Value(documentKey2), testUuid));
157
158 // Document keys matter.
159 ASSERT_LT(token1a, token2);
160 ASSERT_LTE(token1a, token2);
161 ASSERT_GT(token2, token1a);
162 ASSERT_GTE(token2, token1a);
163
164 ResumeToken token3(ResumeTokenData(ts1, Value(documentKey3), testUuid));
165
166 // Order within document keys matters.
167 ASSERT_LT(token1a, token3);
168 ASSERT_LTE(token1a, token3);
169 ASSERT_GT(token3, token1a);
170 ASSERT_GTE(token3, token1a);
171
172 ASSERT_LT(token2, token3);
173 ASSERT_LTE(token2, token3);
174 ASSERT_GT(token3, token2);
175 ASSERT_GTE(token3, token2);
176
177 ResumeToken token4(ResumeTokenData(ts2, Value(documentKey1a), testUuid));
178
179 // Timestamps matter.
180 ASSERT_LT(token1a, token4);
181 ASSERT_LTE(token1a, token4);
182 ASSERT_GT(token4, token1a);
183 ASSERT_GTE(token4, token1a);
184
185 // Timestamps matter more than document key.
186 ASSERT_LT(token3, token4);
187 ASSERT_LTE(token3, token4);
188 ASSERT_GT(token4, token3);
189 ASSERT_GTE(token4, token3);
190
191 ResumeToken token5(ResumeTokenData(ts3, Value(documentKey1a), testUuid));
192
193 // Time matters more than increment in timestamp
194 ASSERT_LT(token4, token5);
195 ASSERT_LTE(token4, token5);
196 ASSERT_GT(token5, token4);
197 ASSERT_GTE(token5, token4);
198 }
199
200 // Tests comparison functions for tokens constructed from the keystring-encoded form.
TEST(ResumeToken,CompareFromEncodedData)201 TEST(ResumeToken, CompareFromEncodedData) {
202 Timestamp ts1(1000, 1);
203 Timestamp ts2(1000, 2);
204 Timestamp ts3(1001, 1);
205 UUID testUuid = UUID::gen();
206 UUID testUuid2 = UUID::gen();
207 Document documentKey1a{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
208 Document documentKey1b{{"_id"_sd, "stuff"_sd},
209 {"otherkey"_sd, Document{{"otherstuff"_sd, 2.0}}}};
210 Document documentKey2{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 3}}}};
211 Document documentKey3{{"_id"_sd, "ztuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 0}}}};
212
213 auto token1a = ResumeToken::parse(
214 ResumeToken(ResumeTokenData(ts1, Value(documentKey1a), testUuid)).toDocument());
215 auto token1b = ResumeToken::parse(
216 ResumeToken(ResumeTokenData(ts1, Value(documentKey1b), testUuid)).toDocument());
217
218 // Equivalent types don't matter.
219 ASSERT_EQ(token1a, token1b);
220 ASSERT_LTE(token1a, token1b);
221 ASSERT_GTE(token1a, token1b);
222
223 // UUIDs matter, but all that really matters is they compare unequal.
224 auto tokenOtherCollection = ResumeToken::parse(
225 ResumeToken(ResumeTokenData(ts1, Value(documentKey1a), testUuid2)).toDocument());
226 ASSERT_NE(token1a, tokenOtherCollection);
227
228 auto token2 = ResumeToken::parse(
229 ResumeToken(ResumeTokenData(ts1, Value(documentKey2), testUuid)).toDocument());
230
231 // Document keys matter.
232 ASSERT_LT(token1a, token2);
233 ASSERT_LTE(token1a, token2);
234 ASSERT_GT(token2, token1a);
235 ASSERT_GTE(token2, token1a);
236
237 auto token3 = ResumeToken::parse(
238 ResumeToken(ResumeTokenData(ts1, Value(documentKey3), testUuid)).toDocument());
239
240 // Order within document keys matters.
241 ASSERT_LT(token1a, token3);
242 ASSERT_LTE(token1a, token3);
243 ASSERT_GT(token3, token1a);
244 ASSERT_GTE(token3, token1a);
245
246 ASSERT_LT(token2, token3);
247 ASSERT_LTE(token2, token3);
248 ASSERT_GT(token3, token2);
249 ASSERT_GTE(token3, token2);
250
251 auto token4 = ResumeToken::parse(
252 ResumeToken(ResumeTokenData(ts2, Value(documentKey1a), testUuid)).toDocument());
253
254 // Timestamps matter.
255 ASSERT_LT(token1a, token4);
256 ASSERT_LTE(token1a, token4);
257 ASSERT_GT(token4, token1a);
258 ASSERT_GTE(token4, token1a);
259
260 // Timestamps matter more than document key.
261 ASSERT_LT(token3, token4);
262 ASSERT_LTE(token3, token4);
263 ASSERT_GT(token4, token3);
264 ASSERT_GTE(token4, token3);
265
266 auto token5 = ResumeToken::parse(
267 ResumeToken(ResumeTokenData(ts3, Value(documentKey1a), testUuid)).toDocument());
268
269 // Time matters more than increment in timestamp
270 ASSERT_LT(token4, token5);
271 ASSERT_LTE(token4, token5);
272 ASSERT_GT(token5, token4);
273 ASSERT_GTE(token5, token4);
274 }
275
TEST(ResumeToken,FailsToParseForInvalidTokenFormats)276 TEST(ResumeToken, FailsToParseForInvalidTokenFormats) {
277 // Missing document.
278 ASSERT_THROWS(ResumeToken::parse(Document()), AssertionException);
279 // Missing data field.
280 ASSERT_THROWS(ResumeToken::parse(Document{{"somefield"_sd, "stuff"_sd}}), AssertionException);
281 // Wrong type data field
282 ASSERT_THROWS(ResumeToken::parse(Document{{"_data"_sd, "string"_sd}}), AssertionException);
283 ASSERT_THROWS(ResumeToken::parse(Document{{"_data"_sd, 0}}), AssertionException);
284
285 // Valid data field, but wrong type typeBits.
286 Timestamp ts(1010, 4);
287 ResumeTokenData tokenData;
288 tokenData.clusterTime = ts;
289 auto goodTokenDoc = ResumeToken(tokenData).toDocument();
290 auto goodData = goodTokenDoc["_data"].getBinData();
291 ASSERT_THROWS(ResumeToken::parse(Document{{"_data"_sd, goodData}, {"_typeBits", "string"_sd}}),
292 AssertionException);
293
294 // Valid data except wrong bindata type.
295 ASSERT_THROWS(ResumeToken::parse(
296 Document{{"_data"_sd, BSONBinData(goodData.data, goodData.length, newUUID)}}),
297 AssertionException);
298 // Valid data, wrong typeBits bindata type.
299 ASSERT_THROWS(
300 ResumeToken::parse(Document{{"_data"_sd, goodData},
301 {"_typeBits", BSONBinData(goodData.data, 0, newUUID)}}),
302 AssertionException);
303 }
304
TEST(ResumeToken,FailsToDecodeInvalidKeyStringBinData)305 TEST(ResumeToken, FailsToDecodeInvalidKeyStringBinData) {
306 Timestamp ts(1010, 4);
307 ResumeTokenData tokenData;
308 tokenData.clusterTime = ts;
309 auto goodTokenDocBinData = ResumeToken(tokenData).toDocument();
310 auto goodData = goodTokenDocBinData["_data"].getBinData();
311 const unsigned char zeroes[] = {0, 0, 0, 0, 0};
312 const unsigned char nonsense[] = {165, 85, 77, 86, 255};
313
314 // Data of correct type, but empty. This won't fail until we try to decode the data.
315 auto emptyToken =
316 ResumeToken::parse(Document{{"_data"_sd, BSONBinData(zeroes, 0, BinDataGeneral)}});
317 ASSERT_THROWS_CODE(emptyToken.getData(), AssertionException, 40649);
318
319 // Data of correct type with a bunch of zeros.
320 auto zeroesToken = ResumeToken::parse(
321 Document{{"_data"_sd, BSONBinData(zeroes, sizeof(zeroes), BinDataGeneral)}});
322 ASSERT_THROWS_CODE(zeroesToken.getData(), AssertionException, 50811);
323
324 // Data of correct type with a bunch of nonsense.
325 auto nonsenseToken = ResumeToken::parse(
326 Document{{"_data"_sd, BSONBinData(nonsense, sizeof(nonsense), BinDataGeneral)}});
327 ASSERT_THROWS_CODE(nonsenseToken.getData(), AssertionException, 50811);
328
329 // Valid data, bad typeBits; note that an all-zeros typebits is valid so it is not tested here.
330 auto badTypeBitsToken = ResumeToken::parse(
331 Document{{"_data"_sd, goodData},
332 {"_typeBits", BSONBinData(nonsense, sizeof(nonsense), BinDataGeneral)}});
333 ASSERT_THROWS_CODE(badTypeBitsToken.getData(), AssertionException, ErrorCodes::Overflow);
334
335 const unsigned char invalidString[] = {
336 60, // CType::kStringLike
337 55, // Non-null terminated
338 };
339 auto invalidStringToken = ResumeToken::parse(
340 Document{{"_data"_sd, BSONBinData(invalidString, sizeof(invalidString), BinDataGeneral)}});
341 ASSERT_THROWS_WITH_CHECK(
342 invalidStringToken.getData(), AssertionException, [](const AssertionException& exception) {
343 ASSERT_EQ(exception.code(), 50816);
344 ASSERT_STRING_CONTAINS(exception.reason(), "Failed to find null terminator in string");
345 });
346 }
347
348 } // namspace
349 } // namspace mongo
350