/** * 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 * . * * 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 #include #include #include #include "mongo/base/string_data.h" #include "mongo/bson/bsonmisc.h" #include "mongo/db/cst/c_node.h" #include "mongo/db/cst/cst_match_translation.h" #include "mongo/db/cst/cst_pipeline_translation.h" #include "mongo/db/cst/key_fieldname.h" #include "mongo/db/cst/key_value.h" #include "mongo/db/matcher/expression_expr.h" #include "mongo/db/matcher/expression_tree.h" #include "mongo/db/matcher/matcher_type_set.h" #include "mongo/util/overloaded_visitor.h" namespace mongo::cst_match_translation { namespace { std::unique_ptr translateMatchPredicate( const CNode::Fieldname& fieldName, const CNode& cst, const boost::intrusive_ptr& expCtx, const ExtensionsCallback& extensionsCallback); std::unique_ptr translatePathExpression(const UserFieldname& fieldName, const CNode::ObjectChildren& object); /** * Walk an array of nodes and produce a vector of MatchExpressions. */ template std::unique_ptr translateTreeExpr(const CNode::ArrayChildren& array, const boost::intrusive_ptr& expCtx, const ExtensionsCallback& extensionsCallback) { auto expr = std::make_unique(); for (auto&& node : array) { // Tree expressions require each element to be it's own match expression object. expr->add(translateMatchExpression(node, expCtx, extensionsCallback)); } return expr; } // Handles predicates of the form : { $not: } std::unique_ptr translateNot(const UserFieldname& fieldName, const CNode& argument) { // $not can accept a regex or an object expression. if (auto regex = stdx::get_if(&argument.payload)) { auto regexExpr = std::make_unique( StringData(fieldName), regex->pattern, regex->flags); return std::make_unique(std::move(regexExpr)); } auto root = std::make_unique(); root->add( translatePathExpression(fieldName, stdx::get(argument.payload))); return std::make_unique(std::move(root)); } std::unique_ptr translateExists(const CNode::Fieldname& fieldName, const CNode& argument) { auto root = std::make_unique(StringData(stdx::get(fieldName))); if (stdx::visit(OverloadedVisitor{ [&](const UserLong& userLong) { return userLong != 0; }, [&](const UserDouble& userDbl) { return userDbl != 0; }, [&](const UserDecimal& userDc) { return userDc.isNotEqual(Decimal128(0)); }, [&](const UserInt& userInt) { return userInt != 0; }, [&](const UserBoolean& b) { return b; }, [&](const UserNull&) { return false; }, [&](const UserUndefined&) { return false; }, [&](auto&&) { return true; }}, argument.payload)) { return root; } return std::make_unique(root.release()); } MatcherTypeSet getMatcherTypeSet(const CNode& argument) { MatcherTypeSet ts; auto add_individual_to_type_set = [&](const CNode& a) { return stdx::visit( OverloadedVisitor{ [&](const UserLong& userLong) { auto valueAsInt = BSON("" << userLong).firstElement().parseIntegerElementToInt(); ts.bsonTypes.insert(static_cast(valueAsInt.getValue())); }, [&](const UserDouble& userDbl) { auto valueAsInt = BSON("" << userDbl).firstElement().parseIntegerElementToInt(); ts.bsonTypes.insert(static_cast(valueAsInt.getValue())); }, [&](const UserDecimal& userDc) { auto valueAsInt = BSON("" << userDc).firstElement().parseIntegerElementToInt(); ts.bsonTypes.insert(static_cast(valueAsInt.getValue())); }, [&](const UserInt& userInt) { auto valueAsInt = BSON("" << userInt).firstElement().parseIntegerElementToInt(); ts.bsonTypes.insert(static_cast(valueAsInt.getValue())); }, [&](const UserString& s) { if (StringData{s} == MatcherTypeSet::kMatchesAllNumbersAlias) { ts.allNumbers = true; return; } auto optValue = findBSONTypeAlias(s); invariant(optValue); ts.bsonTypes.insert(*optValue); }, [&](auto&&) { MONGO_UNREACHABLE; }}, a.payload); }; if (auto children = stdx::get_if(&argument.payload)) { for (const auto& child : (*children)) { add_individual_to_type_set(child); } } else { add_individual_to_type_set(argument); } return ts; } // Handles predicates of the form : { ... } // For example: // { abc: {$not: 5} } // { abc: {$eq: 0} } // { abc: {$gt: 0, $lt: 2} } // Examples of predicates not handled here: // { abc: 5 } // { $expr: ... } // { $where: "return 1" } // Note, this function does not require an ExpressionContext. // The only MatchExpression that requires an ExpressionContext is $expr // (if you include $where, which can desugar to $expr + $function). std::unique_ptr translatePathExpression(const UserFieldname& fieldName, const CNode::ObjectChildren& object) { for (auto&& [op, argument] : object) { switch (stdx::get(op)) { case KeyFieldname::notExpr: return translateNot(fieldName, argument); case KeyFieldname::existsExpr: return translateExists(fieldName, argument); case KeyFieldname::type: return std::make_unique(StringData(fieldName), getMatcherTypeSet(argument)); case KeyFieldname::matchMod: { const auto divisor = stdx::get(argument.payload)[0].numberInt(); const auto remainder = stdx::get(argument.payload)[1].numberInt(); return std::make_unique( StringData(fieldName), divisor, remainder); } default: MONGO_UNREACHABLE; } } MONGO_UNREACHABLE; } // Take a variant and either get (by copying) the T it holds, or construct a default value using // the callable. For example: // getOr(123, []() { return 0; }) == 123 // getOr("x", []() { return 0; }) == 0 template T getOr(const V& myVariant, F makeDefaultValue) { if (auto* value = stdx::get_if(&myVariant)) { return *value; } else { return makeDefaultValue(); } } // Handles predicates of the form : // For example: // { abc: 5 } // { abc: {$lt: 5} } // Examples of predicates not handled here: // { $where: "return 1" } // { $and: ... } std::unique_ptr translateMatchPredicate( const CNode::Fieldname& fieldName, const CNode& cst, const boost::intrusive_ptr& expCtx, const ExtensionsCallback& extensionsCallback) { if (auto keyField = stdx::get_if(&fieldName)) { // Top level match expression. switch (*keyField) { case KeyFieldname::andExpr: return translateTreeExpr( cst.arrayChildren(), expCtx, extensionsCallback); case KeyFieldname::orExpr: return translateTreeExpr( cst.arrayChildren(), expCtx, extensionsCallback); case KeyFieldname::norExpr: return translateTreeExpr( cst.arrayChildren(), expCtx, extensionsCallback); case KeyFieldname::commentExpr: // comment expr is not added to the tree. return nullptr; case KeyFieldname::expr: { // The ExprMatchExpression maintains (shared) ownership of expCtx, // which the Expression from translateExpression depends on. return std::make_unique( cst_pipeline_translation::translateExpression( cst, expCtx.get(), expCtx->variablesParseState), expCtx); } case KeyFieldname::text: { const auto& args = cst.objectChildren(); dassert(verifyFieldnames( { KeyFieldname::caseSensitive, KeyFieldname::diacriticSensitive, KeyFieldname::language, KeyFieldname::search, }, args)); TextMatchExpressionBase::TextParams params; params.caseSensitive = getOr(args[0].second.payload, []() { return TextMatchExpressionBase::kCaseSensitiveDefault; }); params.diacriticSensitive = getOr(args[1].second.payload, []() { return TextMatchExpressionBase::kDiacriticSensitiveDefault; }); params.language = getOr(args[2].second.payload, []() { return ""s; }); params.query = stdx::get(args[3].second.payload); return extensionsCallback.createText(std::move(params)); } case KeyFieldname::where: { std::string code; if (auto str = stdx::get_if(&cst.payload)) { code = *str; } else if (auto js = stdx::get_if(&cst.payload)) { code = std::string{js->code}; } else { MONGO_UNREACHABLE; } return extensionsCallback.createWhere(expCtx, {std::move(code)}); } default: MONGO_UNREACHABLE; } } else { // Expression is over a user fieldname. return stdx::visit( OverloadedVisitor{ [&](const CNode::ObjectChildren& userObject) -> std::unique_ptr { return translatePathExpression(stdx::get(fieldName), userObject); }, [&](const CNode::ArrayChildren& userObject) -> std::unique_ptr { MONGO_UNREACHABLE; }, // Other types are always treated as equality predicates. [&](auto&& userValue) -> std::unique_ptr { return std::make_unique( StringData{stdx::get(fieldName)}, cst_pipeline_translation::translateLiteralLeaf(cst), nullptr, /* TODO SERVER-49486: Add ErrorAnnotation for MatchExpressions */ expCtx->getCollator()); }}, cst.payload); } MONGO_UNREACHABLE; } } // namespace std::unique_ptr translateMatchExpression( const CNode& cst, const boost::intrusive_ptr& expCtx, const ExtensionsCallback& extensionsCallback) { auto root = std::make_unique(); for (const auto& [fieldName, expr] : cst.objectChildren()) { // A nullptr for 'translatedExpression' indicates that the particular operator should not // be added to 'root'. The $comment operator currently follows this convention. if (auto translatedExpression = translateMatchPredicate(fieldName, expr, expCtx, extensionsCallback); translatedExpression) { root->add(std::move(translatedExpression)); } } return root; } bool verifyFieldnames(const std::vector& expected, const std::vector>& actual) { if (expected.size() != actual.size()) return false; for (size_t i = 0; i < expected.size(); ++i) { if (expected[i] != actual[i].first) return false; } return true; } } // namespace mongo::cst_match_translation