// namespace_details_collection_entry.h /** * Copyright (C) 2014 MongoDB 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 . * * 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 GNU Affero General 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage #include "mongo/db/storage/mmap_v1/catalog/namespace_details_collection_entry.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/ops/update.h" #include "mongo/db/storage/mmap_v1/catalog/namespace_details.h" #include "mongo/db/storage/mmap_v1/catalog/namespace_details_rsv1_metadata.h" #include "mongo/db/storage/mmap_v1/mmap_v1_database_catalog_entry.h" #include "mongo/db/storage/record_store.h" #include "mongo/util/log.h" #include "mongo/util/startup_test.h" namespace mongo { using std::string; NamespaceDetailsCollectionCatalogEntry::NamespaceDetailsCollectionCatalogEntry( StringData ns, NamespaceDetails* details, RecordStore* namespacesRecordStore, RecordStore* indexRecordStore, MMAPV1DatabaseCatalogEntry* db ) : CollectionCatalogEntry( ns ), _details( details ), _namespacesRecordStore(namespacesRecordStore), _indexRecordStore( indexRecordStore ), _db( db ) { } CollectionOptions NamespaceDetailsCollectionCatalogEntry::getCollectionOptions(OperationContext* txn) const { CollectionOptions options = _db->getCollectionOptions( txn, ns().ns() ); if (options.flagsSet) { if (options.flags != _details->userFlags) { warning() << "system.namespaces and NamespaceDetails disagree about userFlags." << " system.namespaces: " << options.flags << " NamespaceDetails: " << _details->userFlags; dassert(options.flags == _details->userFlags); } } // Fill in the actual flags from the NamespaceDetails. // Leaving flagsSet alone since it indicates whether the user actively set the flags. options.flags = _details->userFlags; return options; } int NamespaceDetailsCollectionCatalogEntry::getTotalIndexCount( OperationContext* txn ) const { return _details->nIndexes + _details->indexBuildsInProgress; } int NamespaceDetailsCollectionCatalogEntry::getCompletedIndexCount( OperationContext* txn ) const { return _details->nIndexes; } int NamespaceDetailsCollectionCatalogEntry::getMaxAllowedIndexes() const { return NamespaceDetails::NIndexesMax; } void NamespaceDetailsCollectionCatalogEntry::getAllIndexes( OperationContext* txn, std::vector* names ) const { NamespaceDetails::IndexIterator i = _details->ii( true ); while ( i.more() ) { const IndexDetails& id = i.next(); const BSONObj obj = _indexRecordStore->dataFor( txn, id.info.toRecordId() ).toBson(); names->push_back( obj.getStringField("name") ); } } bool NamespaceDetailsCollectionCatalogEntry::isIndexMultikey(OperationContext* txn, StringData idxName) const { int idxNo = _findIndexNumber( txn, idxName ); invariant( idxNo >= 0 ); return isIndexMultikey( idxNo ); } bool NamespaceDetailsCollectionCatalogEntry::isIndexMultikey(int idxNo) const { return (_details->multiKeyIndexBits & (((unsigned long long) 1) << idxNo)) != 0; } bool NamespaceDetailsCollectionCatalogEntry::setIndexIsMultikey(OperationContext* txn, StringData indexName, bool multikey ) { int idxNo = _findIndexNumber( txn, indexName ); invariant( idxNo >= 0 ); return setIndexIsMultikey( txn, idxNo, multikey ); } bool NamespaceDetailsCollectionCatalogEntry::setIndexIsMultikey(OperationContext* txn, int idxNo, bool multikey ) { unsigned long long mask = 1ULL << idxNo; if (multikey) { // Shortcut if the bit is already set correctly if (_details->multiKeyIndexBits & mask) { return false; } *txn->recoveryUnit()->writing(&_details->multiKeyIndexBits) |= mask; } else { // Shortcut if the bit is already set correctly if (!(_details->multiKeyIndexBits & mask)) { return false; } // Invert mask: all 1's except a 0 at the ith bit mask = ~mask; *txn->recoveryUnit()->writing(&_details->multiKeyIndexBits) &= mask; } return true; } RecordId NamespaceDetailsCollectionCatalogEntry::getIndexHead(OperationContext* txn, StringData idxName) const { int idxNo = _findIndexNumber( txn, idxName ); invariant( idxNo >= 0 ); return _details->idx( idxNo ).head.toRecordId(); } BSONObj NamespaceDetailsCollectionCatalogEntry::getIndexSpec( OperationContext* txn, StringData idxName ) const { int idxNo = _findIndexNumber( txn, idxName ); invariant( idxNo >= 0 ); const IndexDetails& id = _details->idx( idxNo ); return _indexRecordStore->dataFor( txn, id.info.toRecordId() ).toBson(); } void NamespaceDetailsCollectionCatalogEntry::setIndexHead( OperationContext* txn, StringData idxName, const RecordId& newHead ) { int idxNo = _findIndexNumber( txn, idxName ); invariant( idxNo >= 0 ); *txn->recoveryUnit()->writing(&_details->idx(idxNo).head) = DiskLoc::fromRecordId(newHead); } bool NamespaceDetailsCollectionCatalogEntry::isIndexReady( OperationContext* txn, StringData idxName ) const { int idxNo = _findIndexNumber( txn, idxName ); invariant( idxNo >= 0 ); return idxNo < getCompletedIndexCount( txn ); } int NamespaceDetailsCollectionCatalogEntry::_findIndexNumber( OperationContext* txn, StringData idxName ) const { NamespaceDetails::IndexIterator i = _details->ii( true ); while ( i.more() ) { const IndexDetails& id = i.next(); int idxNo = i.pos() - 1; const BSONObj obj = _indexRecordStore->dataFor( txn, id.info.toRecordId() ).toBson(); if ( idxName == obj.getStringField("name") ) return idxNo; } return -1; } /* remove bit from a bit array - actually remove its slot, not a clear note: this function does not work with x == 63 -- that is ok but keep in mind in the future if max indexes were extended to exactly 64 it would be a problem */ unsigned long long removeAndSlideBit(unsigned long long b, int x) { unsigned long long tmp = b; return (tmp & ((((unsigned long long) 1) << x)-1)) | ((tmp >> (x+1)) << x); } class IndexUpdateTest : public StartupTest { public: void run() { verify( removeAndSlideBit(1, 0) == 0 ); verify( removeAndSlideBit(2, 0) == 1 ); verify( removeAndSlideBit(2, 1) == 0 ); verify( removeAndSlideBit(255, 1) == 127 ); verify( removeAndSlideBit(21, 2) == 9 ); verify( removeAndSlideBit(0x4000000000000001ULL, 62) == 1 ); } } iu_unittest; Status NamespaceDetailsCollectionCatalogEntry::removeIndex( OperationContext* txn, StringData indexName ) { int idxNo = _findIndexNumber( txn, indexName ); if ( idxNo < 0 ) return Status( ErrorCodes::NamespaceNotFound, "index not found to remove" ); RecordId infoLocation = _details->idx( idxNo ).info.toRecordId(); { // sanity check BSONObj info = _indexRecordStore->dataFor( txn, infoLocation ).toBson(); invariant( info["name"].String() == indexName ); } { // drop the namespace string indexNamespace = IndexDescriptor::makeIndexNamespace( ns().ns(), indexName ); Status status = _db->dropCollection( txn, indexNamespace ); if ( !status.isOK() ) { return status; } } { // all info in the .ns file NamespaceDetails* d = _details->writingWithExtra( txn ); // fix the _multiKeyIndexBits, by moving all bits above me down one d->multiKeyIndexBits = removeAndSlideBit(d->multiKeyIndexBits, idxNo); if ( idxNo >= d->nIndexes ) d->indexBuildsInProgress--; else d->nIndexes--; for ( int i = idxNo; i < getTotalIndexCount( txn ); i++ ) d->idx(i) = d->idx(i+1); d->idx( getTotalIndexCount( txn ) ) = IndexDetails(); } // remove from system.indexes _indexRecordStore->deleteRecord( txn, infoLocation ); return Status::OK(); } Status NamespaceDetailsCollectionCatalogEntry::prepareForIndexBuild( OperationContext* txn, const IndexDescriptor* desc ) { BSONObj spec = desc->infoObj(); // 1) entry in system.indexs StatusWith systemIndexesEntry = _indexRecordStore->insertRecord( txn, spec.objdata(), spec.objsize(), false ); if ( !systemIndexesEntry.isOK() ) return systemIndexesEntry.getStatus(); // 2) NamespaceDetails mods IndexDetails *id; try { id = &_details->idx(getTotalIndexCount( txn ), true); } catch( DBException& ) { _details->allocExtra(txn, ns().ns(), _db->_namespaceIndex, getTotalIndexCount( txn )); id = &_details->idx(getTotalIndexCount( txn ), false); } const DiskLoc infoLoc = DiskLoc::fromRecordId(systemIndexesEntry.getValue()); *txn->recoveryUnit()->writing( &id->info ) = infoLoc; *txn->recoveryUnit()->writing( &id->head ) = DiskLoc(); txn->recoveryUnit()->writingInt( _details->indexBuildsInProgress ) += 1; // 3) indexes entry in .ns file and system.namespaces _db->createNamespaceForIndex(txn, desc->indexNamespace()); return Status::OK(); } void NamespaceDetailsCollectionCatalogEntry::indexBuildSuccess( OperationContext* txn, StringData indexName ) { int idxNo = _findIndexNumber( txn, indexName ); fassert( 17202, idxNo >= 0 ); // Make sure the newly created index is relocated to nIndexes, if it isn't already there if ( idxNo != getCompletedIndexCount( txn ) ) { int toIdxNo = getCompletedIndexCount( txn ); //_details->swapIndex( txn, idxNo, toIdxNo ); // flip main meta data IndexDetails temp = _details->idx(idxNo); *txn->recoveryUnit()->writing(&_details->idx(idxNo)) = _details->idx(toIdxNo); *txn->recoveryUnit()->writing(&_details->idx(toIdxNo)) = temp; // flip multi key bits bool tempMultikey = isIndexMultikey(idxNo); setIndexIsMultikey( txn, idxNo, isIndexMultikey(toIdxNo) ); setIndexIsMultikey( txn, toIdxNo, tempMultikey ); idxNo = toIdxNo; invariant( (idxNo = _findIndexNumber( txn, indexName )) ); } txn->recoveryUnit()->writingInt( _details->indexBuildsInProgress ) -= 1; txn->recoveryUnit()->writingInt( _details->nIndexes ) += 1; invariant( isIndexReady( txn, indexName ) ); } void NamespaceDetailsCollectionCatalogEntry::updateTTLSetting( OperationContext* txn, StringData idxName, long long newExpireSeconds ) { int idx = _findIndexNumber( txn, idxName ); invariant( idx >= 0 ); IndexDetails& indexDetails = _details->idx( idx ); BSONObj obj = _indexRecordStore->dataFor( txn, indexDetails.info.toRecordId() ).toBson(); const BSONElement oldExpireSecs = obj.getField("expireAfterSeconds"); // Important that we set the new value in-place. We are writing directly to the // object here so must be careful not to overwrite with a longer numeric type. char* nonConstPtr = const_cast(oldExpireSecs.value()); switch( oldExpireSecs.type() ) { case EOO: massert( 16631, "index does not have an 'expireAfterSeconds' field", false ); break; case NumberInt: *txn->recoveryUnit()->writing(reinterpret_cast(nonConstPtr)) = newExpireSeconds; break; case NumberDouble: *txn->recoveryUnit()->writing(reinterpret_cast(nonConstPtr)) = newExpireSeconds; break; case NumberLong: *txn->recoveryUnit()->writing(reinterpret_cast(nonConstPtr)) = newExpireSeconds; break; default: massert( 16632, "current 'expireAfterSeconds' is not a number", false ); } } namespace { void updateSystemNamespaces(OperationContext* txn, RecordStore* namespaces, const NamespaceString& ns, const BSONObj& update) { if (!namespaces) return; auto cursor = namespaces->getCursor(txn); while (auto record = cursor->next()) { BSONObj oldEntry = record->data.releaseToBson(); BSONElement e = oldEntry["name"]; if (e.type() != String) continue; if (e.String() != ns.ns()) continue; const BSONObj newEntry = applyUpdateOperators(oldEntry, update); StatusWith result = namespaces->updateRecord(txn, record->id, newEntry.objdata(), newEntry.objsize(), false, NULL); fassert(17486, result.getStatus()); return; } fassertFailed(17488); } } void NamespaceDetailsCollectionCatalogEntry::updateFlags(OperationContext* txn, int newValue) { NamespaceDetailsRSV1MetaData md(ns().ns(), _details); md.replaceUserFlags(txn, newValue); updateSystemNamespaces(txn, _namespacesRecordStore, ns(), BSON("$set" << BSON("options.flags" << newValue))); } void NamespaceDetailsCollectionCatalogEntry::updateValidator(OperationContext* txn, const BSONObj& validator) { updateSystemNamespaces(txn, _namespacesRecordStore, ns(), BSON("$set" << BSON("options.validator" << validator))); } }