TEST(MatchExpressionParserTreeTest, NOT1) { BSONObj query = BSON("x" << BSON("$not" << BSON("$gt" << 5))); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(result.getValue()->matchesBSON(BSON("x" << 2))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 8))); }
TEST(InternalSchemaMatchArrayIndexMatchExpression, EquivalentToClone) { auto filter = fromjson( "{foo: {$_internalSchemaMatchArrayIndex:" "{index: 0, namePlaceholder: 'i', expression: {i: {$type: 'number'}}}}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); auto expr = MatchExpressionParser::parse(filter, expCtx); ASSERT_OK(expr.getStatus()); auto clone = expr.getValue()->shallowClone(); ASSERT_TRUE(expr.getValue()->equivalent(clone.get())); }
TEST(InternalSchemaMatchArrayIndexMatchExpression, HasSingleChild) { auto query = fromjson( "{foo: {$_internalSchemaMatchArrayIndex:" "{index: 0, namePlaceholder: 'i', expression: {i: {$type: 'number'}}}}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); auto objMatch = MatchExpressionParser::parse(query, expCtx); ASSERT_OK(objMatch.getStatus()); ASSERT_EQ(objMatch.getValue()->numChildren(), 1U); ASSERT(objMatch.getValue()->getChild(0)); }
TEST(InternalSchemaAllowedPropertiesMatchExpression, MatchesObjectsWithListedProperties) { auto filter = fromjson( "{$_internalSchemaAllowedProperties: {properties: ['a', 'b']," "namePlaceholder: 'i', patternProperties: [], otherwise: {i: 0}}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); auto expr = MatchExpressionParser::parse(filter, expCtx); ASSERT_OK(expr.getStatus()); ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{a: 1, b: 1}"))); ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{a: 1}"))); ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{b: 1}"))); }
TEST(MatchExpressionParserTreeTest, OREmbedded) { BSONObj query1 = BSON("$or" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); BSONObj query2 = BSON("$or" << BSON_ARRAY(query1)); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query2, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(result.getValue()->matchesBSON(BSON("y" << 2))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); ASSERT(!result.getValue()->matchesBSON(BSON("y" << 1))); }
TEST_F(UnsetNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) { auto update = fromjson("{$unset: {'a.$id': true}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$id"], expCtx)); mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); setPathTaken("a.$id"); ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["$id"])), AssertionException, ErrorCodes::InvalidDBRef, "The DBRef $ref field must be followed by a $id field"); }
TEST_F(UnsetNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { auto update = fromjson("{$unset: {'a.b.c': true}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b.c"], expCtx)); mutablebson::Document doc(fromjson("{a: {b: {c: 1}}}")); setPathTaken("a.b.c"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( node.apply(getApplyParams(doc.root()["a"]["b"]["c"])), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'"); }
TEST_F(PopNodeTest, NoopWhenFirstPathComponentDoesNotExist) { auto update = fromjson("{$pop: {'a.b': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.b"], expCtx)); mmb::Document doc(fromjson("{b: [1, 2, 3]}")); setPathToCreate("a.b"); addIndexedPath("a.b"); auto result = popNode.apply(getApplyParams(doc.root())); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: [1, 2, 3]}"), doc); ASSERT_EQUALS(fromjson("{}"), getLogDoc()); }
TEST(MatchExpressionParserLeafTest, NotRegex1) { BSONObjBuilder b; b.appendRegex("$not", "abc", "i"); BSONObj query = BSON("x" << b.obj()); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << "abc"))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << "ABC"))); ASSERT(result.getValue()->matchesBSON(BSON("x" << "AC"))); }
TEST_F(PopNodeTest, ThrowsWhenPathIsBlockedByAScalar) { auto update = fromjson("{$pop: {'a.b': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.b"], expCtx)); mmb::Document doc(fromjson("{a: 'foo'}")); setPathToCreate("b"); setPathTaken("a"); addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( popNode.apply(getApplyParams(doc.root()["a"])), AssertionException, ErrorCodes::PathNotViable, "Cannot use the part (b) of (a.b) to traverse the element ({a: \"foo\"})"); }
TEST(InternalSchemaMatchArrayIndexMatchExpression, DoesNotMatchArrayIfMatchingElementNotAtIndex) { auto filter = fromjson( "{foo: {$_internalSchemaMatchArrayIndex:" "{index: 0, namePlaceholder: 'i', expression: {i: {$lte: 7}}}}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); auto expr = MatchExpressionParser::parse(filter, expCtx); ASSERT_OK(expr.getStatus()); ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{foo: [33, 0, 1, 2]}"))); filter = fromjson( "{foo: {$_internalSchemaMatchArrayIndex:" "{index: 1, namePlaceholder: 'i', expression: {i: {$lte: 7}}}}}"); expr = MatchExpressionParser::parse(filter, expCtx); ASSERT_OK(expr.getStatus()); ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{foo: [0, 99, 1, 2]}"))); }
TEST_F(PopNodeTest, NoopWhenPathPartiallyExists) { auto update = fromjson("{$pop: {'a.b.c': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.b.c"], expCtx)); mmb::Document doc(fromjson("{a: {}}")); setPathToCreate("b.c"); setPathTaken("a"); addIndexedPath("a.b.c"); auto result = popNode.apply(getApplyParams(doc.root()["a"])); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {}}"), doc); ASSERT_EQUALS(fromjson("{}"), getLogDoc()); }
TEST_F(PopNodeTest, NoopWhenNumericalPathComponentExceedsArrayLength) { auto update = fromjson("{$pop: {'a.0': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.0"], expCtx)); mmb::Document doc(fromjson("{a: []}")); setPathToCreate("0"); setPathTaken("a"); addIndexedPath("a.0"); auto result = popNode.apply(getApplyParams(doc.root()["a"])); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_EQUALS(fromjson("{}"), getLogDoc()); }
TEST_F(UnsetNodeTest, ApplyNoIndexDataNoLogBuilder) { auto update = fromjson("{$unset: {a: 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], expCtx)); mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); setLogBuilderToNull(); auto result = node.apply(getApplyParams(doc.root()["a"])); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(getModifiedPaths(), "{a}"); }
TEST(InternalSchemaMatchArrayIndexMatchExpression, MatchesIfNotEnoughArrayElements) { auto filter = fromjson( "{foo: {$_internalSchemaMatchArrayIndex:" "{index: 0, namePlaceholder: 'i', expression: {i: 1}}}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); auto expr = MatchExpressionParser::parse(filter, expCtx); ASSERT_OK(expr.getStatus()); ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{foo: []}"))); filter = fromjson( "{foo: {$_internalSchemaMatchArrayIndex:" "{index: 4, namePlaceholder: 'i', expression: {i: 1}}}}"); expr = MatchExpressionParser::parse(filter, expCtx); ASSERT_OK(expr.getStatus()); ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{foo: ['no', 'no', 'no', 'no']}"))); }
TEST_F(UnsetNodeTest, ApplyFieldWithDot) { auto update = fromjson("{$unset: {'a.b': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b"], expCtx)); mutablebson::Document doc(fromjson("{'a.b':4, a: {b: 2}}")); setPathTaken("a.b"); addIndexedPath("a"); auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{'a.b':4, a: {}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), getLogDoc()); ASSERT_EQUALS(getModifiedPaths(), "{a.b}"); }
TEST_F(UnsetNodeTest, UnsetNoOpEmptyDoc) { auto update = fromjson("{$unset: {a: 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], expCtx)); mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); auto result = node.apply(getApplyParams(doc.root())); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(fromjson("{}"), getLogDoc()); ASSERT_EQUALS(getModifiedPaths(), "{a}"); }
TEST(InternalSchemaMatchArrayIndexMatchExpression, MatchesArraysWithMatchingElement) { auto filter = fromjson( "{foo: {$_internalSchemaMatchArrayIndex:" "{index: 0, namePlaceholder: 'i', expression: {i: {$elemMatch: {'bar': 7}}}}}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); auto expr = MatchExpressionParser::parse(filter, expCtx); ASSERT_OK(expr.getStatus()); ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{foo: [[{bar: 7}], [{bar: 5}]]}"))); ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{foo: [[{bar: [3, 5, 7]}], [{bar: 5}]]}"))); filter = fromjson( "{baz: {$_internalSchemaMatchArrayIndex:" "{index: 2, namePlaceholder: 'i', expression: {i: {$type: 'string'}}}}}"); expr = MatchExpressionParser::parse(filter, expCtx); ASSERT_OK(expr.getStatus()); ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{baz: [0, 1, '2']}"))); }
ProjectionExecutor(BSONObj projSpec, DefaultIdPolicy defaultIdPolicy, ArrayRecursionPolicy arrayRecursionPolicy) { // Construct a dummy ExpressionContext for ParsedAggregationProjection. It's OK to set the // ExpressionContext's OperationContext and CollatorInterface to 'nullptr' here; since we // ban computed fields from the projection, the ExpressionContext will never be used. boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(nullptr, nullptr)); // Create a ProjectionPolicies object, to be populated based on the passed arguments. ParsedAggregationProjection::ProjectionPolicies projectionPolicies; // Default projection behaviour is to include _id if the projection spec omits it. If the // caller has specified that we should *exclude* _id by default, do so here. We translate // DefaultIdPolicy to ProjectionPolicies::DefaultIdPolicy in order to avoid exposing // internal aggregation types to the query system. projectionPolicies.idPolicy = (defaultIdPolicy == ProjectionExecAgg::DefaultIdPolicy::kIncludeId ? ProjectionPolicies::DefaultIdPolicy::kIncludeId : ProjectionPolicies::DefaultIdPolicy::kExcludeId); // By default, $project will recurse through nested arrays. If the caller has specified that // it should not, we inhibit it from doing so here. We separate this class' internal enum // ArrayRecursionPolicy from ProjectionPolicies::ArrayRecursionPolicy in order to avoid // exposing aggregation types to the query system. projectionPolicies.arrayRecursionPolicy = (arrayRecursionPolicy == ArrayRecursionPolicy::kRecurseNestedArrays ? ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays : ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays); // Inclusion projections permit computed fields by default, so we must explicitly ban them. // Computed fields are implicitly banned for exclusions. projectionPolicies.computedFieldsPolicy = ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields; // Construct a ParsedAggregationProjection for the given projection spec and policies. _projection = ParsedAggregationProjection::create(expCtx, projSpec, projectionPolicies); // For an inclusion, record the exhaustive set of fields retained by the projection. if (getType() == ProjectionType::kInclusionProjection) { DepsTracker depsTracker; _projection->addDependencies(&depsTracker); for (auto&& field : depsTracker.fields) _exhaustivePaths.insert(FieldRef{field}); } }
/** * Updates roleGraph for an update-type oplog operation on admin.system.roles. * * Treats all updates as upserts. */ Status handleOplogUpdate(OperationContext* opCtx, RoleGraph* roleGraph, const BSONObj& updatePattern, const BSONObj& queryPattern) { RoleName roleToUpdate; Status status = getRoleNameFromIdField(queryPattern["_id"], &roleToUpdate); if (!status.isOK()) return status; boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, nullptr)); UpdateDriver driver(std::move(expCtx)); driver.setFromOplogApplication(true); // Oplog updates do not have array filters. std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; driver.parse(updatePattern, arrayFilters); mutablebson::Document roleDocument; status = RoleGraph::getBSONForRole(roleGraph, roleToUpdate, roleDocument.root()); if (status == ErrorCodes::RoleNotFound) { // The query pattern will only contain _id, no other immutable fields are present const FieldRef idFieldRef("_id"); FieldRefSet immutablePaths; invariant(immutablePaths.insert(&idFieldRef)); status = driver.populateDocumentWithQueryFields( opCtx, queryPattern, immutablePaths, roleDocument); } if (!status.isOK()) return status; const bool validateForStorage = false; const FieldRefSet emptyImmutablePaths; status = driver.update(StringData(), &roleDocument, validateForStorage, emptyImmutablePaths); if (!status.isOK()) return status; // Now use the updated document to totally replace the role in the graph! RoleInfo role; status = parseRoleFromDocument(roleDocument.getObject(), &role); if (!status.isOK()) return status; status = roleGraph->replaceRole(role.name, role.roles, role.privileges, role.restrictions); return status; }
TEST_F(UnsetNodeTest, UnsetPositional) { auto update = fromjson("{$unset: {'a.$': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$"], expCtx)); mutablebson::Document doc(fromjson("{a: [0, 1, 2]}")); setPathTaken("a.1"); setMatchedField("1"); addIndexedPath("a"); auto result = node.apply(getApplyParams(doc.root()["a"][1])); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, null, 2]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(fromjson("{$unset: {'a.1': true}}"), getLogDoc()); ASSERT_EQUALS(getModifiedPaths(), "{a.1}"); }
TEST_F(UnsetNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse) { auto update = fromjson("{$unset: {'a.$id': true}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$id"], expCtx)); mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); setPathTaken("a.$id"); addIndexedPath("a"); setValidateForStorage(false); auto result = node.apply(getApplyParams(doc.root()["a"]["$id"])); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); auto updated = BSON("a" << BSON("$ref" << "c")); ASSERT_EQUALS(updated, doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(fromjson("{$unset: {'a.$id': true}}"), getLogDoc()); ASSERT_EQUALS(getModifiedPaths(), "{a.$id}"); }
StatusWith<std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>>> ParsedUpdate::parseArrayFilters(const std::vector<BSONObj>& rawArrayFiltersIn, OperationContext* opCtx, CollatorInterface* collator) { std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFiltersOut; for (auto rawArrayFilter : rawArrayFiltersIn) { boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, collator)); auto parsedArrayFilter = MatchExpressionParser::parse(rawArrayFilter, std::move(expCtx), ExtensionsCallbackNoop(), MatchExpressionParser::kBanAllSpecialFeatures); if (!parsedArrayFilter.isOK()) { return parsedArrayFilter.getStatus().withContext("Error parsing array filter"); } auto parsedArrayFilterWithPlaceholder = ExpressionWithPlaceholder::make(std::move(parsedArrayFilter.getValue())); if (!parsedArrayFilterWithPlaceholder.isOK()) { return parsedArrayFilterWithPlaceholder.getStatus().withContext( "Error parsing array filter"); } auto finalArrayFilter = std::move(parsedArrayFilterWithPlaceholder.getValue()); auto fieldName = finalArrayFilter->getPlaceholder(); if (!fieldName) { return Status( ErrorCodes::FailedToParse, "Cannot use an expression without a top-level field name in arrayFilters"); } if (arrayFiltersOut.find(*fieldName) != arrayFiltersOut.end()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Found multiple array filters with the same top-level field name " << *fieldName); } arrayFiltersOut[*fieldName] = std::move(finalArrayFilter); } return std::move(arrayFiltersOut); }
Status ParsedUpdate::parseArrayFilters() { for (auto rawArrayFilter : _request->getArrayFilters()) { boost::intrusive_ptr<ExpressionContext> expCtx( new ExpressionContext(_opCtx, _collator.get())); auto parsedArrayFilter = MatchExpressionParser::parse(rawArrayFilter, std::move(expCtx), ExtensionsCallbackNoop(), MatchExpressionParser::kBanAllSpecialFeatures); if (!parsedArrayFilter.isOK()) { return parsedArrayFilter.getStatus().withContext("Error parsing array filter"); } auto parsedArrayFilterWithPlaceholder = ExpressionWithPlaceholder::make(std::move(parsedArrayFilter.getValue())); if (!parsedArrayFilterWithPlaceholder.isOK()) { return parsedArrayFilterWithPlaceholder.getStatus().withContext( "Error parsing array filter"); } auto finalArrayFilter = std::move(parsedArrayFilterWithPlaceholder.getValue()); auto fieldName = finalArrayFilter->getPlaceholder(); if (!fieldName) { return Status( ErrorCodes::FailedToParse, "Cannot use an expression without a top-level field name in arrayFilters"); } if (_arrayFilters.find(*fieldName) != _arrayFilters.end()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Found multiple array filters with the same top-level field name " << *fieldName); } _arrayFilters[*fieldName] = std::move(finalArrayFilter); } return Status::OK(); }
void updateSessionEntry(OperationContext* opCtx, const UpdateRequest& updateRequest) { // Current code only supports replacement update. dassert(UpdateDriver::isDocReplacement(updateRequest.getUpdates())); AutoGetCollection autoColl(opCtx, NamespaceString::kSessionTransactionsTableNamespace, MODE_IX); uassert(40527, str::stream() << "Unable to persist transaction state because the session transaction " "collection is missing. This indicates that the " << NamespaceString::kSessionTransactionsTableNamespace.ns() << " collection has been manually deleted.", autoColl.getCollection()); WriteUnitOfWork wuow(opCtx); auto collection = autoColl.getCollection(); auto idIndex = collection->getIndexCatalog()->findIdIndex(opCtx); uassert(40672, str::stream() << "Failed to fetch _id index for " << NamespaceString::kSessionTransactionsTableNamespace.ns(), idIndex); auto indexAccess = collection->getIndexCatalog()->getIndex(idIndex); // Since we are looking up a key inside the _id index, create a key object consisting of only // the _id field. auto idToFetch = updateRequest.getQuery().firstElement(); auto toUpdateIdDoc = idToFetch.wrap(); dassert(idToFetch.fieldNameStringData() == "_id"_sd); auto recordId = indexAccess->findSingle(opCtx, toUpdateIdDoc); auto startingSnapshotId = opCtx->recoveryUnit()->getSnapshotId(); if (recordId.isNull()) { // Upsert case. auto status = collection->insertDocument( opCtx, InsertStatement(updateRequest.getUpdates()), nullptr, true, false); if (status == ErrorCodes::DuplicateKey) { throw WriteConflictException(); } uassertStatusOK(status); wuow.commit(); return; } auto originalRecordData = collection->getRecordStore()->dataFor(opCtx, recordId); auto originalDoc = originalRecordData.toBson(); invariant(collection->getDefaultCollator() == nullptr); boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, nullptr)); auto matcher = fassertStatusOK( 40673, MatchExpressionParser::parse(updateRequest.getQuery(), std::move(expCtx))); if (!matcher->matchesBSON(originalDoc)) { // Document no longer match what we expect so throw WCE to make the caller re-examine. throw WriteConflictException(); } OplogUpdateEntryArgs args; args.nss = NamespaceString::kSessionTransactionsTableNamespace; args.uuid = collection->uuid(); args.update = updateRequest.getUpdates(); args.criteria = toUpdateIdDoc; args.fromMigrate = false; collection->updateDocument(opCtx, recordId, Snapshotted<BSONObj>(startingSnapshotId, originalDoc), updateRequest.getUpdates(), true, // enforceQuota false, // indexesAffected = false because _id is the only index nullptr, &args); wuow.commit(); }
TEST(PopNodeTest, InitFailsBool) { auto update = fromjson("{$pop: {a: true}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_EQ(ErrorCodes::FailedToParse, popNode.init(update["$pop"]["a"], expCtx)); }