diff options
author | Dianna Hohensee <dianna.hohensee@mongodb.com> | 2021-03-05 14:23:08 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-03-05 20:58:46 +0000 |
commit | 69027dee0ae53ef10a0adcc324d06ef12d0f634f (patch) | |
tree | 60ab77125f33d6efc4921576250fa488d725393b | |
parent | 7739da6997795c20924580087a0e09db0a8cf929 (diff) | |
download | mongo-69027dee0ae53ef10a0adcc324d06ef12d0f634f.tar.gz |
SERVER-54929 Helper functions to convert underlying buckets collection index specs to time-series
collection index specs
4 files changed, 111 insertions, 194 deletions
diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index 34ba441a488..b6c1790794f 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -116,87 +116,6 @@ using std::unique_ptr; namespace { /** - * Returns time-series options if 'ns' refers to a time-series collection. - */ -boost::optional<TimeseriesOptions> getTimeseriesOptions(OperationContext* opCtx, - const NamespaceString& ns) { - auto viewCatalog = DatabaseHolder::get(opCtx)->getViewCatalog(opCtx, ns.db()); - if (!viewCatalog) { - return {}; - } - - auto view = viewCatalog->lookupWithoutValidatingDurableViews(opCtx, ns.ns()); - if (!view) { - return {}; - } - - // Return a copy of the time-series options so that we don't refer to the internal state of - // 'viewCatalog' once it goes out of scope. - return view->timeseries(); -} - -/** - * Returns an index key with field names mapped to the bucket collection schema. - */ -BSONObj makeTimeseriesIndexSpecKey(const TimeseriesOptions& timeseriesOptions, - const CollMod& origCmd, - const BSONObj& origKey) { - auto timeField = timeseriesOptions.getTimeField(); - auto metaField = timeseriesOptions.getMetaField(); - - BSONObjBuilder builder; - for (const auto& elem : origKey) { - // Determine if the index requested on the time field is ascending or descending. - // The final index spec will be subjected to a more complete validation in - // index_key_validate::validateKeyPattern(). - if (elem.fieldNameStringData() == timeField) { - uassert( - ErrorCodes::IndexNotFound, - str::stream() << "Invalid index spec for time-series collection: " - << redact(origCmd.toBSON({})) // 'origKey' is included in 'origCmd' - << ". Indexes on the time field must be ascending or descending " - "(numbers only): " - << elem, - elem.isNumber()); - if (elem.number() >= 0) { - builder.appendAs(elem, str::stream() << "control.min." << timeField); - builder.appendAs(elem, str::stream() << "control.max." << timeField); - } else { - builder.appendAs(elem, str::stream() << "control.max." << timeField); - builder.appendAs(elem, str::stream() << "control.min." << timeField); - } - continue; - } - - uassert(ErrorCodes::IndexNotFound, - str::stream() << "Invalid index spec for time-series collection: " - << redact(origCmd.toBSON({})) // 'origKey' is included in 'origCmd' - << ". Index must be on the '" << timeField << "' field: " << elem, - metaField); - - if (elem.fieldNameStringData() == *metaField) { - builder.appendAs(elem, BucketUnpacker::kBucketMetaFieldName); - continue; - } - - if (elem.fieldNameStringData().startsWith(*metaField + ".")) { - builder.appendAs(elem, - str::stream() - << BucketUnpacker::kBucketMetaFieldName << "." - << elem.fieldNameStringData().substr(metaField->size() + 1)); - continue; - } - - uasserted(ErrorCodes::IndexNotFound, - str::stream() << "Invalid index spec for time-series collection: " - << redact(origCmd.toBSON({})) // 'origKey' is included in 'origCmd' - << ". Index must be either on the '" << *metaField << "' or '" - << timeField << "' fields: " << elem); - } - return builder.obj(); -} - -/** * Returns a CollMod on the underlying buckets collection of the time-series collection. * Returns null if 'origCmd' is not for a time-series collection. */ @@ -204,18 +123,24 @@ std::unique_ptr<CollMod> makeTimeseriesCollModCommand(OperationContext* opCtx, const CollMod& origCmd) { const auto& origNs = origCmd.getNamespace(); - auto timeseriesOptions = getTimeseriesOptions(opCtx, origNs); + auto timeseriesOptions = timeseries::getTimeseriesOptions(opCtx, origNs); // Return early with null if we are not working with a time-series collection. if (!timeseriesOptions) { return {}; } - // TODO(SERVER-54639): Map index specs to bucket collection using helper function. auto index = origCmd.getIndex(); if (index && index->getKeyPattern()) { - index->setKeyPattern( - makeTimeseriesIndexSpecKey(*timeseriesOptions, origCmd, *index->getKeyPattern())); + auto bucketsIndexSpecWithStatus = timeseries::convertTimeseriesIndexSpecToBucketsIndexSpec( + *timeseriesOptions, *index->getKeyPattern()); + + uassert(ErrorCodes::IndexNotFound, + str::stream() << bucketsIndexSpecWithStatus.getStatus().toString() + << " Command request: " << redact(origCmd.toBSON({})), + bucketsIndexSpecWithStatus.isOK()); + + index->setKeyPattern(std::move(bucketsIndexSpecWithStatus.getValue())); } auto ns = origNs.makeTimeseriesBucketsNamespace(); diff --git a/src/mongo/db/commands/list_indexes.cpp b/src/mongo/db/commands/list_indexes.cpp index 699efe6555c..5d207392b6a 100644 --- a/src/mongo/db/commands/list_indexes.cpp +++ b/src/mongo/db/commands/list_indexes.cpp @@ -36,7 +36,6 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/catalog/database_holder.h" #include "mongo/db/catalog/list_indexes.h" #include "mongo/db/clientcursor.h" #include "mongo/db/commands.h" @@ -49,7 +48,6 @@ #include "mongo/db/exec/working_set.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/list_indexes_gen.h" -#include "mongo/db/pipeline/document_source_internal_unpack_bucket.h" #include "mongo/db/query/cursor_request.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/query/find_common.h" @@ -57,7 +55,7 @@ #include "mongo/db/service_context.h" #include "mongo/db/storage/durable_catalog.h" #include "mongo/db/storage/storage_engine.h" -#include "mongo/db/views/view_catalog.h" +#include "mongo/db/timeseries/timeseries_index_schema_conversion_functions.h" #include "mongo/logv2/log.h" #include "mongo/util/uuid.h" @@ -65,105 +63,20 @@ namespace mongo { namespace { /** - * Returns time-series options if 'ns' refers to a time-series collection. - */ -boost::optional<TimeseriesOptions> getTimeseriesOptions( - OperationContext* opCtx, const boost::optional<NamespaceString>& ns) { - if (!ns) { - return {}; - } - - auto viewCatalog = DatabaseHolder::get(opCtx)->getViewCatalog(opCtx, ns->db()); - if (!viewCatalog) { - return {}; - } - - auto view = viewCatalog->lookupWithoutValidatingDurableViews(opCtx, ns->ns()); - if (!view) { - return {}; - } - - // Return a copy of the time-series options so that we don't refer to the internal state of - // 'viewCatalog' once it goes out of scope. - return view->timeseries(); -} - -/** - * Returns an index key with field names mapped from the bucket collection schema. - * Returns an empty BSONObj if the index key cannot be converted. - */ -BSONObj makeTimeseriesIndexSpecKey(const TimeseriesOptions& timeseriesOptions, - const BSONObj& origKey) { - auto timeField = timeseriesOptions.getTimeField(); - auto metaField = timeseriesOptions.getMetaField(); - - std::string controlMinTimeField = str::stream() << "control.min." << timeField; - std::string controlMaxTimeField = str::stream() << "control.max." << timeField; - - BSONObjBuilder builder; - for (const auto& elem : origKey) { - // Determine if the index requested on the time field is ascending or descending. - // The final index spec will be subjected to a more complete validation in - // index_key_validate::validateKeyPattern(). - if (elem.fieldNameStringData() == controlMinTimeField) { - if (!elem.isNumber()) { - return {}; - } - builder.appendAs(elem, timeField); - continue; - } else if (elem.fieldNameStringData() == controlMaxTimeField) { - // Skip control.max.<timeField> since the control.min.<timeField> field is enough to - // figure out the direction of the index (ascending/descending). - continue; - } - - if (!metaField) { - return {}; - } - - if (elem.fieldNameStringData() == BucketUnpacker::kBucketMetaFieldName) { - builder.appendAs(elem, *metaField); - continue; - } - - if (elem.fieldNameStringData().startsWith(BucketUnpacker::kBucketMetaFieldName + ".")) { - builder.appendAs(elem, - str::stream() << *metaField << "." - << elem.fieldNameStringData().substr( - BucketUnpacker::kBucketMetaFieldName.size() + 1)); - continue; - } - - return {}; - } - return builder.obj(); -} - -/** + * Converts buckets collection index specs to the time-series collection schema. * Returns a list of index specs mapped from the bucket collection schema. */ std::list<BSONObj> makeTimeseriesIndexSpecs(const TimeseriesOptions& timeseriesOptions, - const std::list<BSONObj>& bucketIndexSpecs) { + const std::list<BSONObj>& bucketsIndexSpecs) { std::list<BSONObj> indexSpecs; - for (const auto& bucketIndexSpec : bucketIndexSpecs) { + for (const auto& bucketsIndexSpec : bucketsIndexSpecs) { // TODO(SERVER-54639): Map index specs from bucket collection using helper function. BSONObjBuilder builder; bool skip = false; - for (const auto& elem : bucketIndexSpec) { + for (const auto& elem : bucketsIndexSpec) { if (elem.fieldNameStringData() == ListIndexesReplyItem::kKeyFieldName) { - // On a time-series collection with 'tm' time field and 'mm' metadata field, - // we may see a compound index on the underlying bucket collection mapped from: - // { - // 'meta.tag1': 1, - // 'control.min.tm': 1, - // 'control.max.tm': 1 - // } - // to an index on the time-series collection: - // { - // 'mm.tag1': 1, - // tm: 1 - // } - auto key = makeTimeseriesIndexSpecKey(timeseriesOptions, elem.Obj()); + auto key = timeseries::convertBucketsIndexSpecToTimeseriesIndexSpec( + timeseriesOptions, elem.Obj()); if (key.isEmpty()) { // Skip index spec due to failed conversion. skip = true; @@ -204,21 +117,22 @@ IndexSpecsWithNamespaceString getIndexSpecsWithNamespaceString(OperationContext* // Since time-series collections don't have UUIDs, we skip the time-series lookup // if the target collection is specified as a UUID. - if (const auto& origNss = origNssOrUUID.nss(); - auto timeseriesOptions = getTimeseriesOptions(opCtx, origNss)) { - auto bucketsNss = origNss->makeTimeseriesBucketsNamespace(); - AutoGetCollectionForReadCommandMaybeLockFree autoColl(opCtx, bucketsNss); - - const CollectionPtr& coll = autoColl.getCollection(); - uassert(ErrorCodes::NamespaceNotFound, - str::stream() << "ns does not exist: " << bucketsNss, - coll); - - return std::make_pair( - makeTimeseriesIndexSpecs( - *timeseriesOptions, - listIndexesInLock(opCtx, coll, bucketsNss, cmd.getIncludeBuildUUIDs())), - *origNss); + if (const auto& origNss = origNssOrUUID.nss()) { + if (auto timeseriesOptions = timeseries::getTimeseriesOptions(opCtx, *origNss)) { + auto bucketsNss = origNss->makeTimeseriesBucketsNamespace(); + AutoGetCollectionForReadCommandMaybeLockFree autoColl(opCtx, bucketsNss); + + const CollectionPtr& coll = autoColl.getCollection(); + uassert(ErrorCodes::NamespaceNotFound, + str::stream() << "ns does not exist: " << bucketsNss, + coll); + + return std::make_pair( + makeTimeseriesIndexSpecs( + *timeseriesOptions, + listIndexesInLock(opCtx, coll, bucketsNss, cmd.getIncludeBuildUUIDs())), + *origNss); + } } AutoGetCollectionForReadCommandMaybeLockFree autoColl(opCtx, origNssOrUUID); diff --git a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp index e9212d3b212..57841a7b83f 100644 --- a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp +++ b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp @@ -123,5 +123,59 @@ StatusWith<BSONObj> convertTimeseriesIndexSpecToBucketsIndexSpec( return builder.obj(); } +BSONObj convertBucketsIndexSpecToTimeseriesIndexSpec(const TimeseriesOptions& timeseriesOptions, + const BSONObj& bucketsIndexSpecBSON) { + auto timeField = timeseriesOptions.getTimeField(); + auto metaField = timeseriesOptions.getMetaField(); + + const std::string controlMinTimeField = str::stream() << "control.min." << timeField; + const std::string controlMaxTimeField = str::stream() << "control.max." << timeField; + + BSONObjBuilder builder; + for (const auto& elem : bucketsIndexSpecBSON) { + // The index specification on the time field is ascending or descending. + if (elem.fieldNameStringData() == controlMinTimeField) { + if (!elem.isNumber()) { + // This index spec on the underlying buckets collection is not valid for + // time-series. Therefore, we will not convert the index spec.. + return {}; + } + + builder.appendAs(elem, timeField); + continue; + } else if (elem.fieldNameStringData() == controlMaxTimeField) { + // Skip 'control.max.<timeField>' since the 'control.min.<timeField>' field is + // sufficient to determine whether the index is ascending or descending. + continue; + } + + if (!metaField) { + // 'elem' is an invalid index spec field for this time-series collection. It does not + // match the time field and there is no metaField set. Therefore, we will not convert + // the index spec. + return {}; + } + + if (elem.fieldNameStringData() == BucketUnpacker::kBucketMetaFieldName) { + builder.appendAs(elem, *metaField); + continue; + } + + if (elem.fieldNameStringData().startsWith(BucketUnpacker::kBucketMetaFieldName + ".")) { + builder.appendAs(elem, + str::stream() << *metaField << "." + << elem.fieldNameStringData().substr( + BucketUnpacker::kBucketMetaFieldName.size() + 1)); + continue; + } + + // 'elem' is an invalid index spec field for this time-series collection. It matches neither + // the time field nor the metaField field. Therefore, we will not convert the index spec. + return {}; + } + + return builder.obj(); +} + } // namespace timeseries } // namespace mongo diff --git a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.h b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.h index 151837bdce9..aec7cda44c7 100644 --- a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.h +++ b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.h @@ -58,5 +58,29 @@ boost::optional<TimeseriesOptions> getTimeseriesOptions(OperationContext* opCtx, StatusWith<BSONObj> convertTimeseriesIndexSpecToBucketsIndexSpec( const TimeseriesOptions& timeseriesOptions, const BSONObj& timeseriesIndexSpecBSON); +/** + * Maps the buckets collection index spec 'bucketsIndexSpecBSON' to the index schema of the + * time-series collection using the information provided in 'timeseriesOptions'. + * + * If 'bucketsIndexSpecBSON' does not match a valid time-series index format, then an empty BSONObj + * is returned. + * + * Conversion Example: + * On a time-series collection with 'tm' time field and 'mm' metadata field, + * we may see a compound index on the underlying bucket collection mapped from: + * { + * 'meta.tag1': 1, + * 'control.min.tm': 1, + * 'control.max.tm': 1 + * } + * to an index on the time-series collection: + * { + * 'mm.tag1': 1, + * 'tm': 1 + * } + */ +BSONObj convertBucketsIndexSpecToTimeseriesIndexSpec(const TimeseriesOptions& timeseriesOptions, + const BSONObj& bucketsIndexSpecBSON); + } // namespace timeseries } // namespace mongo |