diff options
author | Charlie Swanson <cswanson310@gmail.com> | 2016-08-29 14:26:29 -0400 |
---|---|---|
committer | Charlie Swanson <cswanson310@gmail.com> | 2016-09-01 14:08:25 -0400 |
commit | 698cd2555dabf2ab6c1ed4c504d1e2546da0f57a (patch) | |
tree | 206a17f69cf6a1720cb153ba90c29acfd7f565f2 /src/mongo/db/pipeline/document_source_bucket_auto_test.cpp | |
parent | b1014fe1b40a69cd90b27cb336a170317eecc6b7 (diff) | |
download | mongo-698cd2555dabf2ab6c1ed4c504d1e2546da0f57a.tar.gz |
SERVER-24153 Split document_source_test.cpp into one file per stage.
Diffstat (limited to 'src/mongo/db/pipeline/document_source_bucket_auto_test.cpp')
-rw-r--r-- | src/mongo/db/pipeline/document_source_bucket_auto_test.cpp | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp b/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp new file mode 100644 index 00000000000..1950c23f0d9 --- /dev/null +++ b/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp @@ -0,0 +1,626 @@ +/** + * Copyright (C) 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include <boost/intrusive_ptr.hpp> +#include <deque> +#include <vector> + +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsontypes.h" +#include "mongo/bson/json.h" +#include "mongo/db/pipeline/aggregation_context_fixture.h" +#include "mongo/db/pipeline/dependencies.h" +#include "mongo/db/pipeline/document.h" +#include "mongo/db/pipeline/document_source.h" +#include "mongo/db/pipeline/document_value_test_util.h" +#include "mongo/db/pipeline/value.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { +using std::vector; +using std::deque; +using boost::intrusive_ptr; + +class BucketAutoTests : public AggregationContextFixture { +public: + intrusive_ptr<DocumentSource> createBucketAuto(BSONObj bucketAutoSpec) { + return DocumentSourceBucketAuto::createFromBson(bucketAutoSpec.firstElement(), getExpCtx()); + } + + vector<Document> getResults(BSONObj bucketAutoSpec, deque<Document> docs) { + auto bucketAutoStage = createBucketAuto(bucketAutoSpec); + assertBucketAutoType(bucketAutoStage); + + auto source = DocumentSourceMock::create(docs); + bucketAutoStage->setSource(source.get()); + + vector<Document> results; + for (auto next = bucketAutoStage->getNext(); next.isAdvanced(); + next = bucketAutoStage->getNext()) { + results.push_back(next.releaseDocument()); + } + + return results; + } + + void testSerialize(BSONObj bucketAutoSpec, BSONObj expectedObj) { + auto bucketAutoStage = createBucketAuto(bucketAutoSpec); + assertBucketAutoType(bucketAutoStage); + + const bool explain = true; + vector<Value> explainedStages; + bucketAutoStage->serializeToArray(explainedStages, explain); + ASSERT_EQUALS(explainedStages.size(), 1UL); + + Value expectedExplain = Value(expectedObj); + + auto bucketAutoExplain = explainedStages[0]; + ASSERT_VALUE_EQ(bucketAutoExplain["$bucketAuto"], expectedExplain); + } + +private: + void assertBucketAutoType(intrusive_ptr<DocumentSource> documentSource) { + const auto* bucketAutoStage = dynamic_cast<DocumentSourceBucketAuto*>(documentSource.get()); + ASSERT(bucketAutoStage); + } +}; + +TEST_F(BucketAutoTests, ReturnsNoBucketsWhenSourceIsEmpty) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets: 1}}"); + auto results = getResults(bucketAutoSpec, {}); + ASSERT_EQUALS(results.size(), 0UL); +} + +TEST_F(BucketAutoTests, Returns1Of1RequestedBucketWhenAllUniqueValues) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets: 1}}"); + + // Values are 1, 2, 3, 4 + auto intDocs = {Document{{"x", 4}}, Document{{"x", 1}}, Document{{"x", 3}}, Document{{"x", 2}}}; + auto results = getResults(bucketAutoSpec, intDocs); + ASSERT_EQUALS(results.size(), 1UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 1, max : 4}, count : 4}"))); + + // Values are 'a', 'b', 'c', 'd' + auto stringDocs = { + Document{{"x", "d"}}, Document{{"x", "b"}}, Document{{"x", "a"}}, Document{{"x", "c"}}}; + results = getResults(bucketAutoSpec, stringDocs); + ASSERT_EQUALS(results.size(), 1UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 'a', max : 'd'}, count : 4}"))); +} + +TEST_F(BucketAutoTests, Returns1Of1RequestedBucketWithNonUniqueValues) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets: 1}}"); + + // Values are 1, 2, 7, 7, 7 + auto docs = {Document{{"x", 7}}, + Document{{"x", 1}}, + Document{{"x", 7}}, + Document{{"x", 2}}, + Document{{"x", 7}}}; + auto results = getResults(bucketAutoSpec, docs); + ASSERT_EQUALS(results.size(), 1UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 1, max : 7}, count : 5}"))); +} + +TEST_F(BucketAutoTests, Returns1Of1RequestedBucketWhen1ValueInSource) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets: 1}}"); + auto intDocs = {Document{{"x", 1}}}; + auto results = getResults(bucketAutoSpec, intDocs); + ASSERT_EQUALS(results.size(), 1UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 1, max : 1}, count : 1}"))); + + auto stringDocs = {Document{{"x", "a"}}}; + results = getResults(bucketAutoSpec, stringDocs); + ASSERT_EQUALS(results.size(), 1UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 'a', max : 'a'}, count : 1}"))); +} + +TEST_F(BucketAutoTests, Returns2Of2RequestedBucketsWhenSmallestValueHasManyDuplicates) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2}}"); + + // Values are 1, 1, 1, 1, 2 + auto docs = {Document{{"x", 1}}, + Document{{"x", 1}}, + Document{{"x", 1}}, + Document{{"x", 2}}, + Document{{"x", 1}}}; + auto results = getResults(bucketAutoSpec, docs); + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 1, max : 2}, count : 4}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 2, max : 2}, count : 1}"))); +} + +TEST_F(BucketAutoTests, Returns2Of2RequestedBucketsWhenLargestValueHasManyDuplicates) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2}}"); + + // Values are 0, 1, 2, 3, 4, 5, 5, 5, 5 + auto docs = {Document{{"x", 5}}, + Document{{"x", 0}}, + Document{{"x", 2}}, + Document{{"x", 3}}, + Document{{"x", 5}}, + Document{{"x", 1}}, + Document{{"x", 5}}, + Document{{"x", 4}}, + Document{{"x", 5}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 5}, count : 5}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 5, max : 5}, count : 4}"))); +} + +TEST_F(BucketAutoTests, Returns3Of3RequestedBucketsWhenAllUniqueValues) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 3}}"); + + // Values are 0, 1, 2, 3, 4, 5, 6, 7 + auto docs = {Document{{"x", 2}}, + Document{{"x", 4}}, + Document{{"x", 1}}, + Document{{"x", 7}}, + Document{{"x", 0}}, + Document{{"x", 5}}, + Document{{"x", 3}}, + Document{{"x", 6}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 3UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 3}, count : 3}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 3, max : 6}, count : 3}"))); + ASSERT_DOCUMENT_EQ(results[2], Document(fromjson("{_id : {min : 6, max : 7}, count : 2}"))); +} + +TEST_F(BucketAutoTests, Returns2Of3RequestedBucketsWhenLargestValueHasManyDuplicates) { + // In this case, two buckets will be made because the approximate bucket size calculated will be + // 7/3, which rounds to 2. Therefore, the boundaries will be calculated so that values 0 and 1 + // into the first bucket. All of the 2 values will then fall into a second bucket. + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 3}}"); + + // Values are 0, 1, 2, 2, 2, 2, 2 + auto docs = {Document{{"x", 2}}, + Document{{"x", 0}}, + Document{{"x", 2}}, + Document{{"x", 2}}, + Document{{"x", 1}}, + Document{{"x", 2}}, + Document{{"x", 2}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 2}, count : 2}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 2, max : 2}, count : 5}"))); +} + +TEST_F(BucketAutoTests, Returns1Of3RequestedBucketsWhenLargestValueHasManyDuplicates) { + // In this case, one bucket will be made because the approximate bucket size calculated will be + // 8/3, which rounds to 3. Therefore, the boundaries will be calculated so that values 0, 1, and + // 2 fall into the first bucket. Since 2 is repeated many times, all of the 2 values will be + // pulled into the first bucket. + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 3}}"); + + // Values are 0, 1, 2, 2, 2, 2, 2, 2 + auto docs = {Document{{"x", 2}}, + Document{{"x", 2}}, + Document{{"x", 0}}, + Document{{"x", 2}}, + Document{{"x", 2}}, + Document{{"x", 2}}, + Document{{"x", 1}}, + Document{{"x", 2}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 1UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 2}, count : 8}"))); +} + +TEST_F(BucketAutoTests, Returns3Of3RequestedBucketsWhen3ValuesInSource) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 3}}"); + auto docs = {Document{{"x", 0}}, Document{{"x", 1}}, Document{{"x", 2}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 3UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 1}, count : 1}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 1, max : 2}, count : 1}"))); + ASSERT_DOCUMENT_EQ(results[2], Document(fromjson("{_id : {min : 2, max : 2}, count : 1}"))); +} + +TEST_F(BucketAutoTests, Returns3Of10RequestedBucketsWhen3ValuesInSource) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 10}}"); + auto docs = {Document{{"x", 0}}, Document{{"x", 1}}, Document{{"x", 2}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 3UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 1}, count : 1}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 1, max : 2}, count : 1}"))); + ASSERT_DOCUMENT_EQ(results[2], Document(fromjson("{_id : {min : 2, max : 2}, count : 1}"))); +} + +TEST_F(BucketAutoTests, EvaluatesAccumulatorsInOutputField) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, output : {avg : {$avg : '$x'}}}}"); + auto docs = {Document{{"x", 0}}, Document{{"x", 2}}, Document{{"x", 4}}, Document{{"x", 6}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 4}, avg : 1}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 4, max : 6}, avg : 5}"))); +} + +TEST_F(BucketAutoTests, EvaluatesNonFieldPathExpressionInGroupByField) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : {$add : ['$x', 1]}, buckets : 2}}"); + auto docs = {Document{{"x", 0}}, Document{{"x", 1}}, Document{{"x", 2}}, Document{{"x", 3}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 1, max : 3}, count : 2}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 3, max : 4}, count : 2}"))); +} + +TEST_F(BucketAutoTests, RespectsCanonicalTypeOrderingOfValues) { + auto bucketAutoSpec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2}}"); + auto docs = {Document{{"x", "a"}}, + Document{{"x", 1}}, + Document{{"x", "b"}}, + Document{{"x", 2}}, + Document{{"x", 0.0}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0.0, max : 'a'}, count : 3}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 'a', max : 'b'}, count : 2}"))); +} + +TEST_F(BucketAutoTests, SourceNameIsBucketAuto) { + auto bucketAuto = createBucketAuto(fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2}}")); + ASSERT_EQUALS(std::string(bucketAuto->getSourceName()), "$bucketAuto"); +} + +TEST_F(BucketAutoTests, ShouldAddDependenciesOfGroupByFieldAndComputedFields) { + auto bucketAuto = + createBucketAuto(fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, output: {field1 : " + "{$sum : '$a'}, field2 : {$avg : '$b'}}}}")); + + DepsTracker dependencies; + ASSERT_EQUALS(DocumentSource::EXHAUSTIVE_ALL, bucketAuto->getDependencies(&dependencies)); + ASSERT_EQUALS(3U, dependencies.fields.size()); + + // Dependency from 'groupBy' + ASSERT_EQUALS(1U, dependencies.fields.count("x")); + + // Dependencies from 'output' + ASSERT_EQUALS(1U, dependencies.fields.count("a")); + ASSERT_EQUALS(1U, dependencies.fields.count("b")); + + ASSERT_EQUALS(false, dependencies.needWholeDocument); + ASSERT_EQUALS(false, dependencies.getNeedTextScore()); +} + +TEST_F(BucketAutoTests, ShouldNeedTextScoreInDependenciesFromGroupByField) { + auto bucketAuto = + createBucketAuto(fromjson("{$bucketAuto : {groupBy : {$meta: 'textScore'}, buckets : 2}}")); + + DepsTracker dependencies(DepsTracker::MetadataAvailable::kTextScore); + ASSERT_EQUALS(DocumentSource::EXHAUSTIVE_ALL, bucketAuto->getDependencies(&dependencies)); + ASSERT_EQUALS(0U, dependencies.fields.size()); + + ASSERT_EQUALS(false, dependencies.needWholeDocument); + ASSERT_EQUALS(true, dependencies.getNeedTextScore()); +} + +TEST_F(BucketAutoTests, ShouldNeedTextScoreInDependenciesFromOutputField) { + auto bucketAuto = + createBucketAuto(fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, output: {avg : " + "{$avg : {$meta : 'textScore'}}}}}")); + + DepsTracker dependencies(DepsTracker::MetadataAvailable::kTextScore); + ASSERT_EQUALS(DocumentSource::EXHAUSTIVE_ALL, bucketAuto->getDependencies(&dependencies)); + ASSERT_EQUALS(1U, dependencies.fields.size()); + + // Dependency from 'groupBy' + ASSERT_EQUALS(1U, dependencies.fields.count("x")); + + ASSERT_EQUALS(false, dependencies.needWholeDocument); + ASSERT_EQUALS(true, dependencies.getNeedTextScore()); +} + +TEST_F(BucketAutoTests, SerializesDefaultAccumulatorIfOutputFieldIsNotSpecified) { + BSONObj spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2}}"); + BSONObj expected = + fromjson("{groupBy : '$x', buckets : 2, output : {count : {$sum : {$const : 1}}}}"); + + testSerialize(spec, expected); +} + +TEST_F(BucketAutoTests, SerializesOutputFieldIfSpecified) { + BSONObj spec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, output : {field : {$avg : '$x'}}}}"); + BSONObj expected = fromjson("{groupBy : '$x', buckets : 2, output : {field : {$avg : '$x'}}}"); + + testSerialize(spec, expected); +} + +TEST_F(BucketAutoTests, SerializesGranularityFieldIfSpecified) { + BSONObj spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + BSONObj expected = fromjson( + "{groupBy : '$x', buckets : 2, granularity : 'R5', output : {count : {$sum : {$const : " + "1}}}}"); + + testSerialize(spec, expected); +} + +TEST_F(BucketAutoTests, ShouldBeAbleToReParseSerializedStage) { + auto bucketAuto = + createBucketAuto(fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity: 'R5', " + "output : {field : {$avg : '$x'}}}}")); + vector<Value> serialization; + bucketAuto->serializeToArray(serialization); + ASSERT_EQUALS(serialization.size(), 1UL); + ASSERT_EQUALS(serialization[0].getType(), BSONType::Object); + + ASSERT_EQUALS(serialization[0].getDocument().size(), 1UL); + ASSERT_EQUALS(serialization[0].getDocument()["$bucketAuto"].getType(), BSONType::Object); + + auto serializedBson = serialization[0].getDocument().toBson(); + auto roundTripped = createBucketAuto(serializedBson); + + vector<Value> newSerialization; + roundTripped->serializeToArray(newSerialization); + + ASSERT_EQUALS(newSerialization.size(), 1UL); + ASSERT_VALUE_EQ(newSerialization[0], serialization[0]); +} + +TEST_F(BucketAutoTests, ReturnsNoBucketsWhenNoBucketsAreSpecifiedInCreate) { + auto docs = {Document{{"x", 1}}}; + auto mock = DocumentSourceMock::create(docs); + auto bucketAuto = DocumentSourceBucketAuto::create(getExpCtx()); + + bucketAuto->setSource(mock.get()); + ASSERT(bucketAuto->getNext().isEOF()); +} + +TEST_F(BucketAutoTests, FailsWithInvalidNumberOfBuckets) { + auto spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 'test'}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40241); + + spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2147483648}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40242); + + spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 1.5}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40242); + + spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 0}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40243); + + spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : -1}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40243); +} + +TEST_F(BucketAutoTests, FailsWithNonExpressionGroupBy) { + auto spec = fromjson("{$bucketAuto : {groupBy : 'test', buckets : 1}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40239); + + spec = fromjson("{$bucketAuto : {groupBy : {test : 'test'}, buckets : 1}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40239); +} + +TEST_F(BucketAutoTests, FailsWithNonObjectArgument) { + auto spec = fromjson("{$bucketAuto : 'test'}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40240); + + spec = fromjson("{$bucketAuto : [1, 2, 3]}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40240); +} + +TEST_F(BucketAutoTests, FailsWithNonObjectOutput) { + auto spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 1, output : 'test'}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40244); + + spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 1, output : [1, 2, 3]}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40244); + + spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 1, output : 1}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40244); +} + +TEST_F(BucketAutoTests, FailsWhenGroupByMissing) { + auto spec = fromjson("{$bucketAuto : {buckets : 1}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40246); +} + +TEST_F(BucketAutoTests, FailsWhenBucketsMissing) { + auto spec = fromjson("{$bucketAuto : {groupBy : '$x'}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40246); +} + +TEST_F(BucketAutoTests, FailsWithUnknownField) { + auto spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 1, field : 'test'}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40245); +} + +TEST_F(BucketAutoTests, FailsWithInvalidExpressionToAccumulator) { + auto spec = fromjson( + "{$bucketAuto : {groupBy : '$x', buckets : 1, output : {avg : {$avg : ['$x', 1]}}}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40237); + + spec = fromjson( + "{$bucketAuto : {groupBy : '$x', buckets : 1, output : {test : {$avg : '$x', $sum : " + "'$x'}}}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40238); +} + +TEST_F(BucketAutoTests, FailsWithNonAccumulatorObjectOutputField) { + auto spec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 1, output : {field : 'test'}}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40234); + + spec = fromjson("{$bucketAuto : {groupBy : '$x', buckets : 1, output : {field : 1}}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40234); + + spec = fromjson( + "{$bucketAuto : {groupBy : '$x', buckets : 1, output : {test : {field : 'test'}}}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40234); +} + +TEST_F(BucketAutoTests, FailsWithInvalidOutputFieldName) { + auto spec = fromjson( + "{$bucketAuto : {groupBy : '$x', buckets : 1, output : {'field.test' : {$avg : '$x'}}}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40235); + + spec = fromjson( + "{$bucketAuto : {groupBy : '$x', buckets : 1, output : {'$field' : {$avg : '$x'}}}}"); + ASSERT_THROWS_CODE(createBucketAuto(spec), UserException, 40236); +} + +TEST_F(BucketAutoTests, FailsWhenBufferingTooManyDocuments) { + std::deque<Document> inputs; + auto largeStr = std::string(1000, 'b'); + auto inputDoc = Document{{"a", largeStr}}; + ASSERT_GTE(inputDoc.getApproximateSize(), 1000UL); + inputs.push_back(inputDoc); + inputs.push_back(Document{{"a", largeStr}}); + auto mock = DocumentSourceMock::create(inputs); + + const uint64_t maxMemoryUsageBytes = 1000; + const int numBuckets = 1; + auto bucketAuto = + DocumentSourceBucketAuto::create(getExpCtx(), numBuckets, maxMemoryUsageBytes); + bucketAuto->setSource(mock.get()); + ASSERT_THROWS_CODE(bucketAuto->getNext(), UserException, 16819); +} + +TEST_F(BucketAutoTests, ShouldRoundUpMaximumBoundariesWithGranularitySpecified) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + + // Values are 0, 15, 24, 30, 50 + auto docs = {Document{{"x", 24}}, + Document{{"x", 15}}, + Document{{"x", 30}}, + Document{{"x", 50}}, + Document{{"x", 0}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 25}, count : 3}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 25, max : 63}, count : 2}"))); +} + +TEST_F(BucketAutoTests, ShouldRoundDownFirstMinimumBoundaryWithGranularitySpecified) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + + // Values are 1, 15, 24, 30, 50 + auto docs = {Document{{"x", 24}}, + Document{{"x", 15}}, + Document{{"x", 30}}, + Document{{"x", 50}}, + Document{{"x", 1}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0.63, max : 25}, count : 3}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 25, max : 63}, count : 2}"))); +} + +TEST_F(BucketAutoTests, ShouldAbsorbAllValuesSmallerThanAdjustedBoundaryWithGranularitySpecified) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + + auto docs = {Document{{"x", 0}}, + Document{{"x", 5}}, + Document{{"x", 10}}, + Document{{"x", 15}}, + Document{{"x", 30}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 16}, count : 4}"))); + ASSERT_DOCUMENT_EQ(results[1], Document(fromjson("{_id : {min : 16, max : 40}, count : 1}"))); +} + +TEST_F(BucketAutoTests, ShouldBeAbleToAbsorbAllValuesIntoOneBucketWithGranularitySpecified) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + + auto docs = {Document{{"x", 0}}, + Document{{"x", 5}}, + Document{{"x", 10}}, + Document{{"x", 14}}, + Document{{"x", 15}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 1UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 16}, count : 5}"))); +} + +TEST_F(BucketAutoTests, ShouldNotRoundZeroInFirstBucketWithGranularitySpecified) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + + auto docs = {Document{{"x", 0}}, Document{{"x", 0}}, Document{{"x", 1}}, Document{{"x", 1}}}; + auto results = getResults(bucketAutoSpec, docs); + + ASSERT_EQUALS(results.size(), 2UL); + ASSERT_DOCUMENT_EQ(results[0], Document(fromjson("{_id : {min : 0, max : 0.63}, count : 2}"))); + ASSERT_DOCUMENT_EQ(results[1], + Document(fromjson("{_id : {min : 0.63, max : 1.6}, count : 2}"))); +} + +TEST_F(BucketAutoTests, ShouldFailOnNaNWhenGranularitySpecified) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + + auto docs = {Document{{"x", 0}}, + Document{{"x", std::nan("NaN")}}, + Document{{"x", 1}}, + Document{{"x", 1}}}; + ASSERT_THROWS_CODE(getResults(bucketAutoSpec, docs), UserException, 40259); +} + +TEST_F(BucketAutoTests, ShouldFailOnNonNumericValuesWhenGranularitySpecified) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + + auto docs = { + Document{{"x", 0}}, Document{{"x", "test"}}, Document{{"x", 1}}, Document{{"x", 1}}}; + ASSERT_THROWS_CODE(getResults(bucketAutoSpec, docs), UserException, 40258); +} + +TEST_F(BucketAutoTests, ShouldFailOnNegativeNumbersWhenGranularitySpecified) { + auto bucketAutoSpec = + fromjson("{$bucketAuto : {groupBy : '$x', buckets : 2, granularity : 'R5'}}"); + + auto docs = {Document{{"x", 0}}, Document{{"x", -1}}, Document{{"x", 1}}, Document{{"x", 2}}}; + ASSERT_THROWS_CODE(getResults(bucketAutoSpec, docs), UserException, 40260); +} +} // namespace +} // namespace mongo |