diff options
author | David Storch <david.storch@10gen.com> | 2017-09-20 10:26:41 -0400 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2017-09-26 16:47:54 -0400 |
commit | 3a262ee4002375ed7f2e4cdf6cac60b712d42685 (patch) | |
tree | d89668772b5ab8a760dc4d2b3679ac418071f74a /src | |
parent | e5f6970de5e2f5802d4d892597b3db2556e35bcc (diff) | |
download | mongo-3a262ee4002375ed7f2e4cdf6cac60b712d42685.tar.gz |
SERVER-31049 Fix resolution of a view's default collation on mongos.
(cherry picked from commit 5f8d84b1792839a4b372e8479d0eccba6fd64357)
Conflicts:
buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough.yml
buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough_auth.yml
jstests/core/views/views_collation.js
src/mongo/db/commands/run_aggregate.cpp
src/mongo/db/views/resolved_view.cpp
src/mongo/db/views/resolved_view_test.cpp
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/commands/pipeline_command.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/views/resolved_view.cpp | 29 | ||||
-rw-r--r-- | src/mongo/db/views/resolved_view.h | 22 | ||||
-rw-r--r-- | src/mongo/db/views/resolved_view_test.cpp | 91 | ||||
-rw-r--r-- | src/mongo/db/views/view_catalog.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/views/view_catalog_test.cpp | 38 | ||||
-rw-r--r-- | src/mongo/db/views/view_sharding_check.cpp | 6 |
7 files changed, 177 insertions, 23 deletions
diff --git a/src/mongo/db/commands/pipeline_command.cpp b/src/mongo/db/commands/pipeline_command.cpp index 6fe6de34381..03f8ec87e30 100644 --- a/src/mongo/db/commands/pipeline_command.cpp +++ b/src/mongo/db/commands/pipeline_command.cpp @@ -407,10 +407,6 @@ public: return appendCommandStatus(result, resolvedView.getStatus()); } - auto collationSpec = ctx.getView()->defaultCollator() - ? ctx.getView()->defaultCollator()->getSpec().toBSON().getOwned() - : CollationSpec::kSimpleSpec; - // With the view & collation resolved, we can relinquish locks. ctx.releaseLocksForView(); @@ -424,7 +420,6 @@ public: if (!newRequest.isOK()) { return appendCommandStatus(result, newRequest.getStatus()); } - newRequest.getValue().setCollation(collationSpec); bool status = runParsed( txn, origNss, newRequest.getValue(), newCmd.getValue(), errmsg, result); diff --git a/src/mongo/db/views/resolved_view.cpp b/src/mongo/db/views/resolved_view.cpp index d5f4faa7f68..f497cbbb9bd 100644 --- a/src/mongo/db/views/resolved_view.cpp +++ b/src/mongo/db/views/resolved_view.cpp @@ -62,7 +62,27 @@ ResolvedView ResolvedView::fromBSON(BSONObj commandResponseObj) { pipeline.push_back(item.Obj().getOwned()); } - return {ResolvedView(NamespaceString(viewDef["ns"].valueStringData()), pipeline)}; + BSONObj collationSpec; + if (auto collationElt = viewDef["collation"]) { + uassert(40639, + "View definition 'collation' field must be an object", + collationElt.type() == BSONType::Object); + collationSpec = collationElt.embeddedObject().getOwned(); + } + + return {NamespaceString(viewDef["ns"].valueStringData()), + std::move(pipeline), + std::move(collationSpec)}; +} + +BSONObj ResolvedView::toBSON() const { + BSONObjBuilder builder; + builder.append("ns", _namespace.ns()); + builder.append("pipeline", _pipeline); + if (!_defaultCollation.isEmpty()) { + builder.append("collation", _defaultCollation); + } + return builder.obj(); } StatusWith<BSONObj> ResolvedView::asExpandedViewAggregation( @@ -104,6 +124,13 @@ StatusWith<BSONObj> ResolvedView::asExpandedViewAggregation( aggregationBuilder.append("allowDiskUse", true); } + // Operations on a view must always use the default collation of the view. We must have already + // checked that if the user's request specifies a collation, it matches the collation of the + // view. + if (!_defaultCollation.isEmpty()) { + aggregationBuilder.append("collation", _defaultCollation); + } + return aggregationBuilder.obj(); } diff --git a/src/mongo/db/views/resolved_view.h b/src/mongo/db/views/resolved_view.h index a5016ffc4b3..789576c737f 100644 --- a/src/mongo/db/views/resolved_view.h +++ b/src/mongo/db/views/resolved_view.h @@ -44,8 +44,12 @@ class AggregationRequest; */ class ResolvedView { public: - ResolvedView(const NamespaceString& collectionNs, const std::vector<BSONObj>& pipeline) - : _namespace(collectionNs), _pipeline(pipeline) {} + ResolvedView(const NamespaceString& collectionNs, + std::vector<BSONObj> pipeline, + BSONObj defaultCollation) + : _namespace(collectionNs), + _pipeline(std::move(pipeline)), + _defaultCollation(std::move(defaultCollation)) {} /** * Returns whether 'commandResponseObj' contains a CommandOnShardedViewNotSupportedOnMongod @@ -55,6 +59,8 @@ public: static ResolvedView fromBSON(BSONObj commandResponseObj); + BSONObj toBSON() const; + /** * Convert an aggregation command on a view to the equivalent command against the views * underlying collection. @@ -70,9 +76,21 @@ public: return _pipeline; } + const BSONObj& getDefaultCollation() const { + return _defaultCollation; + } + private: NamespaceString _namespace; std::vector<BSONObj> _pipeline; + + // The default collation associated with this view. An empty object means that the default is + // the simple collation. + // + // Currently all operations which run over a view must use the default collation. This means + // that operations on the view which do not specify a collation inherit the default. Operations + // on the view which specify any other collation fail with a user error. + BSONObj _defaultCollation; }; } // namespace mongo diff --git a/src/mongo/db/views/resolved_view_test.cpp b/src/mongo/db/views/resolved_view_test.cpp index eebcda01db5..d8b95308d45 100644 --- a/src/mongo/db/views/resolved_view_test.cpp +++ b/src/mongo/db/views/resolved_view_test.cpp @@ -33,6 +33,7 @@ #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/json.h" #include "mongo/db/namespace_string.h" #include "mongo/db/pipeline/aggregation_request.h" #include "mongo/db/views/resolved_view.h" @@ -50,9 +51,10 @@ namespace { const NamespaceString viewNss("testdb.testview"); const NamespaceString backingNss("testdb.testcoll"); const std::vector<BSONObj> emptyPipeline; +const BSONObj kSimpleCollation; TEST(ResolvedViewTest, ExpandingCmdObjWithEmptyPipelineOnNoOpViewYieldsEmptyPipeline) { - const ResolvedView resolvedView{backingNss, emptyPipeline}; + const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation}; BSONObj cmdObj = BSON("aggregate" << viewNss.coll() << "pipeline" << BSONArray()); auto result = resolvedView.asExpandedViewAggregation(cmdObj); @@ -65,7 +67,7 @@ TEST(ResolvedViewTest, ExpandingCmdObjWithEmptyPipelineOnNoOpViewYieldsEmptyPipe TEST(ResolvedViewTest, ExpandingCmdObjWithNonemptyPipelineAppendsToViewPipeline) { std::vector<BSONObj> viewPipeline{BSON("skip" << 7)}; - const ResolvedView resolvedView{backingNss, viewPipeline}; + const ResolvedView resolvedView{backingNss, viewPipeline, kSimpleCollation}; BSONObj cmdObj = BSON("aggregate" << viewNss.coll() << "pipeline" << BSON_ARRAY(BSON("limit" << 3))); @@ -80,13 +82,13 @@ TEST(ResolvedViewTest, ExpandingCmdObjWithNonemptyPipelineAppendsToViewPipeline) } TEST(ResolvedViewTest, ExpandingCmdObjFailsIfCmdObjIsNotAValidAggregationCommand) { - const ResolvedView resolvedView{backingNss, emptyPipeline}; + const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation}; BSONObj badCmdObj = BSON("invalid" << 0); ASSERT_NOT_OK(resolvedView.asExpandedViewAggregation(badCmdObj).getStatus()); } TEST(ResolvedViewTest, ExpandingAggRequestWithEmptyPipelineOnNoOpViewYieldsEmptyPipeline) { - const ResolvedView resolvedView{backingNss, emptyPipeline}; + const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation}; AggregationRequest aggRequest(viewNss, {}); auto result = resolvedView.asExpandedViewAggregation(aggRequest); @@ -99,7 +101,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestWithEmptyPipelineOnNoOpViewYieldsEmpty TEST(ResolvedViewTest, ExpandingAggRequestWithNonemptyPipelineAppendsToViewPipeline) { std::vector<BSONObj> viewPipeline{BSON("skip" << 7)}; - const ResolvedView resolvedView{backingNss, viewPipeline}; + const ResolvedView resolvedView{backingNss, viewPipeline, kSimpleCollation}; std::vector<BSONObj> userAggregationPipeline = {BSON("limit" << 3)}; AggregationRequest aggRequest(viewNss, userAggregationPipeline); @@ -115,7 +117,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestWithNonemptyPipelineAppendsToViewPipel } TEST(ResolvedViewTest, ExpandingAggRequestPreservesExplain) { - const ResolvedView resolvedView{backingNss, emptyPipeline}; + const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation}; AggregationRequest aggRequest(viewNss, {}); aggRequest.setExplain(true); @@ -130,7 +132,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesExplain) { } TEST(ResolvedViewTest, ExpandingAggRequestPreservesBypassDocumentValidation) { - const ResolvedView resolvedView{backingNss, emptyPipeline}; + const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation}; AggregationRequest aggRequest(viewNss, {}); aggRequest.setBypassDocumentValidation(true); @@ -145,7 +147,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesBypassDocumentValidation) { } TEST(ResolvedViewTest, ExpandingAggRequestPreservesAllowDiskUse) { - const ResolvedView resolvedView{backingNss, emptyPipeline}; + const ResolvedView resolvedView{backingNss, emptyPipeline, kSimpleCollation}; AggregationRequest aggRequest(viewNss, {}); aggRequest.setAllowDiskUse(true); @@ -159,6 +161,27 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesAllowDiskUse) { ASSERT_BSONOBJ_EQ(result.getValue(), expected); } +TEST(ResolvedViewTest, ExpandingAggRequestPreservesDefaultCollationOfView) { + const ResolvedView resolvedView{backingNss, + emptyPipeline, + BSON("locale" + << "fr_CA")}; + ASSERT_BSONOBJ_EQ(resolvedView.getDefaultCollation(), + BSON("locale" + << "fr_CA")); + AggregationRequest aggRequest(viewNss, {}); + + auto result = resolvedView.asExpandedViewAggregation(aggRequest); + ASSERT_OK(result.getStatus()); + + BSONObj expected = + BSON("aggregate" << backingNss.coll() << "pipeline" << BSONArray() << "cursor" << BSONObj() + << "collation" + << BSON("locale" + << "fr_CA")); + ASSERT_BSONOBJ_EQ(result.getValue(), expected); +} + TEST(ResolvedViewTest, FromBSONFailsIfMissingResolvedView) { BSONObj badCmdResponse = BSON("x" << 1); ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), UserException, 40248); @@ -190,6 +213,13 @@ TEST(ResolvedViewTest, FromBSONFailsOnInvalidPipelineType) { ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), UserException, 40251); } +TEST(ResolvedViewTest, FromBSONFailsOnInvalidCollationType) { + BSONObj badCmdResponse = + BSON("resolvedView" << BSON( + "ns" << backingNss.ns() << "pipeline" << BSONArray() << "collation" << 1)); + ASSERT_THROWS_CODE(ResolvedView::fromBSON(badCmdResponse), AssertionException, 40639); +} + TEST(ResolvedViewTest, FromBSONSuccessfullyParsesEmptyBSONArrayIntoEmptyVector) { BSONObj cmdResponse = BSON("resolvedView" << BSON("ns" << backingNss.ns() << "pipeline" << BSONArray())); @@ -222,6 +252,22 @@ TEST(ResolvedViewTest, FromBSONSuccessfullyParsesPopulatedBSONArrayIntoVector) { SimpleBSONObjComparator::kInstance.makeEqualTo())); } +TEST(ResolvedViewTest, FromBSONSuccessfullyParsesCollation) { + BSONObj cmdResponse = BSON( + "resolvedView" << BSON("ns" << backingNss.ns() << "pipeline" << BSONArray() << "collation" + << BSON("locale" + << "fil"))); + const ResolvedView result = ResolvedView::fromBSON(cmdResponse); + ASSERT_EQ(result.getNamespace(), backingNss); + ASSERT(std::equal(emptyPipeline.begin(), + emptyPipeline.end(), + result.getPipeline().begin(), + SimpleBSONObjComparator::kInstance.makeEqualTo())); + ASSERT_BSONOBJ_EQ(result.getDefaultCollation(), + BSON("locale" + << "fil")); +} + TEST(ResolvedViewTest, IsResolvedViewErrorResponseDetectsKickbackErrorCodeSuccessfully) { BSONObj errorResponse = BSON("ok" << 0 << "code" << ErrorCodes::CommandOnShardedViewNotSupportedOnMongod << "errmsg" @@ -235,5 +281,34 @@ TEST(ResolvedViewTest, IsResolvedViewErrorResponseReportsFalseOnNonKickbackError << "View nesting too deep or view cycle detected"); ASSERT_FALSE(ResolvedView::isResolvedViewErrorResponse(errorResponse)); } + +TEST(ResolvedViewTest, ToBSONSerializesCorrectly) { + const ResolvedView resolvedView{backingNss, + std::vector<BSONObj>{BSON("$match" << BSON("x" << 1))}, + BSON("locale" + << "fr_CA")}; + auto serialized = resolvedView.toBSON(); + ASSERT_BSONOBJ_EQ( + serialized, + fromjson( + "{ns: 'testdb.testcoll', pipeline: [{$match: {x: 1}}], collation: {locale: 'fr_CA'}}")); +} + +TEST(ResolvedViewTest, ToBSONOutputCanBeReparsed) { + const ResolvedView resolvedView{backingNss, + emptyPipeline, + BSON("locale" + << "fr_CA")}; + auto serialized = resolvedView.toBSON(); + auto reparsedResolvedView = ResolvedView::fromBSON(BSON("resolvedView" << serialized)); + ASSERT_EQ(reparsedResolvedView.getNamespace(), backingNss); + ASSERT(std::equal(emptyPipeline.begin(), + emptyPipeline.end(), + reparsedResolvedView.getPipeline().begin(), + SimpleBSONObjComparator::kInstance.makeEqualTo())); + ASSERT_BSONOBJ_EQ(reparsedResolvedView.getDefaultCollation(), + BSON("locale" + << "fr_CA")); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/views/view_catalog.cpp b/src/mongo/db/views/view_catalog.cpp index 30cabf24935..d590c509a7e 100644 --- a/src/mongo/db/views/view_catalog.cpp +++ b/src/mongo/db/views/view_catalog.cpp @@ -394,6 +394,7 @@ StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* txn, stdx::lock_guard<stdx::mutex> lk(_mutex); const NamespaceString* resolvedNss = &nss; std::vector<BSONObj> resolvedPipeline; + BSONObj collation; for (int i = 0; i < ViewGraph::kMaxViewDepth; i++) { auto view = _lookup_inlock(txn, resolvedNss->ns()); @@ -408,10 +409,13 @@ StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* txn, str::stream() << "View pipeline exceeds maximum size; maximum size is " << ViewGraph::kMaxViewPipelineSizeBytes}; } - return StatusWith<ResolvedView>({*resolvedNss, resolvedPipeline}); + return StatusWith<ResolvedView>( + {*resolvedNss, std::move(resolvedPipeline), std::move(collation)}); } resolvedNss = &(view->viewOn()); + collation = view->defaultCollator() ? view->defaultCollator()->getSpec().toBSON() + : CollationSpec::kSimpleSpec; // Prepend the underlying view's pipeline to the current working pipeline. const std::vector<BSONObj>& toPrepend = view->pipeline(); @@ -419,7 +423,8 @@ StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* txn, // If the first stage is a $collStats, then we return early with the viewOn namespace. if (toPrepend.size() > 0 && !toPrepend[0]["$collStats"].eoo()) { - return StatusWith<ResolvedView>({*resolvedNss, resolvedPipeline}); + return StatusWith<ResolvedView>( + {*resolvedNss, std::move(resolvedPipeline), std::move(collation)}); } } diff --git a/src/mongo/db/views/view_catalog_test.cpp b/src/mongo/db/views/view_catalog_test.cpp index 1ca54f809df..367b4f73b0c 100644 --- a/src/mongo/db/views/view_catalog_test.cpp +++ b/src/mongo/db/views/view_catalog_test.cpp @@ -38,6 +38,8 @@ #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/query/query_test_service_context.h" #include "mongo/db/server_options.h" #include "mongo/db/service_context_noop.h" @@ -417,6 +419,42 @@ TEST_F(ViewCatalogFixture, ResolveViewCorrectPipeline) { } } +TEST_F(ViewCatalogFixture, ResolveViewCorrectlyExtractsDefaultCollation) { + const NamespaceString view1("db.view1"); + const NamespaceString view2("db.view2"); + const NamespaceString viewOn("db.coll"); + BSONArrayBuilder pipeline1; + BSONArrayBuilder pipeline2; + + pipeline1 << BSON("$match" << BSON("foo" << 1)); + pipeline2 << BSON("$match" << BSON("foo" << 2)); + + BSONObj collation = BSON("locale" + << "mock_reverse_string"); + + ASSERT_OK(viewCatalog.createView(opCtx.get(), view1, viewOn, pipeline1.arr(), collation)); + ASSERT_OK(viewCatalog.createView(opCtx.get(), view2, view1, pipeline2.arr(), collation)); + + auto resolvedView = viewCatalog.resolveView(opCtx.get(), view2); + ASSERT(resolvedView.isOK()); + + ASSERT_EQ(resolvedView.getValue().getNamespace(), viewOn); + + std::vector<BSONObj> expected = {BSON("$match" << BSON("foo" << 1)), + BSON("$match" << BSON("foo" << 2))}; + std::vector<BSONObj> result = resolvedView.getValue().getPipeline(); + ASSERT_EQ(expected.size(), result.size()); + for (uint32_t i = 0; i < expected.size(); i++) { + ASSERT(SimpleBSONObjComparator::kInstance.evaluate(expected[i] == result[i])); + } + + auto expectedCollation = + CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation); + ASSERT_OK(expectedCollation.getStatus()); + ASSERT_BSONOBJ_EQ(resolvedView.getValue().getDefaultCollation(), + expectedCollation.getValue()->getSpec().toBSON()); +} + TEST_F(ViewCatalogFixture, InvalidateThenReload) { const NamespaceString viewName("db.view"); const NamespaceString viewOn("db.coll"); diff --git a/src/mongo/db/views/view_sharding_check.cpp b/src/mongo/db/views/view_sharding_check.cpp index cc662ff2549..90835e03a07 100644 --- a/src/mongo/db/views/view_sharding_check.cpp +++ b/src/mongo/db/views/view_sharding_check.cpp @@ -69,11 +69,7 @@ StatusWith<BSONObj> ViewShardingCheck::getResolvedViewIfSharded(OperationContext return BSONObj(); } - BSONObjBuilder viewDetailBob; - viewDetailBob.append("ns", sourceNss.ns()); - viewDetailBob.append("pipeline", resolvedView.getValue().getPipeline()); - - return viewDetailBob.obj(); + return resolvedView.getValue().toBSON(); } void ViewShardingCheck::appendShardedViewStatus(const BSONObj& resolvedView, BSONObjBuilder* out) { |