summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorMickey. J Winters <mickey.winters@mongodb.com>2021-02-26 22:28:55 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-02-26 23:36:48 +0000
commit811837f75614e7f24457c7e1bb80bff6e688d57f (patch)
tree8bc9c6c0931ae720a8c67d94b476a8a5ff8806e6 /src/mongo/db
parent41cae7a226946bf0626e4f1e28eeb80b42ecfa2e (diff)
downloadmongo-811837f75614e7f24457c7e1bb80bff6e688d57f.tar.gz
SERVER-51549 Support expression $reverseArray in SBE
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/exec/sbe/SConscript1
-rw-r--r--src/mongo/db/exec/sbe/expressions/expression.cpp1
-rw-r--r--src/mongo/db/exec/sbe/expressions/sbe_reverse_array_builtin_test.cpp111
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.cpp61
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.h2
-rw-r--r--src/mongo/db/query/sbe_stage_builder_expression.cpp16
6 files changed, 191 insertions, 1 deletions
diff --git a/src/mongo/db/exec/sbe/SConscript b/src/mongo/db/exec/sbe/SConscript
index 18d2c7819eb..31e94c6c5c1 100644
--- a/src/mongo/db/exec/sbe/SConscript
+++ b/src/mongo/db/exec/sbe/SConscript
@@ -134,6 +134,7 @@ env.CppUnitTest(
'expressions/sbe_mod_expression_test.cpp',
'expressions/sbe_regex_test.cpp',
'expressions/sbe_replace_one_expression_test.cpp',
+ 'expressions/sbe_reverse_array_builtin_test.cpp',
'expressions/sbe_set_expressions_test.cpp',
'expressions/sbe_shard_filter_builtin_test.cpp',
'expressions/sbe_to_upper_to_lower_test.cpp',
diff --git a/src/mongo/db/exec/sbe/expressions/expression.cpp b/src/mongo/db/exec/sbe/expressions/expression.cpp
index ba23b8e9bf1..8783b35d7fd 100644
--- a/src/mongo/db/exec/sbe/expressions/expression.cpp
+++ b/src/mongo/db/exec/sbe/expressions/expression.cpp
@@ -442,6 +442,7 @@ static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = {
{"extractSubArray",
BuiltinFn{[](size_t n) { return n == 2 || n == 3; }, vm::Builtin::extractSubArray, false}},
{"isArrayEmpty", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::isArrayEmpty, false}},
+ {"reverseArray", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::reverseArray, false}},
{"dateAdd", BuiltinFn{[](size_t n) { return n == 5; }, vm::Builtin::dateAdd, false}},
{"hasNullBytes", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::hasNullBytes, false}},
};
diff --git a/src/mongo/db/exec/sbe/expressions/sbe_reverse_array_builtin_test.cpp b/src/mongo/db/exec/sbe/expressions/sbe_reverse_array_builtin_test.cpp
new file mode 100644
index 00000000000..4d32ddf1597
--- /dev/null
+++ b/src/mongo/db/exec/sbe/expressions/sbe_reverse_array_builtin_test.cpp
@@ -0,0 +1,111 @@
+/**
+ * Copyright (C) 2021-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/db/exec/sbe/expression_test_base.h"
+#include "mongo/db/exec/sbe/values/bson.h"
+
+namespace mongo::sbe {
+
+class SBEBuiltinReverseArrayTest : public EExpressionTestFixture {
+protected:
+ using TypedValue = std::pair<value::TypeTags, value::Value>;
+
+ /**
+ * Assert that result of 'reverseArray(array)' is equal to 'expected'.
+ * NOTE: Values behind arguments and the return value of this function are owned by the caller.
+ */
+ void runAndAssertExpression(TypedValue array, TypedValue expected) {
+ // We do not copy array value on purpose. During the copy, order of elements in ArraySet
+ // may change. 'reverseArray' return value depends on the order of elements in the input
+ // array. After copy 'reverse' may return elements in a different order from the one
+ // expected by the caller.
+ value::ViewOfValueAccessor arraySlotAccessor;
+ auto arraySlot = bindAccessor(&arraySlotAccessor);
+ arraySlotAccessor.reset(array.first, array.second);
+ auto arrayExpr = makeE<EVariable>(arraySlot);
+
+ auto reverseArrayExpr = makeE<EFunction>("reverseArray", makeEs(std::move(arrayExpr)));
+ auto compiledExpr = compileExpression(*reverseArrayExpr);
+
+ auto actual = runCompiledExpression(compiledExpr.get());
+ value::ValueGuard actualGuard{actual};
+
+ auto [compareTag, compareValue] =
+ value::compareValue(actual.first, actual.second, expected.first, expected.second);
+ ASSERT_EQ(compareTag, value::TypeTags::NumberInt32);
+ ASSERT_EQ(compareValue, 0);
+ }
+};
+
+TEST_F(SBEBuiltinReverseArrayTest, Array) {
+ for (auto makeArrayFn : {makeBsonArray, makeArray}) {
+ auto testArray = makeArrayFn(BSON_ARRAY(1 << 2 << 3));
+ value::ValueGuard testArrayGuard{testArray};
+
+ auto expectedResult = makeArray(BSON_ARRAY(3 << 2 << 1));
+ value::ValueGuard expectedResultGuard{expectedResult};
+
+ runAndAssertExpression(testArray, expectedResult);
+ }
+}
+
+TEST_F(SBEBuiltinReverseArrayTest, ArraySet) {
+ // This test needs to do a bit more work to find the correct reversed order since ArraySet's
+ // internal order is determined by it's hash function and not the order that elements are added
+ // to it.
+ auto testArray = makeArraySet(BSON_ARRAY(1 << 2 << 3));
+ value::ValueGuard testArrayGuard{testArray};
+ value::ArrayEnumerator testEnumerator{testArray.first, testArray.second};
+
+ std::vector<std::pair<value::TypeTags, value::Value>> testArrayContents;
+ auto expectedResult = value::makeNewArray();
+ value::ValueGuard expectedResultGuard{expectedResult};
+ auto expectedResultView = value::getArrayView(expectedResult.second);
+
+ while (!testEnumerator.atEnd()) {
+ testArrayContents.push_back(testEnumerator.getViewOfValue());
+ testEnumerator.advance();
+ }
+
+ for (auto it = testArrayContents.rbegin(); it != testArrayContents.rend(); ++it) {
+ auto [copyTag, copyVal] = copyValue(it->first, it->second);
+ expectedResultView->push_back(copyTag, copyVal);
+ }
+
+ runAndAssertExpression(testArray, expectedResult);
+}
+
+TEST_F(SBEBuiltinReverseArrayTest, NotArray) {
+ runAndAssertExpression(makeNothing(), makeNothing());
+ runAndAssertExpression(makeInt32(123), makeNothing());
+}
+
+} // namespace mongo::sbe
diff --git a/src/mongo/db/exec/sbe/vm/vm.cpp b/src/mongo/db/exec/sbe/vm/vm.cpp
index 08b51da0f9f..3e061ef2f7c 100644
--- a/src/mongo/db/exec/sbe/vm/vm.cpp
+++ b/src/mongo/db/exec/sbe/vm/vm.cpp
@@ -2834,6 +2834,65 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinGetRegexFlags(A
return {true, strType, strValue};
}
+std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinReverseArray(ArityType arity) {
+ invariant(arity == 1);
+ auto [inputOwned, inputType, inputVal] = getFromStack(0);
+
+ if (!value::isArray(inputType)) {
+ return {false, value::TypeTags::Nothing, 0};
+ }
+
+ auto [resultTag, resultVal] = value::makeNewArray();
+ auto resultView = value::getArrayView(resultVal);
+ value::ValueGuard resultGuard{resultTag, resultVal};
+
+ if (inputType == value::TypeTags::Array) {
+ auto inputView = value::getArrayView(inputVal);
+ size_t inputSize = inputView->size();
+ resultView->reserve(inputSize);
+ for (size_t i = 0; i < inputSize; i++) {
+ auto [origTag, origVal] = inputView->getAt(inputSize - 1 - i);
+ auto [copyTag, copyVal] = copyValue(origTag, origVal);
+ resultView->push_back(copyTag, copyVal);
+ }
+
+ resultGuard.reset();
+ return {true, resultTag, resultVal};
+ } else if (inputType == value::TypeTags::bsonArray || inputType == value::TypeTags::ArraySet) {
+ value::ArrayEnumerator enumerator{inputType, inputVal};
+
+ // Using intermediate vector since bsonArray and ArraySet don't
+ // support reverse iteration.
+ std::vector<std::pair<value::TypeTags, value::Value>> inputContents;
+
+ if (inputType == value::TypeTags::ArraySet) {
+ // Reserve space to avoid resizing on push_back calls.
+ auto arraySetView = value::getArraySetView(inputVal);
+ inputContents.reserve(arraySetView->size());
+ resultView->reserve(arraySetView->size());
+ }
+
+ while (!enumerator.atEnd()) {
+ inputContents.push_back(enumerator.getViewOfValue());
+ enumerator.advance();
+ }
+
+ // Run through the array backwards and copy into the result array.
+ for (auto it = inputContents.rbegin(); it != inputContents.rend(); ++it) {
+ auto [copyTag, copyVal] = copyValue(it->first, it->second);
+ resultView->push_back(copyTag, copyVal);
+ }
+
+ resultGuard.reset();
+ return {true, resultTag, resultVal};
+ } else {
+ // Earlier in this function we bailed out if the `inputType` wasn't
+ // Array, ArraySet or bsonArray, so it should be impossible to reach
+ // this point.
+ MONGO_UNREACHABLE;
+ }
+}
+
std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinDateAdd(ArityType arity) {
invariant(arity == 5);
@@ -3021,6 +3080,8 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builti
return builtinExtractSubArray(arity);
case Builtin::isArrayEmpty:
return builtinIsArrayEmpty(arity);
+ case Builtin::reverseArray:
+ return builtinReverseArray(arity);
case Builtin::dateAdd:
return builtinDateAdd(arity);
case Builtin::hasNullBytes:
diff --git a/src/mongo/db/exec/sbe/vm/vm.h b/src/mongo/db/exec/sbe/vm/vm.h
index baa6fc82732..5dfe25cc536 100644
--- a/src/mongo/db/exec/sbe/vm/vm.h
+++ b/src/mongo/db/exec/sbe/vm/vm.h
@@ -308,6 +308,7 @@ enum class Builtin : uint8_t {
shardFilter,
extractSubArray,
isArrayEmpty,
+ reverseArray,
dateAdd,
hasNullBytes,
getRegexPattern,
@@ -721,6 +722,7 @@ private:
std::tuple<bool, value::TypeTags, value::Value> builtinShardFilter(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinExtractSubArray(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinIsArrayEmpty(ArityType arity);
+ std::tuple<bool, value::TypeTags, value::Value> builtinReverseArray(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinDateAdd(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinHasNullBytes(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinGetRegexPattern(ArityType arity);
diff --git a/src/mongo/db/query/sbe_stage_builder_expression.cpp b/src/mongo/db/query/sbe_stage_builder_expression.cpp
index 820035e8450..397fe5bdb0f 100644
--- a/src/mongo/db/query/sbe_stage_builder_expression.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_expression.cpp
@@ -2307,7 +2307,21 @@ public:
unsupportedExpression(expr->getOpName());
}
void visit(ExpressionReverseArray* expr) final {
- unsupportedExpression(expr->getOpName());
+ auto frameId = _context->frameIdGenerator->generate();
+ auto binds = sbe::makeEs(_context->popExpr());
+ sbe::EVariable inputRef{frameId, 0};
+
+ auto argumentIsNotArray = makeNot(makeFunction("isArray", inputRef.clone()));
+ auto exprRevArr = buildMultiBranchConditional(
+ CaseValuePair{generateNullOrMissing(inputRef),
+ makeConstant(sbe::value::TypeTags::Null, 0)},
+ CaseValuePair{std::move(argumentIsNotArray),
+ sbe::makeE<sbe::EFail>(ErrorCodes::Error{5154901},
+ "$reverseArray argument must be an array")},
+ makeFunction("reverseArray", inputRef.clone()));
+
+ _context->pushExpr(
+ sbe::makeE<sbe::ELocalBind>(frameId, std::move(binds), std::move(exprRevArr)));
}
void visit(ExpressionSlice* expr) final {
unsupportedExpression(expr->getOpName());