/** * Copyright (C) 2021-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/db/timeseries/flat_bson.h" #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/update/document_diff_serialization.h" namespace mongo::timeseries { namespace { constexpr int32_t kMaxLinearSearchLength = 12; constexpr StringData kArrayFieldName = "\0"_sd; // Use a string that is illegal to represent fields in BSON int typeComp(const BSONElement& elem, BSONType type) { return elem.canonicalType() - canonicalizeBSONType(type); }; } // namespace template typename FlatBSONStore::Type FlatBSONStore::Data::type() const { return _type; } template const Value& FlatBSONStore::Data::value() const { return _value; } /** * Flag to indicate if this Data was updated since last clear. */ template bool FlatBSONStore::Data::updated() const { return _updated; } /** * Clear update flag. */ template void FlatBSONStore::Data::clearUpdated() { _updated = false; } template void FlatBSONStore::Data::setUnset() { _type = Type::kUnset; _updated = false; } template void FlatBSONStore::Data::setObject() { _type = Type::kObject; _updated = true; } template void FlatBSONStore::Data::setArray() { _type = Type::kArray; _updated = true; } template void FlatBSONStore::Data::setValue(const BSONElement& elem) { _value.set(elem); _type = Type::kValue; _updated = true; } template FlatBSONStore::Iterator::Iterator( typename FlatBSONStore::Entries::iterator pos) : _pos(pos) {} template typename FlatBSONStore::Iterator::pointer FlatBSONStore::Iterator:: operator->() { return &_pos->_element; } template typename FlatBSONStore::Iterator::reference FlatBSONStore::Iterator::operator*() { return _pos->_element; } template typename FlatBSONStore::Iterator& FlatBSONStore::Iterator:: operator++() { _pos += _pos->_offsetEnd; return *this; } template bool FlatBSONStore::Iterator::operator==( const FlatBSONStore::Iterator& rhs) const { return _pos == rhs._pos; } template bool FlatBSONStore::Iterator::operator!=( const FlatBSONStore::Iterator& rhs) const { return !operator==(rhs); } template FlatBSONStore::ConstIterator::ConstIterator( typename FlatBSONStore::Entries::const_iterator pos) : _pos(pos) {} template typename FlatBSONStore::ConstIterator::pointer FlatBSONStore::ConstIterator::operator->() const { return &_pos->_element; } template typename FlatBSONStore::ConstIterator::reference FlatBSONStore::ConstIterator::operator*() const { return _pos->_element; } template typename FlatBSONStore::ConstIterator& FlatBSONStore::ConstIterator::operator++() { _pos += _pos->_offsetEnd; return *this; } template bool FlatBSONStore::ConstIterator::operator==(const ConstIterator& rhs) const { return _pos == rhs._pos; } template bool FlatBSONStore::ConstIterator::operator!=( const FlatBSONStore::ConstIterator& rhs) const { return !operator==(rhs); } template FlatBSONStore::Obj::Obj( FlatBSONStore::Entries& entries, typename FlatBSONStore::Entries::iterator pos) : _entries(entries), _pos(pos) {} template typename FlatBSONStore::Obj& FlatBSONStore::Obj::operator=( const FlatBSONStore::Obj& rhs) { if (this != &rhs) { _pos = rhs._pos; } return *this; } template typename FlatBSONStore::Obj FlatBSONStore::Obj::object( FlatBSONStore::Iterator pos) const { return {_entries, pos._pos}; } template typename FlatBSONStore::Obj FlatBSONStore::Obj::parent() const { return {_entries, _pos - _pos->_offsetParent}; } template typename FlatBSONStore::Iterator FlatBSONStore::Obj::iterator() const { return {_pos}; } template Element& FlatBSONStore::Obj::element() { return _pos->_element; } template const Element& FlatBSONStore::Obj::element() const { return _pos->_element; } template typename FlatBSONStore::Iterator FlatBSONStore::Obj::search( FlatBSONStore::Iterator first, FlatBSONStore::Iterator last, StringData fieldName) { // Use fast lookup if available. if (_pos->_fieldNameToIndex) { auto it = _pos->_fieldNameToIndex->find(fieldName); if (it == _pos->_fieldNameToIndex->end()) { return last; } return {_pos + it->second}; } // Perform linear search forward. int remainingLinearSearch = kMaxLinearSearchLength; for (; first != last && remainingLinearSearch != 0; ++first, --remainingLinearSearch) { // Entry found. if (first->fieldName() == fieldName) { return first; } // Found entry that is used for an Array, we can claim this field. if (first->isArrayFieldName()) { first->claimArrayFieldNameForObject(fieldName.toString()); return first; } } // Return if we reached our end when doing linear search. if (first == last) { return last; } // We've exhausted the linear search limit, create a map to speedup future searches. Populate it // with all current subelements. _pos->_fieldNameToIndex = std::make_unique>(); auto it = begin(); auto itEnd = end(); for (; it != itEnd; ++it) { (*_pos->_fieldNameToIndex)[it->fieldName().toString()] = it._pos->_offsetParent; } // Retry the search now when the map is created. return search(first, last, fieldName); } template typename FlatBSONStore::Iterator FlatBSONStore::Obj::search( FlatBSONStore::Iterator first, StringData fieldName) { return search(first, end(), fieldName); } template std::pair::Iterator, typename FlatBSONStore::Iterator> FlatBSONStore::Obj::insert(FlatBSONStore::Iterator pos, std::string fieldName) { // Remember our iterator position so we can restore it after inserting a new element. auto index = std::distance(_entries.begin(), _pos); auto inserted = _entries.emplace(pos._pos); _pos = _entries.begin() + index; // Setup our newly created entry. inserted->_offsetEnd = 1; // no subelements inserted->_element.setFieldName(std::move(fieldName)); inserted->_offsetParent = std::distance(_pos, inserted); // Also store our offset in the fast lookup map if it is available. if (_pos->_fieldNameToIndex) { _pos->_fieldNameToIndex->emplace(inserted->_element.fieldName(), inserted->_offsetParent); } // We need to traverse the hiearchy up to the root and modify stored offsets to account for // this newly created entry. All entries with subobjects got their end-offset pushed by one. // All siblings after this entry got their offset to the parent pushed by one. auto it = inserted; auto parent = _pos; // Root object has "self" as parent. while (it != parent) { ++parent->_offsetEnd; auto next = std::next(Iterator(it)); auto end = Iterator(parent + parent->_offsetEnd); for (; next != end; ++next) { ++next._pos->_offsetParent; if (parent->_fieldNameToIndex) { ++parent->_fieldNameToIndex->at(next._pos->_element.fieldName()); } } it = parent; parent = parent - parent->_offsetParent; } return std::make_pair(Iterator(inserted), end()); } template typename FlatBSONStore::Iterator FlatBSONStore::Obj::begin() { return {_pos + 1}; } template typename FlatBSONStore::Iterator FlatBSONStore::Obj::end() { return {_pos + _pos->_offsetEnd}; } template typename FlatBSONStore::ConstIterator FlatBSONStore::Obj::begin() const { return {_pos + 1}; } template typename FlatBSONStore::ConstIterator FlatBSONStore::Obj::end() const { return {_pos + _pos->_offsetEnd}; } template FlatBSONStore::FlatBSONStore() { auto& entry = entries.emplace_back(); entry._offsetEnd = 1; entry._offsetParent = 0; entry._element.initializeRoot(); } template typename std::string FlatBSON::updateStatusString( UpdateStatus updateStatus) { switch (updateStatus) { case UpdateStatus::Updated: return "updated"; case UpdateStatus::Failed: return "failed"; case UpdateStatus::NoChange: return "no change"; } MONGO_UNREACHABLE; } template typename FlatBSON::UpdateStatus FlatBSON::update( const BSONObj& doc, boost::optional omitField, const StringData::ComparatorInterface* stringComparator) { auto obj = _store.root(); return _updateObj(obj, doc, {}, stringComparator, [&omitField](StringData fieldName) { return omitField && fieldName == omitField; }); } template std::tuple::UpdateStatus, typename FlatBSONStore::Iterator, typename FlatBSONStore::Iterator> FlatBSON::_update( typename FlatBSONStore::Obj obj, BSONElement elem, typename Element::UpdateContext updateValues, const StringData::ComparatorInterface* stringComparator) { UpdateStatus status{UpdateStatus::Updated}; if (elem.type() == Object) { std::tie(status, updateValues) = Derived::_shouldUpdateObj(obj, elem, updateValues); // Compare objects element-wise if the stored data may need to be updated. if (status == UpdateStatus::Updated) { status = _updateObj( obj, elem.Obj(), updateValues, stringComparator, [](StringData fieldName) { return false; }); } } else if (elem.type() == Array) { std::tie(status, updateValues) = Derived::_shouldUpdateArr(obj, elem, updateValues); // Compare objects element-wise if the stored data may need to be updated. if (status == UpdateStatus::Updated) { // Use Obj() instead of Array() to avoid instantiating a temporary std::vector. auto elemArray = elem.Obj(); auto elemIt = elemArray.begin(); auto elemEnd = elemArray.end(); auto it = obj.begin(); auto end = obj.end(); for (; elemIt != elemEnd && status != UpdateStatus::Failed; ++elemIt) { UpdateStatus subStatus{UpdateStatus::NoChange}; if (it == end) { std::tie(it, end) = obj.insert(it, kArrayFieldName.toString()); } std::tie(subStatus, it, end) = _update(obj.object(it), *elemIt, updateValues, stringComparator); if (subStatus != UpdateStatus::NoChange) { status = subStatus; } obj = obj.object(it).parent(); ++it; } } } else { // For all scalar types, just update the value directly if needed. status = Derived::_maybeUpdateValue(obj, elem, updateValues, stringComparator); } return {status, obj.iterator(), obj.parent().end()}; } template typename FlatBSON::UpdateStatus FlatBSON::_updateObj( typename FlatBSONStore::Obj& obj, const BSONObj& doc, typename Element::UpdateContext updateContext, const StringData::ComparatorInterface* stringComparator, std::function skipFieldFn) { auto it = obj.begin(); auto end = obj.end(); int allHandledOffset = 0; bool skipped = false; UpdateStatus status{UpdateStatus::NoChange}; for (auto&& elem : doc) { StringData fieldName = elem.fieldNameStringData(); if (skipFieldFn(fieldName)) { continue; } if (it == end && skipped) { // If we are at end but have skipped elements along the way we need to go back and // search in the skipped elements. We do not have to search over the consecutive range // in the beginning of elements already handled. auto begin = obj.begin(); std::advance(begin, allHandledOffset); it = obj.search(begin, fieldName); } if (it == end) { // Field missing, we need to insert it at the end so we preserve the input field order. std::tie(it, end) = obj.insert(it, fieldName.toString()); } else if (it->isArrayFieldName()) { // Entry is only used for Arrays, we can claim the field name. it->claimArrayFieldNameForObject(fieldName.toString()); } else if (it->fieldName() != fieldName) { // Traversing the FlatBSONStore structure in lock-step with the input document resulted // in a miss. This means one of two things. (1) The input document does not contain all // prevously-encountered fields and we need to skip over missing ones. Or (2) the input // document has a different internal field order than previous inserts. We begin by // searching forward to see if we are case (1). auto found = obj.search(std::next(it), fieldName); if (found == end) { // Field not found. We can either be in case (2) or this is a new field that needs // to be inserted. if (skipped) { // Search over the skipped elements in case we are actually in case (2). auto begin = obj.begin(); std::advance(begin, allHandledOffset); found = obj.search(begin, it, fieldName); if (found == it) { // Still not found, insert the new field. Location doesn't matter much // as we are operating on incoming documents of different field orders. // Select the point we know furthest back. std::tie(it, end) = obj.insert(it, fieldName.toString()); } else { it = found; } } else { // All previous elements have been found as we have never skipped, proceed // with inserting this new field. std::tie(it, end) = obj.insert(it, fieldName.toString()); } } else { it = found; skipped = true; } } // It points to either a found existing or a newly inserted entry at this point. Recursively // update it. UpdateStatus subStatus{UpdateStatus::NoChange}; std::tie(subStatus, it, end) = _update(obj.object(it), elem, updateContext, stringComparator); if (subStatus != UpdateStatus::NoChange) { status = subStatus; } // Re-construct obj from the returned iterator. If an insert was performed inside // _update it would dangle. obj = obj.object(it).parent(); // Advance iterator and advance the all handled offset if we have not skipped anything. ++it; if (!skipped) { ++allHandledOffset; } if (status == UpdateStatus::Failed) { // We can fail early to save some time iterating over the rest of the structure. break; } } return status; } template template void FlatBSON::_append(typename FlatBSONStore::Obj obj, BSONObjBuilder* builder, GetDataFn getData) { for (auto it = obj.begin(); it != obj.end(); ++it) { const auto& data = getData(*it); if (data.type() == FlatBSONStore::Type::kValue) { builder->appendAs(data.value().get(), it->fieldName()); } else if (data.type() == FlatBSONStore::Type::kObject) { BSONObjBuilder subObj(builder->subobjStart(it->fieldName())); _append(obj.object(it), &subObj, getData); } else if (data.type() == FlatBSONStore::Type::kArray) { BSONArrayBuilder subArr(builder->subarrayStart(it->fieldName())); _append(obj.object(it), &subArr, getData); } if (data.updated()) _clearUpdated(it, getData); } } template template void FlatBSON::_append(typename FlatBSONStore::Obj obj, BSONArrayBuilder* builder, GetDataFn getData) { for (auto it = obj.begin(); it != obj.end(); ++it) { const auto& data = getData(*it); if (data.type() == FlatBSONStore::Type::kValue) { builder->append(data.value().get()); } else if (data.type() == FlatBSONStore::Type::kObject) { BSONObjBuilder subObj(builder->subobjStart()); _append(obj.object(it), &subObj, getData); } else if (data.type() == FlatBSONStore::Type::kArray) { BSONArrayBuilder subArr(builder->subarrayStart()); _append(obj.object(it), &subArr, getData); } if (data.updated()) _clearUpdated(it, getData); } } template template bool FlatBSON::_appendUpdates( typename FlatBSONStore::Obj obj, BSONObjBuilder* builder, GetDataFn getData) { const typename FlatBSONStore::Data& data = getData(obj.element()); invariant((data.type() == FlatBSONStore::Type::kObject || data.type() == FlatBSONStore::Type::kArray)); bool appended = false; if (data.type() == FlatBSONStore::Type::kObject) { bool hasUpdateSection = false; BSONObjBuilder updateSection; StringMap subDiffs; for (auto it = obj.begin(); it != obj.end(); ++it) { const auto& subdata = getData(*it); if (subdata.updated()) { if (subdata.type() == FlatBSONStore::Type::kObject) { BSONObjBuilder subObj(updateSection.subobjStart(it->fieldName())); _append(obj.object(it), &subObj, getData); } else if (subdata.type() == FlatBSONStore::Type::kArray) { BSONArrayBuilder subArr(updateSection.subarrayStart(it->fieldName())); _append(obj.object(it), &subArr, getData); } else { updateSection.appendAs(subdata.value().get(), it->fieldName()); } _clearUpdated(it, getData); appended = true; hasUpdateSection = true; } else if (subdata.type() != FlatBSONStore::Type::kValue && subdata.type() != FlatBSONStore::Type::kUnset) { BSONObjBuilder subDiff; if (_appendUpdates(obj.object(it), &subDiff, getData)) { // An update occurred at a lower level, so append the sub diff. subDiffs[doc_diff::kSubDiffSectionFieldPrefix + std::string(it->fieldName())] = subDiff.obj(); appended = true; }; } } if (hasUpdateSection) { builder->append(doc_diff::kUpdateSectionFieldName, updateSection.done()); } // Sub diffs are required to come last. for (auto& subDiff : subDiffs) { builder->append(subDiff.first, std::move(subDiff.second)); } } else { builder->append(doc_diff::kArrayHeader, true); DecimalCounter count; for (auto it = obj.begin(); it != obj.end(); ++it) { const auto& subdata = getData(*it); if (subdata.updated()) { std::string updateFieldName = str::stream() << doc_diff::kUpdateSectionFieldName << StringData(count); if (subdata.type() == FlatBSONStore::Type::kObject) { BSONObjBuilder subObj(builder->subobjStart(updateFieldName)); _append(obj.object(it), &subObj, getData); } else if (subdata.type() == FlatBSONStore::Type::kArray) { BSONArrayBuilder subArr(builder->subarrayStart(updateFieldName)); _append(obj.object(it), &subArr, getData); } else { builder->appendAs(subdata.value().get(), updateFieldName); } _clearUpdated(it, getData); appended = true; } else if (subdata.type() != FlatBSONStore::Type::kValue && subdata.type() != FlatBSONStore::Type::kUnset) { BSONObjBuilder subDiff; if (_appendUpdates(obj.object(it), &subDiff, getData)) { // An update occurred at a lower level, so append the sub diff. builder->append(str::stream() << doc_diff::kSubDiffSectionFieldPrefix << StringData(count), subDiff.done()); appended = true; } } ++count; } } return appended; } template template void FlatBSON::_clearUpdated( typename FlatBSONStore::Iterator elem, GetDataFn getData) { auto& data = getData(*elem); data.clearUpdated(); if (data.type() == FlatBSONStore::Type::kObject || data.type() == FlatBSONStore::Type::kArray) { auto obj = _store.root().object(elem); for (auto it = obj.begin(); it != obj.end(); ++it) { _clearUpdated(it, getData); } } } template template void FlatBSON::_setTypeObject( typename FlatBSONStore::Obj& obj, GetDataFn getData) { auto prev = getData(obj.element()).type(); if (prev != FlatBSONStore::Type::kObject) { getData(obj.element()).setObject(); for (auto& subelem : obj) { getData(subelem).setUnset(); } } } template template void FlatBSON::_setTypeArray( typename FlatBSONStore::Obj& obj, GetDataFn getData) { auto prev = getData(obj.element()).type(); if (prev != FlatBSONStore::Type::kArray) { getData(obj.element()).setArray(); for (auto& subelem : obj) { getData(subelem).setUnset(); } } } BSONElement BSONElementValue::get() const { return BSONElement(_buffer.get(), 1, _size); } void BSONElementValue::set(const BSONElement& elem) { auto requiredSize = elem.size() - elem.fieldNameSize() + 1; if (_size < requiredSize) { _buffer = std::make_unique(requiredSize); } // Store element as BSONElement buffer but strip out the field name. _buffer[0] = elem.type(); _buffer[1] = '\0'; memcpy(_buffer.get() + 2, elem.value(), elem.valuesize()); _size = requiredSize; } BSONType BSONElementValue::type() const { return (BSONType)_buffer[0]; } BSONElement BSONTypeValue::get() const { MONGO_UNREACHABLE; } void BSONTypeValue::set(const BSONElement& elem) { _type = elem.type(); } BSONType BSONTypeValue::type() const { return _type; } StringData Element::fieldName() const { return _fieldName; } void Element::setFieldName(std::string&& fieldName) { _fieldName = std::move(fieldName); } bool Element::isArrayFieldName() const { return _fieldName == kArrayFieldName; } void Element::claimArrayFieldNameForObject(std::string name) { invariant(isArrayFieldName()); _fieldName = std::move(name); } void MinMaxElement::initializeRoot() { _min.setObject(); _max.setObject(); } MinMaxStore::Data& MinMaxElement::min() { return _min; } const MinMaxStore::Data& MinMaxElement::min() const { return _min; } MinMaxStore::Data& MinMaxElement::max() { return _max; } const MinMaxStore::Data& MinMaxElement::max() const { return _max; } std::pair MinMax::_shouldUpdateObj( MinMaxStore::Obj& obj, const BSONElement& elem, MinMaxElement::UpdateContext updateValues) { auto shouldUpdateObject = [&](MinMaxStore::Data& data, auto comp) { return data.type() == MinMaxStore::Type::kObject || data.type() == MinMaxStore::Type::kUnset || (data.type() == MinMaxStore::Type::kArray && comp(typeComp(elem, Array), 0)) || (data.type() == MinMaxStore::Type::kValue && comp(typeComp(elem, data.value().type()), 0)); }; bool updateMin = updateValues.min && shouldUpdateObject(obj.element().min(), std::less{}); if (updateMin) { _setTypeObject(obj, GetMin{}); } bool updateMax = updateValues.max && shouldUpdateObject(obj.element().max(), std::greater{}); if (updateMax) { _setTypeObject(obj, GetMax{}); } return std::make_pair((updateMin || updateMax) ? UpdateStatus::Updated : UpdateStatus::NoChange, MinMaxElement::UpdateContext{updateMin, updateMax}); } std::pair MinMax::_shouldUpdateArr( MinMaxStore::Obj& obj, const BSONElement& elem, MinMaxElement::UpdateContext updateValues) { auto shouldUpdateArray = [&](MinMaxStore::Data& data, auto comp) { return data.type() == MinMaxStore::Type::kArray || data.type() == MinMaxStore::Type::kUnset || (data.type() == MinMaxStore::Type::kObject && comp(typeComp(elem, Object), 0)) || (data.type() == MinMaxStore::Type::kValue && comp(typeComp(elem, data.value().type()), 0)); }; bool updateMin = updateValues.min && shouldUpdateArray(obj.element().min(), std::less{}); if (updateMin) { _setTypeArray(obj, GetMin{}); } bool updateMax = updateValues.max && shouldUpdateArray(obj.element().max(), std::greater{}); if (updateMax) { _setTypeArray(obj, GetMax{}); } return std::make_pair((updateMin || updateMax) ? UpdateStatus::Updated : UpdateStatus::NoChange, MinMaxElement::UpdateContext{updateMin, updateMax}); } MinMax::UpdateStatus MinMax::_maybeUpdateValue( MinMaxStore::Obj& obj, const BSONElement& elem, MinMaxElement::UpdateContext updateValues, const StringData::ComparatorInterface* stringComparator) { auto maybeUpdateValue = [&](MinMaxStore::Data& data, auto comp) { if (data.type() == MinMaxStore::Type::kUnset || (data.type() == MinMaxStore::Type::kObject && comp(typeComp(elem, Object), 0)) || (data.type() == MinMaxStore::Type::kArray && comp(typeComp(elem, Array), 0)) || (data.type() == MinMaxStore::Type::kValue && comp(elem.woCompare(data.value().get(), false, stringComparator), 0))) { data.setValue(elem); } }; if (updateValues.min) { maybeUpdateValue(obj.element().min(), std::less<>{}); } if (updateValues.max) { maybeUpdateValue(obj.element().max(), std::greater<>{}); } return (updateValues.min || updateValues.max) ? UpdateStatus::Updated : UpdateStatus::NoChange; } BSONObj MinMax::min() { BSONObjBuilder builder; _append(_store.root(), &builder, GetMin()); return builder.obj(); } BSONObj MinMax::max() { BSONObjBuilder builder; _append(_store.root(), &builder, GetMax()); return builder.obj(); } BSONObj MinMax::minUpdates() { BSONObjBuilder builder; [[maybe_unused]] auto appended = _appendUpdates(_store.root(), &builder, GetMin()); return builder.obj(); } BSONObj MinMax::maxUpdates() { BSONObjBuilder builder; [[maybe_unused]] auto appended = _appendUpdates(_store.root(), &builder, GetMax()); return builder.obj(); } MinMax MinMax::parseFromBSON(const BSONObj& min, const BSONObj& max, const StringData::ComparatorInterface* stringComparator) { MinMax minmax; // The metadata field is already excluded from generated min/max summaries. UpdateStatus status = minmax.update(min, /*metaField=*/boost::none, stringComparator); uassert(ErrorCodes::BadValue, str::stream() << "Failed to update min: " << updateStatusString(status), status != UpdateStatus::Failed); status = minmax.update(max, /*metaField=*/boost::none, stringComparator); uassert(ErrorCodes::BadValue, str::stream() << "Failed to update max: " << updateStatusString(status), status != UpdateStatus::Failed); // Clear the updated state as we're only constructing the object from an existing document. [[maybe_unused]] auto minUpdates = minmax.minUpdates(); [[maybe_unused]] auto maxUpdates = minmax.maxUpdates(); return minmax; } void SchemaElement::initializeRoot() { _data.setObject(); } SchemaStore::Data& SchemaElement::data() { return _data; } const SchemaStore::Data& SchemaElement::data() const { return _data; } Schema Schema::parseFromBSON(const BSONObj& min, const BSONObj& max, const StringData::ComparatorInterface* stringComparator) { Schema schema; // The metadata field is already excluded from generated min/max summaries. UpdateStatus status = schema.update(min, /*metaField=*/boost::none, stringComparator); uassert(ErrorCodes::BadValue, str::stream() << "Failed to update min: " << updateStatusString(status), status != UpdateStatus::Failed); status = schema.update(max, /*metaField=*/boost::none, stringComparator); uassert(ErrorCodes::BadValue, str::stream() << "Failed to update max: " << updateStatusString(status), status != UpdateStatus::Failed); return schema; } std::pair Schema::_shouldUpdateObj( SchemaStore::Obj& obj, const BSONElement& elem, SchemaElement::UpdateContext) { UpdateStatus status{UpdateStatus::Updated}; SchemaStore::Data& data = obj.element().data(); if (data.type() == SchemaStore::Type::kUnset) { _setTypeObject(obj, GetData{}); } else if (data.type() != SchemaStore::Type::kObject) { // Type mismatch status = UpdateStatus::Failed; } return std::make_pair(status, SchemaElement::UpdateContext{}); } std::pair Schema::_shouldUpdateArr( SchemaStore::Obj& obj, const BSONElement& elem, SchemaElement::UpdateContext) { UpdateStatus status{UpdateStatus::Updated}; SchemaStore::Data& data = obj.element().data(); if (data.type() == SchemaStore::Type::kUnset) { _setTypeArray(obj, GetData{}); } else if (data.type() != SchemaStore::Type::kArray) { // Type mismatch status = UpdateStatus::Failed; } return std::make_pair(status, SchemaElement::UpdateContext{}); } Schema::UpdateStatus Schema::_maybeUpdateValue( SchemaStore::Obj& obj, const BSONElement& elem, SchemaElement::UpdateContext, const StringData::ComparatorInterface* stringComparator) { UpdateStatus status{UpdateStatus::Updated}; SchemaStore::Data& data = obj.element().data(); if (data.type() == SchemaStore::Type::kUnset) { data.setValue(elem); } else if (typeComp(elem, data.value().type()) != 0) { // Type mismatch status = UpdateStatus::Failed; } return status; } // Instantiations. template class FlatBSONStore; template class FlatBSON; template class FlatBSONStore; template class FlatBSON; } // namespace mongo::timeseries