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