/** * Copyright (C) 2022-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 "mongo/db/query/fle/server_rewrite.h" #include #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/bsontypes.h" #include "mongo/crypto/encryption_fields_gen.h" #include "mongo/crypto/fle_crypto.h" #include "mongo/crypto/fle_field_schema_gen.h" #include "mongo/crypto/fle_tags.h" #include "mongo/db/fle_crud.h" #include "mongo/db/matcher/expression_expr.h" #include "mongo/db/operation_context.h" #include "mongo/db/pipeline/document_source_geo_near.h" #include "mongo/db/pipeline/document_source_graph_lookup.h" #include "mongo/db/pipeline/document_source_match.h" #include "mongo/db/pipeline/expression.h" #include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/service_context.h" #include "mongo/logv2/log.h" #include "mongo/s/grid.h" #include "mongo/s/transaction_router_resource_yielder.h" #include "mongo/util/assert_util.h" #include "mongo/util/intrusive_counter.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery namespace mongo::fle { // TODO: This is a generally useful helper function that should probably go in some other namespace. std::unique_ptr collatorFromBSON(OperationContext* opCtx, const BSONObj& collation) { std::unique_ptr collator; if (!collation.isEmpty()) { auto statusWithCollator = CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation); uassertStatusOK(statusWithCollator.getStatus()); collator = std::move(statusWithCollator.getValue()); } return collator; } namespace { template boost::intrusive_ptr generateFleEqualMatch(StringData path, const PayloadT& ffp, ExpressionContext* expCtx) { // Generate { $_internalFleEq: { field: "$field_name", server: f_3, counter: cm, edc: k_EDC] } auto tokens = ParsedFindPayload(ffp); uassert(6672401, "Missing required field server encryption token in find payload", tokens.serverToken.has_value()); return make_intrusive( expCtx, ExpressionFieldPath::createPathFromString( expCtx, path.toString(), expCtx->variablesParseState), tokens.serverToken.value().data, tokens.maxCounter.value_or(0LL), tokens.edcToken.data); } template std::unique_ptr generateFleEqualMatchUnique(StringData path, const PayloadT& ffp, ExpressionContext* expCtx) { // Generate { $_internalFleEq: { field: "$field_name", server: f_3, counter: cm, edc: k_EDC] } auto tokens = ParsedFindPayload(ffp); uassert(6672419, "Missing required field server encryption token in find payload", tokens.serverToken.has_value()); return std::make_unique( expCtx, ExpressionFieldPath::createPathFromString( expCtx, path.toString(), expCtx->variablesParseState), tokens.serverToken.value().data, tokens.maxCounter.value_or(0LL), tokens.edcToken.data); } std::unique_ptr generateFleEqualMatchAndExpr(StringData path, const BSONElement ffp, ExpressionContext* expCtx) { auto fleEqualMatch = generateFleEqualMatch(path, ffp, expCtx); return std::make_unique(fleEqualMatch, expCtx); } /** * This section defines a mapping from DocumentSources to the dispatch function to appropriately * handle FLE rewriting for that stage. This should be kept in line with code on the client-side * that marks constants for encryption: we should handle all places where an implicitly-encrypted * value may be for each stage, otherwise we may return non-sensical results. */ static stdx::unordered_map> stageRewriterMap; #define REGISTER_DOCUMENT_SOURCE_FLE_REWRITER(className, rewriterFunc) \ MONGO_INITIALIZER(encryptedAnalyzerFor_##className)(InitializerContext*) { \ \ invariant(stageRewriterMap.find(typeid(className)) == stageRewriterMap.end()); \ stageRewriterMap[typeid(className)] = [&](auto* rewriter, auto* source) { \ rewriterFunc(rewriter, static_cast(source)); \ }; \ } void rewriteMatch(FLEQueryRewriter* rewriter, DocumentSourceMatch* source) { if (auto rewritten = rewriter->rewriteMatchExpression(source->getQuery())) { source->rebuild(rewritten.value()); } } void rewriteGeoNear(FLEQueryRewriter* rewriter, DocumentSourceGeoNear* source) { if (auto rewritten = rewriter->rewriteMatchExpression(source->getQuery())) { source->setQuery(rewritten.value()); } } void rewriteGraphLookUp(FLEQueryRewriter* rewriter, DocumentSourceGraphLookUp* source) { if (auto filter = source->getAdditionalFilter()) { if (auto rewritten = rewriter->rewriteMatchExpression(filter.value())) { source->setAdditionalFilter(rewritten.value()); } } if (auto newExpr = rewriter->rewriteExpression(source->getStartWithField())) { source->setStartWithField(newExpr.release()); } } REGISTER_DOCUMENT_SOURCE_FLE_REWRITER(DocumentSourceMatch, rewriteMatch); REGISTER_DOCUMENT_SOURCE_FLE_REWRITER(DocumentSourceGeoNear, rewriteGeoNear); REGISTER_DOCUMENT_SOURCE_FLE_REWRITER(DocumentSourceGraphLookUp, rewriteGraphLookUp); class FLEExpressionRewriter { public: FLEExpressionRewriter(FLEQueryRewriter* queryRewriter) : queryRewriter(queryRewriter){}; /** * Accepts a vector of expressions to be compared for equality to an encrypted field. For any * expression representing a constant encrypted value, computes the tags for the expression and * rewrites the comparison to a disjunction over __safeContent__. Returns an OR expression of * these disjunctions. If no rewrites were done, returns nullptr. Either all of the expressions * be constant FFPs or none of them should be. * * The final output will look like * {$or: [{$in: [tag0, "$__safeContent__"]}, {$in: [tag1, "$__safeContent__"]}, ...]}. */ std::unique_ptr rewriteInToEncryptedField( const Expression* leftExpr, const std::vector>& equalitiesList) { size_t numFFPs = 0; std::vector> orListElems; for (auto& equality : equalitiesList) { // For each expression representing a FleFindPayload... if (auto constChild = dynamic_cast(equality.get())) { if (!queryRewriter->isFleFindPayload( constChild->getValue(), EncryptedBinDataType::kFLE2FindEqualityPayload)) { continue; } numFFPs++; } } // Finally, construct an $or of all of the $ins. if (numFFPs == 0) { return nullptr; } uassert( 6334102, "If any elements in an comparison expression are encrypted, then all elements should " "be encrypted.", numFFPs == equalitiesList.size()); auto leftFieldPath = dynamic_cast(leftExpr); uassert(6672417, "$in is only supported with Queryable Encryption when the first argument is a " "field path", leftFieldPath != nullptr); if (!queryRewriter->isForceEncryptedCollScan()) { try { for (auto& equality : equalitiesList) { // For each expression representing a FleFindPayload... if (auto constChild = dynamic_cast(equality.get())) { // ... rewrite the payload to a list of tags... auto tags = queryRewriter->rewriteEqualityPayloadAsTags(constChild->getValue()); for (auto&& tagElt : tags) { // ... and for each tag, construct expression {$in: [tag, // "$__safeContent__"]}. std::vector> inVec{ ExpressionConstant::create(queryRewriter->expCtx(), tagElt), ExpressionFieldPath::createPathFromString( queryRewriter->expCtx(), kSafeContent, queryRewriter->expCtx()->variablesParseState)}; orListElems.push_back(make_intrusive( queryRewriter->expCtx(), std::move(inVec))); } } } didRewrite = true; return std::make_unique(queryRewriter->expCtx(), std::move(orListElems)); } catch (const ExceptionFor& ex) { LOGV2_DEBUG(6672403, 2, "FLE Max tag limit hit during aggregation $in rewrite", "__error__"_attr = ex.what()); if (queryRewriter->getEncryptedCollScanMode() != FLEQueryRewriter::EncryptedCollScanMode::kUseIfNeeded) { throw; } // fall through } } for (auto& equality : equalitiesList) { if (auto constChild = dynamic_cast(equality.get())) { auto fleEqExpr = generateFleEqualMatch( leftFieldPath->getFieldPathWithoutCurrentPrefix().fullPath(), constChild->getValue(), queryRewriter->expCtx()); orListElems.push_back(fleEqExpr); } } didRewrite = true; return std::make_unique(queryRewriter->expCtx(), std::move(orListElems)); } // Rewrite a [$eq : [$fieldpath, constant]] or [$eq: [constant, $fieldpath]] // to _internalFleEq: {field: $fieldpath, edc: edcToken, counter: N, server: serverToken} std::unique_ptr rewriteComparisonsToEncryptedField( const std::vector>& equalitiesList) { auto leftConstant = dynamic_cast(equalitiesList[0].get()); auto rightConstant = dynamic_cast(equalitiesList[1].get()); bool isLeftFFP = leftConstant && queryRewriter->isFleFindPayload(leftConstant->getValue(), EncryptedBinDataType::kFLE2FindEqualityPayload); bool isRightFFP = rightConstant && queryRewriter->isFleFindPayload(rightConstant->getValue(), EncryptedBinDataType::kFLE2FindEqualityPayload); uassert(6334100, "Cannot compare two encrypted constants to each other", !(isLeftFFP && isRightFFP)); // No FLE Find Payload if (!isLeftFFP && !isRightFFP) { return nullptr; } auto leftFieldPath = dynamic_cast(equalitiesList[0].get()); auto rightFieldPath = dynamic_cast(equalitiesList[1].get()); uassert( 6672413, "Queryable Encryption only supports comparisons between a field path and a constant", leftFieldPath || rightFieldPath); auto fieldPath = leftFieldPath ? leftFieldPath : rightFieldPath; auto constChild = isLeftFFP ? leftConstant : rightConstant; if (!queryRewriter->isForceEncryptedCollScan()) { try { std::vector> orListElems; auto tags = queryRewriter->rewriteEqualityPayloadAsTags(constChild->getValue()); for (auto&& tagElt : tags) { // ... and for each tag, construct expression {$in: [tag, // "$__safeContent__"]}. std::vector> inVec{ ExpressionConstant::create(queryRewriter->expCtx(), tagElt), ExpressionFieldPath::createPathFromString( queryRewriter->expCtx(), kSafeContent, queryRewriter->expCtx()->variablesParseState)}; orListElems.push_back( make_intrusive(queryRewriter->expCtx(), std::move(inVec))); } didRewrite = true; return std::make_unique(queryRewriter->expCtx(), std::move(orListElems)); } catch (const ExceptionFor& ex) { LOGV2_DEBUG(6672409, 2, "FLE Max tag limit hit during query $in rewrite", "__error__"_attr = ex.what()); if (queryRewriter->getEncryptedCollScanMode() != FLEQueryRewriter::EncryptedCollScanMode::kUseIfNeeded) { throw; } // fall through } } auto fleEqExpr = generateFleEqualMatchUnique(fieldPath->getFieldPathWithoutCurrentPrefix().fullPath(), constChild->getValue(), queryRewriter->expCtx()); didRewrite = true; return fleEqExpr; } std::unique_ptr postVisit(Expression* exp) { if (auto inExpr = dynamic_cast(exp)) { // Rewrite an $in over an encrypted field to an $or. The first child of the $in can be // ignored when rewrites are done; there is no extra information in that child that // doesn't exist in the FFPs in the $in list. if (auto inList = dynamic_cast(inExpr->getOperandList()[1].get())) { return rewriteInToEncryptedField(inExpr->getOperandList()[0].get(), inList->getChildren()); } } else if (auto eqExpr = dynamic_cast(exp); eqExpr && (eqExpr->getOp() == ExpressionCompare::EQ || eqExpr->getOp() == ExpressionCompare::NE)) { // Rewrite an $eq comparing an encrypted field and an encrypted constant to an $or. auto newExpr = rewriteComparisonsToEncryptedField(eqExpr->getChildren()); // Neither child is an encrypted constant, and no rewriting needs to be done. if (!newExpr) { return nullptr; } // Exactly one child was an encrypted constant. The other child can be ignored; there is // no extra information in that child that doesn't exist in the FFP. if (eqExpr->getOp() == ExpressionCompare::NE) { std::vector> notChild{newExpr.release()}; return std::make_unique(queryRewriter->expCtx(), std::move(notChild)); } return newExpr; } return nullptr; } FLEQueryRewriter* queryRewriter; bool didRewrite = false; }; BSONObj rewriteEncryptedFilter(const FLEStateCollectionReader& escReader, const FLEStateCollectionReader& eccReader, boost::intrusive_ptr expCtx, BSONObj filter, EncryptedCollScanModeAllowed mode) { if (auto rewritten = FLEQueryRewriter(expCtx, escReader, eccReader, mode).rewriteMatchExpression(filter)) { return rewritten.value(); } return filter; } class RewriteBase { public: RewriteBase(boost::intrusive_ptr expCtx, const NamespaceString& nss, const EncryptionInformation& encryptInfo) : expCtx(expCtx), db(nss.db()) { auto efc = EncryptionInformationHelpers::getAndValidateSchema(nss, encryptInfo); esc = efc.getEscCollection()->toString(); ecc = efc.getEccCollection()->toString(); } virtual ~RewriteBase(){}; virtual void doRewrite(FLEStateCollectionReader& escReader, FLEStateCollectionReader& eccReader){}; boost::intrusive_ptr expCtx; std::string esc; std::string ecc; std::string db; }; // This class handles rewriting of an entire pipeline. class PipelineRewrite : public RewriteBase { public: PipelineRewrite(const NamespaceString& nss, const EncryptionInformation& encryptInfo, std::unique_ptr toRewrite) : RewriteBase(toRewrite->getContext(), nss, encryptInfo), pipeline(std::move(toRewrite)) {} ~PipelineRewrite(){}; void doRewrite(FLEStateCollectionReader& escReader, FLEStateCollectionReader& eccReader) final { auto rewriter = FLEQueryRewriter(expCtx, escReader, eccReader); for (auto&& source : pipeline->getSources()) { if (stageRewriterMap.find(typeid(*source)) != stageRewriterMap.end()) { stageRewriterMap[typeid(*source)](&rewriter, source.get()); } } } std::unique_ptr getPipeline() { return std::move(pipeline); } private: std::unique_ptr pipeline; }; // This class handles rewriting of a single match expression, represented as a BSONObj. class FilterRewrite : public RewriteBase { public: FilterRewrite(boost::intrusive_ptr expCtx, const NamespaceString& nss, const EncryptionInformation& encryptInfo, const BSONObj toRewrite, EncryptedCollScanModeAllowed mode) : RewriteBase(expCtx, nss, encryptInfo), userFilter(toRewrite), _mode(mode) {} ~FilterRewrite(){}; void doRewrite(FLEStateCollectionReader& escReader, FLEStateCollectionReader& eccReader) final { rewrittenFilter = rewriteEncryptedFilter(escReader, eccReader, expCtx, userFilter, _mode); } const BSONObj userFilter; BSONObj rewrittenFilter; EncryptedCollScanModeAllowed _mode; }; // This helper executes the rewrite(s) inside a transaction. The transaction runs in a separate // executor, and so we can't pass data by reference into the lambda. The provided rewriter should // hold all the data we need to do the rewriting inside the lambda, and is passed in a more // threadsafe shared_ptr. The result of applying the rewrites can be accessed in the RewriteBase. void doFLERewriteInTxn(OperationContext* opCtx, std::shared_ptr sharedBlock, GetTxnCallback getTxn) { auto txn = getTxn(opCtx); auto swCommitResult = txn->runNoThrow( opCtx, [sharedBlock](const txn_api::TransactionClient& txnClient, auto txnExec) { auto makeCollectionReader = [sharedBlock](FLEQueryInterface* queryImpl, const StringData& coll) { NamespaceString nss(sharedBlock->db, coll); auto docCount = queryImpl->countDocuments(nss); return TxnCollectionReader(docCount, queryImpl, nss); }; // Construct FLE rewriter from the transaction client and encryptionInformation. auto queryInterface = FLEQueryInterfaceImpl(txnClient, getGlobalServiceContext()); auto escReader = makeCollectionReader(&queryInterface, sharedBlock->esc); auto eccReader = makeCollectionReader(&queryInterface, sharedBlock->ecc); // Rewrite the MatchExpression. sharedBlock->doRewrite(escReader, eccReader); return SemiFuture::makeReady(); }); uassertStatusOK(swCommitResult); uassertStatusOK(swCommitResult.getValue().cmdStatus); uassertStatusOK(swCommitResult.getValue().getEffectiveStatus()); } } // namespace BSONObj rewriteEncryptedFilterInsideTxn(FLEQueryInterface* queryImpl, const DatabaseName& dbName, const EncryptedFieldConfig& efc, boost::intrusive_ptr expCtx, BSONObj filter, EncryptedCollScanModeAllowed mode) { auto makeCollectionReader = [&](FLEQueryInterface* queryImpl, const StringData& coll) { NamespaceString nss(dbName, coll); auto docCount = queryImpl->countDocuments(nss); return TxnCollectionReader(docCount, queryImpl, nss); }; auto escReader = makeCollectionReader(queryImpl, efc.getEscCollection().value()); auto eccReader = makeCollectionReader(queryImpl, efc.getEccCollection().value()); return rewriteEncryptedFilter(escReader, eccReader, expCtx, filter, mode); } BSONObj rewriteQuery(OperationContext* opCtx, boost::intrusive_ptr expCtx, const NamespaceString& nss, const EncryptionInformation& info, BSONObj filter, GetTxnCallback getTransaction, EncryptedCollScanModeAllowed mode) { auto sharedBlock = std::make_shared(expCtx, nss, info, filter, mode); doFLERewriteInTxn(opCtx, sharedBlock, getTransaction); return sharedBlock->rewrittenFilter.getOwned(); } void processFindCommand(OperationContext* opCtx, const NamespaceString& nss, FindCommandRequest* findCommand, GetTxnCallback getTransaction) { invariant(findCommand->getEncryptionInformation()); auto expCtx = make_intrusive(opCtx, collatorFromBSON(opCtx, findCommand->getCollation()), nss, findCommand->getLegacyRuntimeConstants(), findCommand->getLet()); expCtx->stopExpressionCounters(); findCommand->setFilter(rewriteQuery(opCtx, expCtx, nss, findCommand->getEncryptionInformation().value(), findCommand->getFilter().getOwned(), getTransaction, EncryptedCollScanModeAllowed::kAllow)); // The presence of encryptionInformation is a signal that this is a FLE request that requires // special processing. Once we've rewritten the query, it's no longer a "special" FLE query, but // a normal query that can be executed by the query system like any other, so remove // encryptionInformation. findCommand->setEncryptionInformation(boost::none); } void processCountCommand(OperationContext* opCtx, const NamespaceString& nss, CountCommandRequest* countCommand, GetTxnCallback getTxn) { invariant(countCommand->getEncryptionInformation()); // Count command does not have legacy runtime constants, and does not support user variables // defined in a let expression. auto expCtx = make_intrusive( opCtx, collatorFromBSON(opCtx, countCommand->getCollation().value_or(BSONObj())), nss); expCtx->stopExpressionCounters(); countCommand->setQuery(rewriteQuery(opCtx, expCtx, nss, countCommand->getEncryptionInformation().value(), countCommand->getQuery().getOwned(), getTxn, EncryptedCollScanModeAllowed::kAllow)); // The presence of encryptionInformation is a signal that this is a FLE request that requires // special processing. Once we've rewritten the query, it's no longer a "special" FLE query, but // a normal query that can be executed by the query system like any other, so remove // encryptionInformation. countCommand->setEncryptionInformation(boost::none); } std::unique_ptr processPipeline( OperationContext* opCtx, NamespaceString nss, const EncryptionInformation& encryptInfo, std::unique_ptr toRewrite, GetTxnCallback txn) { auto sharedBlock = std::make_shared(nss, encryptInfo, std::move(toRewrite)); doFLERewriteInTxn(opCtx, sharedBlock, txn); return sharedBlock->getPipeline(); } std::unique_ptr FLEQueryRewriter::rewriteExpression(Expression* expression) { tassert(6334104, "Expected an expression to rewrite but found none", expression); FLEExpressionRewriter expressionRewriter{this}; auto res = expression_walker::walk(expression, &expressionRewriter); _rewroteLastExpression = expressionRewriter.didRewrite; return res; } boost::optional FLEQueryRewriter::rewriteMatchExpression(const BSONObj& filter) { auto expr = uassertStatusOK(MatchExpressionParser::parse(filter, _expCtx)); _rewroteLastExpression = false; if (auto res = _rewrite(expr.get())) { // The rewrite resulted in top-level changes. Serialize the new expression. return res->serialize().getOwned(); } else if (_rewroteLastExpression) { // The rewrite had no top-level changes, but nested expressions were rewritten. Serialize // the parsed expression, which has in-place changes. return expr->serialize().getOwned(); } // No rewrites were done. return boost::none; } std::unique_ptr FLEQueryRewriter::_rewrite(MatchExpression* expr) { switch (expr->matchType()) { case MatchExpression::EQ: return rewriteEq(std::move(static_cast(expr))); case MatchExpression::MATCH_IN: return rewriteIn(std::move(static_cast(expr))); case MatchExpression::AND: case MatchExpression::OR: case MatchExpression::NOT: case MatchExpression::NOR: { for (size_t i = 0; i < expr->numChildren(); i++) { auto child = expr->getChild(i); if (auto newChild = _rewrite(child)) { expr->resetChild(i, newChild.release()); } } return nullptr; } case MatchExpression::ENCRYPTED_BETWEEN: { if (gFeatureFlagFLE2Range.isEnabled(serverGlobalParams.featureCompatibility)) { return rewriteRange( std::move(static_cast(expr))); } return nullptr; } case MatchExpression::EXPRESSION: { // Save the current value of _rewroteLastExpression, since rewriteExpression() may // reset it to false and we may have already done a match expression rewrite. auto didRewrite = _rewroteLastExpression; auto rewritten = rewriteExpression(static_cast(expr)->getExpression().get()); _rewroteLastExpression |= didRewrite; if (rewritten) { return std::make_unique(rewritten.release(), expCtx()); } [[fallthrough]]; } default: return nullptr; } } BSONObj FLEQueryRewriter::rewriteEqualityPayloadAsTags(BSONElement fleFindPayload) const { auto tokens = ParsedFindPayload(fleFindPayload); auto tags = readTags(*_escReader, *_eccReader, tokens.escToken, tokens.eccToken, tokens.edcToken, tokens.maxCounter); auto bab = BSONArrayBuilder(); for (auto tag : tags) { bab.appendBinData(tag.size(), BinDataType::BinDataGeneral, tag.data()); } return bab.obj().getOwned(); } std::vector FLEQueryRewriter::rewriteEqualityPayloadAsTags(Value fleFindPayload) const { auto tokens = ParsedFindPayload(fleFindPayload); auto tags = readTags(*_escReader, *_eccReader, tokens.escToken, tokens.eccToken, tokens.edcToken, tokens.maxCounter); std::vector tagVec; for (auto tag : tags) { tagVec.push_back(Value(BSONBinData(tag.data(), tag.size(), BinDataType::BinDataGeneral))); } return tagVec; } BSONObj FLEQueryRewriter::rewriteRangePayloadAsTags(BSONElement fleFindPayload) const { // TODO: SERVER-67206 return BSONObj(); } std::vector FLEQueryRewriter::rewriteRangePayloadAsTags(Value fleFindPayload) const { // TODO: SERVER-67206 return std::vector({Value(0)}); } std::unique_ptr FLEQueryRewriter::rewriteEq(const EqualityMatchExpression* expr) { auto ffp = expr->getData(); if (!isFleFindPayload(ffp, EncryptedBinDataType::kFLE2FindEqualityPayload)) { return nullptr; } if (_mode != EncryptedCollScanMode::kForceAlways) { try { auto obj = rewriteEqualityPayloadAsTags(ffp); auto tags = std::vector(); obj.elems(tags); auto inExpr = std::make_unique(kSafeContent); inExpr->setBackingBSON(std::move(obj)); auto status = inExpr->setEqualities(std::move(tags)); uassertStatusOK(status); _rewroteLastExpression = true; return inExpr; } catch (const ExceptionFor& ex) { LOGV2_DEBUG(6672410, 2, "FLE Max tag limit hit during query $eq rewrite", "__error__"_attr = ex.what()); if (_mode != EncryptedCollScanMode::kUseIfNeeded) { throw; } // fall through } } auto exprMatch = generateFleEqualMatchAndExpr(expr->path(), ffp, _expCtx.get()); _rewroteLastExpression = true; return exprMatch; } std::unique_ptr FLEQueryRewriter::rewriteIn(const InMatchExpression* expr) { size_t numFFPs = 0; for (auto& eq : expr->getEqualities()) { if (isFleFindPayload(eq, EncryptedBinDataType::kFLE2FindEqualityPayload)) { ++numFFPs; } } if (numFFPs == 0) { return nullptr; } // All elements in an encrypted $in expression should be FFPs. uassert( 6329400, "If any elements in a $in expression are encrypted, then all elements should be encrypted.", numFFPs == expr->getEqualities().size()); if (_mode != EncryptedCollScanMode::kForceAlways) { try { auto backingBSONBuilder = BSONArrayBuilder(); for (auto& eq : expr->getEqualities()) { auto obj = rewriteEqualityPayloadAsTags(eq); for (auto&& elt : obj) { backingBSONBuilder.append(elt); } } auto backingBSON = backingBSONBuilder.arr(); auto allTags = std::vector(); backingBSON.elems(allTags); auto inExpr = std::make_unique(kSafeContent); inExpr->setBackingBSON(std::move(backingBSON)); auto status = inExpr->setEqualities(std::move(allTags)); uassertStatusOK(status); _rewroteLastExpression = true; return inExpr; } catch (const ExceptionFor& ex) { LOGV2_DEBUG(6672411, 2, "FLE Max tag limit hit during query $in rewrite", "__error__"_attr = ex.what()); if (_mode != EncryptedCollScanMode::kUseIfNeeded) { throw; } // fall through } } std::vector> matches; matches.reserve(numFFPs); for (auto& eq : expr->getEqualities()) { auto exprMatch = generateFleEqualMatchAndExpr(expr->path(), eq, _expCtx.get()); matches.push_back(std::move(exprMatch)); } auto orExpr = std::make_unique(std::move(matches)); _rewroteLastExpression = true; return orExpr; } std::unique_ptr FLEQueryRewriter::rewriteRange( const EncryptedBetweenMatchExpression* expr) { auto ffp = expr->rhs(); if (!isFleFindPayload(ffp, EncryptedBinDataType::kFLE2FindRangePayload)) { return nullptr; } auto obj = rewriteRangePayloadAsTags(ffp); auto tags = std::vector(); obj.elems(tags); auto inExpr = std::make_unique(kSafeContent); inExpr->setBackingBSON(std::move(obj)); auto status = inExpr->setEqualities(std::move(tags)); uassertStatusOK(status); _rewroteLastExpression = true; return inExpr; } } // namespace mongo::fle