// bson_collection_catalog_entry.cpp
/**
* 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.
*/
#include "mongo/db/storage/bson_collection_catalog_entry.h"
#include
#include
#include "mongo/db/field_ref.h"
namespace mongo {
namespace {
// An index will fail to get created if the size in bytes of its key pattern is greater than 2048.
// We use that value to represent the largest number of path components we could ever possibly
// expect to see in an indexed field.
const size_t kMaxKeyPatternPathLength = 2048;
char multikeyPathsEncodedAsBytes[kMaxKeyPatternPathLength];
/**
* Encodes 'multikeyPaths' as binary data and appends it to 'bob'.
*
* For example, consider the index {'a.b': 1, 'a.c': 1} where the paths "a" and "a.b" cause it to be
* multikey. The object {'a.b': HexData('0101'), 'a.c': HexData('0100')} would then be appended to
* 'bob'.
*/
void appendMultikeyPathsAsBytes(BSONObj keyPattern,
const MultikeyPaths& multikeyPaths,
BSONObjBuilder* bob) {
size_t i = 0;
for (const auto keyElem : keyPattern) {
StringData keyName = keyElem.fieldNameStringData();
size_t numParts = FieldRef{keyName}.numParts();
invariant(numParts > 0);
invariant(numParts <= kMaxKeyPatternPathLength);
std::fill_n(multikeyPathsEncodedAsBytes, numParts, 0);
for (const auto multikeyComponent : multikeyPaths[i]) {
multikeyPathsEncodedAsBytes[multikeyComponent] = 1;
}
bob->appendBinData(keyName, numParts, BinDataGeneral, &multikeyPathsEncodedAsBytes[0]);
++i;
}
}
/**
* Parses the path-level multikey information encoded as binary data from 'multikeyPathsObj' and
* sets 'multikeyPaths' as that value.
*
* For example, consider the index {'a.b': 1, 'a.c': 1} where the paths "a" and "a.b" cause it to be
* multikey. The binary data {'a.b': HexData('0101'), 'a.c': HexData('0100')} would then be parsed
* into std::vector>{{0U, 1U}, {0U}}.
*/
void parseMultikeyPathsFromBytes(BSONObj multikeyPathsObj, MultikeyPaths* multikeyPaths) {
invariant(multikeyPaths);
for (auto elem : multikeyPathsObj) {
std::set multikeyComponents;
int len;
const char* data = elem.binData(len);
invariant(len > 0);
invariant(static_cast(len) <= kMaxKeyPatternPathLength);
for (int i = 0; i < len; ++i) {
if (data[i]) {
multikeyComponents.insert(i);
}
}
multikeyPaths->push_back(multikeyComponents);
}
}
} // namespace
BSONCollectionCatalogEntry::BSONCollectionCatalogEntry(StringData ns)
: CollectionCatalogEntry(ns) {}
CollectionOptions BSONCollectionCatalogEntry::getCollectionOptions(OperationContext* opCtx) const {
MetaData md = _getMetaData(opCtx);
return md.options;
}
int BSONCollectionCatalogEntry::getTotalIndexCount(OperationContext* opCtx) const {
MetaData md = _getMetaData(opCtx);
return static_cast(md.indexes.size());
}
int BSONCollectionCatalogEntry::getCompletedIndexCount(OperationContext* opCtx) const {
MetaData md = _getMetaData(opCtx);
int num = 0;
for (unsigned i = 0; i < md.indexes.size(); i++) {
if (md.indexes[i].ready)
num++;
}
return num;
}
BSONObj BSONCollectionCatalogEntry::getIndexSpec(OperationContext* opCtx,
StringData indexName) const {
MetaData md = _getMetaData(opCtx);
int offset = md.findIndexOffset(indexName);
invariant(offset >= 0);
return md.indexes[offset].spec.getOwned();
}
void BSONCollectionCatalogEntry::getAllIndexes(OperationContext* opCtx,
std::vector* names) const {
MetaData md = _getMetaData(opCtx);
for (unsigned i = 0; i < md.indexes.size(); i++) {
names->push_back(md.indexes[i].spec["name"].String());
}
}
void BSONCollectionCatalogEntry::getReadyIndexes(OperationContext* opCtx,
std::vector* names) const {
MetaData md = _getMetaData(opCtx);
for (unsigned i = 0; i < md.indexes.size(); i++) {
if (md.indexes[i].ready)
names->push_back(md.indexes[i].spec["name"].String());
}
}
bool BSONCollectionCatalogEntry::isIndexMultikey(OperationContext* opCtx,
StringData indexName,
MultikeyPaths* multikeyPaths) const {
MetaData md = _getMetaData(opCtx);
int offset = md.findIndexOffset(indexName);
invariant(offset >= 0);
if (multikeyPaths && !md.indexes[offset].multikeyPaths.empty()) {
*multikeyPaths = md.indexes[offset].multikeyPaths;
}
return md.indexes[offset].multikey;
}
RecordId BSONCollectionCatalogEntry::getIndexHead(OperationContext* opCtx,
StringData indexName) const {
MetaData md = _getMetaData(opCtx);
int offset = md.findIndexOffset(indexName);
invariant(offset >= 0);
return md.indexes[offset].head;
}
bool BSONCollectionCatalogEntry::isIndexReady(OperationContext* opCtx, StringData indexName) const {
MetaData md = _getMetaData(opCtx);
int offset = md.findIndexOffset(indexName);
invariant(offset >= 0);
return md.indexes[offset].ready;
}
KVPrefix BSONCollectionCatalogEntry::getIndexPrefix(OperationContext* opCtx,
StringData indexName) const {
MetaData md = _getMetaData(opCtx);
int offset = md.findIndexOffset(indexName);
invariant(offset >= 0);
return md.indexes[offset].prefix;
}
// --------------------------
void BSONCollectionCatalogEntry::IndexMetaData::updateTTLSetting(long long newExpireSeconds) {
BSONObjBuilder b;
for (BSONObjIterator bi(spec); bi.more();) {
BSONElement e = bi.next();
if (e.fieldNameStringData() == "expireAfterSeconds") {
continue;
}
b.append(e);
}
b.append("expireAfterSeconds", newExpireSeconds);
spec = b.obj();
}
// --------------------------
int BSONCollectionCatalogEntry::MetaData::findIndexOffset(StringData name) const {
for (unsigned i = 0; i < indexes.size(); i++)
if (indexes[i].name() == name)
return i;
return -1;
}
bool BSONCollectionCatalogEntry::MetaData::eraseIndex(StringData name) {
int indexOffset = findIndexOffset(name);
if (indexOffset < 0) {
return false;
}
indexes.erase(indexes.begin() + indexOffset);
return true;
}
void BSONCollectionCatalogEntry::MetaData::rename(StringData toNS) {
ns = toNS.toString();
for (size_t i = 0; i < indexes.size(); i++) {
BSONObj spec = indexes[i].spec;
BSONObjBuilder b;
// Add the fields in the same order they were in the original specification.
for (auto&& elem : spec) {
if (elem.fieldNameStringData() == "ns") {
b.append("ns", toNS);
} else {
b.append(elem);
}
}
indexes[i].spec = b.obj();
}
}
KVPrefix BSONCollectionCatalogEntry::MetaData::getMaxPrefix() const {
// Use the collection prefix as the initial max value seen. Then compare it with each index
// prefix. Note the oplog has no indexes so the vector of 'IndexMetaData' may be empty.
return std::accumulate(
indexes.begin(), indexes.end(), prefix, [](KVPrefix max, IndexMetaData index) {
return max < index.prefix ? index.prefix : max;
});
}
BSONObj BSONCollectionCatalogEntry::MetaData::toBSON() const {
BSONObjBuilder b;
b.append("ns", ns);
b.append("options", options.toBSON());
{
BSONArrayBuilder arr(b.subarrayStart("indexes"));
for (unsigned i = 0; i < indexes.size(); i++) {
BSONObjBuilder sub(arr.subobjStart());
sub.append("spec", indexes[i].spec);
sub.appendBool("ready", indexes[i].ready);
sub.appendBool("multikey", indexes[i].multikey);
if (!indexes[i].multikeyPaths.empty()) {
BSONObjBuilder subMultikeyPaths(sub.subobjStart("multikeyPaths"));
appendMultikeyPathsAsBytes(indexes[i].spec.getObjectField("key"),
indexes[i].multikeyPaths,
&subMultikeyPaths);
subMultikeyPaths.doneFast();
}
sub.append("head", static_cast(indexes[i].head.repr()));
sub.append("prefix", indexes[i].prefix.toBSONValue());
sub.append("backgroundSecondary", indexes[i].isBackgroundSecondaryBuild);
sub.doneFast();
}
arr.doneFast();
}
b.append("prefix", prefix.toBSONValue());
return b.obj();
}
void BSONCollectionCatalogEntry::MetaData::parse(const BSONObj& obj) {
ns = obj["ns"].valuestrsafe();
if (obj["options"].isABSONObj()) {
options.parse(obj["options"].Obj(), CollectionOptions::parseForStorage)
.transitional_ignore();
}
BSONElement indexList = obj["indexes"];
if (indexList.isABSONObj()) {
for (BSONElement elt : indexList.Obj()) {
BSONObj idx = elt.Obj();
IndexMetaData imd;
imd.spec = idx["spec"].Obj().getOwned();
imd.ready = idx["ready"].trueValue();
if (idx.hasField("head")) {
imd.head = RecordId(idx["head"].Long());
} else {
imd.head = RecordId(idx["head_a"].Int(), idx["head_b"].Int());
}
imd.multikey = idx["multikey"].trueValue();
if (auto multikeyPathsElem = idx["multikeyPaths"]) {
parseMultikeyPathsFromBytes(multikeyPathsElem.Obj(), &imd.multikeyPaths);
}
imd.prefix = KVPrefix::fromBSONElement(idx["prefix"]);
auto bgSecondary = BSONElement(idx["backgroundSecondary"]);
// Opt-in to rebuilding behavior for old-format index catalog objects.
imd.isBackgroundSecondaryBuild = bgSecondary.eoo() || bgSecondary.trueValue();
indexes.push_back(imd);
}
}
prefix = KVPrefix::fromBSONElement(obj["prefix"]);
}
}