StringData FieldRef::dottedSubstring(size_t startPart, size_t endPart) const { if (_size == 0 || startPart >= endPart || endPart > numParts()) return StringData(); if (!_replacements.empty() || _size != _cachedSize) reserialize(); dassert(_replacements.empty() && _size == _cachedSize); StringData result(_dotted); // Fast-path if we want the whole thing if (startPart == 0 && endPart == numParts()) return result; size_t startChar = 0; for (size_t i = 0; i < startPart; ++i) { startChar += getPart(i).size() + 1; // correct for '.' } size_t endChar = startChar; for (size_t i = startPart; i < endPart; ++i) { endChar += getPart(i).size() + 1; } // correct for last '.' if (endPart != numParts()) --endChar; return result.substr(startChar, endChar - startChar); }
bool FieldRef::hasNumericPathComponents() const { for (size_t i = 0; i < numParts(); ++i) { if (isNumericPathComponentStrict(i)) return true; } return false; }
std::set<size_t> FieldRef::getNumericPathComponents(size_t startPart) const { std::set<size_t> numericPathComponents; for (auto i = startPart; i < numParts(); ++i) { if (isNumericPathComponentStrict(i)) numericPathComponents.insert(i); } return numericPathComponents; }
std::string FieldRef::dottedField( size_t offset ) const { std::string res; if (_size == 0 || offset >= numParts() ) { return res; } for (size_t i=offset; i<_size; i++) { if ( i > offset ) res.append(1, '.'); StringData part = getPart(i); res.append(part.rawData(), part.size()); } return res; }
StringData FieldRef::dottedField(size_t offset) const { return dottedSubstring(offset, numParts()); }
UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const { // It would make sense to store fromFieldRef and toFieldRef as members during // RenameNode::init(), but FieldRef is not copyable. auto fromFieldRef = std::make_shared<FieldRef>(_val.fieldName()); FieldRef toFieldRef(_val.valueStringData()); mutablebson::Document& document = applyParams.element.getDocument(); size_t fromIdxFound; mutablebson::Element fromElement(document.end()); auto status = pathsupport::findLongestPrefix(*fromFieldRef, document.root(), &fromIdxFound, &fromElement); if (!status.isOK() || !fromElement.ok() || fromIdxFound != (fromFieldRef->numParts() - 1)) { // We could safely remove this restriction (thereby treating a rename with a non-viable // source path as a no-op), but most updates fail on an attempt to update a non-viable path, // so we throw an error for consistency. if (status == ErrorCodes::PathNotViable) { uassertStatusOK(status); MONGO_UNREACHABLE; // The previous uassertStatusOK should always throw. } // The element we want to rename does not exist. When that happens, we treat the operation // as a no-op. The attempted from/to paths are still considered modified. if (applyParams.modifiedPaths) { applyParams.modifiedPaths->keepShortest(*fromFieldRef); applyParams.modifiedPaths->keepShortest(toFieldRef); } return ApplyResult::noopResult(); } // Renaming through an array is prohibited. Check that our source path does not contain an // array. (The element being renamed may be an array, however.) for (auto currentElement = fromElement.parent(); currentElement != document.root(); currentElement = currentElement.parent()) { invariant(currentElement.ok()); if (BSONType::Array == currentElement.getType()) { auto idElem = mutablebson::findFirstChildNamed(document.root(), "_id"); uasserted(ErrorCodes::BadValue, str::stream() << "The source field cannot be an array element, '" << fromFieldRef->dottedField() << "' in doc with " << (idElem.ok() ? idElem.toString() : "no id") << " has an array field called '" << currentElement.getFieldName() << "'"); } } // Check that our destination path does not contain an array. (If the rename will overwrite an // existing element, that element may be an array. Iff pathToCreate is empty, "element" // represents an element that we are going to overwrite.) for (auto currentElement = applyParams.pathToCreate->empty() ? applyParams.element.parent() : applyParams.element; currentElement != document.root(); currentElement = currentElement.parent()) { invariant(currentElement.ok()); if (BSONType::Array == currentElement.getType()) { auto idElem = mutablebson::findFirstChildNamed(document.root(), "_id"); uasserted(ErrorCodes::BadValue, str::stream() << "The destination field cannot be an array element, '" << toFieldRef.dottedField() << "' in doc with " << (idElem.ok() ? idElem.toString() : "no id") << " has an array field called '" << currentElement.getFieldName() << "'"); } } // Once we've determined that the rename is valid and found the source element, the actual work // gets broken out into a $set operation and an $unset operation. Note that, generally, we // should call the init() method of a ModifierNode before calling its apply() method, but the // init() methods of SetElementNode and UnsetNode don't do anything, so we can skip them. SetElementNode setElement(fromElement); auto setElementApplyResult = setElement.apply(applyParams); ApplyParams unsetParams(applyParams); unsetParams.element = fromElement; unsetParams.pathToCreate = std::make_shared<FieldRef>(); unsetParams.pathTaken = fromFieldRef; UnsetNode unsetElement; auto unsetElementApplyResult = unsetElement.apply(unsetParams); // Determine the final result based on the results of the $set and $unset. ApplyResult applyResult; applyResult.indexesAffected = setElementApplyResult.indexesAffected || unsetElementApplyResult.indexesAffected; // The $unset would only be a no-op if the source element did not exist, in which case we would // have exited early with a no-op result. invariant(!unsetElementApplyResult.noop); return applyResult; }