SERVER-113997 SERVER-114329 Fix legacy timeseries namespace translation in findAndModify explain command (#44558)
Co-authored-by: Joan Bruguera Micó (at MongoDB) <joan.bruguera-mico@mongodb.com> GitOrigin-RevId: 299aeac990ac6467ae0d16a51fc9b0c68d5baa33
This commit is contained in:
parent
3c3ed51901
commit
fa497b53f1
@ -14,6 +14,9 @@ import {extendWorkload} from "jstests/concurrency/fsm_libs/extend_workload.js";
|
||||
import {
|
||||
$config as $baseConfig
|
||||
} from "jstests/concurrency/fsm_workloads/timeseries/timeseries_raw_data_operations.js";
|
||||
import {
|
||||
assertExplainTargetsExpectedTimeseriesNamespace
|
||||
} from "jstests/core/timeseries/libs/viewless_timeseries_util.js";
|
||||
import {getPlanStage} from "jstests/libs/query/analyze_plan.js";
|
||||
|
||||
export const $config = extendWorkload($baseConfig, function($config, $super) {
|
||||
@ -30,10 +33,7 @@ export const $config = extendWorkload($baseConfig, function($config, $super) {
|
||||
"Expected not to find TS_MODIFY stage " + tojson(commandResult);
|
||||
assert(commandResult.command.rawData,
|
||||
`Expected command to include rawData but got ${tojson(commandResult)}`);
|
||||
assert.eq(commandResult.command[commandName],
|
||||
coll.getName(),
|
||||
`Expected command namespace to be ${tojson(coll.getName())} but got ${
|
||||
tojson(commandResult.command[commandName])}`);
|
||||
assertExplainTargetsExpectedTimeseriesNamespace(db, coll, commandResult, commandName);
|
||||
};
|
||||
|
||||
$config.states.explainAggregate = function explainAggregate(db, collName) {
|
||||
|
||||
@ -15,6 +15,9 @@
|
||||
* ]
|
||||
*/
|
||||
|
||||
import {
|
||||
assertExplainTargetsExpectedTimeseriesNamespace
|
||||
} from "jstests/core/timeseries/libs/viewless_timeseries_util.js";
|
||||
import {getPlanStage} from "jstests/libs/query/analyze_plan.js";
|
||||
|
||||
export const $config = (function() {
|
||||
@ -88,12 +91,16 @@ export const $config = (function() {
|
||||
}
|
||||
assert.isnull(getPlanStage(commandResult, "TS_MODIFY")),
|
||||
"Expected not to find TS_MODIFY stage " + tojson(commandResult);
|
||||
assert(commandResult.command.rawData,
|
||||
`Expected command to include rawData but got ${tojson(commandResult)}`);
|
||||
assert.eq(commandResult.command[commandName],
|
||||
coll.getName(),
|
||||
`Expected command namespace to be ${tojson(coll.getName())} but got ${
|
||||
tojson(commandResult.command[commandName])}`);
|
||||
assert(
|
||||
commandResult.command.rawData,
|
||||
`Expected command to include rawData but got ${tojson(commandResult)}`,
|
||||
);
|
||||
assertExplainTargetsExpectedTimeseriesNamespace(db, coll, commandResult, commandName, {
|
||||
// In balancer suites, `coll` may be tracked when the explain command was run,
|
||||
// but then recreateCollection may drop the collection and re-create it as
|
||||
// untracked.
|
||||
mayConcurrentlyTrackOrUntrack: TestData.runningWithBalancer,
|
||||
});
|
||||
},
|
||||
|
||||
handleCollectionDrop: function(fn, opName) {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
|
||||
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
|
||||
import {getTimeseriesCollForRawOps} from "jstests/libs/raw_operation_utils.js";
|
||||
|
||||
export function areViewlessTimeseriesEnabled(db) {
|
||||
return FeatureFlagUtil.isPresentAndEnabled(db, "CreateViewlessTimeseriesCollections");
|
||||
@ -60,3 +61,51 @@ export function isShardedTimeseries(coll) {
|
||||
return FixtureHelpers.isSharded(coll) ||
|
||||
FixtureHelpers.isSharded(getTimeseriesBucketsColl(coll));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the namespace targeted by `commandResult` the command matches `coll`,
|
||||
* modulo quirks of translation to system.buckets for legacy timeseries.
|
||||
*/
|
||||
export function assertExplainTargetsExpectedTimeseriesNamespace(
|
||||
db,
|
||||
coll,
|
||||
commandResult,
|
||||
commandName,
|
||||
{mayConcurrentlyTrackOrUntrack = false} = {},
|
||||
) {
|
||||
let targetColl = (() => {
|
||||
if (commandResult.command.findAndModify &&
|
||||
FixtureHelpers.isTracked(getTimeseriesCollForDDLOps(db, coll)) &&
|
||||
!areViewlessTimeseriesEnabled(db)) {
|
||||
// In sharded clusters for findAndModify over legacy tracked timeseries we convert the
|
||||
// namespace on the router and we send the command with translated namespace to the
|
||||
// shard, thus we expect explain to report the command targeting system.buckets internal
|
||||
// namespace.
|
||||
return getTimeseriesCollForDDLOps(db, coll);
|
||||
}
|
||||
return getTimeseriesCollForRawOps(db, coll);
|
||||
})();
|
||||
|
||||
if (commandResult.command.findAndModify && !areViewlessTimeseriesEnabled(db) &&
|
||||
(mayConcurrentlyTrackOrUntrack ||
|
||||
(TestData.runningWithBalancer &&
|
||||
FixtureHelpers.isTracked(getTimeseriesCollForDDLOps(db, coll)) &&
|
||||
!FixtureHelpers.isSharded(getTimeseriesCollForDDLOps(db, coll))))) {
|
||||
// If the collection is tracked or untracked findAndModify explain returns either the
|
||||
// buckets or main timeseries namespace In suites with enabled balancer the collection could
|
||||
// randomly became tracked.
|
||||
jsTest.log(
|
||||
"Skipping namespace check for findAndModify explain output since we don't know if the collection was tracked or not when the command was executed",
|
||||
);
|
||||
} else {
|
||||
jsTest.log(`commandRes = ${tojson(commandResult)}`);
|
||||
assert.eq(
|
||||
commandResult.command[commandName],
|
||||
targetColl.getName(),
|
||||
`Expected command namespace to be ${tojson(targetColl.getName())} but got ${
|
||||
tojson(
|
||||
commandResult.command[commandName],
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,9 @@ import {
|
||||
getTimeseriesCollForRawOps,
|
||||
kRawOperationSpec
|
||||
} from "jstests/core/libs/raw_operation_utils.js";
|
||||
import {
|
||||
assertExplainTargetsExpectedTimeseriesNamespace
|
||||
} from "jstests/core/timeseries/libs/viewless_timeseries_util.js";
|
||||
import {getPlanStage} from "jstests/libs/query/analyze_plan.js";
|
||||
|
||||
const coll = db[jsTestName()];
|
||||
@ -32,19 +35,23 @@ assert.commandWorked(coll.insert([
|
||||
const assertExplain = function(commandResult, commandName) {
|
||||
assert(commandResult.ok);
|
||||
if (commandResult.command.bulkWrite) {
|
||||
assert.eq(commandResult.command.nsInfo.length,
|
||||
1,
|
||||
`Expected 1 namespace in explain command but got ${
|
||||
commandResult.command.nsInfo.length}`);
|
||||
assert.eq(commandResult.command.nsInfo[0].ns,
|
||||
coll.getFullName(),
|
||||
`Expected command namespace to be ${tojson(coll.getFullName())} but got ${
|
||||
tojson(commandResult.command.nsInfo[0].ns)}`);
|
||||
assert.eq(
|
||||
commandResult.command.nsInfo.length,
|
||||
1,
|
||||
`Expected 1 namespace in explain command but got ${
|
||||
commandResult.command.nsInfo.length}`,
|
||||
);
|
||||
assert.eq(
|
||||
commandResult.command.nsInfo[0].ns,
|
||||
getTimeseriesCollForRawOps(coll).getFullName(),
|
||||
`Expected command namespace to be ${
|
||||
tojson(getTimeseriesCollForRawOps(coll).getFullName())} but got ${
|
||||
tojson(
|
||||
commandResult.command.nsInfo[0].ns,
|
||||
)}`,
|
||||
);
|
||||
} else {
|
||||
assert.eq(commandResult.command[commandName],
|
||||
coll.getName(),
|
||||
`Expected command namespace to be ${tojson(coll.getName())} but got ${
|
||||
tojson(commandResult.command[commandName])}`);
|
||||
assertExplainTargetsExpectedTimeseriesNamespace(db, coll, commandResult, commandName);
|
||||
}
|
||||
assert(commandResult.command.rawData);
|
||||
assert.isnull(getPlanStage(commandResult, "TS_MODIFY")),
|
||||
|
||||
@ -21,10 +21,15 @@ import {ShardingTest} from "jstests/libs/shardingtest.js";
|
||||
const assertExplain = function(coll, commandResult) {
|
||||
const commandName = "findAndModify";
|
||||
assert(commandResult.ok);
|
||||
assert.eq(commandResult.command[commandName],
|
||||
coll.getName(),
|
||||
`Expected command namespace to be ${tojson(coll.getName())} but got ${
|
||||
tojson(commandResult.command[commandName])}`);
|
||||
assert.eq(
|
||||
commandResult.command[commandName],
|
||||
getTimeseriesCollForDDLOps(db, coll).getName(),
|
||||
`Expected command namespace to be ${
|
||||
tojson(getTimeseriesCollForDDLOps(db, coll).getName())} but got ${
|
||||
tojson(
|
||||
commandResult.command[commandName],
|
||||
)}`,
|
||||
);
|
||||
assert(isRawOperationSupported(db) === (commandResult.command.rawData ?? false));
|
||||
assert.isnull(getPlanStage(commandResult, "TS_MODIFY")),
|
||||
"Expected not to find TS_MODIFY stage " + tojson(commandResult);
|
||||
|
||||
@ -491,7 +491,9 @@ namespace {
|
||||
* Replaces the target namespace in the 'cmdObj' by 'bucketNss'. Also sets the
|
||||
* 'isTimeseriesNamespace' flag.
|
||||
*/
|
||||
BSONObj replaceNamespaceByBucketNss(const BSONObj& cmdObj, const NamespaceString& bucketNss) {
|
||||
BSONObj replaceNamespaceByBucketNss(OperationContext* opCtx,
|
||||
const BSONObj& cmdObj,
|
||||
const NamespaceString& bucketNss) {
|
||||
BSONObjBuilder bob;
|
||||
for (const auto& elem : cmdObj) {
|
||||
const auto name = elem.fieldNameStringData();
|
||||
@ -501,11 +503,12 @@ BSONObj replaceNamespaceByBucketNss(const BSONObj& cmdObj, const NamespaceString
|
||||
bob.append(elem);
|
||||
}
|
||||
}
|
||||
// Set this flag so that shards can differentiate a request on a time-series view from a request
|
||||
// on a time-series buckets collection since we replace the target namespace in the command with
|
||||
// the buckets namespace.
|
||||
bob.append(write_ops::FindAndModifyCommandRequest::kIsTimeseriesNamespaceFieldName, true);
|
||||
|
||||
if (!isRawDataOperation(opCtx)) {
|
||||
// Set this flag so that shards can differentiate a request on a time-series view from a
|
||||
// request on a time-series buckets collection since we replace the target namespace in the
|
||||
// command with the buckets namespace.
|
||||
bob.append(write_ops::FindAndModifyCommandRequest::kIsTimeseriesNamespaceFieldName, true);
|
||||
}
|
||||
return bob.obj();
|
||||
}
|
||||
|
||||
@ -639,6 +642,9 @@ Status FindAndModifyCmd::explain(OperationContext* opCtx,
|
||||
auto isTimeseriesViewRequest = false;
|
||||
if (isTrackedTimeseries && !nss.isTimeseriesBucketsCollection()) {
|
||||
nss = std::move(cm.getNss());
|
||||
// If the request is for a view on a sharded timeseries buckets collection, we need to
|
||||
// replace the namespace by buckets collection namespace in the command object.
|
||||
cmdObj = replaceNamespaceByBucketNss(opCtx, cmdObj, nss);
|
||||
if (!isRawDataOperation(opCtx)) {
|
||||
isTimeseriesViewRequest = true;
|
||||
}
|
||||
@ -653,11 +659,6 @@ Status FindAndModifyCmd::explain(OperationContext* opCtx,
|
||||
const auto let = getLet(cmdObj);
|
||||
const auto rc = getLegacyRuntimeConstants(cmdObj);
|
||||
if (cri.hasRoutingTable()) {
|
||||
// If the request is for a view on a sharded timeseries buckets collection, we need to
|
||||
// replace the namespace by buckets collection namespace in the command object.
|
||||
if (isTimeseriesViewRequest) {
|
||||
cmdObj = replaceNamespaceByBucketNss(cmdObj, nss);
|
||||
}
|
||||
auto expCtx = makeExpressionContextWithDefaultsForTargeter(
|
||||
opCtx, nss, cri, collation, boost::none /* verbosity */, let, rc);
|
||||
if (write_without_shard_key::useTwoPhaseProtocol(opCtx,
|
||||
@ -787,24 +788,25 @@ bool FindAndModifyCmd::run(OperationContext* opCtx,
|
||||
diagnostic_printers::ShardKeyDiagnosticPrinter{
|
||||
cm.isSharded() ? cm.getShardKeyPattern().toBSON() : BSONObj()});
|
||||
|
||||
// Append mongoS' runtime constants to the command object before forwarding it to the shard.
|
||||
auto cmdObjForShard = appendLegacyRuntimeConstantsToCommandObject(opCtx, cmdObj);
|
||||
|
||||
auto isTrackedTimeseries = cri.hasRoutingTable() && cm.getTimeseriesFields();
|
||||
auto isTimeseriesViewRequest = false;
|
||||
if (isTrackedTimeseries && !nss.isTimeseriesBucketsCollection()) {
|
||||
// If the request is for a view on a sharded timeseries buckets collection, we need to
|
||||
// replace the namespace by buckets collection namespace in the command object.
|
||||
nss = std::move(cm.getNss());
|
||||
isTimeseriesViewRequest = true;
|
||||
cmdObjForShard = replaceNamespaceByBucketNss(opCtx, cmdObjForShard, nss);
|
||||
if (!isRawDataOperation(opCtx)) {
|
||||
isTimeseriesViewRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: at this point, 'nss' should be the timeseries buckets collection namespace if we're
|
||||
// writing to a sharded timeseries collection.
|
||||
|
||||
// Append mongoS' runtime constants to the command object before forwarding it to the shard.
|
||||
auto cmdObjForShard = appendLegacyRuntimeConstantsToCommandObject(opCtx, cmdObj);
|
||||
if (cri.hasRoutingTable()) {
|
||||
// If the request is for a view on a sharded timeseries buckets collection, we need to
|
||||
// replace the namespace by buckets collection namespace in the command object.
|
||||
if (isTimeseriesViewRequest) {
|
||||
cmdObjForShard = replaceNamespaceByBucketNss(cmdObjForShard, nss);
|
||||
}
|
||||
|
||||
auto letParams = getLet(cmdObjForShard);
|
||||
auto runtimeConstants = getLegacyRuntimeConstants(cmdObjForShard);
|
||||
BSONObj collation = getCollation(cmdObjForShard);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user