summaryrefslogtreecommitdiff
path: root/src/mongo/db/cst
diff options
context:
space:
mode:
authorBilly Donahue <billy.donahue@mongodb.com>2020-11-28 20:55:56 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-28 21:25:54 +0000
commitcca3644a4618b223dd716ef2b98ec856d229a295 (patch)
tree06d1fed6a9a797d396ff17a7b5ae1e5f6e7fe1a3 /src/mongo/db/cst
parenta2eef18e048f4123f7822a0eae68a9760cf682a4 (diff)
downloadmongo-cca3644a4618b223dd716ef2b98ec856d229a295.tar.gz
SERVER-53101 dos2unix all C++ source files
Diffstat (limited to 'src/mongo/db/cst')
-rw-r--r--src/mongo/db/cst/bson_location.h216
-rw-r--r--src/mongo/db/cst/cst_error_test.cpp468
-rw-r--r--src/mongo/db/cst/cst_expression_test.cpp3494
-rw-r--r--src/mongo/db/cst/cst_match_test.cpp874
-rw-r--r--src/mongo/db/cst/cst_parser.h140
-rw-r--r--src/mongo/db/cst/cst_sort_translation_test.cpp314
6 files changed, 2753 insertions, 2753 deletions
diff --git a/src/mongo/db/cst/bson_location.h b/src/mongo/db/cst/bson_location.h
index 1b67e01d801..2c7b6ad7cab 100644
--- a/src/mongo/db/cst/bson_location.h
+++ b/src/mongo/db/cst/bson_location.h
@@ -1,108 +1,108 @@
-/**
- * Copyright (C) 2020-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * 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
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * 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 Server Side 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.
- */
-
-#pragma once
-
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include "mongo/base/string_data.h"
-#include "mongo/bson/bsonobj.h"
-#include "mongo/stdx/variant.h"
-#include "mongo/util/visit_helper.h"
-
-namespace mongo {
-
-/**
- * Represents the location of a specific token in a BSON object.
- */
-class BSONLocation {
-public:
- using LocationPrefix = stdx::variant<unsigned int, StringData>;
- // A location may be either the payload of a BSONElement, or a string representing a fieldname
- // or metadata token (e.g. '{' for the start of an object). Array indices are not represented as
- // a BSONLocation, per se, but instead are part of the list of prefix descriptors.
- using LocationType = stdx::variant<BSONElement, StringData>;
-
- BSONLocation() = default;
- /**
- * Builds a location of a token in the input BSON. The 'prefix' argument is a list of elements
- * that describe the path to 'location'. There must be at least one element in 'prefix',
- * detailing the parser entry point.
- */
- BSONLocation(LocationType location, std::vector<LocationPrefix> prefix)
- : _location(std::move(location)), _prefix(std::move(prefix)) {}
-
- /**
- * Prints this location along with the prefix strings that describe the path to the element. The
- * resulting string is verbose and useful in debugging or syntax errors.
- */
- std::string toString() const {
- std::ostringstream stream;
- stdx::visit(
- visit_helper::Overloaded{
- [&](const BSONElement& elem) { stream << "'" << elem.toString(false) << "'"; },
- [&](const StringData& elem) { stream << "'" << elem << "'"; }},
- _location);
- // The assumption is that there is always at least one prefix that represents the entry
- // point to the parser (e.g. the 'pipeline' argument for an aggregation command).
- invariant(_prefix.size() > 0);
- for (auto it = _prefix.rbegin(); it != _prefix.rend() - 1; ++it) {
- stdx::visit(visit_helper::Overloaded{[&](const unsigned int& index) {
- stream << " within array at index " << index;
- },
- [&](const StringData& pref) {
- stream << " within '" << pref << "'";
- }},
- *it);
- }
-
- // The final prefix (or first element in the vector) is the input description.
- stdx::visit(visit_helper::Overloaded{[&](const unsigned int& index) { MONGO_UNREACHABLE; },
- [&](const StringData& pref) {
- stream << " of input " << pref;
- }},
- _prefix[0]);
- return stream.str();
- }
-
- friend std::ostream& operator<<(std::ostream& stream, const BSONLocation& location) {
- return stream << location.toString();
- }
- friend StringBuilder& operator<<(StringBuilder& stream, const BSONLocation& location) {
- return stream << location.toString();
- }
-
-private:
- LocationType _location;
- std::vector<LocationPrefix> _prefix;
-};
-
-} // namespace mongo
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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.
+ */
+
+#pragma once
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/stdx/variant.h"
+#include "mongo/util/visit_helper.h"
+
+namespace mongo {
+
+/**
+ * Represents the location of a specific token in a BSON object.
+ */
+class BSONLocation {
+public:
+ using LocationPrefix = stdx::variant<unsigned int, StringData>;
+ // A location may be either the payload of a BSONElement, or a string representing a fieldname
+ // or metadata token (e.g. '{' for the start of an object). Array indices are not represented as
+ // a BSONLocation, per se, but instead are part of the list of prefix descriptors.
+ using LocationType = stdx::variant<BSONElement, StringData>;
+
+ BSONLocation() = default;
+ /**
+ * Builds a location of a token in the input BSON. The 'prefix' argument is a list of elements
+ * that describe the path to 'location'. There must be at least one element in 'prefix',
+ * detailing the parser entry point.
+ */
+ BSONLocation(LocationType location, std::vector<LocationPrefix> prefix)
+ : _location(std::move(location)), _prefix(std::move(prefix)) {}
+
+ /**
+ * Prints this location along with the prefix strings that describe the path to the element. The
+ * resulting string is verbose and useful in debugging or syntax errors.
+ */
+ std::string toString() const {
+ std::ostringstream stream;
+ stdx::visit(
+ visit_helper::Overloaded{
+ [&](const BSONElement& elem) { stream << "'" << elem.toString(false) << "'"; },
+ [&](const StringData& elem) { stream << "'" << elem << "'"; }},
+ _location);
+ // The assumption is that there is always at least one prefix that represents the entry
+ // point to the parser (e.g. the 'pipeline' argument for an aggregation command).
+ invariant(_prefix.size() > 0);
+ for (auto it = _prefix.rbegin(); it != _prefix.rend() - 1; ++it) {
+ stdx::visit(visit_helper::Overloaded{[&](const unsigned int& index) {
+ stream << " within array at index " << index;
+ },
+ [&](const StringData& pref) {
+ stream << " within '" << pref << "'";
+ }},
+ *it);
+ }
+
+ // The final prefix (or first element in the vector) is the input description.
+ stdx::visit(visit_helper::Overloaded{[&](const unsigned int& index) { MONGO_UNREACHABLE; },
+ [&](const StringData& pref) {
+ stream << " of input " << pref;
+ }},
+ _prefix[0]);
+ return stream.str();
+ }
+
+ friend std::ostream& operator<<(std::ostream& stream, const BSONLocation& location) {
+ return stream << location.toString();
+ }
+ friend StringBuilder& operator<<(StringBuilder& stream, const BSONLocation& location) {
+ return stream << location.toString();
+ }
+
+private:
+ LocationType _location;
+ std::vector<LocationPrefix> _prefix;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/cst/cst_error_test.cpp b/src/mongo/db/cst/cst_error_test.cpp
index dea0b4a6924..600245b4a1d 100644
--- a/src/mongo/db/cst/cst_error_test.cpp
+++ b/src/mongo/db/cst/cst_error_test.cpp
@@ -1,234 +1,234 @@
-/**
- * Copyright (C) 2020-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * 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
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * 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 Server Side 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 <string>
-
-#include "mongo/bson/json.h"
-#include "mongo/db/cst/bson_lexer.h"
-#include "mongo/db/cst/c_node.h"
-#include "mongo/db/cst/key_fieldname.h"
-#include "mongo/db/cst/key_value.h"
-#include "mongo/db/cst/parser_gen.hpp"
-#include "mongo/unittest/bson_test_util.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-namespace {
-
-TEST(CstErrorTest, EmptyStageSpec) {
- auto input = fromjson("{pipeline: [{}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected end of object at element 'end object' "
- "within array at index 0 of input pipeline");
-}
-
-TEST(CstErrorTest, UnknownStageName) {
- // First stage.
- {
- auto input = fromjson("{pipeline: [{$unknownStage: {}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected $-prefixed fieldname at element "
- "'$unknownStage' within array at index 0 of input pipeline");
- }
- // Subsequent stage.
- {
- auto input = fromjson("{pipeline: [{$limit: 1}, {$unknownStage: {}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected $-prefixed fieldname at element "
- "'$unknownStage' within array at index 1 of input pipeline");
- }
-}
-
-TEST(CstErrorTest, InvalidStageArgument) {
- {
- auto input = fromjson("{pipeline: [{$sample: 2}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected arbitrary integer, expecting object at element '2' within "
- "'$sample' within array at index 0 of input pipeline");
- }
- {
- auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$limit: {}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected object at element 'start object' "
- "within '$limit' within array at index 1 of input pipeline");
- }
-}
-
-TEST(CstErrorTest, UnknownArgumentInStageSpec) {
- {
- auto input = fromjson("{pipeline: [{$sample: {huh: 1}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected fieldname, expecting size argument at element 'huh' within "
- "'$sample' within array at index 0 of input pipeline");
- }
- {
- auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$limit: 1}, {$sample: {huh: 1}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected fieldname, expecting size argument at element 'huh' within "
- "'$sample' within array at index 2 of input pipeline");
- }
-}
-
-TEST(CstErrorTest, InvalidArgumentTypeWithinStageSpec) {
- {
- auto input = fromjson("{pipeline: [{$sample: {size: 'cmon'}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected string at element 'cmon' within 'size' within '$sample' "
- "within array at index 0 of input pipeline");
- }
- {
- auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$sample: {size: true}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected true at element 'true' within 'size' "
- "within '$sample' within array at index 1 of input pipeline");
- }
-}
-
-TEST(CstErrorTest, MissingRequiredArgument) {
- auto input = fromjson("{pipeline: [{$sample: {}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected end of object, expecting size argument at element 'end object' "
- "within '$sample' within array at index 0 of input pipeline");
-}
-
-TEST(CstErrorTest, MissingRequiredArgumentOfMultiArgStage) {
- auto input = fromjson("{pipeline: [{$unionWith: {pipeline: 0.0}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected pipeline argument, expecting coll argument at element 'pipeline' "
- "within '$unionWith' within array at index 0 of input pipeline");
-}
-
-TEST(CstErrorTest, InvalidArgumentTypeForProjectionExpression) {
- auto input = fromjson("{pipeline: [{$project: {a: {$eq: '$b'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected $-prefixed string, expecting array at "
- "element '$b' within '$eq' within "
- "'$project' within array at index 0 of input pipeline");
-}
-
-TEST(CstErrorTest, MixedProjectionTypes) {
- auto input = fromjson("{pipeline: [{$project: {a: 1, b: 0}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "project containing inclusion and/or computed fields must contain no exclusion fields at "
- "element '$project' within array at index 0 of input pipeline");
-}
-
-TEST(CstErrorTest, DeeplyNestedSyntaxError) {
- auto input = fromjson("{pipeline: [{$project: {a: {$and: [1, {$or: [{$eq: '$b'}]}]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected $-prefixed string, expecting array at element '$b' within '$eq' "
- "within "
- "array at index 0 within '$or' within array at index 1 within '$and' within '$project' "
- "within array at index 0 of input pipeline");
-}
-
-TEST(CstErrorTest, SortWithRandomIntFails) {
- auto input = fromjson("{sort: {val: 5}}");
- BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected arbitrary integer at element '5' of input sort");
-}
-
-TEST(CstErrorTest, SortWithInvalidMetaFails) {
- auto input = fromjson("{sort: {val: {$meta: \"str\"}}}");
- BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected string, expecting randVal or textScore "
- "at element 'str' within '$meta' of input sort");
-}
-
-TEST(CstErrorTest, SortWithMetaSiblingKeyFails) {
- auto input = fromjson("{sort: {val: {$meta: \"textScore\", someKey: 4}}}");
- BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected fieldname, expecting end of object at "
- "element 'someKey' of input sort");
-}
-
-} // namespace
-} // namespace mongo
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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 <string>
+
+#include "mongo/bson/json.h"
+#include "mongo/db/cst/bson_lexer.h"
+#include "mongo/db/cst/c_node.h"
+#include "mongo/db/cst/key_fieldname.h"
+#include "mongo/db/cst/key_value.h"
+#include "mongo/db/cst/parser_gen.hpp"
+#include "mongo/unittest/bson_test_util.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(CstErrorTest, EmptyStageSpec) {
+ auto input = fromjson("{pipeline: [{}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected end of object at element 'end object' "
+ "within array at index 0 of input pipeline");
+}
+
+TEST(CstErrorTest, UnknownStageName) {
+ // First stage.
+ {
+ auto input = fromjson("{pipeline: [{$unknownStage: {}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected $-prefixed fieldname at element "
+ "'$unknownStage' within array at index 0 of input pipeline");
+ }
+ // Subsequent stage.
+ {
+ auto input = fromjson("{pipeline: [{$limit: 1}, {$unknownStage: {}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected $-prefixed fieldname at element "
+ "'$unknownStage' within array at index 1 of input pipeline");
+ }
+}
+
+TEST(CstErrorTest, InvalidStageArgument) {
+ {
+ auto input = fromjson("{pipeline: [{$sample: 2}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected arbitrary integer, expecting object at element '2' within "
+ "'$sample' within array at index 0 of input pipeline");
+ }
+ {
+ auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$limit: {}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected object at element 'start object' "
+ "within '$limit' within array at index 1 of input pipeline");
+ }
+}
+
+TEST(CstErrorTest, UnknownArgumentInStageSpec) {
+ {
+ auto input = fromjson("{pipeline: [{$sample: {huh: 1}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected fieldname, expecting size argument at element 'huh' within "
+ "'$sample' within array at index 0 of input pipeline");
+ }
+ {
+ auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$limit: 1}, {$sample: {huh: 1}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected fieldname, expecting size argument at element 'huh' within "
+ "'$sample' within array at index 2 of input pipeline");
+ }
+}
+
+TEST(CstErrorTest, InvalidArgumentTypeWithinStageSpec) {
+ {
+ auto input = fromjson("{pipeline: [{$sample: {size: 'cmon'}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected string at element 'cmon' within 'size' within '$sample' "
+ "within array at index 0 of input pipeline");
+ }
+ {
+ auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$sample: {size: true}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected true at element 'true' within 'size' "
+ "within '$sample' within array at index 1 of input pipeline");
+ }
+}
+
+TEST(CstErrorTest, MissingRequiredArgument) {
+ auto input = fromjson("{pipeline: [{$sample: {}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected end of object, expecting size argument at element 'end object' "
+ "within '$sample' within array at index 0 of input pipeline");
+}
+
+TEST(CstErrorTest, MissingRequiredArgumentOfMultiArgStage) {
+ auto input = fromjson("{pipeline: [{$unionWith: {pipeline: 0.0}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected pipeline argument, expecting coll argument at element 'pipeline' "
+ "within '$unionWith' within array at index 0 of input pipeline");
+}
+
+TEST(CstErrorTest, InvalidArgumentTypeForProjectionExpression) {
+ auto input = fromjson("{pipeline: [{$project: {a: {$eq: '$b'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected $-prefixed string, expecting array at "
+ "element '$b' within '$eq' within "
+ "'$project' within array at index 0 of input pipeline");
+}
+
+TEST(CstErrorTest, MixedProjectionTypes) {
+ auto input = fromjson("{pipeline: [{$project: {a: 1, b: 0}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "project containing inclusion and/or computed fields must contain no exclusion fields at "
+ "element '$project' within array at index 0 of input pipeline");
+}
+
+TEST(CstErrorTest, DeeplyNestedSyntaxError) {
+ auto input = fromjson("{pipeline: [{$project: {a: {$and: [1, {$or: [{$eq: '$b'}]}]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected $-prefixed string, expecting array at element '$b' within '$eq' "
+ "within "
+ "array at index 0 within '$or' within array at index 1 within '$and' within '$project' "
+ "within array at index 0 of input pipeline");
+}
+
+TEST(CstErrorTest, SortWithRandomIntFails) {
+ auto input = fromjson("{sort: {val: 5}}");
+ BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected arbitrary integer at element '5' of input sort");
+}
+
+TEST(CstErrorTest, SortWithInvalidMetaFails) {
+ auto input = fromjson("{sort: {val: {$meta: \"str\"}}}");
+ BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected string, expecting randVal or textScore "
+ "at element 'str' within '$meta' of input sort");
+}
+
+TEST(CstErrorTest, SortWithMetaSiblingKeyFails) {
+ auto input = fromjson("{sort: {val: {$meta: \"textScore\", someKey: 4}}}");
+ BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected fieldname, expecting end of object at "
+ "element 'someKey' of input sort");
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/cst/cst_expression_test.cpp b/src/mongo/db/cst/cst_expression_test.cpp
index 1b789098baa..f3e2754a4ab 100644
--- a/src/mongo/db/cst/cst_expression_test.cpp
+++ b/src/mongo/db/cst/cst_expression_test.cpp
@@ -1,1747 +1,1747 @@
-/**
- * Copyright (C) 2020-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * 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
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * 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 Server Side 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 <string>
-
-#include "mongo/bson/json.h"
-#include "mongo/db/cst/bson_lexer.h"
-#include "mongo/db/cst/c_node.h"
-#include "mongo/db/cst/key_fieldname.h"
-#include "mongo/db/cst/key_value.h"
-#include "mongo/db/cst/parser_gen.hpp"
-#include "mongo/unittest/bson_test_util.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-namespace {
-
-TEST(CstExpressionTest, ParsesProjectWithAnd) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: {_id: 9.10, a: {$and: [4, {$and: [7, 8]}]}, b: {$and: [2, "
- "-3]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: \"<NonZeroKey of type double "
- "9.100000>\", <ProjectionPath a>: { <KeyFieldname andExpr>: [ \"<UserInt 4>\", { "
- "<KeyFieldname andExpr>: [ \"<UserInt 7>\", \"<UserInt 8>\" ] } ] }, <ProjectionPath b>: { "
- "<KeyFieldname andExpr>: [ \"<UserInt 2>\", \"<UserInt -3>\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesProjectWithOr) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: {_id: 9.10, a: {$or: [4, {$or: [7, 8]}]}, b: {$or: [2, -3]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: \"<NonZeroKey of type double "
- "9.100000>\", <ProjectionPath a>: { <KeyFieldname orExpr>: [ \"<UserInt 4>\", { "
- "<KeyFieldname orExpr>: [ \"<UserInt 7>\", \"<UserInt 8>\" ] } ] }, <ProjectionPath b>: { "
- "<KeyFieldname orExpr>: [ \"<UserInt 2>\", \"<UserInt -3>\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesProjectWithNot) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: {_id: 9.10, a: {$not: [4]}, b: {$and: [1.0, {$not: "
- "[true]}]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: \"<NonZeroKey of type "
- "double 9.100000>\", <ProjectionPath a>: { <KeyFieldname notExpr>: [ \"<UserInt 4>\" "
- "] }, <ProjectionPath b>: { <KeyFieldname andExpr>: [ \"<UserDouble 1.000000>\", { "
- "<KeyFieldname notExpr>: [ \"<UserBoolean true>\" ] } ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesComparisonExpressions) {
- auto parseAndTest = [](StringData expr) {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: {_id: {$" + expr + ": [1, 2.5]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: { <KeyFieldname " +
- expr + ">: [ \"<UserInt 1>\", \"<UserDouble 2.500000>\" ] } } }");
- };
-
- for (auto&& expr : {"cmp"_sd, "eq"_sd, "gt"_sd, "gte"_sd, "lt"_sd, "lte"_sd, "ne"_sd}) {
- parseAndTest(expr);
- }
-}
-
-TEST(CstExpressionTest, FailsToParseInvalidComparisonExpressions) {
- auto assertFailsToParse = [](StringData expr) {
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: {_id: {$" + expr + ": [1]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: {_id: {$" + expr + ": [1, 2, 3]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: {_id: {$" + expr + ": 1}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- };
-
- for (auto&& expr : {"cmp"_sd, "eq"_sd, "gt"_sd, "gte"_sd, "lt"_sd, "lte"_sd, "ne"_sd}) {
- assertFailsToParse(expr);
- }
-}
-
-TEST(CstExpressionTest, FailsToParseInvalidConvertExpressions) {
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: {a: {$convert: 'x'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: {a: {$convert: {input: 'x'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesConvertExpressions) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: {a: {$toBool: 1}, b: {$toDate: 1100000000000}, "
- "c: {$toDecimal: 5}, d: {$toDouble: -2}, e: {$toInt: 1.999999}, "
- "f: {$toLong: 1.999999}, g: {$toObjectId: '$_id'}, h: {$toString: false}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname toBool>: "
- "\"<UserInt 1>\" }, <ProjectionPath b>: { <KeyFieldname toDate>: \"<UserLong "
- "1100000000000>\" }, <ProjectionPath c>: { <KeyFieldname toDecimal>: \"<UserInt 5>\" "
- "}, <ProjectionPath d>: { <KeyFieldname toDouble>: \"<UserInt "
- "-2>\" }, <ProjectionPath e>: { <KeyFieldname toInt>: \"<UserDouble 1.999999>\" }, "
- "<ProjectionPath f>: { <KeyFieldname toLong>: \"<UserDouble "
- "1.999999>\" }, <ProjectionPath g>: { <KeyFieldname toObjectId>: \"<AggregationPath "
- "_id>\" }, <ProjectionPath h>: { <KeyFieldname toString>: "
- "\"<UserBoolean false>\" } } }");
-}
-
-TEST(CstExpressionTest, ParsesConvertExpressionsNoOptArgs) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: {a: {$convert: {input: 1, to: 'string'}}, "
- "b: {$convert : {input: 'true', to: 'bool'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname convert>: { "
- "<KeyFieldname inputArg>: \"<UserInt 1>\", <KeyFieldname toArg>: \"<UserString "
- "string>\", <KeyFieldname onErrorArg>: \"<KeyValue absentKey>\", <KeyFieldname "
- "onNullArg>: \"<KeyValue "
- "absentKey>\" } }, <ProjectionPath b>: { <KeyFieldname convert>: { <KeyFieldname "
- "inputArg>: \"<UserString true>\", <KeyFieldname toArg>: "
- "\"<UserString bool>\", <KeyFieldname onErrorArg>: \"<KeyValue absentKey>\", "
- "<KeyFieldname onNullArg>: "
- "\"<KeyValue absentKey>\" } } } }");
-}
-
-TEST(CstExpressionTest, ParsesConvertExpressionsWithOptArgs) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: {a: {$convert: {input: 1, to: 'string', "
- "onError: 'Could not convert'}}, b : {$convert : {input: "
- "true, to : 'double', onNull : 0}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname convert>: { "
- "<KeyFieldname inputArg>: \"<UserInt 1>\", <KeyFieldname toArg>: \"<UserString "
- "string>\", <KeyFieldname onErrorArg>: \"<UserString Could not convert>\", "
- "<KeyFieldname onNullArg>: \"<KeyValue "
- "absentKey>\" } }, <ProjectionPath b>: { <KeyFieldname convert>: { <KeyFieldname "
- "inputArg>: \"<UserBoolean true>\", <KeyFieldname toArg>: "
- "\"<UserString double>\", <KeyFieldname onErrorArg>: \"<KeyValue absentKey>\", "
- "<KeyFieldname onNullArg>: "
- "\"<UserInt 0>\" } } } }");
-}
-
-TEST(CstExpressionTest, ParsesIndexOf) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "b: { $indexOfBytes: ['ABC', 'B']}, "
- "c: { $indexOfCP: [ 'cafeteria', 'e' ] }, "
- "d: { $indexOfBytes: [ 'foo.bar.fi', '.', 5, 7 ] }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath b>: { <KeyFieldname "
- "indexOfBytes>: [ \"<UserString ABC>\", \"<UserString B>\" "
- "] }, <ProjectionPath c>: "
- "{ <KeyFieldname indexOfCP>: [ \"<UserString cafeteria>\", \"<UserString e>\" ] }, "
- "<ProjectionPath d>: { "
- "<KeyFieldname indexOfBytes>: [ \"<UserString foo.bar.fi>\", \"<UserString .>\", "
- "\"<UserInt 5>\", "
- "\"<UserInt 7>\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesDateFromString) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { m: { $dateFromString: { dateString: '2017-02-08T12:10:40.787', "
- "timezone: 'America/New_York' } } }}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath m>: { <KeyFieldname "
- "dateFromString>: { <KeyFieldname dateStringArg>: \"<UserString "
- "2017-02-08T12:10:40.787>\", <KeyFieldname formatArg>: \"<KeyValue absentKey>\", "
- "<KeyFieldname timezoneArg>: "
- "\"<UserString America/New_York>\", <KeyFieldname onErrorArg>: \"<KeyValue "
- "absentKey>\", <KeyFieldname onNullArg>: "
- "\"<KeyValue absentKey>\" } } } }");
-}
-
-TEST(CstExpressionTest, ParsesDateToString) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { m: { $dateToString: { date: '$date', "
- "format: '%Y-%m-%d' } } } } ] }");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath m>: { <KeyFieldname dateToString>: { "
- "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname formatArg>: "
- "\"<UserString %Y-%m-%d>\", <KeyFieldname timezoneArg>: \"<KeyValue absentKey>\", "
- "<KeyFieldname onNullArg>: "
- "\"<KeyValue absentKey>\" } } } }");
-}
-
-TEST(CstExpressionTest, ParsesReplaceStringExpressions) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "h: { $replaceOne: { input: '$name', find: 'Cafe', replacement: 'CAFE' } }, "
- "i: { $replaceAll: { input: 'cafeSeattle', find: 'cafe', replacement: 'CAFE' } } }}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath h>: { <KeyFieldname replaceOne>: { "
- "<KeyFieldname inputArg>: \"<AggregationPath name>\", <KeyFieldname findArg>: "
- "\"<UserString Cafe>\", <KeyFieldname replacementArg>: \"<UserString CAFE>\" } }, "
- "<ProjectionPath i>: { <KeyFieldname replaceAll>: "
- "{ <KeyFieldname inputArg>: \"<UserString cafeSeattle>\", <KeyFieldname findArg>: "
- "\"<UserString cafe>\", "
- "<KeyFieldname replacementArg>: \"<UserString CAFE>\" } } } }");
-}
-
-TEST(CstExpressionTest, ParsesTrim) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "d: { $ltrim: { input: ' ggggoodbyeeeee' } }, "
- "e: { $rtrim: { input: 'ggggoodbyeeeee '} }, "
- "f: { $trim: { input: ' ggggoodbyeeeee', chars: ' ge' } } }}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath d>: { <KeyFieldname ltrim>: { "
- "<KeyFieldname inputArg>: \"<UserString ggggoodbyeeeee>\", <KeyFieldname charsArg>: "
- "\"<KeyValue absentKey>\" } }, <ProjectionPath e>: { <KeyFieldname rtrim>: { "
- "<KeyFieldname inputArg>: \"<UserString ggggoodbyeeeee "
- " >\", <KeyFieldname charsArg>: \"<KeyValue absentKey>\" } }, <ProjectionPath f>: { "
- "<KeyFieldname trim>: { <KeyFieldname inputArg>: \"<UserString "
- " ggggoodbyeeeee>\", <KeyFieldname charsArg>: \"<UserString ge>\" } } } }");
-}
-
-TEST(CstExpressionTest, ParsesToUpperAndLower) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "g: { $toUpper: 'abc' }, "
- "v: { $toLower: 'ABC' }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath g>: { <KeyFieldname toUpper>: "
- "\"<UserString abc>\" }, <ProjectionPath v>: { <KeyFieldname toLower>: \"<UserString "
- "ABC>\" } } }");
-}
-
-TEST(CstExpressionTest, ParsesRegexExpressions) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "j: { $regexFind: { input: '$details', regex: /^[a-z0-9_.+-]/, options: 'i' } }, "
- "k: { $regexFindAll: { input: '$fname', regex: /(C(ar)*)ol/ } }, "
- "l: { $regexMatch: { input: '$description', regex: /lin(e|k)/ } } }}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath j>: { <KeyFieldname regexFind>: "
- "{ <KeyFieldname inputArg>: \"<AggregationPath details>\", <KeyFieldname regexArg>: "
- "\"<UserRegex /^[a-z0-9_.+-]/>\", <KeyFieldname optionsArg>: \"<UserString i>\" } }, "
- "<ProjectionPath k>: { <KeyFieldname regexFindAll>: { "
- "<KeyFieldname inputArg>: \"<AggregationPath fname>\", <KeyFieldname regexArg>: "
- "\"<UserRegex /(C(ar)*)ol/>\", <KeyFieldname optionsArg>: "
- "\"<KeyValue absentKey>\" } }, <ProjectionPath l>: { <KeyFieldname regexMatch>: { "
- "<KeyFieldname inputArg>: \"<AggregationPath description>\", <KeyFieldname regexArg>: "
- "\"<UserRegex /lin(e|k)/>\", <KeyFieldname optionsArg>: \"<KeyValue absentKey>\" } } } }");
-}
-
-TEST(CstExpressionTest, ParsesSubstrExpressions) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "s: { $substr: [ '$quarter', 2, -1 ] }, "
- "t: { $substrBytes: [ '$name', 0, 3 ] }, "
- "u: { $substrCP: [ 'Hello World!', 6, 5 ] }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath s>: { <KeyFieldname substr>: [ "
- "\"<AggregationPath quarter>\", \"<UserInt 2>\", "
- "\"<UserInt -1>\" ] }, <ProjectionPath t>: { <KeyFieldname substrBytes>: [ "
- "\"<AggregationPath name>\", \"<UserInt 0>\", \"<UserInt 3>\" ] }, <ProjectionPath "
- "u>: { <KeyFieldname substrCP>: [ \"<UserString Hello World!>\", \"<UserInt 6>\", "
- "\"<UserInt 5>\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesStringLengthExpressions) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "p: { $strLenBytes: 'cafeteria' }, "
- "q: { $strLenCP: 'Hello World!' }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath p>: { <KeyFieldname strLenBytes>: "
- "\"<UserString cafeteria>\" }, <ProjectionPath q>: { <KeyFieldname strLenCP>: "
- "\"<UserString Hello World!>\" } } }");
-}
-
-TEST(CstExpressionTest, ParsesSplit) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "o: { $split: [ {$toUpper: 'abc'}, '-' ] }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath o>: { <KeyFieldname split>: [ { "
- "<KeyFieldname toUpper>: \"<UserString abc>\" }, "
- "\"<UserString ->\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesStrCaseCmp) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "r: { $strcasecmp: [ '$quarter', '13q4' ] }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath r>: { <KeyFieldname "
- "strcasecmp>: [ \"<AggregationPath quarter>\", \"<UserString "
- "13q4>\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesConcat) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $concat: [ 'item', ' - ', '$description' ]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname concat>: [ "
- "\"<UserString item>\", \"<UserString - >\", \"<AggregationPath description>\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesArrayElemAt) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $arrayElemAt: [ [1, 2, 3, 4] , 1 ] }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "arrayElemAt>: [ [ \"<UserInt 1>\", "
- "\"<UserInt 2>\", \"<UserInt 3>\", \"<UserInt 4>\" ], \"<UserInt 1>\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesArrayToObject) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $arrayToObject: [ [1, 2], [3, 4] ] }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "arrayToObject>: [ [ \"<UserInt 1>\", "
- "\"<UserInt 2>\" ], [ \"<UserInt 3>\", \"<UserInt 4>\" ] ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesConcatArrays) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $concatArrays: [ [1, 2], [3, 4] ] }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "concatArrays>: [ [ \"<UserInt 1>\", "
- "\"<UserInt 2>\" ], [ \"<UserInt 3>\", \"<UserInt 4>\" ] ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesFilter) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $filter: {input: [0, 2], as: \"var\", cond: { $gt: [ \"$$var\", 1 ] }}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname filter>: { "
- "<KeyFieldname asArg>: \"<UserString var>\", <KeyFieldname condArg>: "
- "{ <KeyFieldname gt>: [ \"<AggregationVariablePath var>\", \"<UserInt 1>\" ] }, "
- "<KeyFieldname inputArg>: [ \"<UserInt 0>\", "
- "\"<UserInt 2>\" ] } } } }");
-}
-
-TEST(CstExpressionTest, ParsesFirst) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $first: [0, 2]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "first>: [ \"<UserInt 0>\", \"<UserInt 2>\" ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesIn) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $in: [1, [0, 2]]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname in>: [ "
- "\"<UserInt 1>\", [ \"<UserInt 0>\", \"<UserInt 2>\" "
- "] ] } } }");
-}
-
-TEST(CstExpressionTest, ParsesIndexOfArray) {
- // Parses with Start and End Arguments
- {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $indexOfArray: [ [0, 2], \"$searchExpression\", 0, 1 ]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "indexOfArray>: [ [ \"<UserInt 0>\", \"<UserInt 2>\" ], \"<AggregationPath "
- "searchExpression>\", \"<UserInt 0>\", \"<UserInt 1>\" ] } } }");
- }
- // Parses with just Start argument
- {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $indexOfArray: [ [0, 2], \"$searchExpression\", 0 ]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "indexOfArray>: [ [ \"<UserInt 0>\", \"<UserInt 2>\" ], \"<AggregationPath "
- "searchExpression>\", \"<UserInt 0>\" ] } } }");
- }
- // Parses without Start and End arguments
- {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $indexOfArray: [ [0, 2], \"$searchExpression\" ]}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "indexOfArray>: [ [ \"<UserInt 0>\", \"<UserInt 2>\" ], \"<AggregationPath "
- "searchExpression>\" ] } } }");
- }
-}
-
-TEST(CstExpressionTest, ParsesIsArray) {
- // Parses with argument wrapped in array
- {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $isArray: [ [0, 2] ] }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isArray>: [ [ "
- "\"<UserInt 0>\", \"<UserInt 2>\" ] ] } } }");
- }
- // Parses without argument wrapped in array
- {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $isArray: \"$maybeAnArrayLivesHere\" }}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname isArray>: "
- "\"<AggregationPath maybeAnArrayLivesHere>\" } } }");
- }
-}
-
-TEST(CstExpressionTest, FailsToParseTripleDollar) {
- CNode output;
- auto input = BSON("pipeline" << BSON_ARRAY(BSON("$project" << BSON("a"
- << "$$$triple"))));
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
-}
-
-TEST(CstExpressionTest, FailsToParseLoneDollar) {
- CNode output;
- auto input = BSON("pipeline" << BSON_ARRAY(BSON("$project" << BSON("a"
- << "$"))));
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
-}
-
-TEST(CstExpressionTest, FailsToParseInvalidVarName) {
- CNode output;
- auto input = BSON("pipeline" << BSON_ARRAY(BSON("$project" << BSON("a"
- << "$$invalid"))));
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
-}
-
-TEST(CstExpressionTest, ParsesDateToParts) {
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $dateToParts: {date: '$date', 'timezone': 'America/New_York', 'iso8601': true}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname dateToParts>: { "
- "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname timezoneArg>: "
- "\"<UserString America/New_York>\", "
- "<KeyFieldname iso8601Arg>: \"<UserBoolean true>\" } } } }");
-}
-
-TEST(CstExpressionTest, ParsesDateFromParts) {
- {
- // Non-iso formatted version:
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $dateFromParts: {year: '$year', month: '$month', day: '$day',"
- "hour: '$hour', minute: '$minute', second: '$second', millisecond:"
- " '$millisecs', timezone: 'America/New_York'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dateFromParts>: { "
- "<KeyFieldname yearArg>: \"<AggregationPath year>\", <KeyFieldname monthArg>: "
- "\"<AggregationPath month>\", "
- "<KeyFieldname dayArg>: \"<AggregationPath day>\", <KeyFieldname hourArg>: "
- "\"<AggregationPath hour>\", <KeyFieldname minuteArg>: "
- "\"<AggregationPath minute>\", <KeyFieldname secondArg>: \"<AggregationPath "
- "second>\", <KeyFieldname millisecondArg>: "
- "\"<AggregationPath millisecs>\", <KeyFieldname timezoneArg>: \"<UserString "
- "America/New_York>\" } } } "
- "}");
- }
- {
- // Iso formatted version:
- CNode output;
- auto input = fromjson(
- "{pipeline: [{$project: { "
- "a: { $dateFromParts: {isoWeekYear: '$year', isoWeek: '$isowk', isoDayOfWeek: '$day',"
- "hour: '$hour', minute: '$minute', second: '$second', millisecond:"
- " '$millisecs', timezone: 'America/New_York'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dateFromParts>: { "
- "<KeyFieldname isoWeekYearArg>: \"<AggregationPath year>\", <KeyFieldname "
- "isoWeekArg>: \"<AggregationPath isowk>\", "
- "<KeyFieldname isoDayOfWeekArg>: \"<AggregationPath day>\", <KeyFieldname "
- "hourArg>: \"<AggregationPath hour>\", "
- "<KeyFieldname minuteArg>: "
- "\"<AggregationPath minute>\", <KeyFieldname secondArg>: \"<AggregationPath "
- "second>\", <KeyFieldname millisecondArg>: "
- "\"<AggregationPath millisecs>\", <KeyFieldname timezoneArg>: \"<UserString "
- "America/New_York>\" } } } "
- "}");
- }
-}
-
-TEST(CstExpressionTest, ParsesDayOfDateExpressionsWithDocumentArgument) {
- {
- // $dayOfMonth
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfMonth>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
- {
- // $dayOfWeek
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfWeek>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
- {
- // $dayOfYear
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfYear>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
-}
-
-TEST(CstExpressionTest, FailsToParseDayOfDateExpressionsWithInvalidDocumentArgument) {
- {
- // $dayOfMonth
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- {
- // $dayOfWeek
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- {
- // $dayOfYear
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesDayOfDateExpressionsWithExpressionArgument) {
- {
- // $dayOfMonth
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfMonth>: \"<AggregationPath date>\" } } }");
- }
- {
- // $dayOfWeek
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfWeek>: \"<AggregationPath date>\" } } }");
- }
- {
- // $dayOfYear
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfYear>: \"<AggregationPath date>\" } } }");
- }
-}
-
-TEST(CstExpressionTest, ParsesDayOfMonthWithArrayArgument) {
- // $dayOfMonth
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfMonth>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with non-singleton array argument.
- {
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $dayOfMonth: ['$date', '$timeZone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesDayOfWeekWithArrayArgument) {
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfWeek>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with non-singleton array argument.
- {
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $dayOfWeek: ['$date', '$timeZone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesDayOfYearWithArrayArgument) {
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "dayOfYear>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with non-singleton array argument.
- {
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $dayOfYear: ['$date', '$timeZone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesIsoDateExpressionsWithDocumentArgument) {
- {
- // $isoDayOfWeek
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoDayOfWeek>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
- {
- // $isoWeek
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoWeek>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
- {
- // $isoWeekYear
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeekYear: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoWeekYear>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
-}
-
-TEST(CstExpressionTest, FailsToParseIsoDateExpressionsWithInvalidDocumentArgument) {
- {
- // $isoDayOfWeek
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- {
- // $isoWeek
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- {
- // $isoWeekYear
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $isoWeekYear: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesIsoDateExpressionsWithExpressionArgument) {
- {
- // $isoDayOfWeek
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoDayOfWeek>: \"<AggregationPath date>\" } } }");
- }
- {
- // $isoWeek
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoWeek>: \"<AggregationPath date>\" } } }");
- }
- {
- // $isoWeekYear
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeekYear: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoWeekYear>: \"<AggregationPath date>\" } } }");
- }
-}
-
-TEST(CstExpressionTest, ParsesIsoDayOfWeekWithArrayArgument) {
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoDayOfWeek>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with non-singleton array argument.
- {
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: ['$date', '$timeZone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesIsoWeekWithArrayArgument) {
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoWeek>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with non-singleton array argument.
- {
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $isoWeek: ['$date', '$timeZone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesIsoWeekYearWithArrayArgument) {
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeekYear: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "isoWeekYear>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with non-singleton array argument.
- {
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $isoWeekYear: ['$date', '$timeZone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $isoWeekYear: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesHourExpressionWithDocumentArgument) {
- {
- // $hour
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $hour: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname hour>: { "
- "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname timezoneArg>: "
- "\"<KeyValue absentKey>\" } } } }");
- }
- {
- // Ensure fails to parse with invalid argument-document.
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $hour: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesMillisecondExpressionWithDocumentArgument) {
- {
- // $millisecond
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $millisecond: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "millisecond>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
- {
- // Ensure fails to parse with invalid argument-document.
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $millisecond: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesMinuteExpressionWithDocumentArgument) {
- {
- // $minute
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $minute: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname minute>: { "
- "<KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
- {
- // Ensure fails to parse with invalid argument-document.
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $minute: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesMonthExpressionWithDocumentArgument) {
- {
- // $month
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $month: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname month>: { "
- "<KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
- {
- // Ensure fails to parse with invalid argument-document.
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $month: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesSecondExpressionWithDocumentArgument) {
- {
- // $second
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $second: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname second>: { "
- "<KeyFieldname dateArg>: \"<AggregationPath date>\", "
- "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
- }
- {
- // Ensure fails to parse with invalid argument-document.
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $second: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesWeekExpressionWithDocumentArgument) {
- {
- // $week
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $week: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname week>: { "
- "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname timezoneArg>: "
- "\"<KeyValue absentKey>\" } } } }");
- }
- {
- // Ensure fails to parse with invalid argument-document.
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $week: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesYearExpressionWithDocumentArgument) {
- {
- // $year
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $year: {date: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(
- stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname year>: { "
- "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname timezoneArg>: "
- "\"<KeyValue absentKey>\" } } } }");
- }
- {
- // Ensure fails to parse with invalid argument-document.
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $year: {notDate: '$date'}}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesHourExpressionWithExpressionArgument) {
- // Test with argument as-is.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $hour: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname hour>: "
- "\"<AggregationPath date>\" } } }");
- }
- // Test with argument wrapped in array.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $hour: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname hour>: "
- "[ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with a non-singleton array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $hour: ['$date', '$timezone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $hour: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesMillisecondExpressionWithExpressionArgument) {
- // Test with argument as-is.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $millisecond: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "millisecond>: \"<AggregationPath date>\" } } }");
- }
- // Test with argument wrapped in array.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $millisecond: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "millisecond>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with a non-singleton array argument.
- {
- CNode output;
- auto input =
- fromjson("{pipeline: [{$project: { a: { $millisecond: ['$date', '$timezone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $millisecond: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesMinuteExpressionWithExpressionArgument) {
- // Test with argument as-is.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $minute: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "minute>: \"<AggregationPath date>\" } } }");
- }
- // Test with argument wrapped in array.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $minute: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "minute>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with a non-singleton array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $minute: ['$date', '$timezone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $minute: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesMonthExpressionWithExpressionArgument) {
- // Test with argument as-is.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $month: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "month>: \"<AggregationPath date>\" } } }");
- }
- // Test with argument wrapped in array.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $month: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "month>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with a non-singleton array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $month: ['$date', '$timezone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $month: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesSecondExpressionWithExpressionArgument) {
- // Test with argument as-is.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $second: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "second>: \"<AggregationPath date>\" } } }");
- }
- // Test with argument wrapped in array.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $second: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
- "second>: [ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with a non-singleton array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $second: ['$date', '$timezone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $second: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesWeekExpressionWithExpressionArgument) {
- // Test with argument as-is.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $week: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname week>: "
- "\"<AggregationPath date>\" } } }");
- }
- // Test with argument wrapped in array.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $week: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname week>: "
- "[ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with a non-singleton array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $week: ['$date', '$timezone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $week: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstExpressionTest, ParsesYearExpressionWithExpressionArgument) {
- // Test with argument as-is.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $year: '$date'}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname year>: "
- "\"<AggregationPath date>\" } } }");
- }
- // Test with argument wrapped in array.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $year: ['$date']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
- ASSERT_EQ(1, stages.size());
- ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
- ASSERT_EQ(stages[0].toBson().toString(),
- "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname year>: "
- "[ \"<AggregationPath date>\" ] } } }");
- }
- // Ensure fails to parse with a non-singleton array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $year: ['$date', '$timezone']}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Ensure fails to parse with an empty array argument.
- {
- CNode output;
- auto input = fromjson("{pipeline: [{$project: { a: { $year: []}}}]}");
- BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-} // namespace
-} // namespace mongo
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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 <string>
+
+#include "mongo/bson/json.h"
+#include "mongo/db/cst/bson_lexer.h"
+#include "mongo/db/cst/c_node.h"
+#include "mongo/db/cst/key_fieldname.h"
+#include "mongo/db/cst/key_value.h"
+#include "mongo/db/cst/parser_gen.hpp"
+#include "mongo/unittest/bson_test_util.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(CstExpressionTest, ParsesProjectWithAnd) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: {_id: 9.10, a: {$and: [4, {$and: [7, 8]}]}, b: {$and: [2, "
+ "-3]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: \"<NonZeroKey of type double "
+ "9.100000>\", <ProjectionPath a>: { <KeyFieldname andExpr>: [ \"<UserInt 4>\", { "
+ "<KeyFieldname andExpr>: [ \"<UserInt 7>\", \"<UserInt 8>\" ] } ] }, <ProjectionPath b>: { "
+ "<KeyFieldname andExpr>: [ \"<UserInt 2>\", \"<UserInt -3>\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesProjectWithOr) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: {_id: 9.10, a: {$or: [4, {$or: [7, 8]}]}, b: {$or: [2, -3]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: \"<NonZeroKey of type double "
+ "9.100000>\", <ProjectionPath a>: { <KeyFieldname orExpr>: [ \"<UserInt 4>\", { "
+ "<KeyFieldname orExpr>: [ \"<UserInt 7>\", \"<UserInt 8>\" ] } ] }, <ProjectionPath b>: { "
+ "<KeyFieldname orExpr>: [ \"<UserInt 2>\", \"<UserInt -3>\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesProjectWithNot) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: {_id: 9.10, a: {$not: [4]}, b: {$and: [1.0, {$not: "
+ "[true]}]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: \"<NonZeroKey of type "
+ "double 9.100000>\", <ProjectionPath a>: { <KeyFieldname notExpr>: [ \"<UserInt 4>\" "
+ "] }, <ProjectionPath b>: { <KeyFieldname andExpr>: [ \"<UserDouble 1.000000>\", { "
+ "<KeyFieldname notExpr>: [ \"<UserBoolean true>\" ] } ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesComparisonExpressions) {
+ auto parseAndTest = [](StringData expr) {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: {_id: {$" + expr + ": [1, 2.5]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: { <KeyFieldname " +
+ expr + ">: [ \"<UserInt 1>\", \"<UserDouble 2.500000>\" ] } } }");
+ };
+
+ for (auto&& expr : {"cmp"_sd, "eq"_sd, "gt"_sd, "gte"_sd, "lt"_sd, "lte"_sd, "ne"_sd}) {
+ parseAndTest(expr);
+ }
+}
+
+TEST(CstExpressionTest, FailsToParseInvalidComparisonExpressions) {
+ auto assertFailsToParse = [](StringData expr) {
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: {_id: {$" + expr + ": [1]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: {_id: {$" + expr + ": [1, 2, 3]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: {_id: {$" + expr + ": 1}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ };
+
+ for (auto&& expr : {"cmp"_sd, "eq"_sd, "gt"_sd, "gte"_sd, "lt"_sd, "lte"_sd, "ne"_sd}) {
+ assertFailsToParse(expr);
+ }
+}
+
+TEST(CstExpressionTest, FailsToParseInvalidConvertExpressions) {
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: {a: {$convert: 'x'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: {a: {$convert: {input: 'x'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesConvertExpressions) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: {a: {$toBool: 1}, b: {$toDate: 1100000000000}, "
+ "c: {$toDecimal: 5}, d: {$toDouble: -2}, e: {$toInt: 1.999999}, "
+ "f: {$toLong: 1.999999}, g: {$toObjectId: '$_id'}, h: {$toString: false}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname toBool>: "
+ "\"<UserInt 1>\" }, <ProjectionPath b>: { <KeyFieldname toDate>: \"<UserLong "
+ "1100000000000>\" }, <ProjectionPath c>: { <KeyFieldname toDecimal>: \"<UserInt 5>\" "
+ "}, <ProjectionPath d>: { <KeyFieldname toDouble>: \"<UserInt "
+ "-2>\" }, <ProjectionPath e>: { <KeyFieldname toInt>: \"<UserDouble 1.999999>\" }, "
+ "<ProjectionPath f>: { <KeyFieldname toLong>: \"<UserDouble "
+ "1.999999>\" }, <ProjectionPath g>: { <KeyFieldname toObjectId>: \"<AggregationPath "
+ "_id>\" }, <ProjectionPath h>: { <KeyFieldname toString>: "
+ "\"<UserBoolean false>\" } } }");
+}
+
+TEST(CstExpressionTest, ParsesConvertExpressionsNoOptArgs) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: {a: {$convert: {input: 1, to: 'string'}}, "
+ "b: {$convert : {input: 'true', to: 'bool'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname convert>: { "
+ "<KeyFieldname inputArg>: \"<UserInt 1>\", <KeyFieldname toArg>: \"<UserString "
+ "string>\", <KeyFieldname onErrorArg>: \"<KeyValue absentKey>\", <KeyFieldname "
+ "onNullArg>: \"<KeyValue "
+ "absentKey>\" } }, <ProjectionPath b>: { <KeyFieldname convert>: { <KeyFieldname "
+ "inputArg>: \"<UserString true>\", <KeyFieldname toArg>: "
+ "\"<UserString bool>\", <KeyFieldname onErrorArg>: \"<KeyValue absentKey>\", "
+ "<KeyFieldname onNullArg>: "
+ "\"<KeyValue absentKey>\" } } } }");
+}
+
+TEST(CstExpressionTest, ParsesConvertExpressionsWithOptArgs) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: {a: {$convert: {input: 1, to: 'string', "
+ "onError: 'Could not convert'}}, b : {$convert : {input: "
+ "true, to : 'double', onNull : 0}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname convert>: { "
+ "<KeyFieldname inputArg>: \"<UserInt 1>\", <KeyFieldname toArg>: \"<UserString "
+ "string>\", <KeyFieldname onErrorArg>: \"<UserString Could not convert>\", "
+ "<KeyFieldname onNullArg>: \"<KeyValue "
+ "absentKey>\" } }, <ProjectionPath b>: { <KeyFieldname convert>: { <KeyFieldname "
+ "inputArg>: \"<UserBoolean true>\", <KeyFieldname toArg>: "
+ "\"<UserString double>\", <KeyFieldname onErrorArg>: \"<KeyValue absentKey>\", "
+ "<KeyFieldname onNullArg>: "
+ "\"<UserInt 0>\" } } } }");
+}
+
+TEST(CstExpressionTest, ParsesIndexOf) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "b: { $indexOfBytes: ['ABC', 'B']}, "
+ "c: { $indexOfCP: [ 'cafeteria', 'e' ] }, "
+ "d: { $indexOfBytes: [ 'foo.bar.fi', '.', 5, 7 ] }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath b>: { <KeyFieldname "
+ "indexOfBytes>: [ \"<UserString ABC>\", \"<UserString B>\" "
+ "] }, <ProjectionPath c>: "
+ "{ <KeyFieldname indexOfCP>: [ \"<UserString cafeteria>\", \"<UserString e>\" ] }, "
+ "<ProjectionPath d>: { "
+ "<KeyFieldname indexOfBytes>: [ \"<UserString foo.bar.fi>\", \"<UserString .>\", "
+ "\"<UserInt 5>\", "
+ "\"<UserInt 7>\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesDateFromString) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { m: { $dateFromString: { dateString: '2017-02-08T12:10:40.787', "
+ "timezone: 'America/New_York' } } }}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath m>: { <KeyFieldname "
+ "dateFromString>: { <KeyFieldname dateStringArg>: \"<UserString "
+ "2017-02-08T12:10:40.787>\", <KeyFieldname formatArg>: \"<KeyValue absentKey>\", "
+ "<KeyFieldname timezoneArg>: "
+ "\"<UserString America/New_York>\", <KeyFieldname onErrorArg>: \"<KeyValue "
+ "absentKey>\", <KeyFieldname onNullArg>: "
+ "\"<KeyValue absentKey>\" } } } }");
+}
+
+TEST(CstExpressionTest, ParsesDateToString) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { m: { $dateToString: { date: '$date', "
+ "format: '%Y-%m-%d' } } } } ] }");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath m>: { <KeyFieldname dateToString>: { "
+ "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname formatArg>: "
+ "\"<UserString %Y-%m-%d>\", <KeyFieldname timezoneArg>: \"<KeyValue absentKey>\", "
+ "<KeyFieldname onNullArg>: "
+ "\"<KeyValue absentKey>\" } } } }");
+}
+
+TEST(CstExpressionTest, ParsesReplaceStringExpressions) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "h: { $replaceOne: { input: '$name', find: 'Cafe', replacement: 'CAFE' } }, "
+ "i: { $replaceAll: { input: 'cafeSeattle', find: 'cafe', replacement: 'CAFE' } } }}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath h>: { <KeyFieldname replaceOne>: { "
+ "<KeyFieldname inputArg>: \"<AggregationPath name>\", <KeyFieldname findArg>: "
+ "\"<UserString Cafe>\", <KeyFieldname replacementArg>: \"<UserString CAFE>\" } }, "
+ "<ProjectionPath i>: { <KeyFieldname replaceAll>: "
+ "{ <KeyFieldname inputArg>: \"<UserString cafeSeattle>\", <KeyFieldname findArg>: "
+ "\"<UserString cafe>\", "
+ "<KeyFieldname replacementArg>: \"<UserString CAFE>\" } } } }");
+}
+
+TEST(CstExpressionTest, ParsesTrim) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "d: { $ltrim: { input: ' ggggoodbyeeeee' } }, "
+ "e: { $rtrim: { input: 'ggggoodbyeeeee '} }, "
+ "f: { $trim: { input: ' ggggoodbyeeeee', chars: ' ge' } } }}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath d>: { <KeyFieldname ltrim>: { "
+ "<KeyFieldname inputArg>: \"<UserString ggggoodbyeeeee>\", <KeyFieldname charsArg>: "
+ "\"<KeyValue absentKey>\" } }, <ProjectionPath e>: { <KeyFieldname rtrim>: { "
+ "<KeyFieldname inputArg>: \"<UserString ggggoodbyeeeee "
+ " >\", <KeyFieldname charsArg>: \"<KeyValue absentKey>\" } }, <ProjectionPath f>: { "
+ "<KeyFieldname trim>: { <KeyFieldname inputArg>: \"<UserString "
+ " ggggoodbyeeeee>\", <KeyFieldname charsArg>: \"<UserString ge>\" } } } }");
+}
+
+TEST(CstExpressionTest, ParsesToUpperAndLower) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "g: { $toUpper: 'abc' }, "
+ "v: { $toLower: 'ABC' }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath g>: { <KeyFieldname toUpper>: "
+ "\"<UserString abc>\" }, <ProjectionPath v>: { <KeyFieldname toLower>: \"<UserString "
+ "ABC>\" } } }");
+}
+
+TEST(CstExpressionTest, ParsesRegexExpressions) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "j: { $regexFind: { input: '$details', regex: /^[a-z0-9_.+-]/, options: 'i' } }, "
+ "k: { $regexFindAll: { input: '$fname', regex: /(C(ar)*)ol/ } }, "
+ "l: { $regexMatch: { input: '$description', regex: /lin(e|k)/ } } }}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath j>: { <KeyFieldname regexFind>: "
+ "{ <KeyFieldname inputArg>: \"<AggregationPath details>\", <KeyFieldname regexArg>: "
+ "\"<UserRegex /^[a-z0-9_.+-]/>\", <KeyFieldname optionsArg>: \"<UserString i>\" } }, "
+ "<ProjectionPath k>: { <KeyFieldname regexFindAll>: { "
+ "<KeyFieldname inputArg>: \"<AggregationPath fname>\", <KeyFieldname regexArg>: "
+ "\"<UserRegex /(C(ar)*)ol/>\", <KeyFieldname optionsArg>: "
+ "\"<KeyValue absentKey>\" } }, <ProjectionPath l>: { <KeyFieldname regexMatch>: { "
+ "<KeyFieldname inputArg>: \"<AggregationPath description>\", <KeyFieldname regexArg>: "
+ "\"<UserRegex /lin(e|k)/>\", <KeyFieldname optionsArg>: \"<KeyValue absentKey>\" } } } }");
+}
+
+TEST(CstExpressionTest, ParsesSubstrExpressions) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "s: { $substr: [ '$quarter', 2, -1 ] }, "
+ "t: { $substrBytes: [ '$name', 0, 3 ] }, "
+ "u: { $substrCP: [ 'Hello World!', 6, 5 ] }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath s>: { <KeyFieldname substr>: [ "
+ "\"<AggregationPath quarter>\", \"<UserInt 2>\", "
+ "\"<UserInt -1>\" ] }, <ProjectionPath t>: { <KeyFieldname substrBytes>: [ "
+ "\"<AggregationPath name>\", \"<UserInt 0>\", \"<UserInt 3>\" ] }, <ProjectionPath "
+ "u>: { <KeyFieldname substrCP>: [ \"<UserString Hello World!>\", \"<UserInt 6>\", "
+ "\"<UserInt 5>\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesStringLengthExpressions) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "p: { $strLenBytes: 'cafeteria' }, "
+ "q: { $strLenCP: 'Hello World!' }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath p>: { <KeyFieldname strLenBytes>: "
+ "\"<UserString cafeteria>\" }, <ProjectionPath q>: { <KeyFieldname strLenCP>: "
+ "\"<UserString Hello World!>\" } } }");
+}
+
+TEST(CstExpressionTest, ParsesSplit) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "o: { $split: [ {$toUpper: 'abc'}, '-' ] }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath o>: { <KeyFieldname split>: [ { "
+ "<KeyFieldname toUpper>: \"<UserString abc>\" }, "
+ "\"<UserString ->\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesStrCaseCmp) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "r: { $strcasecmp: [ '$quarter', '13q4' ] }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath r>: { <KeyFieldname "
+ "strcasecmp>: [ \"<AggregationPath quarter>\", \"<UserString "
+ "13q4>\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesConcat) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $concat: [ 'item', ' - ', '$description' ]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname concat>: [ "
+ "\"<UserString item>\", \"<UserString - >\", \"<AggregationPath description>\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesArrayElemAt) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $arrayElemAt: [ [1, 2, 3, 4] , 1 ] }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "arrayElemAt>: [ [ \"<UserInt 1>\", "
+ "\"<UserInt 2>\", \"<UserInt 3>\", \"<UserInt 4>\" ], \"<UserInt 1>\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesArrayToObject) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $arrayToObject: [ [1, 2], [3, 4] ] }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "arrayToObject>: [ [ \"<UserInt 1>\", "
+ "\"<UserInt 2>\" ], [ \"<UserInt 3>\", \"<UserInt 4>\" ] ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesConcatArrays) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $concatArrays: [ [1, 2], [3, 4] ] }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "concatArrays>: [ [ \"<UserInt 1>\", "
+ "\"<UserInt 2>\" ], [ \"<UserInt 3>\", \"<UserInt 4>\" ] ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesFilter) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $filter: {input: [0, 2], as: \"var\", cond: { $gt: [ \"$$var\", 1 ] }}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname filter>: { "
+ "<KeyFieldname asArg>: \"<UserString var>\", <KeyFieldname condArg>: "
+ "{ <KeyFieldname gt>: [ \"<AggregationVariablePath var>\", \"<UserInt 1>\" ] }, "
+ "<KeyFieldname inputArg>: [ \"<UserInt 0>\", "
+ "\"<UserInt 2>\" ] } } } }");
+}
+
+TEST(CstExpressionTest, ParsesFirst) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $first: [0, 2]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "first>: [ \"<UserInt 0>\", \"<UserInt 2>\" ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesIn) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $in: [1, [0, 2]]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname in>: [ "
+ "\"<UserInt 1>\", [ \"<UserInt 0>\", \"<UserInt 2>\" "
+ "] ] } } }");
+}
+
+TEST(CstExpressionTest, ParsesIndexOfArray) {
+ // Parses with Start and End Arguments
+ {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $indexOfArray: [ [0, 2], \"$searchExpression\", 0, 1 ]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "indexOfArray>: [ [ \"<UserInt 0>\", \"<UserInt 2>\" ], \"<AggregationPath "
+ "searchExpression>\", \"<UserInt 0>\", \"<UserInt 1>\" ] } } }");
+ }
+ // Parses with just Start argument
+ {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $indexOfArray: [ [0, 2], \"$searchExpression\", 0 ]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "indexOfArray>: [ [ \"<UserInt 0>\", \"<UserInt 2>\" ], \"<AggregationPath "
+ "searchExpression>\", \"<UserInt 0>\" ] } } }");
+ }
+ // Parses without Start and End arguments
+ {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $indexOfArray: [ [0, 2], \"$searchExpression\" ]}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "indexOfArray>: [ [ \"<UserInt 0>\", \"<UserInt 2>\" ], \"<AggregationPath "
+ "searchExpression>\" ] } } }");
+ }
+}
+
+TEST(CstExpressionTest, ParsesIsArray) {
+ // Parses with argument wrapped in array
+ {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $isArray: [ [0, 2] ] }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isArray>: [ [ "
+ "\"<UserInt 0>\", \"<UserInt 2>\" ] ] } } }");
+ }
+ // Parses without argument wrapped in array
+ {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $isArray: \"$maybeAnArrayLivesHere\" }}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname isArray>: "
+ "\"<AggregationPath maybeAnArrayLivesHere>\" } } }");
+ }
+}
+
+TEST(CstExpressionTest, FailsToParseTripleDollar) {
+ CNode output;
+ auto input = BSON("pipeline" << BSON_ARRAY(BSON("$project" << BSON("a"
+ << "$$$triple"))));
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+}
+
+TEST(CstExpressionTest, FailsToParseLoneDollar) {
+ CNode output;
+ auto input = BSON("pipeline" << BSON_ARRAY(BSON("$project" << BSON("a"
+ << "$"))));
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+}
+
+TEST(CstExpressionTest, FailsToParseInvalidVarName) {
+ CNode output;
+ auto input = BSON("pipeline" << BSON_ARRAY(BSON("$project" << BSON("a"
+ << "$$invalid"))));
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+}
+
+TEST(CstExpressionTest, ParsesDateToParts) {
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $dateToParts: {date: '$date', 'timezone': 'America/New_York', 'iso8601': true}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname dateToParts>: { "
+ "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname timezoneArg>: "
+ "\"<UserString America/New_York>\", "
+ "<KeyFieldname iso8601Arg>: \"<UserBoolean true>\" } } } }");
+}
+
+TEST(CstExpressionTest, ParsesDateFromParts) {
+ {
+ // Non-iso formatted version:
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $dateFromParts: {year: '$year', month: '$month', day: '$day',"
+ "hour: '$hour', minute: '$minute', second: '$second', millisecond:"
+ " '$millisecs', timezone: 'America/New_York'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dateFromParts>: { "
+ "<KeyFieldname yearArg>: \"<AggregationPath year>\", <KeyFieldname monthArg>: "
+ "\"<AggregationPath month>\", "
+ "<KeyFieldname dayArg>: \"<AggregationPath day>\", <KeyFieldname hourArg>: "
+ "\"<AggregationPath hour>\", <KeyFieldname minuteArg>: "
+ "\"<AggregationPath minute>\", <KeyFieldname secondArg>: \"<AggregationPath "
+ "second>\", <KeyFieldname millisecondArg>: "
+ "\"<AggregationPath millisecs>\", <KeyFieldname timezoneArg>: \"<UserString "
+ "America/New_York>\" } } } "
+ "}");
+ }
+ {
+ // Iso formatted version:
+ CNode output;
+ auto input = fromjson(
+ "{pipeline: [{$project: { "
+ "a: { $dateFromParts: {isoWeekYear: '$year', isoWeek: '$isowk', isoDayOfWeek: '$day',"
+ "hour: '$hour', minute: '$minute', second: '$second', millisecond:"
+ " '$millisecs', timezone: 'America/New_York'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dateFromParts>: { "
+ "<KeyFieldname isoWeekYearArg>: \"<AggregationPath year>\", <KeyFieldname "
+ "isoWeekArg>: \"<AggregationPath isowk>\", "
+ "<KeyFieldname isoDayOfWeekArg>: \"<AggregationPath day>\", <KeyFieldname "
+ "hourArg>: \"<AggregationPath hour>\", "
+ "<KeyFieldname minuteArg>: "
+ "\"<AggregationPath minute>\", <KeyFieldname secondArg>: \"<AggregationPath "
+ "second>\", <KeyFieldname millisecondArg>: "
+ "\"<AggregationPath millisecs>\", <KeyFieldname timezoneArg>: \"<UserString "
+ "America/New_York>\" } } } "
+ "}");
+ }
+}
+
+TEST(CstExpressionTest, ParsesDayOfDateExpressionsWithDocumentArgument) {
+ {
+ // $dayOfMonth
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfMonth>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // $dayOfWeek
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfWeek>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // $dayOfYear
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfYear>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+}
+
+TEST(CstExpressionTest, FailsToParseDayOfDateExpressionsWithInvalidDocumentArgument) {
+ {
+ // $dayOfMonth
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ {
+ // $dayOfWeek
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ {
+ // $dayOfYear
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesDayOfDateExpressionsWithExpressionArgument) {
+ {
+ // $dayOfMonth
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfMonth>: \"<AggregationPath date>\" } } }");
+ }
+ {
+ // $dayOfWeek
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfWeek>: \"<AggregationPath date>\" } } }");
+ }
+ {
+ // $dayOfYear
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfYear>: \"<AggregationPath date>\" } } }");
+ }
+}
+
+TEST(CstExpressionTest, ParsesDayOfMonthWithArrayArgument) {
+ // $dayOfMonth
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfMonth>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with non-singleton array argument.
+ {
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $dayOfMonth: ['$date', '$timeZone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfMonth: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesDayOfWeekWithArrayArgument) {
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfWeek>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with non-singleton array argument.
+ {
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $dayOfWeek: ['$date', '$timeZone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfWeek: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesDayOfYearWithArrayArgument) {
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "dayOfYear>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with non-singleton array argument.
+ {
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $dayOfYear: ['$date', '$timeZone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $dayOfYear: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesIsoDateExpressionsWithDocumentArgument) {
+ {
+ // $isoDayOfWeek
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoDayOfWeek>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // $isoWeek
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoWeek>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // $isoWeekYear
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeekYear: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoWeekYear>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+}
+
+TEST(CstExpressionTest, FailsToParseIsoDateExpressionsWithInvalidDocumentArgument) {
+ {
+ // $isoDayOfWeek
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ {
+ // $isoWeek
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ {
+ // $isoWeekYear
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $isoWeekYear: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesIsoDateExpressionsWithExpressionArgument) {
+ {
+ // $isoDayOfWeek
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoDayOfWeek>: \"<AggregationPath date>\" } } }");
+ }
+ {
+ // $isoWeek
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoWeek>: \"<AggregationPath date>\" } } }");
+ }
+ {
+ // $isoWeekYear
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeekYear: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoWeekYear>: \"<AggregationPath date>\" } } }");
+ }
+}
+
+TEST(CstExpressionTest, ParsesIsoDayOfWeekWithArrayArgument) {
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoDayOfWeek>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with non-singleton array argument.
+ {
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: ['$date', '$timeZone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoDayOfWeek: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesIsoWeekWithArrayArgument) {
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoWeek>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with non-singleton array argument.
+ {
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $isoWeek: ['$date', '$timeZone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeek: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesIsoWeekYearWithArrayArgument) {
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeekYear: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "isoWeekYear>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with non-singleton array argument.
+ {
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $isoWeekYear: ['$date', '$timeZone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $isoWeekYear: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesHourExpressionWithDocumentArgument) {
+ {
+ // $hour
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $hour: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname hour>: { "
+ "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname timezoneArg>: "
+ "\"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // Ensure fails to parse with invalid argument-document.
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $hour: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesMillisecondExpressionWithDocumentArgument) {
+ {
+ // $millisecond
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $millisecond: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "millisecond>: { <KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // Ensure fails to parse with invalid argument-document.
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $millisecond: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesMinuteExpressionWithDocumentArgument) {
+ {
+ // $minute
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $minute: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname minute>: { "
+ "<KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // Ensure fails to parse with invalid argument-document.
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $minute: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesMonthExpressionWithDocumentArgument) {
+ {
+ // $month
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $month: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname month>: { "
+ "<KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // Ensure fails to parse with invalid argument-document.
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $month: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesSecondExpressionWithDocumentArgument) {
+ {
+ // $second
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $second: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname second>: { "
+ "<KeyFieldname dateArg>: \"<AggregationPath date>\", "
+ "<KeyFieldname timezoneArg>: \"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // Ensure fails to parse with invalid argument-document.
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $second: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesWeekExpressionWithDocumentArgument) {
+ {
+ // $week
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $week: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname week>: { "
+ "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname timezoneArg>: "
+ "\"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // Ensure fails to parse with invalid argument-document.
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $week: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesYearExpressionWithDocumentArgument) {
+ {
+ // $year
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $year: {date: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(
+ stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname year>: { "
+ "<KeyFieldname dateArg>: \"<AggregationPath date>\", <KeyFieldname timezoneArg>: "
+ "\"<KeyValue absentKey>\" } } } }");
+ }
+ {
+ // Ensure fails to parse with invalid argument-document.
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $year: {notDate: '$date'}}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesHourExpressionWithExpressionArgument) {
+ // Test with argument as-is.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $hour: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname hour>: "
+ "\"<AggregationPath date>\" } } }");
+ }
+ // Test with argument wrapped in array.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $hour: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname hour>: "
+ "[ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with a non-singleton array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $hour: ['$date', '$timezone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $hour: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesMillisecondExpressionWithExpressionArgument) {
+ // Test with argument as-is.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $millisecond: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "millisecond>: \"<AggregationPath date>\" } } }");
+ }
+ // Test with argument wrapped in array.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $millisecond: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "millisecond>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with a non-singleton array argument.
+ {
+ CNode output;
+ auto input =
+ fromjson("{pipeline: [{$project: { a: { $millisecond: ['$date', '$timezone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $millisecond: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesMinuteExpressionWithExpressionArgument) {
+ // Test with argument as-is.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $minute: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "minute>: \"<AggregationPath date>\" } } }");
+ }
+ // Test with argument wrapped in array.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $minute: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "minute>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with a non-singleton array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $minute: ['$date', '$timezone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $minute: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesMonthExpressionWithExpressionArgument) {
+ // Test with argument as-is.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $month: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "month>: \"<AggregationPath date>\" } } }");
+ }
+ // Test with argument wrapped in array.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $month: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "month>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with a non-singleton array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $month: ['$date', '$timezone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $month: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesSecondExpressionWithExpressionArgument) {
+ // Test with argument as-is.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $second: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "second>: \"<AggregationPath date>\" } } }");
+ }
+ // Test with argument wrapped in array.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $second: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname "
+ "second>: [ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with a non-singleton array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $second: ['$date', '$timezone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $second: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesWeekExpressionWithExpressionArgument) {
+ // Test with argument as-is.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $week: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname week>: "
+ "\"<AggregationPath date>\" } } }");
+ }
+ // Test with argument wrapped in array.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $week: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname week>: "
+ "[ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with a non-singleton array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $week: ['$date', '$timezone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $week: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstExpressionTest, ParsesYearExpressionWithExpressionArgument) {
+ // Test with argument as-is.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $year: '$date'}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname year>: "
+ "\"<AggregationPath date>\" } } }");
+ }
+ // Test with argument wrapped in array.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $year: ['$date']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ auto stages = stdx::get<CNode::ArrayChildren>(output.payload);
+ ASSERT_EQ(1, stages.size());
+ ASSERT(KeyFieldname::projectInclusion == stages[0].firstKeyFieldname());
+ ASSERT_EQ(stages[0].toBson().toString(),
+ "{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname year>: "
+ "[ \"<AggregationPath date>\" ] } } }");
+ }
+ // Ensure fails to parse with a non-singleton array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $year: ['$date', '$timezone']}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Ensure fails to parse with an empty array argument.
+ {
+ CNode output;
+ auto input = fromjson("{pipeline: [{$project: { a: { $year: []}}}]}");
+ BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/cst/cst_match_test.cpp b/src/mongo/db/cst/cst_match_test.cpp
index 70e5dde2d19..b266b6fa686 100644
--- a/src/mongo/db/cst/cst_match_test.cpp
+++ b/src/mongo/db/cst/cst_match_test.cpp
@@ -1,437 +1,437 @@
-/**
- * Copyright (C) 2020-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * 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
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * 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 Server Side 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 "mongo/platform/decimal128.h"
-#include <string>
-
-#include "mongo/bson/json.h"
-#include "mongo/db/cst/bson_lexer.h"
-#include "mongo/db/cst/c_node.h"
-#include "mongo/db/cst/key_fieldname.h"
-#include "mongo/db/cst/key_value.h"
-#include "mongo/db/cst/parser_gen.hpp"
-#include "mongo/unittest/bson_test_util.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-namespace {
-
-TEST(CstMatchTest, ParsesEmptyPredicate) {
- CNode output;
- auto input = fromjson("{filter: {}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(), "{}");
-}
-
-TEST(CstMatchTest, ParsesEqualityPredicates) {
- CNode output;
- auto input = fromjson("{filter: {a: 5.0, b: NumberInt(10), _id: NumberLong(15)}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: \"<UserDouble 5.000000>\", <UserFieldname b>: \"<UserInt "
- "10>\", <UserFieldname _id>: \"<UserLong 15>\" }");
-}
-
-TEST(CstMatchTest, ParsesLogicalOperatorsWithOneChild) {
- {
- CNode output;
- auto input = fromjson("{filter: {$and: [{a: 1}]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname andExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
- }
- {
- CNode output;
- auto input = fromjson("{filter: {$or: [{a: 1}]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname orExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
- }
- {
- CNode output;
- auto input = fromjson("{filter: {$nor: [{a: 1}]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname norExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
- }
-}
-
-TEST(CstMatchTest, ParsesLogicalOperatorsWithMultipleChildren) {
- {
- CNode output;
- auto input = fromjson("{filter: {$and: [{a: 1}, {b: 'bee'}]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname andExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
- "<UserFieldname b>: \"<UserString bee>\" } ] }");
- }
- {
- CNode output;
- auto input = fromjson("{filter: {$or: [{a: 1}, {b: 'bee'}]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname orExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
- "<UserFieldname b>: \"<UserString bee>\" } ] }");
- }
- {
- CNode output;
- auto input = fromjson("{filter: {$nor: [{a: 1}, {b: 'bee'}]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname norExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
- "<UserFieldname b>: \"<UserString bee>\" } ] }");
- }
-}
-
-TEST(CstMatchTest, ParsesNotWithRegex) {
- CNode output;
- auto input = fromjson("{filter: {a: {$not: /^a/}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname notExpr>: \"<UserRegex /^a/>\" } }");
-}
-
-TEST(CstMatchTest, ParsesNotWithChildExpression) {
- CNode output;
- auto input = fromjson("{filter: {a: {$not: {$not: /^a/}}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname notExpr>: { <KeyFieldname notExpr>: "
- "\"<UserRegex /^a/>\" } } }");
-}
-
-TEST(CstMatchTest, ParsesExistsWithSimpleValueChild) {
- CNode output;
- auto input = fromjson("{filter: {a: {$exists: true}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname existsExpr>: \"<UserBoolean true>\" } }");
-}
-
-TEST(CstMatchTest, ParsesExistsWithCompoundValueChild) {
- CNode output;
- auto input = fromjson("{filter: {a: {$exists: [\"hello\", 5, true, \"goodbye\"]}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname existsExpr>: [ \"<UserString hello>\", "
- "\"<UserInt 5>\", "
- "\"<UserBoolean true>\", \"<UserString goodbye>\" ] } }");
-}
-
-TEST(CstMatchTest, ParsesExistsWithObjectChild) {
- CNode output;
- auto input = fromjson("{filter: {a: {$exists: {a: false }}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname existsExpr>: { <UserFieldname a>: "
- "\"<UserBoolean false>\" } } }");
-}
-
-TEST(CstMatchTest, ParsesTypeSingleArgument) {
- // Check that $type parses with a string argument - a BSON type alias
- {
- CNode output;
- auto input = fromjson("{filter: {a: {$type: \"bool\" }}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname type>: \"<UserString bool>\" } }");
- }
- // Check that $type parses a number (corresponding to a BSON type)
- {
- CNode output;
- auto input = fromjson("{filter: {a: {$type: 1}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname type>: \"<UserInt 1>\" } }");
- }
-}
-
-TEST(CstMatchTest, ParsersTypeArrayArgument) {
- CNode output;
- auto input = fromjson("{filter: {a: {$type: [\"number\", 5, 127, \"objectId\"]}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname type>: [ \"<UserString number>\", \"<UserInt "
- "5>\", \"<UserInt 127>\", "
- "\"<UserString objectId>\" ] } }");
-}
-
-TEST(CstMatchTest, ParsesCommentWithSimpleValueChild) {
- CNode output;
- auto input = fromjson("{filter: {a: 1, $comment: true}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: \"<UserInt 1>\", <KeyFieldname commentExpr>: \"<UserBoolean "
- "true>\" }");
-}
-
-TEST(CstMatchTest, ParsesCommentWithCompoundValueChild) {
- CNode output;
- auto input = fromjson("{filter: {a: 1, $comment: [\"hi\", 5]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: \"<UserInt 1>\", <KeyFieldname commentExpr>: [ \"<UserString "
- "hi>\", \"<UserInt 5>\" ] }");
-}
-
-TEST(CstMatchTest, ParsesExpr) {
- CNode output;
- auto input = fromjson("{filter: {$expr: 123}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(), "{ <KeyFieldname expr>: \"<UserInt 123>\" }");
-}
-
-TEST(CstMatchTest, ParsesText) {
- CNode output;
- auto input = fromjson("{filter: {$text: {$search: \"abc\"}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname text>: { "
- "<KeyFieldname caseSensitive>: \"<KeyValue absentKey>\", "
- "<KeyFieldname diacriticSensitive>: \"<KeyValue absentKey>\", "
- "<KeyFieldname language>: \"<KeyValue absentKey>\", "
- "<KeyFieldname search>: \"<UserString abc>\" } }");
-}
-
-TEST(CstMatchTest, ParsesTextOptions) {
- CNode output;
- auto input = fromjson(
- "{filter: {$text: {"
- "$search: \"abc\", "
- "$caseSensitive: true, "
- "$diacriticSensitive: true, "
- "$language: \"asdfzxcv\" } } }");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname text>: { "
- "<KeyFieldname caseSensitive>: \"<UserBoolean true>\", "
- "<KeyFieldname diacriticSensitive>: \"<UserBoolean true>\", "
- "<KeyFieldname language>: \"<UserString asdfzxcv>\", "
- "<KeyFieldname search>: \"<UserString abc>\" } }");
-}
-
-TEST(CstMatchTest, ParsesWhere) {
- CNode output;
- auto input = fromjson("{filter: {$where: \"return true;\"}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <KeyFieldname where>: \"<UserString return true;>\" }");
-}
-
-TEST(CstMatchTest, FailsToParseNotWithNonObject) {
- CNode output;
- auto input = fromjson("{filter: {a: {$not: 1}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected 1 (int), expecting object or regex at "
- "element '1' within '$not' of input filter");
-}
-
-TEST(CstMatchTest, FailsToParseUnknownOperatorWithinNotExpression) {
- CNode output;
- auto input = fromjson("{filter: {a: {$not: {$and: [{a: 1}]}}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE(
- ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
-}
-
-TEST(CstMatchTest, FailsToParseNotWithEmptyObject) {
- CNode output;
- auto input = fromjson("{filter: {a: {$not: {}}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE(
- ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
-}
-
-TEST(CstMatchTest, FailsToParseDollarPrefixedPredicates) {
- {
- auto input = fromjson("{filter: {$atan2: [3, 5]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected ATAN2 at element '$atan2' of input filter");
- }
- {
- auto input = fromjson("{filter: {$prefixed: 5}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected $-prefixed fieldname at element '$prefixed' of input filter");
- }
- {
- auto input = fromjson("{filter: {$$ROOT: 5}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected $-prefixed fieldname at element '$$ROOT' of input filter");
- }
-}
-
-TEST(CstMatchTest, FailsToParseDollarPrefixedPredicatesWithinLogicalExpression) {
- auto input = fromjson("{filter: {$and: [{$prefixed: 5}]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(
- ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected $-prefixed fieldname at element '$prefixed' within array at "
- "index 0 within '$and' of input filter");
-}
-
-TEST(CstMatchTest, FailsToParseNonArrayLogicalKeyword) {
- auto input = fromjson("{filter: {$and: {a: 5}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected object, expecting array at element "
- "'start object' within '$and' of input filter");
-}
-
-TEST(CstMatchTest, FailsToParseNonObjectWithinLogicalKeyword) {
- auto input = fromjson("{filter: {$or: [5]}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected arbitrary integer, expecting object at "
- "element '5' within array at index 0 within '$or' of input filter");
-}
-
-TEST(CstMatchTest, FailsToParseLogicalKeywordWithEmptyArray) {
- auto input = fromjson("{filter: {$nor: []}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected end of array, expecting object at "
- "element 'end array' within '$nor' of input filter");
-}
-
-TEST(CstMatchTest, FailsToParseTypeWithBadSpecifier) {
- // Shouldn't parse if the number given isn't a valid BSON type specifier.
- {
- auto input = fromjson("{filter: {a: {$type: 0}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE(
- ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Shouldn't parse if the string given isn't a valid BSON type alias.
- {
- auto input = fromjson("{filter: {$type: {a: \"notABsonType\"}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE(
- ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
- }
- // Shouldn't parse if any argument isn't a valid BSON type alias.
- {
- auto input = fromjson("{filter: {a: {$type: [1, \"number\", \"notABsonType\"]}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE(
- ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
- }
-}
-
-TEST(CstMatchTest, ParsesMod) {
- CNode output;
- auto input = fromjson("{filter: {a: {$mod: [3, 2.0]}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- auto parseTree = ParserGen(lexer, &output);
- ASSERT_EQ(0, parseTree.parse());
- ASSERT_EQ(output.toBson().toString(),
- "{ <UserFieldname a>: { <KeyFieldname matchMod>: ["
- " \"<UserInt 3>\", \"<UserDouble 2.000000>\" ] } }");
-}
-
-TEST(CstMatchTest, FailsToParseModWithEmptyArray) {
- auto input = fromjson("{filter: {a: {$mod: []}}}");
- BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
- ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
- AssertionException,
- ErrorCodes::FailedToParse,
- "syntax error, unexpected end of array at "
- "element 'end array' within '$mod' of input filter");
-}
-
-} // namespace
-} // namespace mongo
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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 "mongo/platform/decimal128.h"
+#include <string>
+
+#include "mongo/bson/json.h"
+#include "mongo/db/cst/bson_lexer.h"
+#include "mongo/db/cst/c_node.h"
+#include "mongo/db/cst/key_fieldname.h"
+#include "mongo/db/cst/key_value.h"
+#include "mongo/db/cst/parser_gen.hpp"
+#include "mongo/unittest/bson_test_util.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(CstMatchTest, ParsesEmptyPredicate) {
+ CNode output;
+ auto input = fromjson("{filter: {}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(), "{}");
+}
+
+TEST(CstMatchTest, ParsesEqualityPredicates) {
+ CNode output;
+ auto input = fromjson("{filter: {a: 5.0, b: NumberInt(10), _id: NumberLong(15)}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: \"<UserDouble 5.000000>\", <UserFieldname b>: \"<UserInt "
+ "10>\", <UserFieldname _id>: \"<UserLong 15>\" }");
+}
+
+TEST(CstMatchTest, ParsesLogicalOperatorsWithOneChild) {
+ {
+ CNode output;
+ auto input = fromjson("{filter: {$and: [{a: 1}]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname andExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
+ }
+ {
+ CNode output;
+ auto input = fromjson("{filter: {$or: [{a: 1}]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname orExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
+ }
+ {
+ CNode output;
+ auto input = fromjson("{filter: {$nor: [{a: 1}]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname norExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
+ }
+}
+
+TEST(CstMatchTest, ParsesLogicalOperatorsWithMultipleChildren) {
+ {
+ CNode output;
+ auto input = fromjson("{filter: {$and: [{a: 1}, {b: 'bee'}]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname andExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
+ "<UserFieldname b>: \"<UserString bee>\" } ] }");
+ }
+ {
+ CNode output;
+ auto input = fromjson("{filter: {$or: [{a: 1}, {b: 'bee'}]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname orExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
+ "<UserFieldname b>: \"<UserString bee>\" } ] }");
+ }
+ {
+ CNode output;
+ auto input = fromjson("{filter: {$nor: [{a: 1}, {b: 'bee'}]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname norExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
+ "<UserFieldname b>: \"<UserString bee>\" } ] }");
+ }
+}
+
+TEST(CstMatchTest, ParsesNotWithRegex) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$not: /^a/}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname notExpr>: \"<UserRegex /^a/>\" } }");
+}
+
+TEST(CstMatchTest, ParsesNotWithChildExpression) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$not: {$not: /^a/}}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname notExpr>: { <KeyFieldname notExpr>: "
+ "\"<UserRegex /^a/>\" } } }");
+}
+
+TEST(CstMatchTest, ParsesExistsWithSimpleValueChild) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$exists: true}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname existsExpr>: \"<UserBoolean true>\" } }");
+}
+
+TEST(CstMatchTest, ParsesExistsWithCompoundValueChild) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$exists: [\"hello\", 5, true, \"goodbye\"]}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname existsExpr>: [ \"<UserString hello>\", "
+ "\"<UserInt 5>\", "
+ "\"<UserBoolean true>\", \"<UserString goodbye>\" ] } }");
+}
+
+TEST(CstMatchTest, ParsesExistsWithObjectChild) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$exists: {a: false }}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname existsExpr>: { <UserFieldname a>: "
+ "\"<UserBoolean false>\" } } }");
+}
+
+TEST(CstMatchTest, ParsesTypeSingleArgument) {
+ // Check that $type parses with a string argument - a BSON type alias
+ {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$type: \"bool\" }}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname type>: \"<UserString bool>\" } }");
+ }
+ // Check that $type parses a number (corresponding to a BSON type)
+ {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$type: 1}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname type>: \"<UserInt 1>\" } }");
+ }
+}
+
+TEST(CstMatchTest, ParsersTypeArrayArgument) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$type: [\"number\", 5, 127, \"objectId\"]}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname type>: [ \"<UserString number>\", \"<UserInt "
+ "5>\", \"<UserInt 127>\", "
+ "\"<UserString objectId>\" ] } }");
+}
+
+TEST(CstMatchTest, ParsesCommentWithSimpleValueChild) {
+ CNode output;
+ auto input = fromjson("{filter: {a: 1, $comment: true}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: \"<UserInt 1>\", <KeyFieldname commentExpr>: \"<UserBoolean "
+ "true>\" }");
+}
+
+TEST(CstMatchTest, ParsesCommentWithCompoundValueChild) {
+ CNode output;
+ auto input = fromjson("{filter: {a: 1, $comment: [\"hi\", 5]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: \"<UserInt 1>\", <KeyFieldname commentExpr>: [ \"<UserString "
+ "hi>\", \"<UserInt 5>\" ] }");
+}
+
+TEST(CstMatchTest, ParsesExpr) {
+ CNode output;
+ auto input = fromjson("{filter: {$expr: 123}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(), "{ <KeyFieldname expr>: \"<UserInt 123>\" }");
+}
+
+TEST(CstMatchTest, ParsesText) {
+ CNode output;
+ auto input = fromjson("{filter: {$text: {$search: \"abc\"}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname text>: { "
+ "<KeyFieldname caseSensitive>: \"<KeyValue absentKey>\", "
+ "<KeyFieldname diacriticSensitive>: \"<KeyValue absentKey>\", "
+ "<KeyFieldname language>: \"<KeyValue absentKey>\", "
+ "<KeyFieldname search>: \"<UserString abc>\" } }");
+}
+
+TEST(CstMatchTest, ParsesTextOptions) {
+ CNode output;
+ auto input = fromjson(
+ "{filter: {$text: {"
+ "$search: \"abc\", "
+ "$caseSensitive: true, "
+ "$diacriticSensitive: true, "
+ "$language: \"asdfzxcv\" } } }");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname text>: { "
+ "<KeyFieldname caseSensitive>: \"<UserBoolean true>\", "
+ "<KeyFieldname diacriticSensitive>: \"<UserBoolean true>\", "
+ "<KeyFieldname language>: \"<UserString asdfzxcv>\", "
+ "<KeyFieldname search>: \"<UserString abc>\" } }");
+}
+
+TEST(CstMatchTest, ParsesWhere) {
+ CNode output;
+ auto input = fromjson("{filter: {$where: \"return true;\"}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <KeyFieldname where>: \"<UserString return true;>\" }");
+}
+
+TEST(CstMatchTest, FailsToParseNotWithNonObject) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$not: 1}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected 1 (int), expecting object or regex at "
+ "element '1' within '$not' of input filter");
+}
+
+TEST(CstMatchTest, FailsToParseUnknownOperatorWithinNotExpression) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$not: {$and: [{a: 1}]}}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE(
+ ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
+}
+
+TEST(CstMatchTest, FailsToParseNotWithEmptyObject) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$not: {}}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE(
+ ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
+}
+
+TEST(CstMatchTest, FailsToParseDollarPrefixedPredicates) {
+ {
+ auto input = fromjson("{filter: {$atan2: [3, 5]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected ATAN2 at element '$atan2' of input filter");
+ }
+ {
+ auto input = fromjson("{filter: {$prefixed: 5}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected $-prefixed fieldname at element '$prefixed' of input filter");
+ }
+ {
+ auto input = fromjson("{filter: {$$ROOT: 5}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected $-prefixed fieldname at element '$$ROOT' of input filter");
+ }
+}
+
+TEST(CstMatchTest, FailsToParseDollarPrefixedPredicatesWithinLogicalExpression) {
+ auto input = fromjson("{filter: {$and: [{$prefixed: 5}]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(
+ ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected $-prefixed fieldname at element '$prefixed' within array at "
+ "index 0 within '$and' of input filter");
+}
+
+TEST(CstMatchTest, FailsToParseNonArrayLogicalKeyword) {
+ auto input = fromjson("{filter: {$and: {a: 5}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected object, expecting array at element "
+ "'start object' within '$and' of input filter");
+}
+
+TEST(CstMatchTest, FailsToParseNonObjectWithinLogicalKeyword) {
+ auto input = fromjson("{filter: {$or: [5]}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected arbitrary integer, expecting object at "
+ "element '5' within array at index 0 within '$or' of input filter");
+}
+
+TEST(CstMatchTest, FailsToParseLogicalKeywordWithEmptyArray) {
+ auto input = fromjson("{filter: {$nor: []}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected end of array, expecting object at "
+ "element 'end array' within '$nor' of input filter");
+}
+
+TEST(CstMatchTest, FailsToParseTypeWithBadSpecifier) {
+ // Shouldn't parse if the number given isn't a valid BSON type specifier.
+ {
+ auto input = fromjson("{filter: {a: {$type: 0}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE(
+ ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Shouldn't parse if the string given isn't a valid BSON type alias.
+ {
+ auto input = fromjson("{filter: {$type: {a: \"notABsonType\"}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE(
+ ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+ // Shouldn't parse if any argument isn't a valid BSON type alias.
+ {
+ auto input = fromjson("{filter: {a: {$type: [1, \"number\", \"notABsonType\"]}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE(
+ ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
+ }
+}
+
+TEST(CstMatchTest, ParsesMod) {
+ CNode output;
+ auto input = fromjson("{filter: {a: {$mod: [3, 2.0]}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ auto parseTree = ParserGen(lexer, &output);
+ ASSERT_EQ(0, parseTree.parse());
+ ASSERT_EQ(output.toBson().toString(),
+ "{ <UserFieldname a>: { <KeyFieldname matchMod>: ["
+ " \"<UserInt 3>\", \"<UserDouble 2.000000>\" ] } }");
+}
+
+TEST(CstMatchTest, FailsToParseModWithEmptyArray) {
+ auto input = fromjson("{filter: {a: {$mod: []}}}");
+ BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
+ ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
+ AssertionException,
+ ErrorCodes::FailedToParse,
+ "syntax error, unexpected end of array at "
+ "element 'end array' within '$mod' of input filter");
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/cst/cst_parser.h b/src/mongo/db/cst/cst_parser.h
index 604359ce354..1d4a3883b13 100644
--- a/src/mongo/db/cst/cst_parser.h
+++ b/src/mongo/db/cst/cst_parser.h
@@ -1,70 +1,70 @@
-/**
- * Copyright (C) 2020-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * 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
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * 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 Server Side 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.
- */
-
-#pragma once
-
-#include "mongo/platform/basic.h"
-
-#include "mongo/db/cst/bson_lexer.h"
-#include "mongo/db/cst/c_node.h"
-#include "mongo/db/cst/cst_match_translation.h"
-#include "mongo/db/cst/cst_sort_translation.h"
-#include "mongo/db/matcher/expression.h"
-#include "mongo/db/matcher/extensions_callback.h"
-#include "mongo/db/pipeline/expression_context.h"
-#include "mongo/db/query/sort_pattern.h"
-
-namespace mongo::cst {
-
-/**
- * Parses the given 'filter' to a MatchExpression. Throws an exception if the filter fails to parse.
- */
-std::unique_ptr<MatchExpression> parseToMatchExpression(
- BSONObj filter,
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- const ExtensionsCallback& extensionsCallback) {
- BSONLexer lexer{filter, ParserGen::token::START_MATCH};
- CNode cst;
- ParserGen(lexer, &cst).parse();
- return cst_match_translation::translateMatchExpression(cst, expCtx, extensionsCallback);
-}
-
-/**
- * Parses the given 'sort' object into a SortPattern. Throws an exception if the sort object fails
- * to parse.
- */
-SortPattern parseToSortPattern(BSONObj sort,
- const boost::intrusive_ptr<ExpressionContext>& expCtx) {
- BSONLexer lexer{sort, ParserGen::token::START_SORT};
- CNode cst;
- ParserGen(lexer, &cst).parse();
- return cst_sort_translation::translateSortSpec(cst, expCtx);
-}
-
-} // namespace mongo::cst
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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.
+ */
+
+#pragma once
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/cst/bson_lexer.h"
+#include "mongo/db/cst/c_node.h"
+#include "mongo/db/cst/cst_match_translation.h"
+#include "mongo/db/cst/cst_sort_translation.h"
+#include "mongo/db/matcher/expression.h"
+#include "mongo/db/matcher/extensions_callback.h"
+#include "mongo/db/pipeline/expression_context.h"
+#include "mongo/db/query/sort_pattern.h"
+
+namespace mongo::cst {
+
+/**
+ * Parses the given 'filter' to a MatchExpression. Throws an exception if the filter fails to parse.
+ */
+std::unique_ptr<MatchExpression> parseToMatchExpression(
+ BSONObj filter,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ const ExtensionsCallback& extensionsCallback) {
+ BSONLexer lexer{filter, ParserGen::token::START_MATCH};
+ CNode cst;
+ ParserGen(lexer, &cst).parse();
+ return cst_match_translation::translateMatchExpression(cst, expCtx, extensionsCallback);
+}
+
+/**
+ * Parses the given 'sort' object into a SortPattern. Throws an exception if the sort object fails
+ * to parse.
+ */
+SortPattern parseToSortPattern(BSONObj sort,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx) {
+ BSONLexer lexer{sort, ParserGen::token::START_SORT};
+ CNode cst;
+ ParserGen(lexer, &cst).parse();
+ return cst_sort_translation::translateSortSpec(cst, expCtx);
+}
+
+} // namespace mongo::cst
diff --git a/src/mongo/db/cst/cst_sort_translation_test.cpp b/src/mongo/db/cst/cst_sort_translation_test.cpp
index 6d431f0e53a..3c0e69979cd 100644
--- a/src/mongo/db/cst/cst_sort_translation_test.cpp
+++ b/src/mongo/db/cst/cst_sort_translation_test.cpp
@@ -1,157 +1,157 @@
-/**
- * Copyright (C) 2020-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * 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
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * 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 Server Side 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 <string>
-
-#include "mongo/db/cst/c_node.h"
-#include "mongo/db/cst/cst_pipeline_translation.h"
-#include "mongo/db/cst/cst_sort_translation.h"
-#include "mongo/db/cst/key_fieldname.h"
-#include "mongo/db/cst/key_value.h"
-#include "mongo/db/namespace_string.h"
-#include "mongo/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/query/sort_pattern.h"
-#include "mongo/db/query/util/make_data_structure.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-namespace {
-
-auto getExpCtx() {
- auto nss = NamespaceString{"db", "coll"};
- return boost::intrusive_ptr<ExpressionContextForTest>{new ExpressionContextForTest(nss)};
-}
-
-void assertSortPatternsEQ(SortPattern correct, SortPattern fromTest) {
- for (size_t i = 0; i < correct.size(); ++i) {
- ASSERT_EQ(correct[i].isAscending, fromTest[i].isAscending);
- if (correct[i].fieldPath) {
- if (fromTest[i].fieldPath) {
- ASSERT_EQ(correct[i].fieldPath->fullPath(), fromTest[i].fieldPath->fullPath());
- } else {
- FAIL("Pattern missing fieldpath");
- }
- } else if (fromTest[i].fieldPath) {
- FAIL("Pattern incorrectly had fieldpath");
- }
- if (correct[i].expression) {
- if (fromTest[i].expression)
- ASSERT_EQ(correct[i].expression->serialize(false).toString(),
- fromTest[i].expression->serialize(false).toString());
- else {
- FAIL("Pattern missing expression");
- }
- } else if (fromTest[i].expression) {
- FAIL("Pattern incorrectly had expression");
- }
- }
-}
-
-TEST(CstSortTranslationTest, BasicSortGeneratesCorrectSortPattern) {
- const auto cst = CNode{CNode::ObjectChildren{
- {SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}}}};
- auto expCtx = getExpCtx();
- auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
- auto correctPattern = SortPattern(fromjson("{val: 1}"), expCtx);
- assertSortPatternsEQ(correctPattern, pattern);
-}
-
-TEST(CstSortTranslationTest, MultiplePartSortGeneratesCorrectSortPattern) {
- {
- const auto cst = CNode{CNode::ObjectChildren{
- {SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}},
- {SortPath{makeVector<std::string>("test")}, CNode{KeyValue::intNegOneKey}}}};
- auto expCtx = getExpCtx();
- auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
- auto correctPattern = SortPattern(fromjson("{val: 1, test: -1}"), expCtx);
- assertSortPatternsEQ(correctPattern, pattern);
- }
- {
- const auto cst = CNode{CNode::ObjectChildren{
- {SortPath{makeVector<std::string>("val")}, CNode{KeyValue::doubleOneKey}},
- {SortPath{makeVector<std::string>("test")}, CNode{KeyValue::intNegOneKey}},
- {SortPath{makeVector<std::string>("third")}, CNode{KeyValue::longNegOneKey}}}};
- auto expCtx = getExpCtx();
- auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
- auto correctPattern = SortPattern(fromjson("{val: 1, test: -1, third: -1}"), expCtx);
- assertSortPatternsEQ(correctPattern, pattern);
- }
- {
- const auto cst = CNode{CNode::ObjectChildren{
- {SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}},
- {SortPath{makeVector<std::string>("test")},
- CNode{
- CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::randVal}}},
- }}}};
- auto expCtx = getExpCtx();
- auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
- auto correctPattern = SortPattern(fromjson("{val: 1, test: {$meta: \"randVal\"}}"), expCtx);
- assertSortPatternsEQ(correctPattern, pattern);
- }
-}
-
-TEST(CstSortTranslationTest, SortWithMetaGeneratesCorrectSortPattern) {
- {
- const auto cst = CNode{CNode::ObjectChildren{
- {SortPath{makeVector<std::string>("val")},
- CNode{
- CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::randVal}}},
- }}}};
- auto expCtx = getExpCtx();
- auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
- auto correctPattern = SortPattern(fromjson("{val: {$meta: \"randVal\"}}"), expCtx);
- assertSortPatternsEQ(correctPattern, pattern);
- }
- {
- const auto cst = CNode{CNode::ObjectChildren{
- {SortPath{makeVector<std::string>("val")},
- CNode{
- CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::textScore}}},
- }}}};
- auto expCtx = getExpCtx();
- auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
- auto correctPattern = SortPattern(fromjson("{val: {$meta: \"textScore\"}}"), expCtx);
- assertSortPatternsEQ(correctPattern, pattern);
- }
-}
-
-TEST(CstSortTranslationTest, SortWithDottedPathTranslatesCorrectly) {
- const auto cst =
- CNode{CNode::ObjectChildren{{SortPath{{"a", "b", "c"}}, CNode{KeyValue::intOneKey}}}};
- auto expCtx = getExpCtx();
- auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
- auto correctPattern = SortPattern(fromjson("{'a.b.c': 1}"), expCtx);
- assertSortPatternsEQ(correctPattern, pattern);
-}
-
-} // namespace
-} // namespace mongo
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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 <string>
+
+#include "mongo/db/cst/c_node.h"
+#include "mongo/db/cst/cst_pipeline_translation.h"
+#include "mongo/db/cst/cst_sort_translation.h"
+#include "mongo/db/cst/key_fieldname.h"
+#include "mongo/db/cst/key_value.h"
+#include "mongo/db/namespace_string.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/db/query/sort_pattern.h"
+#include "mongo/db/query/util/make_data_structure.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+auto getExpCtx() {
+ auto nss = NamespaceString{"db", "coll"};
+ return boost::intrusive_ptr<ExpressionContextForTest>{new ExpressionContextForTest(nss)};
+}
+
+void assertSortPatternsEQ(SortPattern correct, SortPattern fromTest) {
+ for (size_t i = 0; i < correct.size(); ++i) {
+ ASSERT_EQ(correct[i].isAscending, fromTest[i].isAscending);
+ if (correct[i].fieldPath) {
+ if (fromTest[i].fieldPath) {
+ ASSERT_EQ(correct[i].fieldPath->fullPath(), fromTest[i].fieldPath->fullPath());
+ } else {
+ FAIL("Pattern missing fieldpath");
+ }
+ } else if (fromTest[i].fieldPath) {
+ FAIL("Pattern incorrectly had fieldpath");
+ }
+ if (correct[i].expression) {
+ if (fromTest[i].expression)
+ ASSERT_EQ(correct[i].expression->serialize(false).toString(),
+ fromTest[i].expression->serialize(false).toString());
+ else {
+ FAIL("Pattern missing expression");
+ }
+ } else if (fromTest[i].expression) {
+ FAIL("Pattern incorrectly had expression");
+ }
+ }
+}
+
+TEST(CstSortTranslationTest, BasicSortGeneratesCorrectSortPattern) {
+ const auto cst = CNode{CNode::ObjectChildren{
+ {SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}}}};
+ auto expCtx = getExpCtx();
+ auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
+ auto correctPattern = SortPattern(fromjson("{val: 1}"), expCtx);
+ assertSortPatternsEQ(correctPattern, pattern);
+}
+
+TEST(CstSortTranslationTest, MultiplePartSortGeneratesCorrectSortPattern) {
+ {
+ const auto cst = CNode{CNode::ObjectChildren{
+ {SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}},
+ {SortPath{makeVector<std::string>("test")}, CNode{KeyValue::intNegOneKey}}}};
+ auto expCtx = getExpCtx();
+ auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
+ auto correctPattern = SortPattern(fromjson("{val: 1, test: -1}"), expCtx);
+ assertSortPatternsEQ(correctPattern, pattern);
+ }
+ {
+ const auto cst = CNode{CNode::ObjectChildren{
+ {SortPath{makeVector<std::string>("val")}, CNode{KeyValue::doubleOneKey}},
+ {SortPath{makeVector<std::string>("test")}, CNode{KeyValue::intNegOneKey}},
+ {SortPath{makeVector<std::string>("third")}, CNode{KeyValue::longNegOneKey}}}};
+ auto expCtx = getExpCtx();
+ auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
+ auto correctPattern = SortPattern(fromjson("{val: 1, test: -1, third: -1}"), expCtx);
+ assertSortPatternsEQ(correctPattern, pattern);
+ }
+ {
+ const auto cst = CNode{CNode::ObjectChildren{
+ {SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}},
+ {SortPath{makeVector<std::string>("test")},
+ CNode{
+ CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::randVal}}},
+ }}}};
+ auto expCtx = getExpCtx();
+ auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
+ auto correctPattern = SortPattern(fromjson("{val: 1, test: {$meta: \"randVal\"}}"), expCtx);
+ assertSortPatternsEQ(correctPattern, pattern);
+ }
+}
+
+TEST(CstSortTranslationTest, SortWithMetaGeneratesCorrectSortPattern) {
+ {
+ const auto cst = CNode{CNode::ObjectChildren{
+ {SortPath{makeVector<std::string>("val")},
+ CNode{
+ CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::randVal}}},
+ }}}};
+ auto expCtx = getExpCtx();
+ auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
+ auto correctPattern = SortPattern(fromjson("{val: {$meta: \"randVal\"}}"), expCtx);
+ assertSortPatternsEQ(correctPattern, pattern);
+ }
+ {
+ const auto cst = CNode{CNode::ObjectChildren{
+ {SortPath{makeVector<std::string>("val")},
+ CNode{
+ CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::textScore}}},
+ }}}};
+ auto expCtx = getExpCtx();
+ auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
+ auto correctPattern = SortPattern(fromjson("{val: {$meta: \"textScore\"}}"), expCtx);
+ assertSortPatternsEQ(correctPattern, pattern);
+ }
+}
+
+TEST(CstSortTranslationTest, SortWithDottedPathTranslatesCorrectly) {
+ const auto cst =
+ CNode{CNode::ObjectChildren{{SortPath{{"a", "b", "c"}}, CNode{KeyValue::intOneKey}}}};
+ auto expCtx = getExpCtx();
+ auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
+ auto correctPattern = SortPattern(fromjson("{'a.b.c': 1}"), expCtx);
+ assertSortPatternsEQ(correctPattern, pattern);
+}
+
+} // namespace
+} // namespace mongo