/** * Copyright (c) 2011 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "pch.h" #include "mongo/db/pipeline/document.h" #include #include "mongo/db/jsobj.h" #include "mongo/db/pipeline/field_path.h" #include "mongo/util/mongoutils/str.h" namespace mongo { using namespace mongoutils; Position DocumentStorage::findField(StringData requested) const { int reqSize = requested.size(); // get size calculation out of the way if needed if (_numFields >= HASH_TAB_MIN) { // hash lookup const unsigned bucket = bucketForKey(requested); Position pos = _hashTab[bucket]; while (pos.found()) { const ValueElement& elem = getField(pos); if (elem.nameLen == reqSize && memcmp(requested.rawData(), elem._name, reqSize) == 0) { return pos; } // possible collision pos = elem.nextCollision; } } else { // linear scan for (DocumentStorageIterator it = iteratorAll(); !it.atEnd(); it.advance()) { if (it->nameLen == reqSize && memcmp(requested.rawData(), it->_name, reqSize) == 0) { return it.position(); } } } // if we got here, there's no such field return Position(); } Value& DocumentStorage::appendField(StringData name) { Position pos = getNextPosition(); const int nameSize = name.size(); // these are the same for everyone const Position nextCollision; const Value value; // Make room for new field (and padding at end for alignment) const unsigned newUsed = ValueElement::align(_usedBytes + sizeof(ValueElement) + nameSize); if (_buffer + newUsed > _bufferEnd) alloc(newUsed); _usedBytes = newUsed; // Append structure of a ValueElement char* dest = _buffer + pos.index; // must be after alloc since it changes _buffer #define append(x) memcpy(dest, &(x), sizeof(x)); dest += sizeof(x) append(value); append(nextCollision); append(nameSize); name.copyTo( dest, true ); // Padding for alignment handled above #undef append // Make sure next field starts where we expect it fassert(16486, getField(pos).next()->ptr() == _buffer + _usedBytes); _numFields++; if (_numFields > HASH_TAB_MIN) { addFieldToHashTable(pos); } else if (_numFields == HASH_TAB_MIN) { // adds all fields to hash table (including the one we just added) rehash(); } return getField(pos).val; } // Call after adding field to _fields and increasing _numFields void DocumentStorage::addFieldToHashTable(Position pos) { ValueElement& elem = getField(pos); elem.nextCollision = Position(); const unsigned bucket = bucketForKey(elem.nameSD()); Position* posPtr = &_hashTab[bucket]; while (posPtr->found()) { // collision: walk links and add new to end posPtr = &getField(*posPtr).nextCollision; } *posPtr = Position(pos.index); } void DocumentStorage::alloc(unsigned newSize) { const bool firstAlloc = !_buffer; const bool doingRehash = needRehash(); const size_t oldCapacity = _bufferEnd - _buffer; // make new bucket count big enough while (needRehash() || hashTabBuckets() < HASH_TAB_INIT_SIZE) _hashTabMask = hashTabBuckets()*2 - 1; // only allocate power-of-two sized space > 128 bytes size_t capacity = 128; while (capacity < newSize + hashTabBytes()) capacity *= 2; uassert(16490, "Tried to make oversized document", capacity <= size_t(BufferMaxSize)); boost::scoped_array oldBuf(_buffer); _buffer = new char[capacity]; _bufferEnd = _buffer + capacity - hashTabBytes(); if (!firstAlloc) { // This just copies the elements memcpy(_buffer, oldBuf.get(), _usedBytes); if (_numFields >= HASH_TAB_MIN) { // if we were hashing, deal with the hash table if (doingRehash) { rehash(); } else { // no rehash needed so just slide table down to new position memcpy(_hashTab, oldBuf.get() + oldCapacity, hashTabBytes()); } } } } void DocumentStorage::reserveFields(size_t expectedFields) { fassert(16487, !_buffer); unsigned buckets = HASH_TAB_INIT_SIZE; while (buckets < expectedFields) buckets *= 2; _hashTabMask = buckets - 1; // Using expectedFields+1 to allow space for long field names const size_t newSize = (expectedFields+1) * ValueElement::align(sizeof(ValueElement)); uassert(16491, "Tried to make oversized document", newSize <= size_t(BufferMaxSize)); _buffer = new char[newSize + hashTabBytes()]; _bufferEnd = _buffer + newSize; } intrusive_ptr DocumentStorage::clone() const { intrusive_ptr out (new DocumentStorage()); // Make a copy of the buffer. // It is very important that the positions of each field are the same after cloning. const size_t bufferBytes = (_bufferEnd + hashTabBytes()) - _buffer; out->_buffer = new char[bufferBytes]; out->_bufferEnd = out->_buffer + (_bufferEnd - _buffer); memcpy(out->_buffer, _buffer, bufferBytes); // Copy remaining fields out->_usedBytes = _usedBytes; out->_numFields = _numFields; out->_hashTabMask = _hashTabMask; // Tell values that they have been memcpyed (updates ref counts) for (DocumentStorageIterator it = out->iteratorAll(); !it.atEnd(); it.advance()) { it->val.memcpyed(); } return out; } DocumentStorage::~DocumentStorage() { boost::scoped_array deleteBufferAtScopeEnd (_buffer); for (DocumentStorageIterator it = iteratorAll(); !it.atEnd(); it.advance()) { it->val.~Value(); // explicit destructor call } } Document::Document(const BSONObj& bson) { MutableDocument md(bson.nFields()); BSONObjIterator it(bson); while(it.more()) { BSONElement bsonElement(it.next()); md.addField(bsonElement.fieldName(), Value(bsonElement)); } *this = md.freeze(); } BSONObjBuilder& operator << (BSONObjBuilderValueStream& builder, const Document& doc) { BSONObjBuilder subobj(builder.subobjStart()); doc.toBson(&subobj); subobj.doneFast(); return builder.builder(); } void Document::toBson(BSONObjBuilder* pBuilder) const { for (DocumentStorageIterator it = storage().iterator(); !it.atEnd(); it.advance()) { *pBuilder << it->nameSD() << it->val; } } MutableDocument::MutableDocument(size_t expectedFields) : _storageHolder(NULL) , _storage(_storageHolder) { if (expectedFields) { storage().reserveFields(expectedFields); } } MutableValue MutableDocument::getNestedFieldHelper(const FieldPath& dottedField, size_t level) { if (level == dottedField.getPathLength()-1) { return getField(dottedField.getFieldName(level)); } else { MutableDocument nested (getField(dottedField.getFieldName(level))); return nested.getNestedFieldHelper(dottedField, level+1); } } MutableValue MutableDocument::getNestedField(const FieldPath& dottedField) { fassert(16601, dottedField.getPathLength()); return getNestedFieldHelper(dottedField, 0); } MutableValue MutableDocument::getNestedFieldHelper(const vector& positions, size_t level) { if (level == positions.size()-1) { return getField(positions[level]); } else { MutableDocument nested (getField(positions[level])); return nested.getNestedFieldHelper(positions, level+1); } } MutableValue MutableDocument::getNestedField(const vector& positions) { fassert(16488, !positions.empty()); return getNestedFieldHelper(positions, 0); } static Value getNestedFieldHelper(const Document& doc, const FieldPath& fieldNames, vector* positions, size_t level) { const string& fieldName = fieldNames.getFieldName(level); const Position pos = doc.positionOf(fieldName); if (!pos.found()) return Value(); if (positions) positions->push_back(pos); if (level == fieldNames.getPathLength()-1) return doc.getField(pos); Value val = doc.getField(pos); if (val.getType() != Object) return Value(); return getNestedFieldHelper(val.getDocument(), fieldNames, positions, level+1); } const Value Document::getNestedField(const FieldPath& fieldNames, vector* positions) const { fassert(16489, fieldNames.getPathLength()); return getNestedFieldHelper(*this, fieldNames, positions, 0); } size_t Document::getApproximateSize() const { if (!_storage) return 0; // we've allocated no memory size_t size = sizeof(DocumentStorage); size += storage().allocatedBytes(); for (DocumentStorageIterator it = storage().iterator(); !it.atEnd(); it.advance()) { size += it->val.getApproximateSize(); size -= sizeof(Value); // already accounted for above } return size; } void Document::hash_combine(size_t &seed) const { for (DocumentStorageIterator it = storage().iterator(); !it.atEnd(); it.advance()) { StringData name = it->nameSD(); boost::hash_range(seed, name.rawData(), name.rawData() + name.size()); it->val.hash_combine(seed); } } int Document::compare(const Document& rL, const Document& rR) { DocumentStorageIterator lIt = rL.storage().iterator(); DocumentStorageIterator rIt = rR.storage().iterator(); while (true) { if (lIt.atEnd()) { if (rIt.atEnd()) return 0; // documents are the same length return -1; // left document is shorter } if (rIt.atEnd()) return 1; // right document is shorter const ValueElement& rField = rIt.get(); const ValueElement& lField = lIt.get(); const int nameCmp = lField.nameSD().compare(rField.nameSD()); if (nameCmp) return nameCmp; // field names are unequal const int valueCmp = Value::compare(lField.val, rField.val); if (valueCmp) return valueCmp; // fields are unequal rIt.advance(); lIt.advance(); } } string Document::toString() const { if (empty()) return "{}"; StringBuilder out; const char* prefix = "{"; for (DocumentStorageIterator it = storage().iterator(); !it.atEnd(); it.advance()) { out << prefix << it->nameSD() << ": " << it->val.toString(); prefix = ", "; } out << '}'; return out.str(); } void Document::serializeForSorter(BufBuilder& buf) const { const int numElems = size(); buf.appendNum(numElems); for (DocumentStorageIterator it = storage().iterator(); !it.atEnd(); it.advance()) { buf.appendStr(it->nameSD(), /*NUL byte*/ true); it->val.serializeForSorter(buf); } } Document Document::deserializeForSorter(BufReader& buf, const SorterDeserializeSettings&) { const int numElems = buf.read(); MutableDocument doc(numElems); for (int i = 0; i < numElems; i++) { StringData name = buf.readCStr(); doc.addField(name, Value::deserializeForSorter(buf, Value::SorterDeserializeSettings())); } return doc.freeze(); } }