SERVER-111411: Fix resharding with forceDistribution on time series collections (#50466)
Co-authored-by: Abdul Qadeer <abdul.qadeer@mongodb.com> GitOrigin-RevId: 1a2c62dc3bbf411f6c1b50b95fda1b332a49b9bd
This commit is contained in:
parent
5f7e594d07
commit
5cc1d48094
@ -528,6 +528,8 @@ last-continuous:
|
||||
ticket: SERVER-112273
|
||||
- test_file: jstests/replsets/election_term_over_int_max.js
|
||||
ticket: SERVER-119413
|
||||
- test_file: jstests/sharding/resharding_timeseries/reshard_timeseries_force_redistribution.js
|
||||
ticket: SERVER-111411
|
||||
- test_file: jstests/replsets/oplog_fetch_lag_metric.js
|
||||
ticket: SERVER-116300
|
||||
suites: null
|
||||
@ -1115,6 +1117,8 @@ last-lts:
|
||||
ticket: SERVER-112273
|
||||
- test_file: jstests/replsets/election_term_over_int_max.js
|
||||
ticket: SERVER-119413
|
||||
- test_file: jstests/sharding/resharding_timeseries/reshard_timeseries_force_redistribution.js
|
||||
ticket: SERVER-111411
|
||||
- test_file: jstests/replsets/oplog_fetch_lag_metric.js
|
||||
ticket: SERVER-116300
|
||||
suites: null
|
||||
|
||||
@ -102,6 +102,8 @@ export var ReshardingTest = class {
|
||||
this._primaryShardName = undefined;
|
||||
/** @private */
|
||||
this._underlyingSourceNs = undefined;
|
||||
/** @private */
|
||||
this._timeseriesMetaField = undefined;
|
||||
|
||||
// Properties set by startReshardingInBackground() and withReshardingInBackground().
|
||||
/** @private */
|
||||
@ -336,24 +338,7 @@ export var ReshardingTest = class {
|
||||
|
||||
this._primaryShardName = primaryShardName;
|
||||
|
||||
let tempCollNamePrefix = "system.resharding";
|
||||
// TODO SERVER-101784 simplify once only viewless timeseries collections exist.
|
||||
if (collOptions.timeseries !== undefined && !areViewlessTimeseriesEnabled(this._st.s)) {
|
||||
let bucketUUID = getUUIDFromListCollections(
|
||||
sourceDB, getTimeseriesBucketsColl(sourceCollection.getName()));
|
||||
|
||||
assert.neq(bucketUUID, null, `can't find ns: ${this._ns} after creating chunks`);
|
||||
|
||||
this._sourceCollectionUUID = bucketUUID;
|
||||
|
||||
tempCollNamePrefix = getTimeseriesBucketsColl("resharding");
|
||||
this._underlyingSourceNs =
|
||||
`${sourceDB.getName()}.${getTimeseriesBucketsColl(sourceCollection.getName())}`;
|
||||
} else {
|
||||
this._sourceCollectionUUID = getUUIDFromListCollections(sourceDB, collName);
|
||||
this._underlyingSourceNs = this._ns;
|
||||
}
|
||||
|
||||
const tempCollNamePrefix = this._setupTimeseriesState(sourceDB, collName, collOptions);
|
||||
const sourceCollectionUUIDString = extractUUIDFromObject(this._sourceCollectionUUID);
|
||||
this._tempNs = `${sourceDB.getName()}.${tempCollNamePrefix}.${sourceCollectionUUIDString}`;
|
||||
|
||||
@ -391,27 +376,9 @@ export var ReshardingTest = class {
|
||||
CreateShardedCollectionUtil.shardCollectionWithChunks(
|
||||
sourceCollection, shardKeyPattern, chunks, collOptions);
|
||||
|
||||
let tempCollNamePrefix = "system.resharding";
|
||||
// TODO SERVER-101784 simplify once only viewless timeseries collections exist.
|
||||
if (collOptions.timeseries !== undefined && !areViewlessTimeseriesEnabled(this._st.s)) {
|
||||
let bucketUUID = getUUIDFromListCollections(
|
||||
sourceDB, getTimeseriesBucketsColl(sourceCollection.getName()));
|
||||
|
||||
assert.neq(bucketUUID, null, `can't find ns: ${this._ns} after creating chunks`);
|
||||
|
||||
this._sourceCollectionUUID = bucketUUID;
|
||||
|
||||
tempCollNamePrefix = getTimeseriesBucketsColl("resharding");
|
||||
this._underlyingSourceNs =
|
||||
`${sourceDB.getName()}.${getTimeseriesBucketsColl(sourceCollection.getName())}`;
|
||||
} else {
|
||||
this._sourceCollectionUUID =
|
||||
getUUIDFromListCollections(sourceDB, sourceCollection.getName());
|
||||
this._underlyingSourceNs = this._ns;
|
||||
}
|
||||
|
||||
const tempCollNamePrefix =
|
||||
this._setupTimeseriesState(sourceDB, sourceCollection.getName(), collOptions);
|
||||
const sourceCollectionUUIDString = extractUUIDFromObject(this._sourceCollectionUUID);
|
||||
|
||||
this._tempNs = `${sourceDB.getName()}.${tempCollNamePrefix}.${sourceCollectionUUIDString}`;
|
||||
|
||||
return sourceCollection;
|
||||
@ -1038,6 +1005,64 @@ export var ReshardingTest = class {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up internal state for timeseries collections, including UUID, namespace, and metaField.
|
||||
* Handles both viewless and non-viewless timeseries collections.
|
||||
* @private
|
||||
* @returns {string} The temp collection name prefix to use.
|
||||
*/
|
||||
_setupTimeseriesState(sourceDB, collName, collOptions) {
|
||||
let tempCollNamePrefix = "system.resharding";
|
||||
|
||||
// TODO SERVER-101784 simplify once only viewless timeseries collections exist.
|
||||
if (collOptions.timeseries !== undefined && !areViewlessTimeseriesEnabled(this._st.s)) {
|
||||
let bucketUUID =
|
||||
getUUIDFromListCollections(sourceDB, getTimeseriesBucketsColl(collName));
|
||||
|
||||
assert.neq(bucketUUID, null, `can't find ns: ${this._ns} after creating chunks`);
|
||||
|
||||
this._sourceCollectionUUID = bucketUUID;
|
||||
this._timeseriesMetaField = collOptions.timeseries.metaField;
|
||||
|
||||
tempCollNamePrefix = getTimeseriesBucketsColl("resharding");
|
||||
this._underlyingSourceNs =
|
||||
`${sourceDB.getName()}.${getTimeseriesBucketsColl(collName)}`;
|
||||
} else {
|
||||
this._sourceCollectionUUID = getUUIDFromListCollections(sourceDB, collName);
|
||||
this._underlyingSourceNs = this._ns;
|
||||
// For viewless timeseries, still store metaField for shard key translation.
|
||||
if (collOptions.timeseries !== undefined) {
|
||||
this._timeseriesMetaField = collOptions.timeseries.metaField;
|
||||
}
|
||||
}
|
||||
|
||||
return tempCollNamePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a user-provided shard key to the internal format for timeseries collections.
|
||||
* For timeseries collections, the metaField name (e.g., "metadata") is replaced with "meta".
|
||||
* For non-timeseries collections, returns the shard key unchanged.
|
||||
* @private
|
||||
*/
|
||||
_translateTimeseriesShardKey(shardKey) {
|
||||
if (this._timeseriesMetaField === undefined) {
|
||||
return shardKey;
|
||||
}
|
||||
|
||||
const translatedKey = {};
|
||||
for (const [field, value] of Object.entries(shardKey)) {
|
||||
if (field === this._timeseriesMetaField) {
|
||||
translatedKey["meta"] = value;
|
||||
} else if (field.startsWith(this._timeseriesMetaField + ".")) {
|
||||
translatedKey["meta" + field.substring(this._timeseriesMetaField.length)] = value;
|
||||
} else {
|
||||
translatedKey[field] = value;
|
||||
}
|
||||
}
|
||||
return translatedKey;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
_checkCoordinatorPostState(expectedErrorCode) {
|
||||
assert.eq(
|
||||
@ -1071,12 +1096,17 @@ export var ReshardingTest = class {
|
||||
`didn't find config.collections entry for ${this._underlyingSourceNs}`);
|
||||
|
||||
if (expectedErrorCode === ErrorCodes.OK) {
|
||||
assert.eq(this._newShardKey,
|
||||
collEntry.key,
|
||||
"shard key pattern didn't change despite resharding having succeeded");
|
||||
assert.neq(this._sourceCollectionUUID,
|
||||
collEntry.uuid,
|
||||
"collection UUID didn't change despite resharding having succeeded");
|
||||
const expectedShardKey = this._translateTimeseriesShardKey(this._newShardKey);
|
||||
assert.eq(
|
||||
expectedShardKey,
|
||||
collEntry.key,
|
||||
"shard key pattern didn't change despite resharding having succeeded",
|
||||
);
|
||||
assert.neq(
|
||||
this._sourceCollectionUUID,
|
||||
collEntry.uuid,
|
||||
"collection UUID didn't change despite resharding having succeeded",
|
||||
);
|
||||
} else {
|
||||
assert.eq(this._currentShardKey,
|
||||
collEntry.key,
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Tests that resharding a timeseries collection with forceRedistribution works correctly
|
||||
* when using the same shard key.
|
||||
*
|
||||
* @tags: [
|
||||
* requires_fcv_80,
|
||||
* featureFlagReshardingForTimeseries,
|
||||
* ]
|
||||
*/
|
||||
|
||||
import {ReshardingTest} from "jstests/sharding/libs/resharding_test_fixture.js";
|
||||
|
||||
const dbName = jsTestName();
|
||||
const collName = "coll";
|
||||
const ns = `${dbName}.${collName}`;
|
||||
|
||||
const reshardingTest = new ReshardingTest({numDonors: 2, numRecipients: 2, reshardInPlace: true});
|
||||
reshardingTest.setup();
|
||||
|
||||
const donorShardNames = reshardingTest.donorShardNames;
|
||||
|
||||
const timeseriesInfo = {
|
||||
timeField: "ts",
|
||||
metaField: "metadata",
|
||||
};
|
||||
|
||||
// This will be translated internally to {"meta.x": 1}.
|
||||
const shardKeyPattern = {
|
||||
"metadata.x": 1,
|
||||
};
|
||||
|
||||
const coll = reshardingTest.createShardedCollection({
|
||||
ns: ns,
|
||||
shardKeyPattern: shardKeyPattern,
|
||||
chunks: [
|
||||
{min: {"meta.x": MinKey}, max: {"meta.x": 0}, shard: donorShardNames[0]},
|
||||
{min: {"meta.x": 0}, max: {"meta.x": MaxKey}, shard: donorShardNames[1]},
|
||||
],
|
||||
collOptions: {
|
||||
timeseries: timeseriesInfo,
|
||||
},
|
||||
});
|
||||
|
||||
assert.commandWorked(
|
||||
coll.insert([
|
||||
{data: 1, ts: new Date(), metadata: {x: -1, y: -1}},
|
||||
{data: 2, ts: new Date(), metadata: {x: 1, y: 1}},
|
||||
]),
|
||||
);
|
||||
|
||||
reshardingTest.withReshardingInBackground(
|
||||
{
|
||||
newShardKeyPattern: shardKeyPattern,
|
||||
newChunks: [
|
||||
{min: {"meta.x": MinKey}, max: {"meta.x": 0}, shard: donorShardNames[0]},
|
||||
{min: {"meta.x": 0}, max: {"meta.x": MaxKey}, shard: donorShardNames[1]},
|
||||
],
|
||||
forceRedistribution: true,
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
|
||||
reshardingTest.teardown();
|
||||
@ -1685,6 +1685,7 @@ mongo_cc_unit_test(
|
||||
"session_catalog_migration_destination_test.cpp",
|
||||
"session_catalog_migration_source_test.cpp",
|
||||
"shard_key_index_util_test.cpp",
|
||||
"shard_key_util_test.cpp",
|
||||
"shard_local_test.cpp",
|
||||
"shard_metadata_util_test.cpp",
|
||||
"shard_server_catalog_cache_loader_test.cpp",
|
||||
|
||||
@ -139,12 +139,24 @@ ExecutorFuture<void> ReshardCollectionCoordinator::_runImpl(
|
||||
<< "' not found in cluster catalog",
|
||||
cmOld.hasRoutingTable());
|
||||
|
||||
auto provenance = _doc.getProvenance();
|
||||
auto currentShardKey = cmOld.getShardKeyPattern().getKeyPattern().toBSON();
|
||||
|
||||
BSONObj translatedKey;
|
||||
if (cmOld.isTimeseriesCollection() &&
|
||||
resharding::isOrdinaryReshardCollection(provenance)) {
|
||||
auto tsOptions = cmOld.getTimeseriesFields().get().getTimeseriesOptions();
|
||||
translatedKey =
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, _doc.getKey());
|
||||
}
|
||||
|
||||
StateDoc newDoc(_doc);
|
||||
newDoc.setOldShardKey(cmOld.getShardKeyPattern().getKeyPattern().toBSON());
|
||||
newDoc.setOldCollectionUUID(cmOld.getUUID());
|
||||
_updateStateDocument(opCtx, std::move(newDoc));
|
||||
|
||||
ConfigsvrReshardCollection configsvrReshardCollection(nss(), _doc.getKey());
|
||||
auto finalShardKey = translatedKey.isEmpty() ? _doc.getKey() : translatedKey;
|
||||
ConfigsvrReshardCollection configsvrReshardCollection(nss(), finalShardKey);
|
||||
configsvrReshardCollection.setDbName(nss().dbName());
|
||||
configsvrReshardCollection.setUnique(_doc.getUnique());
|
||||
configsvrReshardCollection.setCollation(_doc.getCollation());
|
||||
@ -184,7 +196,6 @@ ExecutorFuture<void> ReshardCollectionCoordinator::_runImpl(
|
||||
_doc.getPerformVerification());
|
||||
configsvrReshardCollection.setPerformVerification(_doc.getPerformVerification());
|
||||
|
||||
auto provenance = _doc.getProvenance();
|
||||
if (resharding::isMoveCollection(provenance)) {
|
||||
uassert(ErrorCodes::NamespaceNotFound,
|
||||
str::stream()
|
||||
@ -230,7 +241,7 @@ ExecutorFuture<void> ReshardCollectionCoordinator::_runImpl(
|
||||
"when using the forceRedistribution option. The "
|
||||
"forceRedistribution option is meant for redistributing the "
|
||||
"collection to a different set of shards.",
|
||||
cmOld.getShardKeyPattern().isShardKey(_doc.getKey()));
|
||||
cmOld.getShardKeyPattern().isShardKey(finalShardKey));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -514,6 +514,10 @@ bool isUnshardCollection(const boost::optional<ReshardingProvenanceEnum>& proven
|
||||
return provenance && provenance.get() == ReshardingProvenanceEnum::kUnshardCollection;
|
||||
}
|
||||
|
||||
bool isOrdinaryReshardCollection(const boost::optional<ReshardingProvenanceEnum>& provenance) {
|
||||
return provenance && provenance.get() == ReshardingProvenanceEnum::kReshardCollection;
|
||||
}
|
||||
|
||||
std::shared_ptr<ThreadPool> makeThreadPoolForMarkKilledExecutor(const std::string& poolName) {
|
||||
return std::make_shared<ThreadPool>([&] {
|
||||
ThreadPool::Options options;
|
||||
@ -561,17 +565,18 @@ ReshardingCoordinatorDocument createReshardingCoordinatorDoc(
|
||||
auto existingUUID = collEntry.getUuid();
|
||||
auto shardKeySpec = request.getKey();
|
||||
|
||||
// moveCollection/unshardCollection are called with _id as the new shard key since
|
||||
// that's an acceptable value for tracked unsharded collections so we can skip this.
|
||||
// TODO: SERVER-119524 Remove this after 8.3 becomes last LTS.
|
||||
// We need to perform translation of shard key for timeseries collections if the shard key is
|
||||
// not already translated to raw key format to provide backwards compatibility.
|
||||
if (collEntry.getTimeseriesFields() &&
|
||||
(!setProvenance ||
|
||||
(*request.getProvenance() == ReshardingProvenanceEnum::kReshardCollection))) {
|
||||
auto tsOptions = collEntry.getTimeseriesFields().get().getTimeseriesOptions();
|
||||
shardkeyutil::validateTimeseriesShardKey(
|
||||
tsOptions.getTimeField(), tsOptions.getMetaField(), request.getKey());
|
||||
shardKeySpec =
|
||||
uassertStatusOK(timeseries::createBucketsShardKeySpecFromTimeseriesShardKeySpec(
|
||||
tsOptions, request.getKey()));
|
||||
auto isTranslated = shardkeyutil::isRawTimeseriesShardKey(tsOptions, request.getKey());
|
||||
if (!isTranslated) {
|
||||
shardKeySpec =
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, request.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
auto tempReshardingNss = resharding::constructTemporaryReshardingNss(nss, collEntry.getUuid());
|
||||
|
||||
@ -412,6 +412,11 @@ void validateShardDistribution(const std::vector<ShardKeyRange>& shardDistributi
|
||||
OperationContext* opCtx,
|
||||
const ShardKeyPattern& keyPattern);
|
||||
|
||||
/**
|
||||
* Returns true if the provenance is reshardcollection.
|
||||
*/
|
||||
bool isOrdinaryReshardCollection(const boost::optional<ReshardingProvenanceEnum>& provenance);
|
||||
|
||||
/**
|
||||
* Returns true if the provenance is moveCollection or balancerMoveCollection.
|
||||
*/
|
||||
|
||||
@ -352,6 +352,35 @@ void validateTimeseriesShardKey(StringData timeFieldName,
|
||||
}
|
||||
}
|
||||
|
||||
BSONObj validateAndTranslateTimeseriesShardKey(const TimeseriesOptions& tsOptions,
|
||||
const BSONObj& tsShardKey) {
|
||||
shardkeyutil::validateTimeseriesShardKey(
|
||||
tsOptions.getTimeField(), tsOptions.getMetaField(), tsShardKey);
|
||||
|
||||
return uassertStatusOK(
|
||||
timeseries::createBucketsShardKeySpecFromTimeseriesShardKeySpec(tsOptions, tsShardKey));
|
||||
}
|
||||
|
||||
bool isRawTimeseriesShardKey(const TimeseriesOptions& tsOptions, const BSONObj& tsShardKey) {
|
||||
if (tsShardKey.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const auto& timeFieldName = tsOptions.getTimeField();
|
||||
const auto& metaFieldName = tsOptions.getMetaField();
|
||||
for (const auto& elem : tsShardKey) {
|
||||
const auto& fieldName = elem.fieldNameStringData();
|
||||
if (fieldName == timeFieldName ||
|
||||
(metaFieldName &&
|
||||
(fieldName == *metaFieldName || fieldName.starts_with(*metaFieldName + ".")))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// After checking if any field matches user-facing field names, we need to check if it is a
|
||||
// valid bucket format key to reject invalid keys.
|
||||
return timeseries::createTimeseriesIndexFromBucketsIndexSpec(tsOptions, tsShardKey).has_value();
|
||||
}
|
||||
|
||||
// TODO: SERVER-64187 move calls to validateShardKeyIsNotEncrypted into
|
||||
// validateShardKeyIndexExistsOrCreateIfPossible
|
||||
void validateShardKeyIsNotEncrypted(OperationContext* opCtx,
|
||||
|
||||
@ -242,5 +242,20 @@ void validateTimeseriesShardKey(StringData timeFieldName,
|
||||
boost::optional<StringData> metaFieldName,
|
||||
const BSONObj& shardKeyPattern);
|
||||
|
||||
/**
|
||||
* Validates that 'tsShardKey' uses the user-facing time-series field names (timeField and
|
||||
* metaField), then translates it to the internal buckets collection format.
|
||||
* Throws if the shard key contains fields other than the defined timeField or metaField.
|
||||
*/
|
||||
BSONObj validateAndTranslateTimeseriesShardKey(const TimeseriesOptions& tsOptions,
|
||||
const BSONObj& tsShardKey);
|
||||
|
||||
/**
|
||||
* Returns true if the given shard key is a valid key already in the internal buckets collection
|
||||
* raw key format. Returns false if the key uses user-facing field names (requiring translation)
|
||||
* or if the key is not a recognized timeseries shard key.
|
||||
*/
|
||||
bool isRawTimeseriesShardKey(const TimeseriesOptions& tsOptions, const BSONObj& tsShardKey);
|
||||
|
||||
} // namespace shardkeyutil
|
||||
} // namespace mongo
|
||||
|
||||
304
src/mongo/db/s/shard_key_util_test.cpp
Normal file
304
src/mongo/db/s/shard_key_util_test.cpp
Normal file
@ -0,0 +1,304 @@
|
||||
/**
|
||||
* Copyright (C) 2026-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
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* 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/db/s/shard_key_util.h"
|
||||
|
||||
#include "mongo/base/string_data.h"
|
||||
#include "mongo/bson/bsonmisc.h"
|
||||
#include "mongo/bson/bsonobj.h"
|
||||
#include "mongo/db/timeseries/timeseries_gen.h"
|
||||
#include "mongo/unittest/assert.h"
|
||||
#include "mongo/unittest/unittest.h"
|
||||
#include "mongo/util/assert_util.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/optional/optional.hpp>
|
||||
|
||||
namespace mongo {
|
||||
namespace {
|
||||
|
||||
TimeseriesOptions makeTimeseriesOptions(StringData timeField,
|
||||
boost::optional<StringData> metaField) {
|
||||
TimeseriesOptions options{std::string{timeField}};
|
||||
if (metaField) {
|
||||
options.setMetaField(*metaField);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
// Tests for isRawTimeseriesShardKey
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, UserFacingTimeFieldKeyIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("time" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, UserFacingMetaFieldKeyIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("hostId" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, UserFacingMetaSubfieldKeyIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("hostId.x" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, UserFacingCompoundKeyIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(
|
||||
shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("hostId" << 1 << "time" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, TranslatedMetaFieldKeyIsTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("meta" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, TranslatedMetaSubfieldKeyIsTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("meta.x" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, TranslatedTimeFieldKeyIsTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("control.min.time" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, TranslatedCompoundKeyIsTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(
|
||||
tsOptions, BSON("meta" << 1 << "control.min.time" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, MetaFieldNamedMetaIsHandledCorrectly) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("meta"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("meta" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, TimeFieldOnlyNoMetaFieldIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("ts", boost::none);
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("ts" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, TranslatedTimeFieldNoMetaFieldIsTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("ts", boost::none);
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("control.min.ts" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, HashedMetaFieldKeyIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("hostId.x" << "hashed")));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, TranslatedHashedMetaFieldKeyIsTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("meta.x" << "hashed")));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, InvalidIdFieldIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("_id" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, InvalidArbitraryFieldIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("someRandomField" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, EmptyKeyIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSONObj()));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, ControlMaxTimeFieldIsTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("control.max.time" << -1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, ControlWithoutMinMaxIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("control" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, ControlMinWithWrongFieldIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(
|
||||
shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("control.min.wrongField" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, MixedValidAndInvalidFieldsIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("meta" << 1 << "_id" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, MixedUserFacingAndTranslatedFieldsIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(
|
||||
shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("hostId" << 1 << "meta" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, MixedTranslatedTimeAndInvalidFieldIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(
|
||||
tsOptions, BSON("control.min.time" << 1 << "_id" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, MixedValidMetaAndInvalidControlFieldIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(
|
||||
tsOptions, BSON("meta" << 1 << "control.min.wrongField" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, TranslatedDescendingCompoundKeyIsTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(
|
||||
tsOptions, BSON("meta" << 1 << "control.max.time" << -1 << "control.min.time" << -1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, NoMetaFieldWithInvalidFieldIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("ts", boost::none);
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("_id" << 1)));
|
||||
}
|
||||
|
||||
TEST(IsRawTimeseriesShardKeyTest, NoMetaFieldWithMetaKeyIsNotTranslated) {
|
||||
auto tsOptions = makeTimeseriesOptions("ts", boost::none);
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, BSON("meta" << 1)));
|
||||
}
|
||||
|
||||
// Tests for validateAndTranslateTimeseriesShardKey
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, MetaFieldKeyTranslatesCorrectly) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
auto result =
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, BSON("hostId" << 1));
|
||||
ASSERT_BSONOBJ_EQ(result, BSON("meta" << 1));
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, MetaSubfieldKeyTranslatesCorrectly) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
auto result =
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, BSON("hostId.x" << 1));
|
||||
ASSERT_BSONOBJ_EQ(result, BSON("meta.x" << 1));
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, TimeFieldKeyTranslatesCorrectly) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
auto result =
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, BSON("time" << 1));
|
||||
ASSERT_BSONOBJ_EQ(result, BSON("control.min.time" << 1));
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, CompoundKeyTranslatesCorrectly) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
auto result = shardkeyutil::validateAndTranslateTimeseriesShardKey(
|
||||
tsOptions, BSON("hostId" << 1 << "time" << 1));
|
||||
ASSERT_BSONOBJ_EQ(result, BSON("meta" << 1 << "control.min.time" << 1));
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, HashedMetaFieldKeyTranslatesCorrectly) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
auto result = shardkeyutil::validateAndTranslateTimeseriesShardKey(
|
||||
tsOptions, BSON("hostId.x" << "hashed"));
|
||||
ASSERT_BSONOBJ_EQ(result, BSON("meta.x" << "hashed"));
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, DescendingTimeFieldKeyTranslatesCorrectly) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
auto result =
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, BSON("time" << -1));
|
||||
ASSERT_BSONOBJ_EQ(result, BSON("control.max.time" << -1 << "control.min.time" << -1));
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, InvalidFieldThrows5914001) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_THROWS_CODE(
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, BSON("_id" << 1)),
|
||||
AssertionException,
|
||||
5914001);
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, InvalidArbitraryFieldThrows5914001) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_THROWS_CODE(shardkeyutil::validateAndTranslateTimeseriesShardKey(
|
||||
tsOptions, BSON("someRandomField" << 1)),
|
||||
AssertionException,
|
||||
5914001);
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, TimeFieldNotAtEndThrows5914000) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_THROWS_CODE(shardkeyutil::validateAndTranslateTimeseriesShardKey(
|
||||
tsOptions, BSON("time" << 1 << "hostId" << 1)),
|
||||
AssertionException,
|
||||
5914000);
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, TimeFieldWithHashedThrows880031) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
ASSERT_THROWS_CODE(
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, BSON("time" << "hashed")),
|
||||
AssertionException,
|
||||
880031);
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, NoMetaFieldWithMetaKeyThrows5914001) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", boost::none);
|
||||
ASSERT_THROWS_CODE(
|
||||
shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, BSON("someField" << 1)),
|
||||
AssertionException,
|
||||
5914001);
|
||||
}
|
||||
|
||||
TEST(ValidateAndTranslateTimeseriesShardKeyTest, NoMetaFieldTimeKeyTranslatesCorrectly) {
|
||||
auto tsOptions = makeTimeseriesOptions("ts", boost::none);
|
||||
auto result = shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, BSON("ts" << 1));
|
||||
ASSERT_BSONOBJ_EQ(result, BSON("control.min.ts" << 1));
|
||||
}
|
||||
|
||||
TEST(TimeseriesShardKeyValidationFlowTest, InvalidKeyIsRejectedByValidationFlow) {
|
||||
auto tsOptions = makeTimeseriesOptions("time", StringData("hostId"));
|
||||
|
||||
auto invalidKey = BSON("_id" << 1);
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, invalidKey));
|
||||
ASSERT_THROWS_CODE(shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, invalidKey),
|
||||
AssertionException,
|
||||
5914001);
|
||||
|
||||
auto userFacingKey = BSON("hostId" << 1);
|
||||
ASSERT_FALSE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, userFacingKey));
|
||||
auto result = shardkeyutil::validateAndTranslateTimeseriesShardKey(tsOptions, userFacingKey);
|
||||
ASSERT_BSONOBJ_EQ(result, BSON("meta" << 1));
|
||||
|
||||
auto translatedKey = BSON("meta" << 1);
|
||||
ASSERT_TRUE(shardkeyutil::isRawTimeseriesShardKey(tsOptions, translatedKey));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mongo
|
||||
Loading…
Reference in New Issue
Block a user