FieldRef UpdateIndexData::getCanonicalIndexField(const FieldRef& path) { if (path.numParts() <= 1) return path; // The first part of the path must always be a valid field name, since it's not possible to // store a top-level array or '$' field name in a document. FieldRef buf(path.getPart(0)); for (size_t i = 1; i < path.numParts(); ++i) { auto pathComponent = path.getPart(i); if (pathComponent == "$"_sd) { continue; } if (FieldRef::isNumericPathComponentLenient(pathComponent)) { // Peek ahead to see if the next component is also all digits. This implies that the // update is attempting to create a numeric field name which would violate the // "ambiguous field name in array" constraint for multi-key indexes. Break early in this // case and conservatively return that this path affects the prefix of the consecutive // numerical path components. For instance, an input such as 'a.0.1.b.c' would return // the canonical index path of 'a'. if ((i + 1) < path.numParts() && FieldRef::isNumericPathComponentLenient(path.getPart(i + 1))) { break; } continue; } buf.appendPart(pathComponent); } return buf; }
Status setElementAtPath(const FieldRef& path, const BSONElement& value, mutablebson::Document* doc) { size_t deepestElemPathPart; mutablebson::Element deepestElem(doc->end()); // Get the existing parents of this path Status status = findLongestPrefix(path, doc->root(), &deepestElemPathPart, &deepestElem); // TODO: All this is pretty awkward, why not return the position immediately after the // consumed path or use a signed sentinel? Why is it a special case when we've consumed the // whole path? if (!status.isOK() && status.code() != ErrorCodes::NonExistentPath) return status; // Inc the path by one *unless* we matched nothing if (status.code() != ErrorCodes::NonExistentPath) { ++deepestElemPathPart; } else { deepestElemPathPart = 0; deepestElem = doc->root(); } if (deepestElemPathPart == path.numParts()) { // The full path exists already in the document, so just set a value return deepestElem.setValueBSONElement(value); } else { // Construct the rest of the path we need with empty documents and set the value StringData leafFieldName = path.getPart(path.numParts() - 1); mutablebson::Element leafElem = doc->makeElementWithNewFieldName(leafFieldName, value); dassert(leafElem.ok()); return createPathAt(path, deepestElemPathPart, deepestElem, leafElem); } }
Status basicIsUpdatable(const FieldRef& field) { StringData firstPart = field.getPart(0); if (firstPart.compare("_id") == 0) { return Status(ErrorCodes::BadValue, "updated cannot affect the _id"); } return Status::OK(); }
void BSONElementIterator::ArrayIterationState::reset(const FieldRef& ref, int start) { restOfPath = ref.dottedField(start).toString(); hasMore = restOfPath.size() > 0; if (hasMore) { nextPieceOfPath = ref.getPart(start); nextPieceOfPathIsNumber = isAllDigits(nextPieceOfPath); } else { nextPieceOfPathIsNumber = false; } }
bool hasArrayFilter(const FieldRef& fieldRef) { auto size = fieldRef.numParts(); for (size_t i = 0; i < size; i++) { auto fieldPart = fieldRef.getPart(i); if (isArrayFilterIdentifier(fieldPart)) { return true; } } return false; }
int FieldRef::compare(const FieldRef& other) const { const size_t toCompare = std::min(_size, other._size); for (size_t i = 0; i < toCompare; i++) { if (getPart(i) == other.getPart(i)) { continue; } return getPart(i) < other.getPart(i) ? -1 : 1; } const size_t rest = _size - toCompare; const size_t otherRest = other._size - toCompare; if ((rest == 0) && (otherRest == 0)) { return 0; } else if (rest < otherRest) { return -1; } else { return 1; } }
Status UpdateDriver::createFromQuery(const BSONObj& query, mutablebson::Document& doc) { BSONObjIteratorSorted i(query); while (i.more()) { BSONElement e = i.next(); // TODO: get this logic/exclude-list from the query system? if (e.fieldName()[0] == '$' || e.fieldNameStringData() == "_id") continue; if (e.type() == Object && e.embeddedObject().firstElementFieldName()[0] == '$') { // we have something like { x : { $gt : 5 } } // this can be a query piece // or can be a dbref or something int op = e.embeddedObject().firstElement().getGtLtOp(); if (op > 0) { // This means this is a $gt type filter, so don't make it part of the new // object. continue; } if (mongoutils::str::equals(e.embeddedObject().firstElement().fieldName(), "$not")) { // A $not filter operator is not detected in getGtLtOp() and should not // become part of the new object. continue; } } // Add to the field to doc after expanding and checking for conflicts. FieldRef elemName; const StringData& elemNameSD(e.fieldNameStringData()); elemName.parse(elemNameSD); size_t pos; mutablebson::Element* elemFound = NULL; Status status = pathsupport::findLongestPrefix(elemName, doc.root(), &pos, elemFound); // Not NonExistentPath, of OK, return if (!(status.code() == ErrorCodes::NonExistentPath || status.isOK())) return status; status = pathsupport::createPathAt(elemName, 0, doc.root(), doc.makeElementWithNewFieldName( elemName.getPart(elemName.numParts()-1), e)); if (!status.isOK()) return status; } return Status::OK(); }
Status isUpdatable(const FieldRef& field) { Status status = basicIsUpdatable(field); if (! status.isOK()) { return status; } StringData firstPart = field.getPart(0); if (firstPart[0] == '$') { return Status(ErrorCodes::BadValue, "field name cannot start with $"); } return Status::OK(); }
bool FieldRef::isPrefixOf( const FieldRef& other ) const { // Can't be a prefix if equal to or larger if ( other._size <= _size ) return false; for ( size_t i = 0; i < _size; i++ ) { // Get parts StringData part = getPart( i ); StringData otherPart = other.getPart( i ); // If the parts are diff, can't be a prefix if ( part != otherPart ) return false; } return true; }
size_t FieldRef::commonPrefixSize(const FieldRef& other) const { if (_size == 0 || other._size == 0) { return 0; } size_t maxPrefixSize = std::min(_size - 1, other._size - 1); size_t prefixSize = 0; while (prefixSize <= maxPrefixSize) { if (getPart(prefixSize) != other.getPart(prefixSize)) { break; } prefixSize++; } return prefixSize; }
bool isPositional(const FieldRef& fieldRef, size_t* pos, size_t* count) { // 'count' is optional. size_t dummy; if (count == NULL) { count = &dummy; } *count = 0; size_t size = fieldRef.numParts(); for (size_t i = 0; i < size; i++) { StringData fieldPart = fieldRef.getPart(i); if ((fieldPart.size() == 1) && (fieldPart[0] == '$')) { if (*count == 0) *pos = i; (*count)++; } } return *count > 0; }
BSONElement getFieldDottedOrArray( const BSONObj& doc, const FieldRef& path, size_t* idxPath ) { if ( path.numParts() == 0 ) return doc.getField( "" ); BSONElement res; BSONObj curr = doc; bool stop = false; size_t partNum = 0; while ( partNum < path.numParts() && !stop ) { res = curr.getField( path.getPart( partNum ) ); switch ( res.type() ) { case EOO: stop = true; break; case Object: curr = res.Obj(); ++partNum; break; case Array: stop = true; break; default: if ( partNum+1 < path.numParts() ) { res = BSONElement(); } stop = true; } } *idxPath = partNum; return res; }
Status isUpdatable(const FieldRef& field) { const size_t numParts = field.numParts(); if (numParts == 0) { return Status(ErrorCodes::EmptyFieldName, "An empty update path is not valid."); } for (size_t i = 0; i != numParts; ++i) { const StringData part = field.getPart(i); if (part.empty()) { return Status(ErrorCodes::EmptyFieldName, mongolutils::str::stream() << "The update path '" << field.dottedField() << "' contains an empty field name, which is not allowed."); } } return Status::OK(); }
bool UpdateIndexData::mightBeIndexed(const FieldRef& path) const { if (_allPathsIndexed) { return true; } FieldRef canonicalPath = getCanonicalIndexField(path); for (const auto& idx : _canonicalPaths) { if (_startsWith(canonicalPath, idx) || _startsWith(idx, canonicalPath)) return true; } for (const auto& pathComponent : _pathComponents) { for (size_t partIdx = 0; partIdx < path.numParts(); ++partIdx) { if (pathComponent == path.getPart(partIdx)) { return true; } } } return false; }
bool LeafMatchExpression::_matches( const FieldRef& fieldRef, const MatchableDocument* doc, MatchDetails* details ) const { bool traversedArray = false; size_t idxPath = 0; BSONElement e = doc->getFieldDottedOrArray( fieldRef, &idxPath, &traversedArray ); if ( e.type() != Array || traversedArray ) { return matchesSingleElement( e ); } string rest = fieldRef.dottedField( idxPath + 1 ); StringData next; bool nextIsNumber = false; if ( rest.size() > 0 ) { next = fieldRef.getPart( idxPath + 1 ); nextIsNumber = isAllDigits( next ); } BSONObjIterator i( e.Obj() ); while ( i.more() ) { BSONElement x = i.next(); bool found = false; if ( rest.size() == 0 ) { found = matchesSingleElement( x ); } else if ( x.type() == Object ) { FieldRef myFieldRef; myFieldRef.parse( rest ); BSONMatchableDocument myDoc( x.Obj() ); found = _matches( myFieldRef, &myDoc, NULL ); } if ( !found && nextIsNumber && next == x.fieldName() ) { string reallyNext = fieldRef.dottedField( idxPath + 2 ); if ( reallyNext.size() == 0 ) { found = matchesSingleElement( x ); } else if ( x.isABSONObj() ) { // TODO: this is slow FieldRef myFieldRef; myFieldRef.parse( "x." + reallyNext ); BSONObjBuilder b; b.appendAs( x, "x" ); BSONObj temp = b.obj(); BSONMatchableDocument myDoc( temp ); found = _matches( myFieldRef, &myDoc, NULL ); } } if ( found ) { if ( !_allHaveToMatch ) { if ( details && details->needRecord() ) { // this block doesn't have to be inside the _allHaveToMatch handler // but this matches the old semantics details->setElemMatchKey( x.fieldName() ); } return true; } } else if ( _allHaveToMatch ) { return false; } } if ( rest.size() > 0 ) { // we're supposed to have gone further down return false; } return matchesSingleElement( e ); }
Status ModifierPush::init(const BSONElement& modExpr) { // // field name analysis // // Break down the field name into its 'dotted' components (aka parts) and check that // the field is fit for updates. _fieldRef.parse(modExpr.fieldName()); Status status = fieldchecker::isUpdatable(_fieldRef); if (! status.isOK()) { return status; } // If a $-positional operator was used, get the index in which it occurred // and ensure only one occurrence. size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (foundDollar && foundCount > 1) { return Status(ErrorCodes::BadValue, "too many positional($) elements found."); } // // value analysis // // Are the target push values safe to store? BSONElement sliceElem; BSONElement sortElem; switch (modExpr.type()) { case Array: if (! modExpr.Obj().okForStorage()) { return Status(ErrorCodes::BadValue, "cannot use '$' or '.' as values"); } if (_pushMode == PUSH_ALL) { _eachMode = true; Status status = parseEachMode(PUSH_ALL, modExpr, &_eachElem, &sliceElem, &sortElem); if (!status.isOK()) { return status; } } else { _val = modExpr; } break; case Object: if (_pushMode == PUSH_ALL) { return Status(ErrorCodes::BadValue, "$pushAll requires an array of values"); } // If any known clause ($each, $slice, or $sort) is present, we'd assume // we're using the $each variation of push and would parse accodingly. _eachMode = inEachMode(modExpr); if (_eachMode) { Status status = parseEachMode(PUSH_NORMAL, modExpr, &_eachElem, &sliceElem, &sortElem); if (!status.isOK()) { return status; } } else { if (! modExpr.Obj().okForStorage()) { return Status(ErrorCodes::BadValue, "cannot use '$' as values"); } _val = modExpr; } break; default: if (_pushMode == PUSH_ALL) { return Status(ErrorCodes::BadValue, "$pushAll requires an array of values"); } _val = modExpr; break; } // Is slice present and correct? if (sliceElem.type() != EOO) { if (_pushMode == PUSH_ALL) { return Status(ErrorCodes::BadValue, "cannot use $slice in $pushAll"); } if (!sliceElem.isNumber()) { return Status(ErrorCodes::BadValue, "$slice must be a numeric value"); } // If the value of slice is not fraction, even if it's a double, we allow it. The // reason here is that the shell will use doubles by default unless told otherwise. double fractional = sliceElem.numberDouble(); if (fractional - static_cast<int64_t>(fractional) != 0) { return Status(ErrorCodes::BadValue, "$slice in $push cannot be fractional"); } _slice = sliceElem.numberLong(); if (_slice > 0) { return Status(ErrorCodes::BadValue, "$slice in $push must be zero or negative"); } _slicePresent = true; } // Is sort present and correct? if (sortElem.type() != EOO) { if (_pushMode == PUSH_ALL) { return Status(ErrorCodes::BadValue, "cannot use $sort in $pushAll"); } if (!_slicePresent) { return Status(ErrorCodes::BadValue, "$sort requires $slice to be present"); } else if (sortElem.type() != Object) { return Status(ErrorCodes::BadValue, "invalid $sort clause"); } BSONObj sortObj = sortElem.embeddedObject(); if (sortObj.isEmpty()) { return Status(ErrorCodes::BadValue, "sort parttern is empty"); } // Check if the sort pattern is sound. BSONObjIterator sortIter(sortObj); while (sortIter.more()) { BSONElement sortPatternElem = sortIter.next(); // We require either <field>: 1 or -1 for asc and desc. if (!isPatternElement(sortPatternElem)) { return Status(ErrorCodes::BadValue, "$sort elements' must be either 1 or -1"); } // All fields parts must be valid. FieldRef sortField; sortField.parse(sortPatternElem.fieldName()); if (sortField.numParts() == 0) { return Status(ErrorCodes::BadValue, "$sort field cannot be empty"); } for (size_t i = 0; i < sortField.numParts(); i++) { if (sortField.getPart(i).size() == 0) { return Status(ErrorCodes::BadValue, "empty field in dotted sort pattern"); } } } _sort = PatternElementCmp(sortElem.embeddedObject()); _sortPresent = true; } return Status::OK(); }
Status createPathAt(const FieldRef& prefix, size_t idxFound, mutablebson::Element elemFound, mutablebson::Element newElem) { Status status = Status::OK(); // Sanity check that 'idxField' is an actual part. const size_t size = prefix.numParts(); if (idxFound >= size) { return Status(ErrorCodes::BadValue, "index larger than path size"); } mutablebson::Document& doc = elemFound.getDocument(); // If we are creating children under an array and a numeric index is next, then perhaps // we need padding. size_t i = idxFound; bool inArray = false; if (elemFound.getType() == mongol::Array) { size_t newIdx = 0; if (!isNumeric(prefix.getPart(idxFound), &newIdx)) { return Status(ErrorCodes::InvalidPath, "Array require numeric fields"); } status = maybePadTo(&elemFound, newIdx); if (!status.isOK()) { return status; } // If there is a next field, that would be an array element. We'd like to mark that // field because we create array elements differently than we do regular objects. if (++i < size) { inArray = true; } } // Create all the remaining parts but the last one. for (; i < size - 1; i++) { mutablebson::Element elem = doc.makeElementObject(prefix.getPart(i)); if (!elem.ok()) { return Status(ErrorCodes::InternalError, "cannot create path"); } // If this field is an array element, we wrap it in an object (because array // elements are wraped in { "N": <element> } objects. if (inArray) { // TODO pass empty StringData to makeElementObject, when that's supported. mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */); if (!arrayObj.ok()) { return Status(ErrorCodes::InternalError, "cannot create item on array"); } status = arrayObj.pushBack(elem); if (!status.isOK()) { return status; } status = elemFound.pushBack(arrayObj); if (!status.isOK()) { return status; } inArray = false; } else { status = elemFound.pushBack(elem); if (!status.isOK()) { return status; } } elemFound = elem; } // Attach the last element. Here again, if we're in a field that is an array element, // we wrap it in an object first. if (inArray) { // TODO pass empty StringData to makeElementObject, when that's supported. mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */); if (!arrayObj.ok()) { return Status(ErrorCodes::InternalError, "cannot create item on array"); } status = arrayObj.pushBack(newElem); if (!status.isOK()) { return status; } status = elemFound.pushBack(arrayObj); if (!status.isOK()) { return status; } } else { status = elemFound.pushBack(newElem); if (!status.isOK()) { return status; } } return Status::OK(); }
Status findLongestPrefix(const FieldRef& prefix, mutablebson::Element root, size_t* idxFound, mutablebson::Element* elemFound) { // If root is empty or the prefix is so, there's no point in looking for a prefix. const size_t prefixSize = prefix.numParts(); if (!root.hasChildren() || prefixSize == 0) { return Status(ErrorCodes::NonExistentPath, "either the document or the path are empty"); } // Loop through prefix's parts. At each iteration, check that the part ('curr') exists // in 'root' and that the type of the previous part ('prev') allows for children. mutablebson::Element curr = root; mutablebson::Element prev = root; size_t i = 0; size_t numericPart = 0; bool viable = true; for (; i < prefixSize; i++) { // If prefix wants to reach 'curr' by applying a non-numeric index to an array // 'prev', or if 'curr' wants to traverse a leaf 'prev', then we'd be in a // non-viable path (see definition on the header file). StringData prefixPart = prefix.getPart(i); prev = curr; switch (curr.getType()) { case Object: curr = prev[prefixPart]; break; case Array: if (!isNumeric(prefixPart, &numericPart)) { viable = false; } else { curr = prev[numericPart]; } break; default: viable = false; } // If we couldn't find the next field part of the prefix in the document or if the // field part we're in constitutes a non-viable path, we can stop looking. if (!curr.ok() || !viable) { break; } } // We broke out of the loop because one of four things happened. (a) 'prefix' and // 'root' have nothing in common, (b) 'prefix' is not viable in 'root', (c) not all the // parts in 'prefix' exist in 'root', or (d) all parts do. In each case, we need to // figure out what index and Element pointer to return. if (i == 0) { return Status(ErrorCodes::NonExistentPath, "cannot find path in the document"); } else if (!viable) { *idxFound = i - 1; *elemFound = prev; return Status(ErrorCodes::PathNotViable, mongolutils::str::stream() << "cannot use the part (" << prefix.getPart(i - 1) << " of " << prefix.dottedField() << ") to traverse the element ({" << curr.toString() << "})"); } else if (curr.ok()) { *idxFound = i - 1; *elemFound = curr; return Status::OK(); } else { *idxFound = i - 1; *elemFound = prev; return Status::OK(); } }