// index_catalog_entry.cpp
/**
* Copyright (C) 2013 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 .
*
* 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::kIndex
#include "mongo/platform/basic.h"
#include "mongo/db/catalog/index_catalog_entry.h"
#include
#include "mongo/db/catalog/collection_catalog_entry.h"
#include "mongo/db/catalog/head_manager.h"
#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/index/index_access_method.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/matcher/extensions_callback_disallow_extensions.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/query/collation/collator_factory_interface.h"
#include "mongo/db/service_context.h"
#include "mongo/util/log.h"
#include "mongo/util/scopeguard.h"
namespace mongo {
using std::string;
class HeadManagerImpl : public HeadManager {
public:
HeadManagerImpl(IndexCatalogEntry* ice) : _catalogEntry(ice) {}
virtual ~HeadManagerImpl() {}
const RecordId getHead(OperationContext* txn) const {
return _catalogEntry->head(txn);
}
void setHead(OperationContext* txn, const RecordId newHead) {
_catalogEntry->setHead(txn, newHead);
}
private:
// Not owned here.
IndexCatalogEntry* _catalogEntry;
};
IndexCatalogEntry::IndexCatalogEntry(OperationContext* txn,
StringData ns,
CollectionCatalogEntry* collection,
IndexDescriptor* descriptor,
CollectionInfoCache* infoCache)
: _ns(ns.toString()),
_collection(collection),
_descriptor(descriptor),
_infoCache(infoCache),
_headManager(new HeadManagerImpl(this)),
_ordering(Ordering::make(descriptor->keyPattern())),
_isReady(false) {
_descriptor->_cachedEntry = this;
_isReady = _catalogIsReady(txn);
_head = _catalogHead(txn);
{
stdx::lock_guard lk(_indexMultikeyPathsMutex);
_isMultikey.store(_catalogIsMultikey(txn, &_indexMultikeyPaths));
_indexTracksPathLevelMultikeyInfo = !_indexMultikeyPaths.empty();
}
if (BSONElement collationElement = _descriptor->getInfoElement("collation")) {
invariant(collationElement.isABSONObj());
BSONObj collation = collationElement.Obj();
auto statusWithCollator =
CollatorFactoryInterface::get(txn->getServiceContext())->makeFromBSON(collation);
// Index spec should have already been validated.
invariantOK(statusWithCollator.getStatus());
_collator = std::move(statusWithCollator.getValue());
}
if (BSONElement filterElement = _descriptor->getInfoElement("partialFilterExpression")) {
invariant(filterElement.isABSONObj());
BSONObj filter = filterElement.Obj();
StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse(
filter, ExtensionsCallbackDisallowExtensions(), _collator.get());
// this should be checked in create, so can blow up here
invariantOK(statusWithMatcher.getStatus());
_filterExpression = std::move(statusWithMatcher.getValue());
LOG(2) << "have filter expression for " << _ns << " " << _descriptor->indexName() << " "
<< redact(filter);
}
}
IndexCatalogEntry::~IndexCatalogEntry() {
_descriptor->_cachedEntry = NULL; // defensive
delete _headManager;
delete _descriptor;
}
void IndexCatalogEntry::init(std::unique_ptr accessMethod) {
invariant(!_accessMethod);
_accessMethod = std::move(accessMethod);
}
const RecordId& IndexCatalogEntry::head(OperationContext* txn) const {
DEV invariant(_head == _catalogHead(txn));
return _head;
}
bool IndexCatalogEntry::isReady(OperationContext* txn) const {
DEV invariant(_isReady == _catalogIsReady(txn));
return _isReady;
}
bool IndexCatalogEntry::isMultikey() const {
return _isMultikey.load();
}
MultikeyPaths IndexCatalogEntry::getMultikeyPaths(OperationContext* txn) const {
stdx::lock_guard lk(_indexMultikeyPathsMutex);
return _indexMultikeyPaths;
}
// ---
void IndexCatalogEntry::setIsReady(bool newIsReady) {
_isReady = newIsReady;
}
class IndexCatalogEntry::SetHeadChange : public RecoveryUnit::Change {
public:
SetHeadChange(IndexCatalogEntry* ice, RecordId oldHead) : _ice(ice), _oldHead(oldHead) {}
virtual void commit() {}
virtual void rollback() {
_ice->_head = _oldHead;
}
IndexCatalogEntry* _ice;
const RecordId _oldHead;
};
void IndexCatalogEntry::setHead(OperationContext* txn, RecordId newHead) {
_collection->setIndexHead(txn, _descriptor->indexName(), newHead);
txn->recoveryUnit()->registerChange(new SetHeadChange(this, _head));
_head = newHead;
}
/**
* RAII class, which associates a new RecoveryUnit with an OperationContext for the purposes
* of simulating a side-transaction. Takes ownership of the new recovery unit and frees it at
* destruction time.
*/
class RecoveryUnitSwap {
public:
RecoveryUnitSwap(OperationContext* txn, RecoveryUnit* newRecoveryUnit)
: _txn(txn),
_oldRecoveryUnit(_txn->releaseRecoveryUnit()),
_oldRecoveryUnitState(
_txn->setRecoveryUnit(newRecoveryUnit, OperationContext::kNotInUnitOfWork)),
_newRecoveryUnit(newRecoveryUnit) {}
~RecoveryUnitSwap() {
_txn->releaseRecoveryUnit();
_txn->setRecoveryUnit(_oldRecoveryUnit, _oldRecoveryUnitState);
}
private:
// Not owned
OperationContext* const _txn;
// Owned, but life-time is not controlled
RecoveryUnit* const _oldRecoveryUnit;
OperationContext::RecoveryUnitState const _oldRecoveryUnitState;
// Owned and life-time is controlled
const std::unique_ptr _newRecoveryUnit;
};
void IndexCatalogEntry::setMultikey(OperationContext* txn, const MultikeyPaths& multikeyPaths) {
if (!_indexTracksPathLevelMultikeyInfo && isMultikey()) {
// If the index is already set as multikey and we don't have any path-level information to
// update, then there's nothing more for us to do.
return;
}
if (_indexTracksPathLevelMultikeyInfo) {
stdx::lock_guard lk(_indexMultikeyPathsMutex);
invariant(multikeyPaths.size() == _indexMultikeyPaths.size());
bool newPathIsMultikey = false;
for (size_t i = 0; i < multikeyPaths.size(); ++i) {
if (!std::includes(_indexMultikeyPaths[i].begin(),
_indexMultikeyPaths[i].end(),
multikeyPaths[i].begin(),
multikeyPaths[i].end())) {
// If 'multikeyPaths' contains a new path component that causes this index to be
// multikey, then we must update the index metadata in the CollectionCatalogEntry.
newPathIsMultikey = true;
break;
}
}
if (!newPathIsMultikey) {
// Otherwise, if all the path components in 'multikeyPaths' are already tracked in
// '_indexMultikeyPaths', then there's nothing more for us to do.
return;
}
}
{
// Only one thread should set the multi-key value per collection, because the metadata for a
// collection is one large document.
Lock::ResourceLock collMDLock(txn->lockState(), ResourceId(RESOURCE_METADATA, _ns), MODE_X);
if (!_indexTracksPathLevelMultikeyInfo && isMultikey()) {
// It's possible that we raced with another thread when acquiring the MD lock. If the
// index is already set as multikey and we don't have any path-level information to
// update, then there's nothing more for us to do.
return;
}
// This effectively emulates a side-transaction off the main transaction, which invoked
// setMultikey. The reason we need is to avoid artificial WriteConflicts, which happen with
// snapshot isolation.
{
StorageEngine* storageEngine = getGlobalServiceContext()->getGlobalStorageEngine();
RecoveryUnitSwap ruSwap(txn, storageEngine->newRecoveryUnit());
WriteUnitOfWork wuow(txn);
// It's possible that the index type (e.g. ascending/descending index) supports tracking
// path-level multikey information, but this particular index doesn't.
// CollectionCatalogEntry::setIndexIsMultikey() requires that we discard the path-level
// multikey information in order to avoid unintentionally setting path-level multikey
// information on an index created before 3.4.
if (_collection->setIndexIsMultikey(
txn,
_descriptor->indexName(),
_indexTracksPathLevelMultikeyInfo ? multikeyPaths : MultikeyPaths{})) {
if (_infoCache) {
LOG(1) << _ns << ": clearing plan cache - index " << _descriptor->keyPattern()
<< " set to multi key.";
_infoCache->clearQueryCache();
}
}
wuow.commit();
}
}
_isMultikey.store(true);
if (_indexTracksPathLevelMultikeyInfo) {
stdx::lock_guard lk(_indexMultikeyPathsMutex);
for (size_t i = 0; i < multikeyPaths.size(); ++i) {
_indexMultikeyPaths[i].insert(multikeyPaths[i].begin(), multikeyPaths[i].end());
}
}
}
// ----
bool IndexCatalogEntry::_catalogIsReady(OperationContext* txn) const {
return _collection->isIndexReady(txn, _descriptor->indexName());
}
RecordId IndexCatalogEntry::_catalogHead(OperationContext* txn) const {
return _collection->getIndexHead(txn, _descriptor->indexName());
}
bool IndexCatalogEntry::_catalogIsMultikey(OperationContext* txn,
MultikeyPaths* multikeyPaths) const {
return _collection->isIndexMultikey(txn, _descriptor->indexName(), multikeyPaths);
}
// ------------------
const IndexCatalogEntry* IndexCatalogEntryContainer::find(const IndexDescriptor* desc) const {
if (desc->_cachedEntry)
return desc->_cachedEntry;
for (const_iterator i = begin(); i != end(); ++i) {
const IndexCatalogEntry* e = *i;
if (e->descriptor() == desc)
return e;
}
return NULL;
}
IndexCatalogEntry* IndexCatalogEntryContainer::find(const IndexDescriptor* desc) {
if (desc->_cachedEntry)
return desc->_cachedEntry;
for (iterator i = begin(); i != end(); ++i) {
IndexCatalogEntry* e = *i;
if (e->descriptor() == desc)
return e;
}
return NULL;
}
IndexCatalogEntry* IndexCatalogEntryContainer::find(const string& name) {
for (iterator i = begin(); i != end(); ++i) {
IndexCatalogEntry* e = *i;
if (e->descriptor()->indexName() == name)
return e;
}
return NULL;
}
IndexCatalogEntry* IndexCatalogEntryContainer::release(const IndexDescriptor* desc) {
for (std::vector::iterator i = _entries.mutableVector().begin();
i != _entries.mutableVector().end();
++i) {
IndexCatalogEntry* e = *i;
if (e->descriptor() != desc)
continue;
_entries.mutableVector().erase(i);
return e;
}
return NULL;
}
} // namespace mongo