/** * Copyright (C) 2018-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/platform/basic.h" #include "mongo/db/exec/working_set_common.h" #include #include "mongo/bson/simple_bsonobj_comparator.h" #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/health_log_gen.h" #include "mongo/db/catalog/health_log_interface.h" #include "mongo/db/exec/working_set.h" #include "mongo/db/index/index_access_method.h" #include "mongo/db/query/canonical_query.h" #include "mongo/db/service_context.h" #include "mongo/db/storage/execution_context.h" #include "mongo/db/storage/index_entry_comparison.h" #include "mongo/logv2/log.h" #include "mongo/util/stacktrace.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery namespace mongo { namespace { std::string indexKeyVectorDebugString(const std::vector& keyData) { StringBuilder sb; sb << "["; if (keyData.size() > 0) { auto it = keyData.begin(); sb << "(key: " << redact(it->keyData) << ", index key pattern: " << it->indexKeyPattern << ")"; while (++it != keyData.end()) { sb << ", (key: " << redact(it->keyData) << ", index key pattern: " << it->indexKeyPattern << ")"; } } sb << "]"; return sb.str(); } } // namespace // static bool WorkingSetCommon::fetch(OperationContext* opCtx, WorkingSet* workingSet, WorkingSetID id, SeekableRecordCursor* cursor, const CollectionPtr& collection, const NamespaceString& ns) { WorkingSetMember* member = workingSet->get(id); // We should have a RecordId but need to retrieve the obj. Get the obj now and reset all WSM // state appropriately. invariant(member->hasRecordId()); auto record = cursor->seekExact(member->recordId); if (!record) { // The record referenced by this index entry is gone. If the query yielded some time after // we first examined the index entry, then it's likely that the record was deleted while we // were yielding. However, if the snapshot id hasn't changed since the index lookup, then // there could not have been a yield, meaning the document we are searching for has been // deleted. // One possibility is that the record was deleted by a prepared transaction, but if we are // not ignoring prepare conflicts, then this definitely indicates an error. std::vector::iterator keyDataIt; if (member->getState() == WorkingSetMember::RID_AND_IDX && opCtx->recoveryUnit()->getPrepareConflictBehavior() == PrepareConflictBehavior::kEnforce && (keyDataIt = std::find_if(member->keyData.begin(), member->keyData.end(), [currentSnapshotId = opCtx->recoveryUnit()->getSnapshotId()]( const auto& keyDatum) { return keyDatum.snapshotId == currentSnapshotId; })) != member->keyData.end()) { auto indexKeyEntryToObjFn = [](const IndexKeyDatum& ikd) { BSONObjBuilder builder; // Rehydrate the index key fields to prevent duplicate "" fields from being logged. builder.append( "key"_sd, redact(IndexKeyEntry::rehydrateKey(ikd.indexKeyPattern, ikd.keyData))); builder.append("pattern"_sd, ikd.indexKeyPattern); return builder.obj(); }; HealthLogEntry entry; entry.setNss(ns); entry.setTimestamp(Date_t::now()); entry.setSeverity(SeverityEnum::Error); entry.setScope(ScopeEnum::Index); entry.setOperation("Index scan"); entry.setMsg("Erroneous index key found with reference to non-existent record id"); BSONObjBuilder bob; bob.append("recordId", member->recordId.toString()); const BSONArray indexKeyData = logv2::seqLog( boost::make_transform_iterator(member->keyData.begin(), indexKeyEntryToObjFn), boost::make_transform_iterator(member->keyData.end(), indexKeyEntryToObjFn)) .toBSONArray(); bob.append("indexKeyData", indexKeyData); bob.appendElements(getStackTrace().getBSONRepresentation()); entry.setData(bob.obj()); HealthLogInterface::get(opCtx)->log(entry); auto options = [&] { if (opCtx->recoveryUnit()->getDataCorruptionDetectionMode() == DataCorruptionDetectionMode::kThrow) { return logv2::LogOptions{ logv2::UserAssertAfterLog(ErrorCodes::DataCorruptionDetected)}; } else { return logv2::LogOptions(logv2::LogComponent::kAutomaticDetermination); } }(); LOGV2_ERROR_OPTIONS( 4615603, options, "Erroneous index key found with reference to non-existent record id. Consider " "dropping and then re-creating the index and then running the validate command " "on the collection.", logAttrs(ns), "recordId"_attr = member->recordId, "indexKeyData"_attr = indexKeyData); } return false; } auto currentSnapshotId = opCtx->recoveryUnit()->getSnapshotId(); member->resetDocument(currentSnapshotId, record->data.releaseToBson()); // Make sure that all of the keyData is still valid for this copy of the document. This ensures // both that index-provided filters and sort orders still hold. // // TODO provide a way for the query planner to opt out of this checking if it is unneeded due to // the structure of the plan. if (member->getState() == WorkingSetMember::RID_AND_IDX) { auto& executionCtx = StorageExecutionContext::get(opCtx); for (size_t i = 0; i < member->keyData.size(); i++) { auto&& memberKey = member->keyData[i]; // If this key was obtained in the current snapshot, then move on to the next key. There // is no way for this key to be inconsistent with the document it points to. if (memberKey.snapshotId == currentSnapshotId) { continue; } auto keys = executionCtx.keys(); SharedBufferFragmentBuilder pool(KeyString::HeapBuilder::kHeapAllocatorDefaultBytes); // There's no need to compute the prefixes of the indexed fields that cause the // index to be multikey when ensuring the keyData is still valid. KeyStringSet* multikeyMetadataKeys = nullptr; MultikeyPaths* multikeyPaths = nullptr; const StringData indexIdent = workingSet->retrieveIndexIdent(memberKey.indexId); auto desc = collection->getIndexCatalog()->findIndexByIdent(opCtx, indexIdent); invariant(desc, str::stream() << "Index entry not found for index with ident " << indexIdent << " on collection " << collection->ns().toStringForErrorMsg()); auto* iam = desc->getEntry()->accessMethod()->asSortedData(); iam->getKeys(opCtx, collection, pool, member->doc.value().toBson(), InsertDeleteOptions::ConstraintEnforcementMode::kEnforceConstraints, SortedDataIndexAccessMethod::GetKeysContext::kValidatingKeys, keys.get(), multikeyMetadataKeys, multikeyPaths, member->recordId); KeyString::HeapBuilder keyString(iam->getSortedDataInterface()->getKeyStringVersion(), memberKey.keyData, iam->getSortedDataInterface()->getOrdering(), member->recordId); if (!keys->count(keyString.release())) { // document would no longer be at this position in the index. return false; } } } member->keyData.clear(); workingSet->transitionToRecordIdAndObj(id); return true; } } // namespace mongo