summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorAnne Lim <anne.lim@mongodb.com>2017-07-24 14:54:14 -0400
committerAnne Lim <anne.lim@mongodb.com>2017-08-10 08:23:50 -0400
commit7741128c7fe6066e968d6de972af25531e38db18 (patch)
tree24e8295e26070fc94393c01d32b3b0770aa0688d /src/mongo
parent2173ffb9fd23c6894c6820fbf8c08aa876392b55 (diff)
downloadmongo-7741128c7fe6066e968d6de972af25531e38db18.tar.gz
SERVER-29586: Create a $_internalSchemaAllElemMatchFromIndex MatchExpression
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/matcher/SConscript2
-rw-r--r--src/mongo/db/matcher/expression.h1
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp61
-rw-r--r--src/mongo/db/matcher/expression_parser.h1
-rw-r--r--src/mongo/db/matcher/expression_serialization_test.cpp11
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp79
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h79
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp103
-rw-r--r--src/mongo/db/matcher/schema/expression_parser_schema_test.cpp40
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp1
-rw-r--r--src/mongo/db/pipeline/document_source_match_test.cpp3
-rw-r--r--src/mongo/db/query/plan_cache.cpp5
12 files changed, 386 insertions, 0 deletions
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index 4f7af21e9a1..da6e5fda909 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -47,6 +47,7 @@ env.Library(
'match_details.cpp',
'matchable.cpp',
'matcher.cpp',
+ 'schema/expression_internal_schema_all_elem_match_from_index.cpp',
'schema/expression_internal_schema_cond.cpp',
'schema/expression_internal_schema_fmod.cpp',
'schema/expression_internal_schema_match_array_index.cpp',
@@ -84,6 +85,7 @@ env.CppUnitTest(
'expression_tree_test.cpp',
'expression_with_placeholder_test.cpp',
'path_accepting_keyword_test.cpp',
+ 'schema/expression_internal_schema_all_elem_match_from_index_test.cpp',
'schema/expression_internal_schema_cond_test.cpp',
'schema/expression_internal_schema_fmod_test.cpp',
'schema/expression_internal_schema_match_array_index_test.cpp',
diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h
index cac23a28f3d..03d9bac6ac2 100644
--- a/src/mongo/db/matcher/expression.h
+++ b/src/mongo/db/matcher/expression.h
@@ -99,6 +99,7 @@ public:
INTERNAL_2D_POINT_IN_ANNULUS,
// JSON Schema expressions.
+ INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX,
INTERNAL_SCHEMA_COND,
INTERNAL_SCHEMA_FMOD,
INTERNAL_SCHEMA_MAX_ITEMS,
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 1eff1a1138a..e1cd121ae25 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -27,6 +27,7 @@
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
+#include "mongo/platform/basic.h"
#include "mongo/db/matcher/expression_parser.h"
@@ -40,6 +41,7 @@
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/expression_tree.h"
#include "mongo/db/matcher/expression_with_placeholder.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h"
#include "mongo/db/matcher/schema/expression_internal_schema_cond.h"
#include "mongo/db/matcher/schema/expression_internal_schema_fmod.h"
#include "mongo/db/matcher/schema/expression_internal_schema_match_array_index.h"
@@ -373,6 +375,63 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(
case PathAcceptingKeyword::INTERNAL_SCHEMA_MATCH_ARRAY_INDEX: {
return _parseInternalSchemaMatchArrayIndex(name, e, collator);
}
+
+ case PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX: {
+ if (e.type() != BSONType::Array) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream()
+ << InternalSchemaAllElemMatchFromIndexMatchExpression::kName
+ << " must be an array");
+ }
+ auto elemMatchObj = e.embeddedObject();
+ auto iter = elemMatchObj.begin();
+ if (!iter.more()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream()
+ << InternalSchemaAllElemMatchFromIndexMatchExpression::kName
+ << " must be an array of size 2");
+ }
+ auto first = iter.next();
+ auto parsedIndex = parseIntegerElementToNonNegativeLong(first);
+ if (!parsedIndex.isOK()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream()
+ << "first element of "
+ << InternalSchemaAllElemMatchFromIndexMatchExpression::kName
+ << " must be a non-negative integer");
+ }
+ if (!iter.more()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream()
+ << InternalSchemaAllElemMatchFromIndexMatchExpression::kName
+ << " must be an array of size 2");
+ }
+ auto second = iter.next();
+ if (iter.more()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream()
+ << InternalSchemaAllElemMatchFromIndexMatchExpression::kName
+ << " has too many elements, must be an array of size 2");
+ }
+ if (second.type() != BSONType::Object) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream()
+ << "second element of "
+ << InternalSchemaAllElemMatchFromIndexMatchExpression::kName
+ << "must be an object");
+ }
+ StatusWithMatchExpression query =
+ _parse(second.embeddedObject(), collator, expCtx, topLevel);
+ if (!query.isOK()) {
+ return query.getStatus();
+ }
+ auto expr = stdx::make_unique<InternalSchemaAllElemMatchFromIndexMatchExpression>();
+ auto status = expr->init(name, parsedIndex.getValue(), std::move(query.getValue()));
+ if (!status.isOK()) {
+ return status;
+ }
+ return {std::move(expr)};
+ }
}
return {Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "not handled: " << e.fieldName())};
@@ -1456,6 +1515,8 @@ MONGO_INITIALIZER(MatchExpressionParser)(InitializerContext* context) {
{"bitsAllClear", PathAcceptingKeyword::BITS_ALL_CLEAR},
{"bitsAnySet", PathAcceptingKeyword::BITS_ANY_SET},
{"bitsAnyClear", PathAcceptingKeyword::BITS_ANY_CLEAR},
+ {"_internalSchemaAllElemMatchFromIndex",
+ PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX},
{"_internalSchemaFmod", PathAcceptingKeyword::INTERNAL_SCHEMA_FMOD},
{"_internalSchemaMinItems", PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS},
{"_internalSchemaMaxItems", PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS},
diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h
index 88ac25551a9..63cf24c39df 100644
--- a/src/mongo/db/matcher/expression_parser.h
+++ b/src/mongo/db/matcher/expression_parser.h
@@ -68,6 +68,7 @@ enum class PathAcceptingKeyword {
BITS_ALL_CLEAR,
BITS_ANY_SET,
BITS_ANY_CLEAR,
+ INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX,
INTERNAL_SCHEMA_FMOD,
INTERNAL_SCHEMA_MIN_ITEMS,
INTERNAL_SCHEMA_MAX_ITEMS,
diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp
index 3b0a1ef2cf9..379fd6635d1 100644
--- a/src/mongo/db/matcher/expression_serialization_test.cpp
+++ b/src/mongo/db/matcher/expression_serialization_test.cpp
@@ -970,6 +970,17 @@ TEST(SerializeBasic, ExpressionAlwaysFalseSerializesCorrectly) {
ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
}
+TEST(SerializeInternalSchema, ExpressionInternalSchemaAllElemMatchFromIndexSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$_internalSchemaAllElemMatchFromIndex: [2, {y: 1}]}}"),
+ ExtensionsCallbackDisallowExtensions(),
+ kSimpleCollator);
+ Matcher reserialized(serialize(original.getMatchExpression()),
+ ExtensionsCallbackDisallowExtensions(),
+ kSimpleCollator);
+ ASSERT_BSONOBJ_EQ(*reserialized.getQuery(),
+ fromjson("{x: {$_internalSchemaAllElemMatchFromIndex: [2, {y: {$eq: 1}}]}}"));
+}
+
TEST(SerializeInternalSchema, ExpressionInternalSchemaMinItemsSerializesCorrectly) {
Matcher original(fromjson("{x: {$_internalSchemaMinItems: 1}}"),
ExtensionsCallbackDisallowExtensions(),
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp
new file mode 100644
index 00000000000..dd918d8791b
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp
@@ -0,0 +1,79 @@
+/**
+ * Copyright (C) 2017 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h"
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+
+namespace mongo {
+
+constexpr StringData InternalSchemaAllElemMatchFromIndexMatchExpression::kName;
+
+std::unique_ptr<MatchExpression> InternalSchemaAllElemMatchFromIndexMatchExpression::shallowClone()
+ const {
+ auto clone = stdx::make_unique<InternalSchemaAllElemMatchFromIndexMatchExpression>();
+ invariantOK(clone->init(path(), _index, _query->shallowClone()));
+ if (getTag()) {
+ clone->setTag(getTag()->clone());
+ }
+ return std::move(clone);
+}
+
+bool InternalSchemaAllElemMatchFromIndexMatchExpression::equivalent(
+ const MatchExpression* other) const {
+ if (matchType() != other->matchType()) {
+ return false;
+ }
+ const InternalSchemaAllElemMatchFromIndexMatchExpression* realOther =
+ static_cast<const InternalSchemaAllElemMatchFromIndexMatchExpression*>(other);
+ return (_index == realOther->_index && _query->equivalent(realOther->_query.get()));
+}
+
+void InternalSchemaAllElemMatchFromIndexMatchExpression::debugString(StringBuilder& debug,
+ int level) const {
+ _debugAddSpace(debug, level);
+ debug << kName << "\n";
+ debug << " index: " << _index << ", query:\n";
+ _query->debugString(debug, level + 1);
+}
+
+void InternalSchemaAllElemMatchFromIndexMatchExpression::serialize(BSONObjBuilder* out) const {
+ BSONObjBuilder objMatchBob(out->subobjStart(path()));
+ BSONArrayBuilder subArray(objMatchBob.subarrayStart(kName));
+ subArray.append(_index);
+ {
+ BSONObjBuilder eBuilder(subArray.subobjStart());
+ _query->serialize(&eBuilder);
+ eBuilder.doneFast();
+ }
+ subArray.doneFast();
+ objMatchBob.doneFast();
+}
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h
new file mode 100644
index 00000000000..3f7e79fda8f
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h
@@ -0,0 +1,79 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+#pragma once
+
+#include "mongo/base/string_data.h"
+#include "mongo/db/matcher/expression.h"
+#include "mongo/db/matcher/expression_array.h"
+
+namespace mongo {
+/**
+ * A match expression similar to $elemMatch, but only matches arrays for which every element
+ * matches the sub-expression.
+ */
+class InternalSchemaAllElemMatchFromIndexMatchExpression final
+ : public ArrayMatchingMatchExpression {
+public:
+ static constexpr StringData kName = "$_internalSchemaAllElemMatchFromIndex"_sd;
+
+ InternalSchemaAllElemMatchFromIndexMatchExpression()
+ : ArrayMatchingMatchExpression(MatchExpression::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX) {
+ }
+
+ Status init(StringData path, long long index, std::unique_ptr<MatchExpression> query) {
+ _index = index;
+ _query = std::move(query);
+ return setPath(path);
+ }
+
+ std::unique_ptr<MatchExpression> shallowClone() const final;
+
+ bool matchesArray(const BSONObj& array, MatchDetails*) const final {
+ auto iter = array.begin();
+ for (int i = 0; iter.more() && i < _index; i++) {
+ iter.next();
+ }
+ while (iter.more()) {
+ if (!_query->matchesBSONElement(iter.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void debugString(StringBuilder& debug, int level) const final;
+
+ void serialize(BSONObjBuilder* out) const final;
+
+ bool equivalent(const MatchExpression* other) const final;
+
+private:
+ long long _index;
+ std::unique_ptr<MatchExpression> _query;
+};
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp
new file mode 100644
index 00000000000..a0e0d6573ba
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp
@@ -0,0 +1,103 @@
+/**
+ * Copyright (C) 2017 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/jsobj.h"
+#include "mongo/db/json.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/matcher/extensions_callback_disallow_extensions.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+constexpr CollatorInterface* kSimpleCollator = nullptr;
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, MatchesEmptyQuery) {
+ auto query = fromjson("{a: {$_internalSchemaAllElemMatchFromIndex: [2, {}]}}");
+ auto expr = MatchExpressionParser::parse(
+ query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_OK(expr.getStatus());
+ ASSERT_TRUE(expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 3 << 4))));
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, MatchesValidQueries) {
+ auto query = fromjson("{a: {$_internalSchemaAllElemMatchFromIndex: [2, {a: {$lt: 5}}]}}");
+ auto expr = MatchExpressionParser::parse(
+ query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_OK(expr.getStatus());
+ ASSERT_TRUE(expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 3 << 4))));
+
+ ASSERT_TRUE(expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 3 << 4))));
+ ASSERT_TRUE(expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(10 << 2 << 3 << 4))));
+ ASSERT_TRUE(expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(10 << 20 << 3 << 4))));
+ ASSERT_FALSE(expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 3 << 40))));
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, RejectsNonArrayElements) {
+ auto query = fromjson("{a: {$_internalSchemaAllElemMatchFromIndex: [2, {a: {$lt: 5}}]}}");
+ auto expr = MatchExpressionParser::parse(
+ query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_OK(expr.getStatus());
+ ASSERT_FALSE(expr.getValue()->matchesBSON(BSON("a" << BSON("a" << 1))));
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, MatchesArraysWithLessElementsThanIndex) {
+ auto query = fromjson("{a: {$_internalSchemaAllElemMatchFromIndex: [2, {a: {$lt: 5}}]}}");
+ auto expr = MatchExpressionParser::parse(
+ query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_OK(expr.getStatus());
+ ASSERT_TRUE(expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(1))));
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, NestedArraysMatchSubexpression) {
+ auto query = fromjson("{a: {$_internalSchemaAllElemMatchFromIndex: [2, {a: {$lt: 5}}]}}");
+ auto expr = MatchExpressionParser::parse(
+ query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_OK(expr.getStatus());
+ ASSERT_TRUE(
+ expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << BSON_ARRAY(3 << 4) << 4))));
+ ASSERT_TRUE(
+ expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << BSON_ARRAY(6 << 4) << 4))));
+ ASSERT_FALSE(
+ expr.getValue()->matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << BSON_ARRAY(5 << 6) << 4))));
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, MatchedQueriesWithDottedPaths) {
+ auto query = fromjson("{'a.b': {$_internalSchemaAllElemMatchFromIndex: [2, {a: {$lt: 5}}]}}");
+ auto expr = MatchExpressionParser::parse(
+ query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_OK(expr.getStatus());
+ ASSERT_TRUE(
+ expr.getValue()->matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(1 << 2 << 3 << 4)))));
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp b/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp
index 6afe8dce846..c738d0f34f2 100644
--- a/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp
+++ b/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp
@@ -586,5 +586,45 @@ TEST(MatchExpressionParserSchemaTest, MatchArrayIndexParsesSuccessfully) {
ASSERT_FALSE(matchArrayIndex.getValue()->matchesBSON(fromjson("{foo: [2, 'blah']}")));
ASSERT_FALSE(matchArrayIndex.getValue()->matchesBSON(fromjson("{foo: [{x: 'baz'}]}")));
}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, FailsToParseWithNegativeIndex) {
+ BSONObj matchPredicate =
+ fromjson("{$_internalSchemaAllElemMatchFromIndex: [-2, {a: { $lt: 0 }}]}");
+ auto expr = MatchExpressionParser::parse(
+ matchPredicate, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_NOT_OK(expr.getStatus());
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, FailsToParseWithNonObjectExpression) {
+ BSONObj matchPredicate = fromjson("{$_internalSchemaAllElemMatchFromIndex: [-2, 4]}");
+ auto expr = MatchExpressionParser::parse(
+ matchPredicate, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_NOT_OK(expr.getStatus());
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, FailsToParseWithInvalidExpression) {
+ BSONObj matchPredicate =
+ fromjson("{$_internalSchemaAllElemMatchFromIndex: [-2, {$fakeExpression: 4}]}");
+ auto expr = MatchExpressionParser::parse(
+ matchPredicate, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_NOT_OK(expr.getStatus());
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, FailsToParseWithEmptyArray) {
+ BSONObj matchPredicate = fromjson("{$_internalSchemaAllElemMatchFromIndex: []}");
+ auto expr = MatchExpressionParser::parse(
+ matchPredicate, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_NOT_OK(expr.getStatus());
+}
+
+TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, ParsesCorreclyWithValidInput) {
+ auto query = fromjson("{a: {$_internalSchemaAllElemMatchFromIndex: [2, {a: { $lt: 4 }}]}}");
+ auto expr = MatchExpressionParser::parse(
+ query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator);
+ ASSERT_OK(expr.getStatus());
+
+ ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{a: [5, 3, 3, 3, 3, 3]}")));
+ ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{a: [3, 3, 3, 5, 3, 3]}")));
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp
index 250aa387c01..d9436221087 100644
--- a/src/mongo/db/pipeline/document_source_match.cpp
+++ b/src/mongo/db/pipeline/document_source_match.cpp
@@ -261,6 +261,7 @@ Document redactSafePortionDollarOps(BSONObj expr) {
case PathAcceptingKeyword::EXISTS:
case PathAcceptingKeyword::WITHIN:
case PathAcceptingKeyword::GEO_INTERSECTS:
+ case PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX:
case PathAcceptingKeyword::INTERNAL_SCHEMA_FMOD:
case PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS:
case PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS:
diff --git a/src/mongo/db/pipeline/document_source_match_test.cpp b/src/mongo/db/pipeline/document_source_match_test.cpp
index 6eb793d76d6..86ff30f9fd3 100644
--- a/src/mongo/db/pipeline/document_source_match_test.cpp
+++ b/src/mongo/db/pipeline/document_source_match_test.cpp
@@ -123,6 +123,9 @@ TEST_F(DocumentSourceMatchTest, RedactSafePortion) {
"{index: 0, namePlaceholder: 'i', expression: {i: {$gt: 0}}}}}",
"{}");
+ assertExpectedRedactSafePortion(
+ "{a: {$_internalSchemaAllElemMatchFromIndex: [3, {a: {$lt: 4}}]}}", "{}");
+
// Combinations
assertExpectedRedactSafePortion("{a:1, b: 'asdf'}", "{a:1, b: 'asdf'}");
diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp
index 013e1c8163c..0965298f849 100644
--- a/src/mongo/db/query/plan_cache.cpp
+++ b/src/mongo/db/query/plan_cache.cpp
@@ -173,6 +173,11 @@ const char* encodeMatchType(MatchExpression::MatchType mt) {
case MatchExpression::BITS_ANY_CLEAR:
return "yc";
+ break;
+
+ case MatchExpression::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX:
+ return "internalSchemaAllElemMatchFromIndex";
+ break;
case MatchExpression::INTERNAL_SCHEMA_COND:
return "internalSchemaCond";