Status ModifierBit::prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(root.getDocument())); // If we have a $-positional field, it is time to bind it to an actual field part. if (_posDollar) { if (matchedField.empty()) { return Status(ErrorCodes::BadValue, str::stream() << "The positional operator did not find the match " "needed from the query. Unexpanded update: " << _fieldRef.dottedField()); } _fieldRef.setPart(_posDollar, matchedField); } // Locate the field name in 'root'. Status status = pathsupport::findLongestPrefix( _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound); // FindLongestPrefix may say the path does not exist at all, which is fine here, or // that the path was not viable or otherwise wrong, in which case, the mod cannot // proceed. if (status.code() == ErrorCodes::NonExistentPath) { _preparedState->elemFound = root.getDocument().end(); } else if (!status.isOK()) { return status; } // We register interest in the field name. The driver needs this info to sort out if // there is any conflict among mods. execInfo->fieldRef[0] = &_fieldRef; // // in-place and no-op logic // // If the field path is not fully present, then this mod cannot be in place, nor is a // noOp. if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) { // If no target element exists, the value we will write is the result of applying // the operation to a zero-initialized integer element. _preparedState->newValue = apply(SafeNum(static_cast<int32_t>(0))); return Status::OK(); } if (!_preparedState->elemFound.isIntegral()) { mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); return Status(ErrorCodes::BadValue, str::stream() << "Cannot apply $bit to a value of non-integral type." << idElem.toString() << " has the field " << _preparedState->elemFound.getFieldName() << " of non-integer type " << typeName(_preparedState->elemFound.getType())); } const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum(); // Apply the op over the existing value and the mod value, and capture the result. _preparedState->newValue = apply(currentValue); if (!_preparedState->newValue.isValid()) { // TODO: Include list of ops, if that is easy, at some future point. return Status(ErrorCodes::BadValue, str::stream() << "Failed to apply $bit operations to current value: " << currentValue.debugString()); } // If the values are identical (same type, same value), then this is a no-op. if (_preparedState->newValue.isIdentical(currentValue)) { _preparedState->noOp = execInfo->noOp = true; return Status::OK(); } return Status::OK(); }
BSONObj FTSSpec::fixSpec( const BSONObj& spec ) { if ( spec["textIndexVersion"].numberInt() == TEXT_INDEX_VERSION_1 ) { return _fixSpecV1( spec ); } map<string,int> m; BSONObj keyPattern; { BSONObjBuilder b; // Populate m and keyPattern. { bool addedFtsStuff = false; BSONObjIterator i( spec["key"].Obj() ); while ( i.more() ) { BSONElement e = i.next(); if ( str::equals( e.fieldName(), "_fts" ) ) { uassert( 17271, "expecting _fts:\"text\"", INDEX_NAME == e.valuestrsafe() ); addedFtsStuff = true; b.append( e ); } else if ( str::equals( e.fieldName(), "_ftsx" ) ) { uassert( 17272, "expecting _ftsx:1", e.numberInt() == 1 ); b.append( e ); } else if ( e.type() == String && INDEX_NAME == e.valuestr() ) { if ( !addedFtsStuff ) { _addFTSStuff( &b ); addedFtsStuff = true; } m[e.fieldName()] = 1; } else { uassert( 17273, "expected value 1 or -1 for non-text key in compound index", e.numberInt() == 1 || e.numberInt() == -1 ); b.append( e ); } } verify( addedFtsStuff ); } keyPattern = b.obj(); // Verify that index key is in the correct format: extraBefore fields, then text // fields, then extraAfter fields. { BSONObjIterator i( spec["key"].Obj() ); BSONElement e; // extraBefore fields do { verify( i.more() ); e = i.next(); } while ( INDEX_NAME != e.valuestrsafe() ); // text fields bool alreadyFixed = str::equals( e.fieldName(), "_fts" ); if ( alreadyFixed ) { uassert( 17288, "expected _ftsx after _fts", i.more() ); e = i.next(); uassert( 17274, "expected _ftsx after _fts", str::equals( e.fieldName(), "_ftsx" ) ); e = i.next(); } else { do { uassert( 17289, "text index with reserved fields _fts/ftsx not allowed", !str::equals( e.fieldName(), "_fts" ) && !str::equals( e.fieldName(), "_ftsx" ) ); e = i.next(); } while ( !e.eoo() && INDEX_NAME == e.valuestrsafe() ); } // extraAfterFields while ( !e.eoo() ) { uassert( 17290, "compound text index key suffix fields must have value 1", e.numberInt() == 1 && !str::equals( "_ftsx", e.fieldName() ) ); e = i.next(); } } } if ( spec["weights"].type() == Object ) { BSONObjIterator i( spec["weights"].Obj() ); while ( i.more() ) { BSONElement e = i.next(); uassert( 17283, "weight for text index needs numeric type", e.isNumber() ); m[e.fieldName()] = e.numberInt(); // Verify weight refers to a valid field. if ( str::equals( e.fieldName(), "$**" ) ) { continue; } FieldRef keyField( e.fieldName() ); uassert( 17294, "weight cannot be on an empty field", keyField.numParts() != 0 ); for ( size_t i = 0; i < keyField.numParts(); i++ ) { StringData part = keyField.getPart(i); uassert( 17291, "weight cannot have empty path component", !part.empty() ); uassert( 17292, "weight cannot have path component with $ prefix", !part.startsWith( "$" ) ); } } } else if ( spec["weights"].str() == WILDCARD ) { m[WILDCARD] = 1; } else if ( !spec["weights"].eoo() ) { uasserted( 17284, "text index option 'weights' must be an object" ); } BSONObj weights; { BSONObjBuilder b; for ( map<string,int>::iterator i = m.begin(); i != m.end(); ++i ) { uassert( 16674, "score for word too high", i->second > 0 && i->second < MAX_WORD_WEIGHT ); b.append( i->first, i->second ); } weights = b.obj(); } BSONElement default_language_elt = spec["default_language"]; string default_language( default_language_elt.str() ); if ( default_language_elt.eoo() ) { default_language = moduleDefaultLanguage; } else { uassert( 17263, "default_language needs a string type", default_language_elt.type() == String ); } uassert( 17264, "default_language is not valid", FTSLanguage::make( default_language, TEXT_INDEX_VERSION_2 ).getStatus().isOK() ); BSONElement language_override_elt = spec["language_override"]; string language_override( language_override_elt.str() ); if ( language_override_elt.eoo() ) { language_override = "language"; } else { uassert( 17136, "language_override is not valid", language_override_elt.type() == String && validateOverride( language_override ) ); } int version = -1; int textIndexVersion = TEXT_INDEX_VERSION_2; BSONObjBuilder b; BSONObjIterator i( spec ); while ( i.more() ) { BSONElement e = i.next(); if ( str::equals( e.fieldName(), "key" ) ) { b.append( "key", keyPattern ); } else if ( str::equals( e.fieldName(), "weights" ) ) { b.append( "weights", weights ); weights = BSONObj(); } else if ( str::equals( e.fieldName(), "default_language" ) ) { b.append( "default_language", default_language); default_language = ""; } else if ( str::equals( e.fieldName(), "language_override" ) ) { b.append( "language_override", language_override); language_override = ""; } else if ( str::equals( e.fieldName(), "v" ) ) { version = e.numberInt(); } else if ( str::equals( e.fieldName(), "textIndexVersion" ) ) { uassert( 17293, "text index option 'textIndexVersion' must be a number", e.isNumber() ); textIndexVersion = e.numberInt(); uassert( 16730, str::stream() << "bad textIndexVersion: " << textIndexVersion, textIndexVersion == TEXT_INDEX_VERSION_2 ); } else { b.append( e ); } } if ( !weights.isEmpty() ) b.append( "weights", weights ); if ( !default_language.empty() ) b.append( "default_language", default_language); if ( !language_override.empty() ) b.append( "language_override", language_override); if ( version >= 0 ) b.append( "v", version ); b.append( "textIndexVersion", textIndexVersion ); return b.obj(); }
Status validateKeyPattern(const BSONObj& key) { const ErrorCodes::Error code = ErrorCodes::CannotCreateIndex; if (key.objsize() > 2048) return Status(code, "Index key pattern too large."); if (key.isEmpty()) return Status(code, "Index keys cannot be empty."); string pluginName = IndexNames::findPluginName(key); if (pluginName.size()) { if (!IndexNames::isKnownName(pluginName)) return Status( code, mongoutils::str::stream() << "Unknown index plugin '" << pluginName << '\''); } BSONObjIterator it(key); while (it.more()) { BSONElement keyElement = it.next(); if (keyElement.isNumber()) { double value = keyElement.number(); if (std::isnan(value)) { return {code, "Values in the index key pattern cannot be NaN."}; } else if (value == 0.0) { return {code, "Values in the index key pattern cannot be 0."}; } } else if (keyElement.type() != String) { return {code, str::stream() << "Values in index key pattern cannot be of type " << typeName(keyElement.type()) << ". Only numbers > 0, numbers < 0, and strings are allowed."}; } if (keyElement.type() == String && pluginName != keyElement.str()) { return Status(code, "Can't use more than one index plugin for a single index."); } // Ensure that the fields on which we are building the index are valid: a field must not // begin with a '$' unless it is part of a DBRef or text index, and a field path cannot // contain an empty field. If a field cannot be created or updated, it should not be // indexable. FieldRef keyField(keyElement.fieldName()); const size_t numParts = keyField.numParts(); if (numParts == 0) { return Status(code, "Index keys cannot be an empty field."); } // "$**" is acceptable for a text index. if (mongoutils::str::equals(keyElement.fieldName(), "$**") && keyElement.valuestrsafe() == IndexNames::TEXT) continue; if (mongoutils::str::equals(keyElement.fieldName(), "_fts") && keyElement.valuestrsafe() != IndexNames::TEXT) { return Status(code, "Index key contains an illegal field name: '_fts'"); } for (size_t i = 0; i != numParts; ++i) { const StringData part = keyField.getPart(i); // Check if the index key path contains an empty field. if (part.empty()) { return Status(code, "Index keys cannot contain an empty field."); } if (part[0] != '$') continue; // Check if the '$'-prefixed field is part of a DBRef: since we don't have the // necessary context to validate whether this is a proper DBRef, we allow index // creation on '$'-prefixed names that match those used in a DBRef. const bool mightBePartOfDbRef = (i != 0) && (part == "$db" || part == "$id" || part == "$ref"); if (!mightBePartOfDbRef) { return Status(code, "Index key contains an illegal field name: " "field name starts with '$'."); } } } return Status::OK(); }
Status ModifierAddToSet::prepare(mb::Element root, StringData matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(root.getDocument())); // If we have a $-positional field, it is time to bind it to an actual field part. if (_posDollar) { if (matchedField.empty()) { return Status(ErrorCodes::BadValue, str::stream() << "The positional operator did not find the match " "needed from the query. Unexpanded update: " << _fieldRef.dottedField()); } _fieldRef.setPart(_posDollar, matchedField); } // Locate the field name in 'root'. Status status = pathsupport::findLongestPrefix( _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound); // FindLongestPrefix may say the path does not exist at all, which is fine here, or // that the path was not viable or otherwise wrong, in which case, the mod cannot // proceed. if (status.code() == ErrorCodes::NonExistentPath) { _preparedState->elemFound = root.getDocument().end(); } else if (!status.isOK()) { return status; } // We register interest in the field name. The driver needs this info to sort out if // there is any conflict among mods. execInfo->fieldRef[0] = &_fieldRef; // // in-place and no-op logic // // If the field path is not fully present, then this mod cannot be in place, nor is a // noOp. if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) { // If no target element exists, we will simply be creating a new array. _preparedState->addAll = true; return Status::OK(); } // This operation only applies to arrays if (_preparedState->elemFound.getType() != mongo::Array) { mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); return Status(ErrorCodes::BadValue, str::stream() << "Cannot apply $addToSet to a non-array field. Field named '" << _preparedState->elemFound.getFieldName() << "' has a non-array type " << typeName(_preparedState->elemFound.getType()) << " in the document " << idElem.toString()); } // If the array is empty, then we don't need to check anything: all of the values are // going to be added. if (!_preparedState->elemFound.hasChildren()) { _preparedState->addAll = true; return Status::OK(); } // For each value in the $each clause, compare it against the values in the array. If // the element is not present, record it as one to add. mb::Element eachIter = _val.leftChild(); while (eachIter.ok()) { mb::Element where = mb::findElement(_preparedState->elemFound.leftChild(), mb::woEqualTo(eachIter, false)); if (!where.ok()) { // The element was not found. Record the element from $each as one to be added. _preparedState->elementsToAdd.push_back(eachIter); } eachIter = eachIter.rightSibling(); } // If we didn't find any elements to add, then this is a no-op. if (_preparedState->elementsToAdd.empty()) { _preparedState->noOp = execInfo->noOp = true; } return Status::OK(); }
Status ModifierSet::prepare(mutablebson::Element root, const StringData& matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(&root.getDocument())); // If we have a $-positional field, it is time to bind it to an actual field part. if (_posDollar) { if (matchedField.empty()) { return Status(ErrorCodes::BadValue, "matched field not provided"); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // Locate the field name in 'root'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remainin path, if missing, will // be created during the apply. Status status = pathsupport::findLongestPrefix(_fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound); // FindLongestPrefix may say the path does not exist at all, which is fine here, or // that the path was not viable or otherwise wrong, in which case, the mod cannot // proceed. if (status.code() == ErrorCodes::NonExistentPath) { _preparedState->elemFound = root.getDocument().end(); } else if (_modOptions.fromReplication && status.code() == ErrorCodes::PathNotViable) { // If we are coming from replication and it is an invalid path, // then push on indicating that we had a blocking element, which we stopped at _preparedState->elemIsBlocking = true; } else if (!status.isOK()) { return status; } if (_setMode == SET_ON_INSERT) { execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT; } // We register interest in the field name. The driver needs this info to sort out if // there is any conflict among mods. execInfo->fieldRef[0] = &_fieldRef; // // in-place and no-op logic // // If the field path is not fully present, then this mod cannot be in place, nor is a // noOp. if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts()-1)) { return Status::OK(); } // If the value being $set is the same as the one already in the doc, than this is a // noOp. if (_preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts()-1) && _preparedState->elemFound.compareWithBSONElement(_val, false /*ignore field*/) == 0) { execInfo->noOp = _preparedState->noOp = true; } return Status::OK(); }
StatusWith<BSONObj> validateIndexSpec( const BSONObj& indexSpec, const NamespaceString& expectedNamespace, const ServerGlobalParams::FeatureCompatibility& featureCompatibility) { bool hasKeyPatternField = false; bool hasNamespaceField = false; bool hasVersionField = false; bool hasCollationField = false; auto fieldNamesValidStatus = validateIndexSpecFieldNames(indexSpec); if (!fieldNamesValidStatus.isOK()) { return fieldNamesValidStatus; } boost::optional<IndexVersion> resolvedIndexVersion; for (auto&& indexSpecElem : indexSpec) { auto indexSpecElemFieldName = indexSpecElem.fieldNameStringData(); if (IndexDescriptor::kKeyPatternFieldName == indexSpecElemFieldName) { if (indexSpecElem.type() != BSONType::Object) { return {ErrorCodes::TypeMismatch, str::stream() << "The field '" << IndexDescriptor::kKeyPatternFieldName << "' must be an object, but got " << typeName(indexSpecElem.type())}; } std::vector<StringData> keys; for (auto&& keyElem : indexSpecElem.Obj()) { auto keyElemFieldName = keyElem.fieldNameStringData(); if (std::find(keys.begin(), keys.end(), keyElemFieldName) != keys.end()) { return {ErrorCodes::BadValue, str::stream() << "The field '" << keyElemFieldName << "' appears multiple times in the index key pattern " << indexSpecElem.Obj()}; } keys.push_back(keyElemFieldName); } hasKeyPatternField = true; } else if (IndexDescriptor::kNamespaceFieldName == indexSpecElemFieldName) { if (indexSpecElem.type() != BSONType::String) { return {ErrorCodes::TypeMismatch, str::stream() << "The field '" << IndexDescriptor::kNamespaceFieldName << "' must be a string, but got " << typeName(indexSpecElem.type())}; } StringData ns = indexSpecElem.valueStringData(); if (ns.empty()) { return {ErrorCodes::BadValue, str::stream() << "The field '" << IndexDescriptor::kNamespaceFieldName << "' cannot be an empty string"}; } if (ns != expectedNamespace.ns()) { return {ErrorCodes::BadValue, str::stream() << "The value of the field '" << IndexDescriptor::kNamespaceFieldName << "' (" << ns << ") doesn't match the namespace '" << expectedNamespace.ns() << "'"}; } hasNamespaceField = true; } else if (IndexDescriptor::kIndexVersionFieldName == indexSpecElemFieldName) { if (!indexSpecElem.isNumber()) { return {ErrorCodes::TypeMismatch, str::stream() << "The field '" << IndexDescriptor::kIndexVersionFieldName << "' must be a number, but got " << typeName(indexSpecElem.type())}; } auto requestedIndexVersionAsInt = representAs<int>(indexSpecElem.number()); if (!requestedIndexVersionAsInt) { return {ErrorCodes::BadValue, str::stream() << "Index version must be representable as a 32-bit integer, but got " << indexSpecElem.toString(false, false)}; } const IndexVersion requestedIndexVersion = static_cast<IndexVersion>(*requestedIndexVersionAsInt); auto creationAllowedStatus = IndexDescriptor::isIndexVersionAllowedForCreation( requestedIndexVersion, featureCompatibility, indexSpec); if (!creationAllowedStatus.isOK()) { return creationAllowedStatus; } hasVersionField = true; resolvedIndexVersion = requestedIndexVersion; } else if (IndexDescriptor::kCollationFieldName == indexSpecElemFieldName) { if (indexSpecElem.type() != BSONType::Object) { return {ErrorCodes::TypeMismatch, str::stream() << "The field '" << IndexDescriptor::kNamespaceFieldName << "' must be an object, but got " << typeName(indexSpecElem.type())}; } hasCollationField = true; } else { // We can assume field name is valid at this point. Validation of fieldname is handled // prior to this in validateIndexSpecFieldNames(). continue; } } if (!resolvedIndexVersion) { resolvedIndexVersion = IndexDescriptor::getDefaultIndexVersion(featureCompatibility.version.load()); } if (!hasKeyPatternField) { return {ErrorCodes::FailedToParse, str::stream() << "The '" << IndexDescriptor::kKeyPatternFieldName << "' field is a required property of an index specification"}; } if (hasCollationField && *resolvedIndexVersion < IndexVersion::kV2) { return {ErrorCodes::CannotCreateIndex, str::stream() << "Invalid index specification " << indexSpec << "; cannot create an index with the '" << IndexDescriptor::kCollationFieldName << "' option and " << IndexDescriptor::kIndexVersionFieldName << "=" << static_cast<int>(*resolvedIndexVersion)}; } if (!hasNamespaceField || !hasVersionField) { BSONObjBuilder bob; if (!hasNamespaceField) { // We create a new index specification with the 'ns' field set as 'expectedNamespace' if // the field was omitted. bob.append(IndexDescriptor::kNamespaceFieldName, expectedNamespace.ns()); } if (!hasVersionField) { // We create a new index specification with the 'v' field set as 'defaultIndexVersion' // if the field was omitted. bob.append(IndexDescriptor::kIndexVersionFieldName, static_cast<int>(*resolvedIndexVersion)); } bob.appendElements(indexSpec); return bob.obj(); } return indexSpec; }
Status ModifierPull::prepare(mb::Element root, StringData matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(root.getDocument())); // If we have a $-positional field, it is time to bind it to an actual field part. if (_posDollar) { if (matchedField.empty()) { return Status(ErrorCodes::BadValue, str::stream() << "The positional operator did not find the match " "needed from the query. Unexpanded update: " << _fieldRef.dottedField()); } _fieldRef.setPart(_posDollar, matchedField); } // Locate the field name in 'root'. Status status = pathsupport::findLongestPrefix( _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound); // FindLongestPrefix may say the path does not exist at all, which is fine here, or // that the path was not viable or otherwise wrong, in which case, the mod cannot // proceed. if (status.code() == ErrorCodes::NonExistentPath) { _preparedState->elemFound = root.getDocument().end(); } else if (!status.isOK()) { return status; } // We register interest in the field name. The driver needs this info to sort out if // there is any conflict among mods. execInfo->fieldRef[0] = &_fieldRef; if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) { // If no target element exists, then there is nothing to do here. _preparedState->noOp = execInfo->noOp = true; return Status::OK(); } // This operation only applies to arrays if (_preparedState->elemFound.getType() != mongo::Array) return Status(ErrorCodes::BadValue, "Cannot apply $pull to a non-array value"); // If the array is empty, there is nothing to pull, so this is a noop. if (!_preparedState->elemFound.hasChildren()) { _preparedState->noOp = execInfo->noOp = true; return Status::OK(); } // Walk the values in the array mb::Element cursor = _preparedState->elemFound.leftChild(); while (cursor.ok()) { if (isMatch(cursor)) _preparedState->elementsToRemove.push_back(cursor); cursor = cursor.rightSibling(); } // If we didn't find any elements to add, then this is a no-op, and therefore in place. if (_preparedState->elementsToRemove.empty()) { _preparedState->noOp = execInfo->noOp = true; } return Status::OK(); }
void BSONComparatorInterfaceBase<T>::hashCombineBSONElement( size_t& hash, BSONElement elemToHash, ComparisonRulesSet rules, const StringData::ComparatorInterface* stringComparator) { boost::hash_combine(hash, elemToHash.canonicalType()); const StringData fieldName = elemToHash.fieldNameStringData(); if ((rules & ComparisonRules::kConsiderFieldName) && !fieldName.empty()) { SimpleStringDataComparator::kInstance.hash_combine(hash, fieldName); } switch (elemToHash.type()) { case mongo::EOO: case mongo::Undefined: case mongo::jstNULL: case mongo::MaxKey: case mongo::MinKey: // These are valueless types break; case mongo::Bool: boost::hash_combine(hash, elemToHash.boolean()); break; case mongo::bsonTimestamp: boost::hash_combine(hash, elemToHash.timestamp().asULL()); break; case mongo::Date: boost::hash_combine(hash, elemToHash.date().asInt64()); break; case mongo::NumberDecimal: { const Decimal128 dcml = elemToHash.numberDecimal(); if (dcml.toAbs().isGreater(Decimal128(std::numeric_limits<double>::max(), Decimal128::kRoundTo34Digits, Decimal128::kRoundTowardZero)) && !dcml.isInfinite() && !dcml.isNaN()) { // Normalize our decimal to force equivalent decimals // in the same cohort to hash to the same value Decimal128 dcmlNorm(dcml.normalize()); boost::hash_combine(hash, dcmlNorm.getValue().low64); boost::hash_combine(hash, dcmlNorm.getValue().high64); break; } // Else, fall through and convert the decimal to a double and hash. // At this point the decimal fits into the range of doubles, is infinity, or is NaN, // which doubles have a cheaper representation for. } case mongo::NumberDouble: case mongo::NumberLong: case mongo::NumberInt: { // This converts all numbers to doubles, which ignores the low-order bits of // NumberLongs > 2**53 and precise decimal numbers without double representations, // but that is ok since the hash will still be the same for equal numbers and is // still likely to be different for different numbers. (Note: this issue only // applies for decimals when they are outside of the valid double range. See // the above case.) // SERVER-16851 const double dbl = elemToHash.numberDouble(); if (std::isnan(dbl)) { boost::hash_combine(hash, std::numeric_limits<double>::quiet_NaN()); } else { boost::hash_combine(hash, dbl); } break; } case mongo::jstOID: elemToHash.__oid().hash_combine(hash); break; case mongo::String: { if (stringComparator) { stringComparator->hash_combine(hash, elemToHash.valueStringData()); } else { SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.valueStringData()); } break; } case mongo::Code: case mongo::Symbol: SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.valueStringData()); break; case mongo::Object: case mongo::Array: hashCombineBSONObj(hash, elemToHash.embeddedObject(), rules | ComparisonRules::kConsiderFieldName, stringComparator); break; case mongo::DBRef: case mongo::BinData: // All bytes of the value are required to be identical. SimpleStringDataComparator::kInstance.hash_combine( hash, StringData(elemToHash.value(), elemToHash.valuesize())); break; case mongo::RegEx: SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.regex()); SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.regexFlags()); break; case mongo::CodeWScope: { SimpleStringDataComparator::kInstance.hash_combine( hash, StringData(elemToHash.codeWScopeCode(), elemToHash.codeWScopeCodeLen())); hashCombineBSONObj(hash, elemToHash.codeWScopeObject(), rules | ComparisonRules::kConsiderFieldName, &SimpleStringDataComparator::kInstance); break; } } }
bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result) { BSONElement first = cmdObj.firstElement(); uassert(28528, str::stream() << "Argument to listIndexes must be of type String, not " << typeName(first.type()), first.type() == String); StringData collectionName = first.valueStringData(); uassert(28529, str::stream() << "Argument to listIndexes must be a collection name, " << "not the empty string", !collectionName.empty()); const NamespaceString ns(dbname, collectionName); const long long defaultBatchSize = std::numeric_limits<long long>::max(); long long batchSize; Status parseCursorStatus = parseCommandCursorOptions(cmdObj, defaultBatchSize, &batchSize); if (!parseCursorStatus.isOK()) { return appendCommandStatus(result, parseCursorStatus); } AutoGetCollectionForRead autoColl(txn, ns); if (!autoColl.getDb()) { return appendCommandStatus(result, Status(ErrorCodes::NamespaceNotFound, "no database")); } const Collection* collection = autoColl.getCollection(); if (!collection) { return appendCommandStatus(result, Status(ErrorCodes::NamespaceNotFound, "no collection")); } const CollectionCatalogEntry* cce = collection->getCatalogEntry(); invariant(cce); vector<string> indexNames; MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { indexNames.clear(); cce->getAllIndexes(txn, &indexNames); } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "listIndexes", ns.ns()); auto ws = make_unique<WorkingSet>(); auto root = make_unique<QueuedDataStage>(txn, ws.get()); for (size_t i = 0; i < indexNames.size(); i++) { BSONObj indexSpec; MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { indexSpec = cce->getIndexSpec(txn, indexNames[i]); } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "listIndexes", ns.ns()); WorkingSetID id = ws->allocate(); WorkingSetMember* member = ws->get(id); member->keyData.clear(); member->loc = RecordId(); member->obj = Snapshotted<BSONObj>(SnapshotId(), indexSpec.getOwned()); member->transitionToOwnedObj(); root->pushBack(id); } std::string cursorNamespace = str::stream() << dbname << ".$cmd." << name << "." << ns.coll(); dassert(NamespaceString(cursorNamespace).isValid()); dassert(NamespaceString(cursorNamespace).isListIndexesCursorNS()); dassert(ns == NamespaceString(cursorNamespace).getTargetNSForListIndexes()); auto statusWithPlanExecutor = PlanExecutor::make( txn, std::move(ws), std::move(root), cursorNamespace, PlanExecutor::YIELD_MANUAL); if (!statusWithPlanExecutor.isOK()) { return appendCommandStatus(result, statusWithPlanExecutor.getStatus()); } unique_ptr<PlanExecutor> exec = std::move(statusWithPlanExecutor.getValue()); BSONArrayBuilder firstBatch; const int byteLimit = FindCommon::kMaxBytesToReturnToClientAtOnce; for (long long objCount = 0; objCount < batchSize && firstBatch.len() < byteLimit; objCount++) { BSONObj next; PlanExecutor::ExecState state = exec->getNext(&next, NULL); if (state == PlanExecutor::IS_EOF) { break; } invariant(state == PlanExecutor::ADVANCED); firstBatch.append(next); } CursorId cursorId = 0LL; if (!exec->isEOF()) { exec->saveState(); exec->detachFromOperationContext(); ClientCursor* cursor = new ClientCursor(CursorManager::getGlobalCursorManager(), exec.release(), cursorNamespace, txn->recoveryUnit()->isReadingFromMajorityCommittedSnapshot()); cursorId = cursor->cursorid(); } appendCursorResponseObject(cursorId, cursorNamespace, firstBatch.arr(), &result); return true; }
Status ModifierRename::prepare(mutablebson::Element root, const StringData& matchedField, ExecInfo* execInfo) { // Rename doesn't work with positional fields ($) dassert(matchedField.empty()); _preparedState.reset(new PreparedState(root)); // Locate the to field name in 'root', which must exist. size_t fromIdxFound; Status status = pathsupport::findLongestPrefix(_fromFieldRef, root, &fromIdxFound, &_preparedState->fromElemFound); const bool sourceExists = (_preparedState->fromElemFound.ok() && fromIdxFound == (_fromFieldRef.numParts() - 1)); // If we can't find the full element in the from field then we can't do anything. if (!status.isOK() || !sourceExists) { execInfo->noOp = true; _preparedState->fromElemFound = root.getDocument().end(); // TODO: remove this special case from existing behavior if (status.code() == ErrorCodes::PathNotViable) { return status; } return Status::OK(); } // Ensure no array in ancestry if what we found is not at the root mutablebson::Element curr = _preparedState->fromElemFound.parent(); if (curr != curr.getDocument().root()) while (curr.ok() && (curr != curr.getDocument().root())) { if (curr.getType() == Array) return Status(ErrorCodes::BadValue, str::stream() << "The source field cannot be an array element, '" << _fromFieldRef.dottedField() << "' in doc with " << findElementNamed(root.leftChild(), "_id").toString() << " has an array field called '" << curr.getFieldName() << "'"); curr = curr.parent(); } // "To" side validation below status = pathsupport::findLongestPrefix(_toFieldRef, root, &_preparedState->toIdxFound, &_preparedState->toElemFound); // FindLongestPrefix may return not viable or any other error and then we cannot proceed. if (status.code() == ErrorCodes::NonExistentPath) { // Not an error condition as we will create the "to" path as needed. } else if (!status.isOK()) { return status; } const bool destExists = _preparedState->toElemFound.ok() && (_preparedState->toIdxFound == (_toFieldRef.numParts()-1)); // Ensure no array in ancestry of "to" Element // Set to either parent, or node depending on if the full path element was found curr = (destExists ? _preparedState->toElemFound.parent() : _preparedState->toElemFound); if (curr != curr.getDocument().root()) { while (curr.ok()) { if (curr.getType() == Array) return Status(ErrorCodes::BadValue, str::stream() << "The destination field cannot be an array element, '" << _fromFieldRef.dottedField() << "' in doc with " << findElementNamed(root.leftChild(), "_id").toString() << " has an array field called '" << curr.getFieldName() << "'"); curr = curr.parent(); } } // We register interest in the field name. The driver needs this info to sort out if // there is any conflict among mods. execInfo->fieldRef[0] = &_fromFieldRef; execInfo->fieldRef[1] = &_toFieldRef; execInfo->noOp = false; return Status::OK(); }
StatusWith<BSONObj> validateIndexSpec(const BSONObj& indexSpec, const NamespaceString& expectedNamespace) { bool hasKeyPatternField = false; bool hasNamespaceField = false; for (auto&& indexSpecElem : indexSpec) { auto indexSpecElemFieldName = indexSpecElem.fieldNameStringData(); if (kKeyPatternFieldName == indexSpecElemFieldName) { if (indexSpecElem.type() != BSONType::Object) { return {ErrorCodes::TypeMismatch, str::stream() << "The field '" << kKeyPatternFieldName << "' must be an object, but got " << typeName(indexSpecElem.type())}; } std::vector<StringData> keys; for (auto&& keyElem : indexSpecElem.Obj()) { auto keyElemFieldName = keyElem.fieldNameStringData(); if (std::find(keys.begin(), keys.end(), keyElemFieldName) != keys.end()) { return {ErrorCodes::BadValue, str::stream() << "The field '" << keyElemFieldName << "' appears multiple times in the index key pattern " << indexSpecElem.Obj()}; } keys.push_back(keyElemFieldName); } hasKeyPatternField = true; } else if (kNamespaceFieldName == indexSpecElemFieldName) { if (indexSpecElem.type() != BSONType::String) { return {ErrorCodes::TypeMismatch, str::stream() << "The field '" << kNamespaceFieldName << "' must be a string, but got " << typeName(indexSpecElem.type())}; } StringData ns = indexSpecElem.valueStringData(); if (ns.empty()) { return {ErrorCodes::BadValue, str::stream() << "The field '" << kNamespaceFieldName << "' cannot be an empty string"}; } if (ns != expectedNamespace.ns()) { return {ErrorCodes::BadValue, str::stream() << "The value of the field '" << kNamespaceFieldName << "' (" << ns << ") doesn't match the namespace '" << expectedNamespace.ns() << "'"}; } hasNamespaceField = true; } else if (kVersionFieldName == indexSpecElemFieldName) { if (!indexSpecElem.isNumber()) { return {ErrorCodes::TypeMismatch, str::stream() << "The field '" << kVersionFieldName << "' must be a number, but got " << typeName(indexSpecElem.type())}; } if (kIndexVersionV0 == indexSpecElem.numberInt()) { return {ErrorCodes::CannotCreateIndex, str::stream() << "Invalid index specification " << indexSpec << "; cannot create an index with " << kVersionFieldName << "=" << kIndexVersionV0}; } } else { // TODO SERVER-769: Validate index options specified in the "createIndexes" command. continue; } } if (!hasKeyPatternField) { return {ErrorCodes::FailedToParse, str::stream() << "The '" << kKeyPatternFieldName << "' field is a required property of an index specification"}; } if (!hasNamespaceField) { // We create a new index specification with the 'ns' field set as 'expectedNamespace' if the // field was omitted. BSONObjBuilder bob; bob.append(kNamespaceFieldName, expectedNamespace.ns()); bob.appendElements(indexSpec); return bob.obj(); } return indexSpec; }
Status ModifierPullAll::prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(&root.getDocument())); // If we have a $-positional field, it is time to bind it to an actual field part. if (_positionalPathIndex) { if (matchedField.empty()) { return Status(ErrorCodes::BadValue, str::stream() << "The positional operator did not find the match " "needed from the query. Unexpanded update: " << _fieldRef.dottedField()); } _fieldRef.setPart(_positionalPathIndex, matchedField); } // Locate the field name in 'root'. Note that if we don't have the full path in the // doc, there isn't anything to unset, really. Status status = pathsupport::findLongestPrefix( _fieldRef, root, &_preparedState->pathFoundIndex, &_preparedState->pathFoundElement); // Check if we didn't find the full path if (status.isOK()) { const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1)); if (!destExists) { execInfo->noOp = true; } else { // If the path exists, we require the target field to be already an // array. if (_preparedState->pathFoundElement.getType() != Array) { mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); return Status( ErrorCodes::BadValue, str::stream() << "Can only apply $pullAll to an array. " << idElem.toString() << " has the field " << _preparedState->pathFoundElement.getFieldName() << " of non-array type " << typeName(_preparedState->pathFoundElement.getType())); } // No children, nothing to do -- not an error state if (!_preparedState->pathFoundElement.hasChildren()) { execInfo->noOp = true; } else { mutablebson::Element elem = _preparedState->pathFoundElement.leftChild(); while (elem.ok()) { if (std::find_if(_elementsToFind.begin(), _elementsToFind.end(), mutableElementEqualsBSONElement(elem, _collator)) != _elementsToFind.end()) { _preparedState->elementsToRemove.push_back(elem); } elem = elem.rightSibling(); } // Nothing to remove so it is a noOp. if (_preparedState->elementsToRemove.empty()) execInfo->noOp = true; } } } else { // Let the caller know we can't do anything given the mod, _fieldRef, and doc. execInfo->noOp = true; // okay if path not found if (status.code() == ErrorCodes::NonExistentPath) status = Status::OK(); } // Let the caller know what field we care about execInfo->fieldRef[0] = &_fieldRef; return status; }
Status ModifierBit::prepare(mutablebson::Element root, const StringData& matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(root.getDocument())); // If we have a $-positional field, it is time to bind it to an actual field part. if (_posDollar) { if (matchedField.empty()) { return Status(ErrorCodes::BadValue, "matched field not provided"); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // Locate the field name in 'root'. Status status = pathsupport::findLongestPrefix(_fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound); // FindLongestPrefix may say the path does not exist at all, which is fine here, or // that the path was not viable or otherwise wrong, in which case, the mod cannot // proceed. if (status.code() == ErrorCodes::NonExistentPath) { _preparedState->elemFound = root.getDocument().end(); } else if (!status.isOK()) { return status; } // We register interest in the field name. The driver needs this info to sort out if // there is any conflict among mods. execInfo->fieldRef[0] = &_fieldRef; // // in-place and no-op logic // // If the field path is not fully present, then this mod cannot be in place, nor is a // noOp. if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) { // If no target element exists, the value we will write is the result of applying // the operation to a zero-initialized integer element. _preparedState->newValue = apply(SafeNum(static_cast<int>(0))); return Status::OK(); } if (!_preparedState->elemFound.isIntegral()) return Status( ErrorCodes::BadValue, "Cannot apply $bit to a value of non-integral type"); const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum(); // Apply the op over the existing value and the mod value, and capture the result. _preparedState->newValue = apply(currentValue); if (!_preparedState->newValue.isValid()) return Status(ErrorCodes::BadValue, "Failed to apply $bit to current value"); // If the values are identical (same type, same value), then this is a no-op. if (_preparedState->newValue.isIdentical(currentValue)) { _preparedState->noOp = execInfo->noOp = true; return Status::OK(); } return Status::OK(); }
Status ModifierInc::prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(root.getDocument())); // If we have a $-positional field, it is time to bind it to an actual field part. if (_posDollar) { if (matchedField.empty()) { return Status(ErrorCodes::BadValue, str::stream() << "The positional operator did not find the match " "needed from the query. Unexpanded update: " << _fieldRef.dottedField()); } _fieldRef.setPart(_posDollar, matchedField); } // Locate the field name in 'root'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remaining path, if missing, will // be created during the apply. Status status = pathsupport::findLongestPrefix( _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound); // FindLongestPrefix may say the path does not exist at all, which is fine here, or // that the path was not viable or otherwise wrong, in which case, the mod cannot // proceed. if (status.code() == ErrorCodes::NonExistentPath) { _preparedState->elemFound = root.getDocument().end(); } else if (!status.isOK()) { return status; } // We register interest in the field name. The driver needs this info to sort out if // there is any conflict among mods. execInfo->fieldRef[0] = &_fieldRef; // Capture the value we are going to write. At this point, there may not be a value // against which to operate, so the result will be simply _val. _preparedState->newValue = _val; // // in-place and no-op logic // // If the field path is not fully present, then this mod cannot be in place, nor is a // noOp. if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) { // For multiplication, we treat ops against missing as yielding zero. We take // advantage here of the promotion rules for SafeNum; the expression below will // always yield a zero of the same type of operand that the user provided // (e.g. double). if (_mode == MODE_MUL) _preparedState->newValue *= SafeNum(static_cast<int>(0)); return Status::OK(); } // If the value being $inc'ed is the same as the one already in the doc, than this is a // noOp. if (!_preparedState->elemFound.isNumeric()) { mb::Element idElem = mb::findFirstChildNamed(root, "_id"); return Status(ErrorCodes::TypeMismatch, str::stream() << "Cannot apply " << (_mode == MODE_INC ? "$inc" : "$mul") << " to a value of non-numeric type. {" << idElem.toString() << "} has the field '" << _preparedState->elemFound.getFieldName() << "' of non-numeric type " << typeName(_preparedState->elemFound.getType())); } const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum(); // Update newValue w.r.t to the current value of the found element. if (_mode == MODE_INC) _preparedState->newValue += currentValue; else _preparedState->newValue *= currentValue; // If the result of the addition is invalid, we must return an error. if (!_preparedState->newValue.isValid()) { mb::Element idElem = mb::findFirstChildNamed(root, "_id"); return Status(ErrorCodes::BadValue, str::stream() << "Failed to apply $inc operations to current value (" << currentValue.debugString() << ") for document {" << idElem.toString() << "}"); } // If the values are identical (same type, same value), then this is a no-op. if (_preparedState->newValue.isIdentical(currentValue)) { _preparedState->noOp = execInfo->noOp = true; return Status::OK(); } return Status::OK(); }
Status HostAndPort::initialize(StringData s) { size_t colonPos = s.rfind(':'); StringData hostPart = s.substr(0, colonPos); // handle ipv6 hostPart (which we require to be wrapped in []s) const size_t openBracketPos = s.find('['); const size_t closeBracketPos = s.find(']'); if (openBracketPos != std::string::npos) { if (openBracketPos != 0) { return Status(ErrorCodes::FailedToParse, str::stream() << "'[' present, but not first character in " << s.toString()); } if (closeBracketPos == std::string::npos) { return Status(ErrorCodes::FailedToParse, str::stream() << "ipv6 address is missing closing ']' in hostname in " << s.toString()); } hostPart = s.substr(openBracketPos + 1, closeBracketPos - openBracketPos - 1); // prevent accidental assignment of port to the value of the final portion of hostPart if (colonPos < closeBracketPos) { colonPos = std::string::npos; } else if (colonPos != closeBracketPos + 1) { return Status(ErrorCodes::FailedToParse, str::stream() << "Extraneous characters between ']' and pre-port ':'" << " in " << s.toString()); } } else if (closeBracketPos != std::string::npos) { return Status(ErrorCodes::FailedToParse, str::stream() << "']' present without '[' in " << s.toString()); } else if (s.find(':') != colonPos) { return Status(ErrorCodes::FailedToParse, str::stream() << "More than one ':' detected. If this is an ipv6 address," << " it needs to be surrounded by '[' and ']'; " << s.toString()); } if (hostPart.empty()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Empty host component parsing HostAndPort from \"" << escape(s.toString()) << "\""); } int port; if (colonPos != std::string::npos) { const StringData portPart = s.substr(colonPos + 1); Status status = parseNumberFromStringWithBase(portPart, 10, &port); if (!status.isOK()) { return status; } if (port <= 0) { return Status(ErrorCodes::FailedToParse, str::stream() << "Port number " << port << " out of range parsing HostAndPort from \"" << escape(s.toString()) << "\""); } } else { port = -1; } _host = hostPart.toString(); _port = port; return Status::OK(); }
Status validateKeyPattern(const BSONObj& key, IndexDescriptor::IndexVersion indexVersion) { const ErrorCodes::Error code = ErrorCodes::CannotCreateIndex; if (key.objsize() > 2048) return Status(code, "Index key pattern too large."); if (key.isEmpty()) return Status(code, "Index keys cannot be empty."); string pluginName = IndexNames::findPluginName(key); if (pluginName.size()) { if (!IndexNames::isKnownName(pluginName)) return Status( code, mongoutils::str::stream() << "Unknown index plugin '" << pluginName << '\''); } BSONObjIterator it(key); while (it.more()) { BSONElement keyElement = it.next(); switch (indexVersion) { case IndexVersion::kV0: case IndexVersion::kV1: { if (keyElement.type() == BSONType::Object || keyElement.type() == BSONType::Array) { return {code, str::stream() << "Values in index key pattern cannot be of type " << typeName(keyElement.type()) << " for index version v:" << static_cast<int>(indexVersion)}; } if (pluginName == IndexNames::ALLPATHS) { return {code, str::stream() << "'" << pluginName << "' index plugin is not allowed with index version v:" << static_cast<int>(indexVersion)}; } break; } case IndexVersion::kV2: { if (keyElement.isNumber()) { double value = keyElement.number(); if (std::isnan(value)) { return {code, "Values in the index key pattern cannot be NaN."}; } else if (value == 0.0) { return {code, "Values in the index key pattern cannot be 0."}; } } else if (keyElement.type() != BSONType::String) { return {code, str::stream() << "Values in v:2 index key pattern cannot be of type " << typeName(keyElement.type()) << ". Only numbers > 0, numbers < 0, and strings are allowed."}; } break; } default: MONGO_UNREACHABLE; } if (keyElement.type() == String && pluginName != keyElement.str()) { return Status(code, "Can't use more than one index plugin for a single index."); } // Check if the all paths index is compounded. If it is the key is invalid because // compounded all paths indexes are disallowed. if (pluginName == IndexNames::ALLPATHS && key.nFields() != 1) { return Status(code, "all paths indexes do not allow compounding"); } // Ensure that the fields on which we are building the index are valid: a field must not // begin with a '$' unless it is part of an allPaths, DBRef or text index, and a field path // cannot contain an empty field. If a field cannot be created or updated, it should not be // indexable. FieldRef keyField(keyElement.fieldName()); const size_t numParts = keyField.numParts(); if (numParts == 0) { return Status(code, "Index keys cannot be an empty field."); } // "$**" is acceptable for a text index or all paths index. if (mongoutils::str::equals(keyElement.fieldName(), "$**") && ((keyElement.isNumber()) || (keyElement.valuestrsafe() == IndexNames::TEXT))) continue; if (mongoutils::str::equals(keyElement.fieldName(), "_fts") && keyElement.valuestrsafe() != IndexNames::TEXT) { return Status(code, "Index key contains an illegal field name: '_fts'"); } for (size_t i = 0; i != numParts; ++i) { const StringData part = keyField.getPart(i); // Check if the index key path contains an empty field. if (part.empty()) { return Status(code, "Index keys cannot contain an empty field."); } if (part[0] != '$') continue; // Check if the '$'-prefixed field is part of a DBRef: since we don't have the // necessary context to validate whether this is a proper DBRef, we allow index // creation on '$'-prefixed names that match those used in a DBRef. const bool mightBePartOfDbRef = (i != 0) && (part == "$db" || part == "$id" || part == "$ref"); const bool isPartOfAllPaths = (i == numParts - 1) && (part == "$**") && (pluginName == IndexNames::ALLPATHS); if (!mightBePartOfDbRef && !isPartOfAllPaths) { return Status(code, "Index key contains an illegal field name: " "field name starts with '$'."); } } } return Status::OK(); }
StatusWithMatchExpression JSONSchemaParser::_parse(StringData path, BSONObj schema) { // Map from JSON Schema keyword to the corresponding element from 'schema', or to an empty // BSONElement if the JSON Schema keyword is not specified. StringMap<BSONElement> keywordMap{{kSchemaTypeKeyword, {}}, {kSchemaPropertiesKeyword, {}}, {kSchemaMaximumKeyword, {}}, {kSchemaMinimumKeyword, {}}, {kSchemaExclusiveMaximumKeyword, {}}, {kSchemaExclusiveMinimumKeyword, {}}, {kSchemaMaxLengthKeyword, {}}, {kSchemaMinLengthKeyword, {}}, {kSchemaPatternKeyword, {}}, {kSchemaMultipleOfKeyword, {}}}; for (auto&& elt : schema) { auto it = keywordMap.find(elt.fieldNameStringData()); if (it == keywordMap.end()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Unknown $jsonSchema keyword: " << elt.fieldNameStringData()); } if (it->second) { return Status(ErrorCodes::FailedToParse, str::stream() << "Duplicate $jsonSchema keyword: " << elt.fieldNameStringData()); } keywordMap[elt.fieldNameStringData()] = elt; } auto typeExpr = parseType(path, keywordMap[kSchemaTypeKeyword]); if (!typeExpr.isOK()) { return typeExpr.getStatus(); } auto andExpr = stdx::make_unique<AndMatchExpression>(); if (auto propertiesElt = keywordMap[kSchemaPropertiesKeyword]) { auto propertiesExpr = _parseProperties(path, propertiesElt, typeExpr.getValue().get()); if (!propertiesExpr.isOK()) { return propertiesExpr; } andExpr->add(propertiesExpr.getValue().release()); } if (auto maximumElt = keywordMap[kSchemaMaximumKeyword]) { bool isExclusiveMaximum = false; if (auto exclusiveMaximumElt = keywordMap[kSchemaExclusiveMaximumKeyword]) { if (!exclusiveMaximumElt.isBoolean()) { return {Status(ErrorCodes::TypeMismatch, str::stream() << "$jsonSchema keyword '" << kSchemaExclusiveMaximumKeyword << "' must be a boolean")}; } else { isExclusiveMaximum = exclusiveMaximumElt.boolean(); } } auto maxExpr = parseMaximum(path, maximumElt, typeExpr.getValue().get(), isExclusiveMaximum); if (!maxExpr.isOK()) { return maxExpr; } andExpr->add(maxExpr.getValue().release()); } else if (keywordMap[kSchemaExclusiveMaximumKeyword]) { // If "exclusiveMaximum" is present, "maximum" must also be present. return {Status(ErrorCodes::FailedToParse, str::stream() << "$jsonSchema keyword '" << kSchemaMaximumKeyword << "' must be a present if " << kSchemaExclusiveMaximumKeyword << " is present")}; } if (auto minimumElt = keywordMap[kSchemaMinimumKeyword]) { bool isExclusiveMinimum = false; if (auto exclusiveMinimumElt = keywordMap[kSchemaExclusiveMinimumKeyword]) { if (!exclusiveMinimumElt.isBoolean()) { return {Status(ErrorCodes::TypeMismatch, str::stream() << "$jsonSchema keyword '" << kSchemaExclusiveMinimumKeyword << "' must be a boolean")}; } else { isExclusiveMinimum = exclusiveMinimumElt.boolean(); } } auto minExpr = parseMinimum(path, minimumElt, typeExpr.getValue().get(), isExclusiveMinimum); if (!minExpr.isOK()) { return minExpr; } andExpr->add(minExpr.getValue().release()); } else if (keywordMap[kSchemaExclusiveMinimumKeyword]) { // If "exclusiveMinimum" is present, "minimum" must also be present. return {Status(ErrorCodes::FailedToParse, str::stream() << "$jsonSchema keyword '" << kSchemaMinimumKeyword << "' must be a present if " << kSchemaExclusiveMinimumKeyword << " is present")}; } if (auto maxLengthElt = keywordMap[kSchemaMaxLengthKeyword]) { auto maxLengthExpr = parseStrLength<InternalSchemaMaxLengthMatchExpression>( path, maxLengthElt, typeExpr.getValue().get(), kSchemaMaxLengthKeyword); if (!maxLengthExpr.isOK()) { return maxLengthExpr; } andExpr->add(maxLengthExpr.getValue().release()); } if (auto minLengthElt = keywordMap[kSchemaMinLengthKeyword]) { auto minLengthExpr = parseStrLength<InternalSchemaMinLengthMatchExpression>( path, minLengthElt, typeExpr.getValue().get(), kSchemaMinLengthKeyword); if (!minLengthExpr.isOK()) { return minLengthExpr; } andExpr->add(minLengthExpr.getValue().release()); } if (auto patternElt = keywordMap[kSchemaPatternKeyword]) { auto patternExpr = parsePattern(path, patternElt, typeExpr.getValue().get()); if (!patternExpr.isOK()) { return patternExpr; } andExpr->add(patternExpr.getValue().release()); } if (auto multipleOfElt = keywordMap[kSchemaMultipleOfKeyword]) { auto multipleOfExpr = parseMultipleOf(path, multipleOfElt, typeExpr.getValue().get()); if (!multipleOfExpr.isOK()) { return multipleOfExpr; } andExpr->add(multipleOfExpr.getValue().release()); } if (path.empty() && typeExpr.getValue() && typeExpr.getValue()->getBSONType() != BSONType::Object) { // This is a top-level schema which requires that the type is something other than // "object". Since we only know how to store objects, this schema matches nothing. return {stdx::make_unique<AlwaysFalseMatchExpression>()}; } if (!path.empty() && typeExpr.getValue()) { andExpr->add(makeTypeRestriction(std::move(typeExpr.getValue())).release()); } return {std::move(andExpr)}; }