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 <vector>
34
35 #include "mongo/bson/bsonmisc.h"
36 #include "mongo/bson/bsonobj.h"
37 #include "mongo/bson/bsonobjbuilder.h"
38 #include "mongo/bson/json.h"
39 #include "mongo/db/namespace_string.h"
40 #include "mongo/db/pipeline/aggregation_request.h"
41 #include "mongo/db/pipeline/document.h"
42 #include "mongo/db/views/resolved_view.h"
43 #include "mongo/unittest/unittest.h"
44
45 namespace mongo {
46 namespace {
47
48 const NamespaceString viewNss("testdb.testview");
49 const NamespaceString backingNss("testdb.testcoll");
50 const std::vector<BSONObj> emptyPipeline;
51 const BSONObj kDefaultCursorOptionDocument =
52 BSON(AggregationRequest::kBatchSizeName << AggregationRequest::kDefaultBatchSize);
53 const BSONObj kSimpleCollation;
54
TEST(ResolvedViewTest,ExpandingAggRequestWithEmptyPipelineOnNoOpViewYieldsEmptyPipeline)55 TEST(ResolvedViewTest, ExpandingAggRequestWithEmptyPipelineOnNoOpViewYieldsEmptyPipeline) {
56 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
57 AggregationRequest requestOnView{viewNss, emptyPipeline};
58
59 auto result = resolvedView.asExpandedViewAggregation(requestOnView);
60 BSONObj expected =
61 BSON("aggregate" << backingNss.coll() << "pipeline" << BSONArray() << "cursor"
62 << kDefaultCursorOptionDocument);
63 ASSERT_BSONOBJ_EQ(result.serializeToCommandObj().toBson(), expected);
64 }
65
TEST(ResolvedViewTest,ExpandingAggRequestWithNonemptyPipelineAppendsToViewPipeline)66 TEST(ResolvedViewTest, ExpandingAggRequestWithNonemptyPipelineAppendsToViewPipeline) {
67 std::vector<BSONObj> viewPipeline{BSON("skip" << 7)};
68 const ResolvedView resolvedView{backingNss, viewPipeline, kSimpleCollation};
69 AggregationRequest requestOnView{viewNss, std::vector<BSONObj>{BSON("limit" << 3)}};
70
71 auto result = resolvedView.asExpandedViewAggregation(requestOnView);
72
73 BSONObj expected = BSON("aggregate" << backingNss.coll() << "pipeline"
74 << BSON_ARRAY(BSON("skip" << 7) << BSON("limit" << 3))
75 << "cursor"
76 << kDefaultCursorOptionDocument);
77 ASSERT_BSONOBJ_EQ(result.serializeToCommandObj().toBson(), expected);
78 }
79
TEST(ResolvedViewTest,ExpandingAggRequestPreservesExplain)80 TEST(ResolvedViewTest, ExpandingAggRequestPreservesExplain) {
81 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
82 AggregationRequest aggRequest{viewNss, {}};
83 aggRequest.setExplain(ExplainOptions::Verbosity::kExecStats);
84
85 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
86 ASSERT(result.getExplain());
87 ASSERT(*result.getExplain() == ExplainOptions::Verbosity::kExecStats);
88 }
89
TEST(ResolvedViewTest,ExpandingAggRequestWithCursorAndExplainOnlyPreservesExplain)90 TEST(ResolvedViewTest, ExpandingAggRequestWithCursorAndExplainOnlyPreservesExplain) {
91 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
92 AggregationRequest aggRequest{viewNss, {}};
93 aggRequest.setBatchSize(10);
94 aggRequest.setExplain(ExplainOptions::Verbosity::kExecStats);
95
96 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
97 ASSERT(result.getExplain());
98 ASSERT(*result.getExplain() == ExplainOptions::Verbosity::kExecStats);
99 ASSERT_EQ(result.getBatchSize(), AggregationRequest::kDefaultBatchSize);
100 }
101
TEST(ResolvedViewTest,ExpandingAggRequestPreservesBypassDocumentValidation)102 TEST(ResolvedViewTest, ExpandingAggRequestPreservesBypassDocumentValidation) {
103 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
104 AggregationRequest aggRequest(viewNss, {});
105 aggRequest.setBypassDocumentValidation(true);
106
107 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
108 ASSERT_TRUE(result.shouldBypassDocumentValidation());
109 }
110
TEST(ResolvedViewTest,ExpandingAggRequestPreservesAllowDiskUse)111 TEST(ResolvedViewTest, ExpandingAggRequestPreservesAllowDiskUse) {
112 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
113 AggregationRequest aggRequest(viewNss, {});
114 aggRequest.setAllowDiskUse(true);
115
116 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
117 ASSERT_TRUE(result.shouldAllowDiskUse());
118 }
119
TEST(ResolvedViewTest,ExpandingAggRequestPreservesHint)120 TEST(ResolvedViewTest, ExpandingAggRequestPreservesHint) {
121 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
122 AggregationRequest aggRequest(viewNss, {});
123 aggRequest.setHint(BSON("a" << 1));
124
125 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
126 ASSERT_BSONOBJ_EQ(result.getHint(), BSON("a" << 1));
127 }
128
TEST(ResolvedViewTest,ExpandingAggRequestPreservesReadPreference)129 TEST(ResolvedViewTest, ExpandingAggRequestPreservesReadPreference) {
130 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
131 AggregationRequest aggRequest(viewNss, {});
132 aggRequest.setUnwrappedReadPref(BSON("$readPreference"
133 << "nearest"));
134
135 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
136 ASSERT_BSONOBJ_EQ(result.getUnwrappedReadPref(),
137 BSON("$readPreference"
138 << "nearest"));
139 }
140
TEST(ResolvedViewTest,ExpandingAggRequestPreservesReadConcern)141 TEST(ResolvedViewTest, ExpandingAggRequestPreservesReadConcern) {
142 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
143 AggregationRequest aggRequest(viewNss, {});
144 aggRequest.setReadConcern(BSON("level"
145 << "linearizable"));
146
147 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
148 ASSERT_BSONOBJ_EQ(result.getReadConcern(),
149 BSON("level"
150 << "linearizable"));
151 }
152
TEST(ResolvedViewTest,ExpandingAggRequestPreservesMaxTimeMS)153 TEST(ResolvedViewTest, ExpandingAggRequestPreservesMaxTimeMS) {
154 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
155 AggregationRequest aggRequest(viewNss, {});
156 aggRequest.setMaxTimeMS(100u);
157
158 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
159 ASSERT_EQ(result.getMaxTimeMS(), 100u);
160 }
161
TEST(ResolvedViewTest,ExpandingAggRequestPreservesDefaultCollationOfView)162 TEST(ResolvedViewTest, ExpandingAggRequestPreservesDefaultCollationOfView) {
163 const ResolvedView resolvedView{backingNss,
164 emptyPipeline,
165 BSON("locale"
166 << "fr_CA")};
167 ASSERT_BSONOBJ_EQ(resolvedView.getDefaultCollation(),
168 BSON("locale"
169 << "fr_CA"));
170 AggregationRequest aggRequest(viewNss, {});
171
172 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
173 ASSERT_BSONOBJ_EQ(result.getCollation(),
174 BSON("locale"
175 << "fr_CA"));
176 }
177
TEST(ResolvedViewTest,ExpandingAggRequestPreservesComment)178 TEST(ResolvedViewTest, ExpandingAggRequestPreservesComment) {
179 const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation};
180 AggregationRequest aggRequest(viewNss, {});
181 aggRequest.setComment("agg_comment");
182
183 auto result = resolvedView.asExpandedViewAggregation(aggRequest);
184 ASSERT_EQ(result.getComment(), "agg_comment");
185 }
186
TEST(ResolvedViewTest,FromBSONFailsIfMissingResolvedView)187 TEST(ResolvedViewTest, FromBSONFailsIfMissingResolvedView) {
188 BSONObj badCmdResponse = BSON("x" << 1);
189 ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40248);
190 }
191
TEST(ResolvedViewTest,FromBSONFailsOnResolvedViewBadType)192 TEST(ResolvedViewTest, FromBSONFailsOnResolvedViewBadType) {
193 BSONObj badCmdResponse = BSON("resolvedView" << 7);
194 ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40249);
195 }
196
TEST(ResolvedViewTest,FromBSONFailsIfMissingViewNs)197 TEST(ResolvedViewTest, FromBSONFailsIfMissingViewNs) {
198 BSONObj badCmdResponse = BSON("resolvedView" << BSON("pipeline" << BSONArray()));
199 ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40250);
200 }
201
TEST(ResolvedViewTest,FromBSONFailsOnInvalidViewNsType)202 TEST(ResolvedViewTest, FromBSONFailsOnInvalidViewNsType) {
203 BSONObj badCmdResponse = BSON("resolvedView" << BSON("ns" << 8));
204 ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40250);
205 }
206
TEST(ResolvedViewTest,FromBSONFailsIfMissingPipeline)207 TEST(ResolvedViewTest, FromBSONFailsIfMissingPipeline) {
208 BSONObj badCmdResponse = BSON("resolvedView" << BSON("ns" << backingNss.ns()));
209 ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40251);
210 }
211
TEST(ResolvedViewTest,FromBSONFailsOnInvalidPipelineType)212 TEST(ResolvedViewTest, FromBSONFailsOnInvalidPipelineType) {
213 BSONObj badCmdResponse =
214 BSON("resolvedView" << BSON("ns" << backingNss.ns() << "pipeline" << 7));
215 ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40251);
216 }
217
TEST(ResolvedViewTest,FromBSONFailsOnInvalidCollationType)218 TEST(ResolvedViewTest, FromBSONFailsOnInvalidCollationType) {
219 BSONObj badCmdResponse =
220 BSON("resolvedView" << BSON(
221 "ns" << backingNss.ns() << "pipeline" << BSONArray() << "collation" << 1));
222 ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40639);
223 }
224
TEST(ResolvedViewTest,FromBSONSuccessfullyParsesEmptyBSONArrayIntoEmptyVector)225 TEST(ResolvedViewTest, FromBSONSuccessfullyParsesEmptyBSONArrayIntoEmptyVector) {
226 BSONObj cmdResponse =
227 BSON("resolvedView" << BSON("ns" << backingNss.ns() << "pipeline" << BSONArray()));
228 const ResolvedView result = ResolvedView::fromBSON(cmdResponse);
229 ASSERT_EQ(result.getNamespace(), backingNss);
230 ASSERT(std::equal(emptyPipeline.begin(),
231 emptyPipeline.end(),
232 result.getPipeline().begin(),
233 SimpleBSONObjComparator::kInstance.makeEqualTo()));
234 }
235
TEST(ResolvedViewTest,FromBSONSuccessfullyParsesCollation)236 TEST(ResolvedViewTest, FromBSONSuccessfullyParsesCollation) {
237 BSONObj cmdResponse = BSON(
238 "resolvedView" << BSON("ns" << backingNss.ns() << "pipeline" << BSONArray() << "collation"
239 << BSON("locale"
240 << "fil")));
241 const ResolvedView result = ResolvedView::fromBSON(cmdResponse);
242 ASSERT_EQ(result.getNamespace(), backingNss);
243 ASSERT(std::equal(emptyPipeline.begin(),
244 emptyPipeline.end(),
245 result.getPipeline().begin(),
246 SimpleBSONObjComparator::kInstance.makeEqualTo()));
247 ASSERT_BSONOBJ_EQ(result.getDefaultCollation(),
248 BSON("locale"
249 << "fil"));
250 }
251
TEST(ResolvedViewTest,FromBSONSuccessfullyParsesPopulatedBSONArrayIntoVector)252 TEST(ResolvedViewTest, FromBSONSuccessfullyParsesPopulatedBSONArrayIntoVector) {
253 BSONObj matchStage = BSON("$match" << BSON("x" << 1));
254 BSONObj sortStage = BSON("$sort" << BSON("y" << -1));
255 BSONObj limitStage = BSON("$limit" << 7);
256
257 BSONArray pipeline = BSON_ARRAY(matchStage << sortStage << limitStage);
258 BSONObj cmdResponse = BSON("resolvedView" << BSON("ns"
259 << "testdb.testcoll"
260 << "pipeline"
261 << pipeline));
262
263 const ResolvedView result = ResolvedView::fromBSON(cmdResponse);
264 ASSERT_EQ(result.getNamespace(), backingNss);
265
266 std::vector<BSONObj> expectedPipeline{matchStage, sortStage, limitStage};
267 ASSERT(std::equal(expectedPipeline.begin(),
268 expectedPipeline.end(),
269 result.getPipeline().begin(),
270 SimpleBSONObjComparator::kInstance.makeEqualTo()));
271 }
272
TEST(ResolvedViewTest,IsResolvedViewErrorResponseDetectsKickbackErrorCodeSuccessfully)273 TEST(ResolvedViewTest, IsResolvedViewErrorResponseDetectsKickbackErrorCodeSuccessfully) {
274 BSONObj errorResponse =
275 BSON("ok" << 0 << "code" << ErrorCodes::CommandOnShardedViewNotSupportedOnMongod << "errmsg"
276 << "This view is sharded and cannot be run on mongod");
277 ASSERT(ResolvedView::isResolvedViewErrorResponse(errorResponse));
278 }
279
TEST(ResolvedViewTest,IsResolvedViewErrorResponseReportsFalseOnNonKickbackErrorCode)280 TEST(ResolvedViewTest, IsResolvedViewErrorResponseReportsFalseOnNonKickbackErrorCode) {
281 BSONObj errorResponse =
282 BSON("ok" << 0 << "code" << ErrorCodes::ViewDepthLimitExceeded << "errmsg"
283 << "View nesting too deep or view cycle detected");
284 ASSERT_FALSE(ResolvedView::isResolvedViewErrorResponse(errorResponse));
285 }
286
TEST(ResolvedViewTest,ToBSONSerializesCorrectly)287 TEST(ResolvedViewTest, ToBSONSerializesCorrectly) {
288 const ResolvedView resolvedView{backingNss,
289 std::vector<BSONObj>{BSON("$match" << BSON("x" << 1))},
290 BSON("locale"
291 << "fr_CA")};
292 auto serialized = resolvedView.toBSON();
293 ASSERT_BSONOBJ_EQ(
294 serialized,
295 fromjson(
296 "{ns: 'testdb.testcoll', pipeline: [{$match: {x: 1}}], collation: {locale: 'fr_CA'}}"));
297 }
298
TEST(ResolvedViewTest,ToBSONOutputCanBeReparsed)299 TEST(ResolvedViewTest, ToBSONOutputCanBeReparsed) {
300 const ResolvedView resolvedView{backingNss,
301 emptyPipeline,
302 BSON("locale"
303 << "fr_CA")};
304 auto serialized = resolvedView.toBSON();
305 auto reparsedResolvedView = ResolvedView::fromBSON(BSON("resolvedView" << serialized));
306 ASSERT_EQ(reparsedResolvedView.getNamespace(), backingNss);
307 ASSERT(std::equal(emptyPipeline.begin(),
308 emptyPipeline.end(),
309 reparsedResolvedView.getPipeline().begin(),
310 SimpleBSONObjComparator::kInstance.makeEqualTo()));
311 ASSERT_BSONOBJ_EQ(reparsedResolvedView.getDefaultCollation(),
312 BSON("locale"
313 << "fr_CA"));
314 }
315 } // namespace
316 } // namespace mongo
317