summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2017-09-20 10:26:41 -0400
committerDavid Storch <david.storch@10gen.com>2017-09-26 16:47:54 -0400
commit3a262ee4002375ed7f2e4cdf6cac60b712d42685 (patch)
treed89668772b5ab8a760dc4d2b3679ac418071f74a /src
parente5f6970de5e2f5802d4d892597b3db2556e35bcc (diff)
downloadmongo-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.cpp5
-rw-r--r--src/mongo/db/views/resolved_view.cpp29
-rw-r--r--src/mongo/db/views/resolved_view.h22
-rw-r--r--src/mongo/db/views/resolved_view_test.cpp91
-rw-r--r--src/mongo/db/views/view_catalog.cpp9
-rw-r--r--src/mongo/db/views/view_catalog_test.cpp38
-rw-r--r--src/mongo/db/views/view_sharding_check.cpp6
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) {