mirror of https://github.com/mongodb/mongo
SERVER-107427 Add originalQueryShapeHash to slow query logs on mongod (#44945)
GitOrigin-RevId: 76a1410ab52e0aed023e695368090661647d80b7
This commit is contained in:
parent
dbfa36321a
commit
785512528b
|
|
@ -0,0 +1,378 @@
|
||||||
|
// Tests that 'originalQueryShapeHash' appears in slow query logs on the shards when the command
|
||||||
|
// originates from the router. 'originalQueryShapeHash' is not expected to appear for getMore and
|
||||||
|
// explain.
|
||||||
|
//
|
||||||
|
// @tags: [
|
||||||
|
// requires_profiling,
|
||||||
|
// # Profile command doesn't support stepdowns.
|
||||||
|
// does_not_support_stepdowns,
|
||||||
|
// # Cowardly refusing to run test that interacts with the system profiler as the 'system.profile'
|
||||||
|
// # collection is not replicated.
|
||||||
|
// does_not_support_causal_consistency,
|
||||||
|
// # Does not support transactions as the test issues getMores which cannot be started in a transaction.
|
||||||
|
// does_not_support_transactions,
|
||||||
|
// requires_fcv_83,
|
||||||
|
// ]
|
||||||
|
|
||||||
|
import {after, before, describe, it} from "jstests/libs/mochalite.js";
|
||||||
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
||||||
|
import {getExplainCommand} from "jstests/libs/cmd_object_utils.js";
|
||||||
|
|
||||||
|
describe("originalQueryShapeHash appears in slow logs", function () {
|
||||||
|
let st;
|
||||||
|
let routerDB;
|
||||||
|
let shard0DB;
|
||||||
|
|
||||||
|
// Collection configurations to test.
|
||||||
|
const collNames = {
|
||||||
|
unsharded: "unsharded_coll",
|
||||||
|
two_shard: "sharded_two_shards_coll",
|
||||||
|
one_shard: "sharded_one_shard_coll",
|
||||||
|
};
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
st = new ShardingTest({shards: 2, mongos: 1});
|
||||||
|
assert(st.adminCommand({enableSharding: jsTestName(), primaryShard: st.shard0.shardName}));
|
||||||
|
routerDB = st.s.getDB(jsTestName());
|
||||||
|
shard0DB = st.shard0.getDB(jsTestName());
|
||||||
|
|
||||||
|
// Set up unsharded collection.
|
||||||
|
const unshardedColl = routerDB[collNames.unsharded];
|
||||||
|
unshardedColl.drop();
|
||||||
|
unshardedColl.insertMany([
|
||||||
|
{x: 4, y: 1},
|
||||||
|
{x: 4, y: 2},
|
||||||
|
{x: 5, y: 2},
|
||||||
|
{x: 6, y: 3},
|
||||||
|
]);
|
||||||
|
assert.commandWorked(routerDB.adminCommand({untrackUnshardedCollection: unshardedColl.getFullName()}));
|
||||||
|
|
||||||
|
// Set up sharded collection on two shards.
|
||||||
|
const shardedTwoShardsColl = routerDB[collNames.two_shard];
|
||||||
|
shardedTwoShardsColl.drop();
|
||||||
|
shardedTwoShardsColl.insertMany([
|
||||||
|
{x: 4, y: 1},
|
||||||
|
{x: 4, y: 2},
|
||||||
|
{x: 5, y: 2},
|
||||||
|
{x: 6, y: 3},
|
||||||
|
{x: 7, y: 3},
|
||||||
|
]);
|
||||||
|
shardedTwoShardsColl.createIndex({y: 1});
|
||||||
|
assert.commandWorked(routerDB.adminCommand({shardCollection: shardedTwoShardsColl.getFullName(), key: {y: 1}}));
|
||||||
|
// Split and move chunks to ensure data is on both shards.
|
||||||
|
assert.commandWorked(routerDB.adminCommand({split: shardedTwoShardsColl.getFullName(), middle: {y: 2}}));
|
||||||
|
assert.commandWorked(
|
||||||
|
routerDB.adminCommand({
|
||||||
|
moveChunk: shardedTwoShardsColl.getFullName(),
|
||||||
|
find: {y: 3},
|
||||||
|
to: st.shard1.shardName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up sharded collection on one shard.
|
||||||
|
const shardedOneShardColl = routerDB[collNames.one_shard];
|
||||||
|
shardedOneShardColl.drop();
|
||||||
|
shardedOneShardColl.insertMany([
|
||||||
|
{x: 4, y: 1},
|
||||||
|
{x: 4, y: 2},
|
||||||
|
{x: 5, y: 2},
|
||||||
|
{x: 6, y: 3},
|
||||||
|
]);
|
||||||
|
shardedOneShardColl.createIndex({y: 1});
|
||||||
|
assert.commandWorked(routerDB.adminCommand({shardCollection: shardedOneShardColl.getFullName(), key: {y: 1}}));
|
||||||
|
|
||||||
|
// Set slow query threshold to -1 so every query gets logged on the router and shards.
|
||||||
|
routerDB.setProfilingLevel(0, -1);
|
||||||
|
shard0DB.setProfilingLevel(0, -1);
|
||||||
|
const shard1DB = st.shard1.getDB(jsTestName());
|
||||||
|
shard1DB.setProfilingLevel(0, -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
st.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
function getSlowQueryLogLines({queryComment, testDB, commandType = null}) {
|
||||||
|
const slowQueryLogs = assert
|
||||||
|
.commandWorked(testDB.adminCommand({getLog: "global"}))
|
||||||
|
.log.map((entry) => JSON.parse(entry))
|
||||||
|
.filter((entry) => {
|
||||||
|
if (entry.msg !== "Slow query" || !entry.attr || !entry.attr.command) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (commandType === "getMore") {
|
||||||
|
return entry.attr.command.getMore && entry.attr.originatingCommand.comment == queryComment;
|
||||||
|
}
|
||||||
|
return entry.attr.command.comment == queryComment && !entry.attr.command.getMore;
|
||||||
|
});
|
||||||
|
jsTest.log.debug("Slow query logs", {queryComment, commandType, slowQueryLogs});
|
||||||
|
return slowQueryLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSlowQueryLogLinesFromComment({queryComment, testDB, commandType = null}) {
|
||||||
|
const slowQueryLogs = getSlowQueryLogLines({queryComment, testDB, commandType});
|
||||||
|
assert(
|
||||||
|
slowQueryLogs.length > 0,
|
||||||
|
`No slow query log found for comment: ${queryComment}, commandType: ${commandType}`,
|
||||||
|
);
|
||||||
|
return slowQueryLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asserts whether a hash field should appear in slow query logs. If it should appear, asserts
|
||||||
|
// all slow query logs have the same value. The 'key' parameter specifies which field to check:
|
||||||
|
// - "queryShapeHash": checks log.attr.queryShapeHash
|
||||||
|
// - "originalQueryShapeHash": checks log.attr.command.originalQueryShapeHash
|
||||||
|
function assertHashInSlowLogs({comment, testDB, key, shouldAppear = true}) {
|
||||||
|
const slowQueryLogs = getSlowQueryLogLinesFromComment({queryComment: comment, testDB});
|
||||||
|
let hashValue;
|
||||||
|
slowQueryLogs.forEach((slowQueryLog) => {
|
||||||
|
const value =
|
||||||
|
key === "originalQueryShapeHash"
|
||||||
|
? slowQueryLog.attr.command.originalQueryShapeHash
|
||||||
|
: slowQueryLog.attr.queryShapeHash;
|
||||||
|
assert(
|
||||||
|
shouldAppear == Boolean(value),
|
||||||
|
`${key} expected to ${shouldAppear ? "appear" : "not appear"} in slow query log. Received: ` +
|
||||||
|
tojson(slowQueryLog),
|
||||||
|
);
|
||||||
|
if (hashValue) {
|
||||||
|
assert.eq(
|
||||||
|
hashValue,
|
||||||
|
value,
|
||||||
|
`Inconsistent ${key} in slow logs. Slow Query Log: ` + tojson(slowQueryLog),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
hashValue = value;
|
||||||
|
});
|
||||||
|
return hashValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testQueryShapeHash(query) {
|
||||||
|
// Run command to create slow query logs.
|
||||||
|
const result = assert.commandWorked(routerDB.runCommand(query));
|
||||||
|
|
||||||
|
// Assert 'originalQueryShapeHash' doesn't appear on the router.
|
||||||
|
assertHashInSlowLogs({
|
||||||
|
comment: query.comment,
|
||||||
|
testDB: routerDB,
|
||||||
|
key: "originalQueryShapeHash",
|
||||||
|
shouldAppear: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert 'originalQueryShapeHash' appears on the shard(s).
|
||||||
|
const originalQueryShapeHash = assertHashInSlowLogs({
|
||||||
|
comment: query.comment,
|
||||||
|
testDB: shard0DB,
|
||||||
|
key: "originalQueryShapeHash",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 'originalQueryShapeHash' on the shard should be the same as 'queryShapeHash' on the router.
|
||||||
|
const routerQueryShapeHash = assertHashInSlowLogs({
|
||||||
|
comment: query.comment,
|
||||||
|
testDB: routerDB,
|
||||||
|
key: "queryShapeHash",
|
||||||
|
});
|
||||||
|
assert.eq(
|
||||||
|
routerQueryShapeHash,
|
||||||
|
originalQueryShapeHash,
|
||||||
|
"originalQueryShapeHash and routerQueryShapeHash do not match",
|
||||||
|
);
|
||||||
|
|
||||||
|
// If cursor is present, issue getMores and verify 'originalQueryShapeHash' doesn't appear.
|
||||||
|
// TODO SERVER-115109 update test to verify 'originalQueryShapeHash' does appear.
|
||||||
|
if (result.cursor) {
|
||||||
|
const commandCursor = new DBCommandCursor(routerDB, result);
|
||||||
|
commandCursor.itcount(); // exhaust the cursor
|
||||||
|
const getMoreLogs = [
|
||||||
|
...getSlowQueryLogLines({queryComment: query.comment, testDB: shard0DB, commandType: "getMore"}),
|
||||||
|
...getSlowQueryLogLines({queryComment: query.comment, testDB: routerDB, commandType: "getMore"}),
|
||||||
|
];
|
||||||
|
const logsWithOriginalHash = getMoreLogs.filter((log) => log.attr.command.originalQueryShapeHash);
|
||||||
|
assert.eq(
|
||||||
|
logsWithOriginalHash.length,
|
||||||
|
0,
|
||||||
|
"getMore should not have originalQueryShapeHash: " + tojson(logsWithOriginalHash),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run explain. 'originalQueryShapeHash' should not appear in the slow query logs for explain.
|
||||||
|
const explainComment = query.comment + "_explain";
|
||||||
|
const explainQuery = {...query, comment: explainComment};
|
||||||
|
assert.commandWorked(routerDB.runCommand(getExplainCommand(explainQuery)));
|
||||||
|
assertHashInSlowLogs({
|
||||||
|
comment: explainComment,
|
||||||
|
testDB: routerDB,
|
||||||
|
key: "originalQueryShapeHash",
|
||||||
|
shouldAppear: false,
|
||||||
|
});
|
||||||
|
assertHashInSlowLogs({
|
||||||
|
comment: explainComment,
|
||||||
|
testDB: shard0DB,
|
||||||
|
key: "originalQueryShapeHash",
|
||||||
|
shouldAppear: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate test cases for each collection type.
|
||||||
|
Object.values(collNames).forEach((collName) => {
|
||||||
|
const viewName = collName + "_view";
|
||||||
|
|
||||||
|
describe(`running tests on ${collName}`, function () {
|
||||||
|
before(function () {
|
||||||
|
// Create a view on top of this collection for view tests.
|
||||||
|
routerDB[viewName].drop();
|
||||||
|
assert.commandWorked(routerDB.createView(viewName, collName, [{$addFields: {z: 1}}]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be reported for find", function () {
|
||||||
|
testQueryShapeHash({
|
||||||
|
find: collName,
|
||||||
|
filter: {x: 4},
|
||||||
|
batchSize: 0,
|
||||||
|
comment: `!!find ${collName} test`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be reported for find on a view", function () {
|
||||||
|
testQueryShapeHash({
|
||||||
|
find: viewName,
|
||||||
|
filter: {x: 4},
|
||||||
|
batchSize: 0,
|
||||||
|
comment: `!!find view ${collName} test`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be reported for aggregate", function () {
|
||||||
|
testQueryShapeHash({
|
||||||
|
aggregate: collName,
|
||||||
|
pipeline: [{$match: {x: 4}}],
|
||||||
|
cursor: {batchSize: 0},
|
||||||
|
comment: `!!aggregate ${collName} test`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be reported for aggregate on a view", function () {
|
||||||
|
testQueryShapeHash({
|
||||||
|
aggregate: viewName,
|
||||||
|
pipeline: [{$match: {x: 4}}],
|
||||||
|
cursor: {batchSize: 0},
|
||||||
|
comment: `!!aggregate view ${collName} test`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be reported for count", function () {
|
||||||
|
testQueryShapeHash({
|
||||||
|
count: collName,
|
||||||
|
query: {x: 4},
|
||||||
|
comment: `!!count ${collName} test`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be reported for count on a view", function () {
|
||||||
|
testQueryShapeHash({
|
||||||
|
count: viewName,
|
||||||
|
query: {x: 4},
|
||||||
|
comment: `!!count view ${collName} test`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be reported for distinct", function () {
|
||||||
|
testQueryShapeHash({
|
||||||
|
distinct: collName,
|
||||||
|
key: "x",
|
||||||
|
query: {x: 4},
|
||||||
|
comment: `!!distinct ${collName} test`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be reported for distinct on a view", function () {
|
||||||
|
testQueryShapeHash({
|
||||||
|
distinct: viewName,
|
||||||
|
key: "x",
|
||||||
|
query: {x: 4},
|
||||||
|
comment: `!!distinct view ${collName} test`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tests that 'originalQueryShapeHash' cannot be set by users.
|
||||||
|
const exampleHash = "90114C24B1F2EB8B3EBCD0F8387199B8C85D3D202DD68126CD9143AFC684E6BF";
|
||||||
|
const collName = collNames.unsharded;
|
||||||
|
|
||||||
|
it("should error when find includes originalQueryShapeHash on router", function () {
|
||||||
|
const query = {
|
||||||
|
find: collName,
|
||||||
|
filter: {x: 4},
|
||||||
|
originalQueryShapeHash: exampleHash,
|
||||||
|
};
|
||||||
|
assert.commandFailedWithCode(routerDB.runCommand(query), 10742703);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error when find includes originalQueryShapeHash on shard", function () {
|
||||||
|
const query = {
|
||||||
|
find: collName,
|
||||||
|
filter: {x: 4},
|
||||||
|
originalQueryShapeHash: exampleHash,
|
||||||
|
};
|
||||||
|
assert.commandFailedWithCode(shard0DB.runCommand(query), 10742702);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error when aggregate includes originalQueryShapeHash on router", function () {
|
||||||
|
const query = {
|
||||||
|
aggregate: collName,
|
||||||
|
pipeline: [{$match: {x: 4}}],
|
||||||
|
cursor: {},
|
||||||
|
originalQueryShapeHash: exampleHash,
|
||||||
|
};
|
||||||
|
assert.commandFailedWithCode(routerDB.runCommand(query), 10742706);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error when aggregate includes originalQueryShapeHash on shard", function () {
|
||||||
|
const query = {
|
||||||
|
aggregate: collName,
|
||||||
|
pipeline: [{$match: {x: 4}}],
|
||||||
|
cursor: {},
|
||||||
|
originalQueryShapeHash: exampleHash,
|
||||||
|
};
|
||||||
|
assert.commandFailedWithCode(shard0DB.runCommand(query), 10742706);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error when count includes originalQueryShapeHash on router", function () {
|
||||||
|
const query = {
|
||||||
|
count: collName,
|
||||||
|
query: {x: 4},
|
||||||
|
originalQueryShapeHash: exampleHash,
|
||||||
|
};
|
||||||
|
assert.commandFailedWithCode(routerDB.runCommand(query), 10742704);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error when count includes originalQueryShapeHash on shard", function () {
|
||||||
|
const query = {
|
||||||
|
count: collName,
|
||||||
|
query: {x: 4},
|
||||||
|
originalQueryShapeHash: exampleHash,
|
||||||
|
};
|
||||||
|
assert.commandFailedWithCode(shard0DB.runCommand(query), 10742702);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error when distinct includes originalQueryShapeHash on router", function () {
|
||||||
|
const query = {
|
||||||
|
distinct: collName,
|
||||||
|
key: "x",
|
||||||
|
query: {x: 4},
|
||||||
|
originalQueryShapeHash: exampleHash,
|
||||||
|
};
|
||||||
|
assert.commandFailedWithCode(routerDB.runCommand(query), 10742700);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error when distinct includes originalQueryShapeHash on shard", function () {
|
||||||
|
const query = {
|
||||||
|
distinct: collName,
|
||||||
|
key: "x",
|
||||||
|
query: {x: 4},
|
||||||
|
originalQueryShapeHash: exampleHash,
|
||||||
|
};
|
||||||
|
assert.commandFailedWithCode(shard0DB.runCommand(query), 10742702);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -469,7 +469,7 @@ void AggExState::performValidationChecks() const {
|
||||||
auto& liteParsedPipeline = _aggReqDerivatives->liteParsedPipeline;
|
auto& liteParsedPipeline = _aggReqDerivatives->liteParsedPipeline;
|
||||||
|
|
||||||
liteParsedPipeline.validate(_opCtx);
|
liteParsedPipeline.validate(_opCtx);
|
||||||
aggregation_request_helper::validateRequestForAPIVersion(_opCtx, request);
|
aggregation_request_helper::validateRequestWithClient(_opCtx, request);
|
||||||
aggregation_request_helper::validateRequestFromClusterQueryWithoutShardKey(request);
|
aggregation_request_helper::validateRequestFromClusterQueryWithoutShardKey(request);
|
||||||
|
|
||||||
// If we are in a transaction, check whether the parsed pipeline supports being in
|
// If we are in a transaction, check whether the parsed pipeline supports being in
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,9 @@
|
||||||
#include "mongo/db/query/plan_explainer.h"
|
#include "mongo/db/query/plan_explainer.h"
|
||||||
#include "mongo/db/query/plan_summary_stats.h"
|
#include "mongo/db/query/plan_summary_stats.h"
|
||||||
#include "mongo/db/query/query_settings/query_settings_gen.h"
|
#include "mongo/db/query/query_settings/query_settings_gen.h"
|
||||||
|
#include "mongo/db/query/query_shape/count_cmd_shape.h"
|
||||||
|
#include "mongo/db/query/query_shape/query_shape_hash.h"
|
||||||
|
#include "mongo/db/query/query_shape/shape_helpers.h"
|
||||||
#include "mongo/db/query/query_stats/count_key.h"
|
#include "mongo/db/query/query_stats/count_key.h"
|
||||||
#include "mongo/db/query/query_stats/query_stats.h"
|
#include "mongo/db/query/query_stats/query_stats.h"
|
||||||
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
||||||
|
|
@ -366,7 +369,7 @@ public:
|
||||||
parsed_find_command::parseFromCount(expCtx, request(), *extensionsCallback, ns));
|
parsed_find_command::parseFromCount(expCtx, request(), *extensionsCallback, ns));
|
||||||
|
|
||||||
registerRequestForQueryStats(
|
registerRequestForQueryStats(
|
||||||
opCtx, expCtx, curOp, *collOrViewAcquisition, request(), *parsedFind);
|
opCtx, expCtx, curOp, *collOrViewAcquisition, request(), *parsedFind, ns);
|
||||||
|
|
||||||
if (collOrViewAcquisition) {
|
if (collOrViewAcquisition) {
|
||||||
if (collOrViewAcquisition->isView() ||
|
if (collOrViewAcquisition->isView() ||
|
||||||
|
|
@ -507,19 +510,29 @@ public:
|
||||||
CurOp* curOp,
|
CurOp* curOp,
|
||||||
const CollectionOrViewAcquisition& collectionOrView,
|
const CollectionOrViewAcquisition& collectionOrView,
|
||||||
const CountCommandRequest& req,
|
const CountCommandRequest& req,
|
||||||
const ParsedFindCommand& parsedFind) {
|
const ParsedFindCommand& parsedFind,
|
||||||
|
const NamespaceString& ns) {
|
||||||
|
// Compute QueryShapeHash and record it in CurOp.
|
||||||
|
query_shape::DeferredQueryShape deferredShape{[&]() {
|
||||||
|
return shape_helpers::tryMakeShape<query_shape::CountCmdShape>(
|
||||||
|
parsedFind, req.getLimit().has_value(), req.getSkip().has_value());
|
||||||
|
}};
|
||||||
|
boost::optional<query_shape::QueryShapeHash> queryShapeHash =
|
||||||
|
CurOp::get(opCtx)->debug().ensureQueryShapeHash(opCtx, [&]() {
|
||||||
|
return shape_helpers::computeQueryShapeHash(expCtx, deferredShape, ns);
|
||||||
|
});
|
||||||
|
|
||||||
if (feature_flags::gFeatureFlagQueryStatsCountDistinct
|
if (feature_flags::gFeatureFlagQueryStatsCountDistinct
|
||||||
.isEnabledUseLastLTSFCVWhenUninitialized(
|
.isEnabledUseLastLTSFCVWhenUninitialized(
|
||||||
VersionContext::getDecoration(opCtx),
|
VersionContext::getDecoration(opCtx),
|
||||||
serverGlobalParams.featureCompatibility.acquireFCVSnapshot())) {
|
serverGlobalParams.featureCompatibility.acquireFCVSnapshot())) {
|
||||||
query_stats::registerRequest(opCtx, _ns, [&]() {
|
query_stats::registerRequest(opCtx, _ns, [&]() {
|
||||||
|
uassertStatusOKWithContext(deferredShape->getStatus(),
|
||||||
|
"Failed to compute query shape");
|
||||||
return std::make_unique<query_stats::CountKey>(
|
return std::make_unique<query_stats::CountKey>(
|
||||||
expCtx,
|
expCtx,
|
||||||
parsedFind,
|
req,
|
||||||
req.getLimit().has_value(),
|
std::move(deferredShape->getValue()),
|
||||||
req.getSkip().has_value(),
|
|
||||||
req.getReadConcern(),
|
|
||||||
req.getMaxTimeMS().has_value(),
|
|
||||||
collectionOrView.getCollectionType());
|
collectionOrView.getCollectionType());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@
|
||||||
#include "mongo/db/query/query_shape/query_shape.h"
|
#include "mongo/db/query/query_shape/query_shape.h"
|
||||||
#include "mongo/db/query/query_stats/distinct_key.h"
|
#include "mongo/db/query/query_stats/distinct_key.h"
|
||||||
#include "mongo/db/query/query_stats/query_stats.h"
|
#include "mongo/db/query/query_stats/query_stats.h"
|
||||||
|
#include "mongo/db/query/query_utils.h"
|
||||||
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
||||||
#include "mongo/db/query/timeseries/timeseries_translation.h"
|
#include "mongo/db/query/timeseries/timeseries_translation.h"
|
||||||
#include "mongo/db/query/view_response_formatter.h"
|
#include "mongo/db/query/view_response_formatter.h"
|
||||||
|
|
@ -142,12 +143,7 @@ std::unique_ptr<CanonicalQuery> parseDistinctCmd(
|
||||||
// Start the query planning timer right after parsing.
|
// Start the query planning timer right after parsing.
|
||||||
CurOp::get(opCtx)->beginQueryPlanningTimer();
|
CurOp::get(opCtx)->beginQueryPlanningTimer();
|
||||||
|
|
||||||
// Forbid users from passing 'querySettings' explicitly.
|
assertInternalParamsAreSetByInternalClients(opCtx->getClient(), *distinctCommand);
|
||||||
uassert(7923000,
|
|
||||||
"BSON field 'querySettings' is an unknown field",
|
|
||||||
query_settings::allowQuerySettingsFromClient(opCtx->getClient()) ||
|
|
||||||
!distinctCommand->getQuerySettings().has_value());
|
|
||||||
|
|
||||||
auto expCtx = ExpressionContextBuilder{}
|
auto expCtx = ExpressionContextBuilder{}
|
||||||
.fromRequest(opCtx, *distinctCommand, defaultCollator)
|
.fromRequest(opCtx, *distinctCommand, defaultCollator)
|
||||||
.ns(nss)
|
.ns(nss)
|
||||||
|
|
|
||||||
|
|
@ -1164,11 +1164,7 @@ private:
|
||||||
CommandHelpers::ensureValidCollectionName(nss.nss());
|
CommandHelpers::ensureValidCollectionName(nss.nss());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forbid users from passing 'querySettings' explicitly.
|
assertInternalParamsAreSetByInternalClients(opCtx->getClient(), *findCommand);
|
||||||
uassert(7746901,
|
|
||||||
"BSON field 'querySettings' is an unknown field",
|
|
||||||
query_settings::allowQuerySettingsFromClient(opCtx->getClient()) ||
|
|
||||||
!findCommand->getQuerySettings().has_value());
|
|
||||||
|
|
||||||
uassert(ErrorCodes::FailedToParse,
|
uassert(ErrorCodes::FailedToParse,
|
||||||
"Use of forcedPlanSolutionHash not permitted.",
|
"Use of forcedPlanSolutionHash not permitted.",
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ imports:
|
||||||
- "mongo/db/query/hint.idl"
|
- "mongo/db/query/hint.idl"
|
||||||
- "mongo/db/query/query_settings/query_settings.idl"
|
- "mongo/db/query/query_settings/query_settings.idl"
|
||||||
- "mongo/db/write_concern_options.idl"
|
- "mongo/db/write_concern_options.idl"
|
||||||
|
- "mongo/db/query/query_shape/query_shape_hash.idl"
|
||||||
|
|
||||||
types:
|
types:
|
||||||
pipeline:
|
pipeline:
|
||||||
|
|
@ -389,3 +390,11 @@ commands:
|
||||||
type: safeInt64
|
type: safeInt64
|
||||||
optional: true
|
optional: true
|
||||||
stability: internal
|
stability: internal
|
||||||
|
originalQueryShapeHash:
|
||||||
|
description:
|
||||||
|
"The query shape hash of the first query to enter the query system. For example
|
||||||
|
if the router received a query and this is the shard's portion of the query,
|
||||||
|
this hash will be the hash of the query that the router saw."
|
||||||
|
type: QueryShapeHash
|
||||||
|
optional: true
|
||||||
|
stability: internal
|
||||||
|
|
|
||||||
|
|
@ -165,8 +165,8 @@ void validate(const AggregateCommandRequest& aggregate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateRequestForAPIVersion(const OperationContext* opCtx,
|
void validateRequestWithClient(const OperationContext* opCtx,
|
||||||
const AggregateCommandRequest& request) {
|
const AggregateCommandRequest& request) {
|
||||||
invariant(opCtx);
|
invariant(opCtx);
|
||||||
|
|
||||||
auto apiParameters = APIParameters::get(opCtx);
|
auto apiParameters = APIParameters::get(opCtx);
|
||||||
|
|
@ -187,6 +187,13 @@ void validateRequestForAPIVersion(const OperationContext* opCtx,
|
||||||
<< apiVersion,
|
<< apiVersion,
|
||||||
isInternalThreadOrClient);
|
isInternalThreadOrClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forbid users from passing 'originalQueryShapeHash' explicitly.
|
||||||
|
if (request.getOriginalQueryShapeHash()) {
|
||||||
|
uassert(10742706,
|
||||||
|
"BSON field 'originalQueryShapeHash' is an unknown field",
|
||||||
|
isInternalThreadOrClient || client->isInDirectClient());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateRequestFromClusterQueryWithoutShardKey(const AggregateCommandRequest& request) {
|
void validateRequestFromClusterQueryWithoutShardKey(const AggregateCommandRequest& request) {
|
||||||
|
|
|
||||||
|
|
@ -105,11 +105,11 @@ void addQuerySettingsToRequest(AggregateCommandRequest& request,
|
||||||
const boost::intrusive_ptr<ExpressionContext>& expCtx);
|
const boost::intrusive_ptr<ExpressionContext>& expCtx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates if 'AggregateCommandRequest' specs complies with API versioning. Throws uassert in case
|
* Validates if 'AggregateCommandRequest' specs complies with the current Client, which is required
|
||||||
* of any failure.
|
* for API versioning checks. Throws uassert in case of any failure.
|
||||||
*/
|
*/
|
||||||
void validateRequestForAPIVersion(const OperationContext* opCtx,
|
void validateRequestWithClient(const OperationContext* opCtx,
|
||||||
const AggregateCommandRequest& request);
|
const AggregateCommandRequest& request);
|
||||||
/**
|
/**
|
||||||
* Validates if 'AggregateCommandRequest' sets the "isClusterQueryWithoutShardKeyCmd" field then the
|
* Validates if 'AggregateCommandRequest' sets the "isClusterQueryWithoutShardKeyCmd" field then the
|
||||||
* request must have been fromRouter.
|
* request must have been fromRouter.
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,10 @@ TEST(AggregationRequestTest, ShouldSerializeOptionalValuesIfSet) {
|
||||||
request.setIsClusterQueryWithoutShardKeyCmd(true);
|
request.setIsClusterQueryWithoutShardKeyCmd(true);
|
||||||
|
|
||||||
request.setIncludeQueryStatsMetrics(true);
|
request.setIncludeQueryStatsMetrics(true);
|
||||||
|
const BSONObj query = BSON("hello" << 1);
|
||||||
|
const HashBlock<SHA256BlockTraits> queryShapeHash =
|
||||||
|
SHA256Block::computeHash((const uint8_t*)query.objdata(), query.objsize());
|
||||||
|
request.setOriginalQueryShapeHash(queryShapeHash);
|
||||||
|
|
||||||
auto expectedSerialization = Document{
|
auto expectedSerialization = Document{
|
||||||
{AggregateCommandRequest::kCommandName, nss.coll()},
|
{AggregateCommandRequest::kCommandName, nss.coll()},
|
||||||
|
|
@ -248,6 +252,7 @@ TEST(AggregationRequestTest, ShouldSerializeOptionalValuesIfSet) {
|
||||||
{AggregateCommandRequest::kCollectionUUIDFieldName, uuid},
|
{AggregateCommandRequest::kCollectionUUIDFieldName, uuid},
|
||||||
{AggregateCommandRequest::kIsClusterQueryWithoutShardKeyCmdFieldName, true},
|
{AggregateCommandRequest::kIsClusterQueryWithoutShardKeyCmdFieldName, true},
|
||||||
{AggregateCommandRequest::kIncludeQueryStatsMetricsFieldName, true},
|
{AggregateCommandRequest::kIncludeQueryStatsMetricsFieldName, true},
|
||||||
|
{AggregateCommandRequest::kOriginalQueryShapeHashFieldName, queryShapeHash.toHexString()},
|
||||||
{query_request_helper::cmdOptionMaxTimeMS, 10},
|
{query_request_helper::cmdOptionMaxTimeMS, 10},
|
||||||
{repl::ReadConcernArgs::kReadConcernFieldName, readConcernObj},
|
{repl::ReadConcernArgs::kReadConcernFieldName, readConcernObj},
|
||||||
{query_request_helper::kUnwrappedReadPrefField, readPrefObj}};
|
{query_request_helper::kUnwrappedReadPrefField, readPrefObj}};
|
||||||
|
|
|
||||||
|
|
@ -380,6 +380,7 @@ idl_generator(
|
||||||
"//src/mongo/db:basic_types_gen",
|
"//src/mongo/db:basic_types_gen",
|
||||||
"//src/mongo/db/auth:access_checks_gen",
|
"//src/mongo/db/auth:access_checks_gen",
|
||||||
"//src/mongo/db/auth:action_type_gen",
|
"//src/mongo/db/auth:action_type_gen",
|
||||||
|
"//src/mongo/db/query/query_shape:query_shape_hash_gen",
|
||||||
"//src/mongo/idl:generic_argument_gen",
|
"//src/mongo/idl:generic_argument_gen",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ imports:
|
||||||
- "mongo/db/auth/action_type.idl"
|
- "mongo/db/auth/action_type.idl"
|
||||||
- "mongo/db/auth/access_checks.idl"
|
- "mongo/db/auth/access_checks.idl"
|
||||||
- "mongo/db/query/hint.idl"
|
- "mongo/db/query/hint.idl"
|
||||||
|
- "mongo/db/query/query_shape/query_shape_hash.idl"
|
||||||
|
|
||||||
types:
|
types:
|
||||||
countLimit:
|
countLimit:
|
||||||
|
|
@ -142,3 +143,11 @@ commands:
|
||||||
description: "Indicates whether or not query stats metrics should be included in the response."
|
description: "Indicates whether or not query stats metrics should be included in the response."
|
||||||
type: optionalBool
|
type: optionalBool
|
||||||
stability: internal
|
stability: internal
|
||||||
|
originalQueryShapeHash:
|
||||||
|
description:
|
||||||
|
"The query shape hash of the first query to enter the query system. For example
|
||||||
|
if the router received a query and this is the shard's portion of the query,
|
||||||
|
this hash will be the hash of the query that the router saw."
|
||||||
|
type: QueryShapeHash
|
||||||
|
optional: true
|
||||||
|
stability: internal
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ imports:
|
||||||
- "mongo/db/basic_types.idl"
|
- "mongo/db/basic_types.idl"
|
||||||
- "mongo/db/query/hint.idl"
|
- "mongo/db/query/hint.idl"
|
||||||
- "mongo/db/query/query_settings/query_settings.idl"
|
- "mongo/db/query/query_settings/query_settings.idl"
|
||||||
|
- "mongo/db/query/query_shape/query_shape_hash.idl"
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
distinct:
|
distinct:
|
||||||
|
|
@ -75,3 +76,10 @@ commands:
|
||||||
includeQueryStatsMetrics:
|
includeQueryStatsMetrics:
|
||||||
description: "Determines whether or not to include query stats metrics in the response."
|
description: "Determines whether or not to include query stats metrics in the response."
|
||||||
type: optionalBool
|
type: optionalBool
|
||||||
|
originalQueryShapeHash:
|
||||||
|
description:
|
||||||
|
"The query shape hash of the first query to enter the query system. For example
|
||||||
|
if the router received a query and this is the shard's portion of the query,
|
||||||
|
this hash will be the hash of the query that the router saw."
|
||||||
|
type: QueryShapeHash
|
||||||
|
optional: true
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ imports:
|
||||||
- "mongo/db/query/client_cursor/cursor_response.idl"
|
- "mongo/db/query/client_cursor/cursor_response.idl"
|
||||||
- "mongo/db/query/hint.idl"
|
- "mongo/db/query/hint.idl"
|
||||||
- "mongo/db/query/query_settings/query_settings.idl"
|
- "mongo/db/query/query_settings/query_settings.idl"
|
||||||
|
- "mongo/db/query/query_shape/query_shape_hash.idl"
|
||||||
|
|
||||||
types:
|
types:
|
||||||
boolNoOpSerializer:
|
boolNoOpSerializer:
|
||||||
|
|
@ -264,3 +265,11 @@ commands:
|
||||||
type: safeInt64
|
type: safeInt64
|
||||||
optional: true
|
optional: true
|
||||||
stability: internal
|
stability: internal
|
||||||
|
originalQueryShapeHash:
|
||||||
|
description:
|
||||||
|
"The query shape hash of the first query to enter the query system. For example
|
||||||
|
if the router received a query and this is the shard's portion of the query,
|
||||||
|
this hash will be the hash of the query that the router saw."
|
||||||
|
type: QueryShapeHash
|
||||||
|
optional: true
|
||||||
|
stability: internal
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
#include "mongo/db/query/compiler/logical_model/projection/projection_parser.h"
|
#include "mongo/db/query/compiler/logical_model/projection/projection_parser.h"
|
||||||
#include "mongo/db/query/query_planner_common.h"
|
#include "mongo/db/query/query_planner_common.h"
|
||||||
#include "mongo/db/query/query_request_helper.h"
|
#include "mongo/db/query/query_request_helper.h"
|
||||||
|
#include "mongo/db/query/query_utils.h"
|
||||||
#include "mongo/util/assert_util.h"
|
#include "mongo/util/assert_util.h"
|
||||||
#include "mongo/util/str.h"
|
#include "mongo/util/str.h"
|
||||||
|
|
||||||
|
|
@ -356,6 +357,9 @@ StatusWith<std::unique_ptr<ParsedFindCommand>> parseFromCount(
|
||||||
CollatorInterface::collatorsMatch(collator.get(), expCtx->getCollator()));
|
CollatorInterface::collatorsMatch(collator.get(), expCtx->getCollator()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertInternalParamsAreSetByInternalClients(expCtx->getOperationContext()->getClient(),
|
||||||
|
countCommand);
|
||||||
|
|
||||||
// Copy necessary count command fields to find command. Notably, the skip and limit fields are
|
// Copy necessary count command fields to find command. Notably, the skip and limit fields are
|
||||||
// _not_ copied because the count stage in the PlanExecutor already applies the skip and limit,
|
// _not_ copied because the count stage in the PlanExecutor already applies the skip and limit,
|
||||||
// and thus copying the fields would involve erroneously skipping and limiting twice.
|
// and thus copying the fields would involve erroneously skipping and limiting twice.
|
||||||
|
|
|
||||||
|
|
@ -328,3 +328,10 @@ feature_flags:
|
||||||
default: false
|
default: false
|
||||||
fcv_gated: false
|
fcv_gated: false
|
||||||
incremental_rollout_phase: in_development
|
incremental_rollout_phase: in_development
|
||||||
|
|
||||||
|
featureFlagOriginalQueryShapeHash:
|
||||||
|
description: "Feature flag to to pass 'originalQueryShapeHash' field in commands from router to shards."
|
||||||
|
cpp_varname: gFeatureFlagOriginalQueryShapeHash
|
||||||
|
default: true
|
||||||
|
version: 8.3
|
||||||
|
fcv_gated: true
|
||||||
|
|
|
||||||
|
|
@ -297,10 +297,6 @@ RepresentativeQueryInfo createRepresentativeInfoAgg(OperationContext* opCtx,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool requestComesFromRouterOrSentDirectlyToShard(Client* client) {
|
|
||||||
return client->isInternalClient() || client->isInDirectClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
void validateIndexKeyPatternStructure(const IndexHint& hint) {
|
void validateIndexKeyPatternStructure(const IndexHint& hint) {
|
||||||
if (auto&& keyPattern = hint.getIndexKeyPattern()) {
|
if (auto&& keyPattern = hint.getIndexKeyPattern()) {
|
||||||
uassert(9646000, "key pattern index can't be empty", keyPattern->nFields() > 0);
|
uassert(9646000, "key pattern index can't be empty", keyPattern->nFields() > 0);
|
||||||
|
|
@ -563,7 +559,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* opCtx = expCtx->getOperationContext();
|
auto* opCtx = expCtx->getOperationContext();
|
||||||
if (requestComesFromRouterOrSentDirectlyToShard(opCtx->getClient()) ||
|
if (isInternalOrDirectClient(opCtx->getClient()) ||
|
||||||
querySettingsFromOriginalCommand.has_value()) {
|
querySettingsFromOriginalCommand.has_value()) {
|
||||||
return querySettingsFromOriginalCommand.get_value_or(QuerySettings());
|
return querySettingsFromOriginalCommand.get_value_or(QuerySettings());
|
||||||
}
|
}
|
||||||
|
|
@ -849,7 +845,7 @@ bool allowQuerySettingsFromClient(Client* client) {
|
||||||
// - comes from router (internal client), which has already performed the query settings lookup
|
// - comes from router (internal client), which has already performed the query settings lookup
|
||||||
// or
|
// or
|
||||||
// - has been created interally and is executed via DBDirectClient.
|
// - has been created interally and is executed via DBDirectClient.
|
||||||
return requestComesFromRouterOrSentDirectlyToShard(client);
|
return isInternalOrDirectClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDefault(const QuerySettings& settings) {
|
bool isDefault(const QuerySettings& settings) {
|
||||||
|
|
|
||||||
|
|
@ -44,17 +44,14 @@ namespace mongo::query_stats {
|
||||||
class CountKey final : public Key {
|
class CountKey final : public Key {
|
||||||
public:
|
public:
|
||||||
CountKey(const boost::intrusive_ptr<ExpressionContext>& expCtx,
|
CountKey(const boost::intrusive_ptr<ExpressionContext>& expCtx,
|
||||||
const ParsedFindCommand& findCommand,
|
const CountCommandRequest& request,
|
||||||
bool hasLimit,
|
std::unique_ptr<query_shape::Shape> countShape,
|
||||||
bool hasSkip,
|
|
||||||
const boost::optional<repl::ReadConcernArgs>& readConcern,
|
|
||||||
bool hasMaxTimeMS,
|
|
||||||
query_shape::CollectionType collectionType = query_shape::CollectionType::kUnknown)
|
query_shape::CollectionType collectionType = query_shape::CollectionType::kUnknown)
|
||||||
: Key(expCtx->getOperationContext(),
|
: Key(expCtx->getOperationContext(),
|
||||||
std::make_unique<query_shape::CountCmdShape>(findCommand, hasLimit, hasSkip),
|
std::move(countShape),
|
||||||
findCommand.findCommandRequest->getHint(),
|
request.getHint(),
|
||||||
readConcern,
|
request.getReadConcern(),
|
||||||
hasMaxTimeMS,
|
request.getMaxTimeMS().has_value(),
|
||||||
collectionType) {}
|
collectionType) {}
|
||||||
|
|
||||||
// The default implementation of hashing for smart pointers is not a good one for our purposes.
|
// The default implementation of hashing for smart pointers is not a good one for our purposes.
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,13 @@ public:
|
||||||
make_intrusive<ExpressionContextForTest>();
|
make_intrusive<ExpressionContextForTest>();
|
||||||
const SerializationOptions opts =
|
const SerializationOptions opts =
|
||||||
SerializationOptions(SerializationOptions::kRepresentativeQueryShapeSerializeOptions);
|
SerializationOptions(SerializationOptions::kRepresentativeQueryShapeSerializeOptions);
|
||||||
const std::unique_ptr<ParsedFindCommand> parsedRequest =
|
|
||||||
uassertStatusOK(parsed_find_command::parseFromCount(
|
std::unique_ptr<query_shape::CountCmdShape> makeCountShapeFromRequest(CountCommandRequest req) {
|
||||||
expCtx, CountCommandRequest(testNss), ExtensionsCallbackNoop(), testNss));
|
const std::unique_ptr<ParsedFindCommand> parsedRequest = uassertStatusOK(
|
||||||
|
parsed_find_command::parseFromCount(expCtx, req, ExtensionsCallbackNoop(), testNss));
|
||||||
|
return std::make_unique<query_shape::CountCmdShape>(
|
||||||
|
*parsedRequest, req.getLimit().has_value(), req.getSkip().has_value());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,13 +62,9 @@ public:
|
||||||
|
|
||||||
// Test that a count command without any fields generates the expected key.
|
// Test that a count command without any fields generates the expected key.
|
||||||
TEST_F(CountKeyTest, DefaultCountKey) {
|
TEST_F(CountKeyTest, DefaultCountKey) {
|
||||||
const auto key = std::make_unique<CountKey>(expCtx,
|
const CountCommandRequest& countReq = CountCommandRequest(testNss);
|
||||||
*parsedRequest,
|
const auto key = std::make_unique<CountKey>(
|
||||||
false /* hasLimit */,
|
expCtx, countReq, makeCountShapeFromRequest(countReq), collectionType);
|
||||||
false /* hasSkip */,
|
|
||||||
boost::none /* readConcern */,
|
|
||||||
false /* maxTimeMS */,
|
|
||||||
collectionType);
|
|
||||||
|
|
||||||
const auto expectedKey = fromjson(
|
const auto expectedKey = fromjson(
|
||||||
R"({
|
R"({
|
||||||
|
|
@ -80,14 +80,10 @@ TEST_F(CountKeyTest, DefaultCountKey) {
|
||||||
|
|
||||||
// Test that the hint parameter is included in the key.
|
// Test that the hint parameter is included in the key.
|
||||||
TEST_F(CountKeyTest, CountHintKey) {
|
TEST_F(CountKeyTest, CountHintKey) {
|
||||||
parsedRequest->findCommandRequest->setHint(BSON("a" << 1));
|
CountCommandRequest request = CountCommandRequest(testNss);
|
||||||
const auto key = std::make_unique<CountKey>(expCtx,
|
request.setHint(BSON("a" << 1));
|
||||||
*parsedRequest,
|
const auto key = std::make_unique<CountKey>(
|
||||||
false /* hasLimit */,
|
expCtx, request, makeCountShapeFromRequest(request), collectionType);
|
||||||
false /* hasSkip */,
|
|
||||||
boost::none /* readConcern */,
|
|
||||||
false /* maxTimeMS */,
|
|
||||||
collectionType);
|
|
||||||
|
|
||||||
const auto expectedKey = fromjson(
|
const auto expectedKey = fromjson(
|
||||||
R"({
|
R"({
|
||||||
|
|
@ -104,13 +100,10 @@ TEST_F(CountKeyTest, CountHintKey) {
|
||||||
|
|
||||||
// Test that the readConcern parameter is included in the key.
|
// Test that the readConcern parameter is included in the key.
|
||||||
TEST_F(CountKeyTest, CountReadConcernKey) {
|
TEST_F(CountKeyTest, CountReadConcernKey) {
|
||||||
const auto key = std::make_unique<CountKey>(expCtx,
|
CountCommandRequest request = CountCommandRequest(testNss);
|
||||||
*parsedRequest,
|
request.setReadConcern(repl::ReadConcernArgs::kLocal);
|
||||||
false /* hasLimit */,
|
const auto key = std::make_unique<CountKey>(
|
||||||
false /* hasSkip */,
|
expCtx, request, makeCountShapeFromRequest(request), collectionType);
|
||||||
repl::ReadConcernArgs::kLocal,
|
|
||||||
false /* maxTimeMS */,
|
|
||||||
collectionType);
|
|
||||||
|
|
||||||
const auto expectedKey = fromjson(
|
const auto expectedKey = fromjson(
|
||||||
R"({
|
R"({
|
||||||
|
|
@ -127,13 +120,10 @@ TEST_F(CountKeyTest, CountReadConcernKey) {
|
||||||
|
|
||||||
// Test that the maxTimeMS parameter is included in the key.
|
// Test that the maxTimeMS parameter is included in the key.
|
||||||
TEST_F(CountKeyTest, CountMaxTimeMSKey) {
|
TEST_F(CountKeyTest, CountMaxTimeMSKey) {
|
||||||
const auto key = std::make_unique<CountKey>(expCtx,
|
CountCommandRequest request = CountCommandRequest(testNss);
|
||||||
*parsedRequest,
|
request.setMaxTimeMS(1000);
|
||||||
false /* hasLimit */,
|
const auto key = std::make_unique<CountKey>(
|
||||||
false /* hasSkip */,
|
expCtx, request, makeCountShapeFromRequest(request), collectionType);
|
||||||
boost::none /* readConcern */,
|
|
||||||
true /* maxTimeMS */,
|
|
||||||
collectionType);
|
|
||||||
|
|
||||||
const auto expectedKey = fromjson(
|
const auto expectedKey = fromjson(
|
||||||
R"({
|
R"({
|
||||||
|
|
@ -150,15 +140,11 @@ TEST_F(CountKeyTest, CountMaxTimeMSKey) {
|
||||||
|
|
||||||
// Test that the comment parameter is included in the key.
|
// Test that the comment parameter is included in the key.
|
||||||
TEST_F(CountKeyTest, CountCommentKey) {
|
TEST_F(CountKeyTest, CountCommentKey) {
|
||||||
|
CountCommandRequest request = CountCommandRequest(testNss);
|
||||||
const auto comment = BSON("comment" << "hello");
|
const auto comment = BSON("comment" << "hello");
|
||||||
expCtx->getOperationContext()->setComment(comment);
|
expCtx->getOperationContext()->setComment(comment);
|
||||||
const auto key = std::make_unique<CountKey>(expCtx,
|
const auto key = std::make_unique<CountKey>(
|
||||||
*parsedRequest,
|
expCtx, request, makeCountShapeFromRequest(request), collectionType);
|
||||||
false /* hasLimit */,
|
|
||||||
false /* hasSkip */,
|
|
||||||
boost::none /* readConcern */,
|
|
||||||
false /* maxTimeMS */,
|
|
||||||
collectionType);
|
|
||||||
|
|
||||||
const auto expectedKey = fromjson(
|
const auto expectedKey = fromjson(
|
||||||
R"({
|
R"({
|
||||||
|
|
|
||||||
|
|
@ -150,5 +150,35 @@ inline ExpressEligibility isExpressEligible(OperationContext* opCtx,
|
||||||
return ExpressEligibility::Ineligible;
|
return ExpressEligibility::Ineligible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool isInternalOrDirectClient(Client* client) {
|
||||||
|
return client->isInternalClient() || client->isInDirectClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that users did not specify the internal fields 'originalQueryShapeHash' and
|
||||||
|
* 'querySettings' directly.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
concept hasOriginalQueryShapeHash = requires(const T& t) { t.getOriginalQueryShapeHash(); };
|
||||||
|
template <typename T>
|
||||||
|
concept hasQuerySettings = requires(const T& t) { t.getQuerySettings(); };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires hasOriginalQueryShapeHash<T>
|
||||||
|
void assertInternalParamsAreSetByInternalClients(Client* client, T& req) {
|
||||||
|
const bool isInternalOrDirect = isInternalOrDirectClient(client);
|
||||||
|
|
||||||
|
// Only check 'querySettings' if the command accepts it.
|
||||||
|
if constexpr (hasQuerySettings<T>) {
|
||||||
|
uassert(7923000,
|
||||||
|
"BSON field 'querySettings' is an unknown field",
|
||||||
|
isInternalOrDirect || !req.getQuerySettings().has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
uassert(10742702,
|
||||||
|
"BSON field 'originalQueryShapeHash' is an unknown field",
|
||||||
|
isInternalOrDirect || !req.getOriginalQueryShapeHash().has_value());
|
||||||
|
}
|
||||||
|
|
||||||
bool isSortSbeCompatible(const SortPattern& sortPattern);
|
bool isSortSbeCompatible(const SortPattern& sortPattern);
|
||||||
} // namespace mongo
|
} // namespace mongo
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesUnsetFields) {
|
||||||
ASSERT_FALSE(result.getIsHybridSearch().has_value());
|
ASSERT_FALSE(result.getIsHybridSearch().has_value());
|
||||||
ASSERT_FALSE(result.getReadConcern().has_value());
|
ASSERT_FALSE(result.getReadConcern().has_value());
|
||||||
ASSERT_FALSE(result.getUnwrappedReadPref().has_value());
|
ASSERT_FALSE(result.getUnwrappedReadPref().has_value());
|
||||||
|
ASSERT_FALSE(result.getOriginalQueryShapeHash().has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ResolvedViewTest, ExpandingAggRequestPreservesMostFields) {
|
TEST(ResolvedViewTest, ExpandingAggRequestPreservesMostFields) {
|
||||||
|
|
@ -202,6 +203,10 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesMostFields) {
|
||||||
aggRequest.setResumeAfter(BSON("rid" << 12345));
|
aggRequest.setResumeAfter(BSON("rid" << 12345));
|
||||||
aggRequest.setStartAt(BSON("rid" << 67890));
|
aggRequest.setStartAt(BSON("rid" << 67890));
|
||||||
aggRequest.setIncludeQueryStatsMetrics(true);
|
aggRequest.setIncludeQueryStatsMetrics(true);
|
||||||
|
const BSONObj query = BSON("hello" << 1);
|
||||||
|
const HashBlock<SHA256BlockTraits> queryShapeHash =
|
||||||
|
SHA256Block::computeHash((const uint8_t*)query.objdata(), query.objsize());
|
||||||
|
aggRequest.setOriginalQueryShapeHash(queryShapeHash);
|
||||||
aggRequest.setIsHybridSearch(true);
|
aggRequest.setIsHybridSearch(true);
|
||||||
aggRequest.setMaxTimeMS(100u);
|
aggRequest.setMaxTimeMS(100u);
|
||||||
aggRequest.setReadConcern(repl::ReadConcernArgs::kLinearizable);
|
aggRequest.setReadConcern(repl::ReadConcernArgs::kLinearizable);
|
||||||
|
|
@ -242,6 +247,7 @@ TEST(ResolvedViewTest, ExpandingAggRequestPreservesMostFields) {
|
||||||
ASSERT_BSONOBJ_EQ(result.getResumeAfter().value(), BSON("rid" << 12345));
|
ASSERT_BSONOBJ_EQ(result.getResumeAfter().value(), BSON("rid" << 12345));
|
||||||
ASSERT_BSONOBJ_EQ(result.getStartAt().value(), BSON("rid" << 67890));
|
ASSERT_BSONOBJ_EQ(result.getStartAt().value(), BSON("rid" << 67890));
|
||||||
ASSERT_TRUE(result.getIncludeQueryStatsMetrics());
|
ASSERT_TRUE(result.getIncludeQueryStatsMetrics());
|
||||||
|
ASSERT_EQ(result.getOriginalQueryShapeHash(), queryShapeHash);
|
||||||
ASSERT_TRUE(result.getIsHybridSearch().value_or(false));
|
ASSERT_TRUE(result.getIsHybridSearch().value_or(false));
|
||||||
ASSERT_EQ(result.getMaxTimeMS().value(), 100u);
|
ASSERT_EQ(result.getMaxTimeMS().value(), 100u);
|
||||||
ASSERT_BSONOBJ_EQ(result.getReadConcern()->toBSONInner(), BSON("level" << "linearizable"));
|
ASSERT_BSONOBJ_EQ(result.getReadConcern()->toBSONInner(), BSON("level" << "linearizable"));
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,14 @@
|
||||||
#include "mongo/db/auth/validated_tenancy_scope.h"
|
#include "mongo/db/auth/validated_tenancy_scope.h"
|
||||||
#include "mongo/db/commands.h"
|
#include "mongo/db/commands.h"
|
||||||
#include "mongo/db/fle_crud.h"
|
#include "mongo/db/fle_crud.h"
|
||||||
|
#include "mongo/db/operation_context.h"
|
||||||
#include "mongo/db/pipeline/expression_context_builder.h"
|
#include "mongo/db/pipeline/expression_context_builder.h"
|
||||||
#include "mongo/db/pipeline/expression_context_diagnostic_printer.h"
|
#include "mongo/db/pipeline/expression_context_diagnostic_printer.h"
|
||||||
#include "mongo/db/pipeline/query_request_conversion.h"
|
#include "mongo/db/pipeline/query_request_conversion.h"
|
||||||
#include "mongo/db/query/count_command_gen.h"
|
#include "mongo/db/query/count_command_gen.h"
|
||||||
|
#include "mongo/db/query/query_shape/count_cmd_shape.h"
|
||||||
|
#include "mongo/db/query/query_shape/query_shape_hash.h"
|
||||||
|
#include "mongo/db/query/query_shape/shape_helpers.h"
|
||||||
#include "mongo/db/query/query_stats/count_key.h"
|
#include "mongo/db/query/query_stats/count_key.h"
|
||||||
#include "mongo/db/query/query_stats/query_stats.h"
|
#include "mongo/db/query/query_stats/query_stats.h"
|
||||||
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
||||||
|
|
@ -66,13 +70,29 @@
|
||||||
|
|
||||||
namespace mongo {
|
namespace mongo {
|
||||||
|
|
||||||
inline BSONObj prepareCountForPassthrough(const BSONObj& cmdObj, bool requestQueryStats) {
|
inline BSONObj prepareCountForPassthrough(const OperationContext* opCtx,
|
||||||
if (!requestQueryStats) {
|
const BSONObj& cmdObj,
|
||||||
return CommandHelpers::filterCommandRequestForPassthrough(cmdObj);
|
bool requestQueryStats) {
|
||||||
|
BSONObjBuilder bob(cmdObj);
|
||||||
|
|
||||||
|
// Pass the queryShapeHash to the shards. We must validate that all participating shards can
|
||||||
|
// understand 'originalQueryShapeHash' and therefore check the feature flag. We use the last
|
||||||
|
// LTS when the FCV is uninitialized, since count commands can run during initial sync. This is
|
||||||
|
// because the feature is exclusively for observability enhancements and should only be applied
|
||||||
|
// when we are confident that the shard can correctly read this field, ensuring the query will
|
||||||
|
// not error.
|
||||||
|
if (feature_flags::gFeatureFlagOriginalQueryShapeHash.isEnabledUseLastLTSFCVWhenUninitialized(
|
||||||
|
VersionContext::getDecoration(opCtx),
|
||||||
|
serverGlobalParams.featureCompatibility.acquireFCVSnapshot())) {
|
||||||
|
if (auto&& queryShapeHash = CurOp::get(opCtx)->debug().getQueryShapeHash()) {
|
||||||
|
bob.append(CountCommandRequest::kOriginalQueryShapeHashFieldName,
|
||||||
|
queryShapeHash->toHexString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (requestQueryStats) {
|
||||||
|
bob.append(CountCommandRequest::kIncludeQueryStatsMetricsFieldName, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
BSONObjBuilder bob(cmdObj);
|
|
||||||
bob.append("includeQueryStatsMetrics", true);
|
|
||||||
return CommandHelpers::filterCommandRequestForPassthrough(bob.done());
|
return CommandHelpers::filterCommandRequestForPassthrough(bob.done());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,6 +128,34 @@ inline bool convertAndRunAggregateIfViewlessTimeseries(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void createShapeAndRegisterQueryStats(const boost::intrusive_ptr<ExpressionContext>& expCtx,
|
||||||
|
const CountCommandRequest& countRequest,
|
||||||
|
const NamespaceString& nss) {
|
||||||
|
const std::unique_ptr<ParsedFindCommand> parsedFind = uassertStatusOK(
|
||||||
|
parsed_find_command::parseFromCount(expCtx, countRequest, ExtensionsCallbackNoop(), nss));
|
||||||
|
|
||||||
|
// Compute QueryShapeHash and record it in CurOp.
|
||||||
|
OperationContext* opCtx = expCtx->getOperationContext();
|
||||||
|
const query_shape::DeferredQueryShape deferredShape{[&]() {
|
||||||
|
return shape_helpers::tryMakeShape<query_shape::CountCmdShape>(
|
||||||
|
*parsedFind, countRequest.getLimit().has_value(), countRequest.getSkip().has_value());
|
||||||
|
}};
|
||||||
|
boost::optional<query_shape::QueryShapeHash> queryShapeHash =
|
||||||
|
CurOp::get(opCtx)->debug().ensureQueryShapeHash(opCtx, [&]() {
|
||||||
|
return shape_helpers::computeQueryShapeHash(expCtx, deferredShape, nss);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (feature_flags::gFeatureFlagQueryStatsCountDistinct.isEnabled(
|
||||||
|
VersionContext::getDecoration(opCtx),
|
||||||
|
serverGlobalParams.featureCompatibility.acquireFCVSnapshot())) {
|
||||||
|
query_stats::registerRequest(opCtx, nss, [&]() {
|
||||||
|
uassertStatusOKWithContext(deferredShape->getStatus(), "Failed to compute query shape");
|
||||||
|
return std::make_unique<query_stats::CountKey>(
|
||||||
|
expCtx, countRequest, std::move(deferredShape->getValue()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the find command on mongos.
|
* Implements the find command on mongos.
|
||||||
*/
|
*/
|
||||||
|
|
@ -206,6 +254,11 @@ public:
|
||||||
auto countRequest =
|
auto countRequest =
|
||||||
CountCommandRequest::parse(cmdObj, IDLParserContext("count"));
|
CountCommandRequest::parse(cmdObj, IDLParserContext("count"));
|
||||||
|
|
||||||
|
// Forbid users from passing 'originalQueryShapeHash' explicitly.
|
||||||
|
uassert(10742704,
|
||||||
|
"BSON field 'originalQueryShapeHash' is an unknown field",
|
||||||
|
!countRequest.getOriginalQueryShapeHash().has_value());
|
||||||
|
|
||||||
// Create an RAII object that prints the collection's shard key in the case of a
|
// Create an RAII object that prints the collection's shard key in the case of a
|
||||||
// tassert or crash.
|
// tassert or crash.
|
||||||
const auto& cri = routingCtx.getCollectionRoutingInfo(nss);
|
const auto& cri = routingCtx.getCollectionRoutingInfo(nss);
|
||||||
|
|
@ -235,22 +288,7 @@ public:
|
||||||
ScopedDebugInfo expCtxDiagnostics(
|
ScopedDebugInfo expCtxDiagnostics(
|
||||||
"ExpCtxDiagnostics", diagnostic_printers::ExpressionContextPrinter{expCtx});
|
"ExpCtxDiagnostics", diagnostic_printers::ExpressionContextPrinter{expCtx});
|
||||||
|
|
||||||
const auto parsedFind = uassertStatusOK(parsed_find_command::parseFromCount(
|
createShapeAndRegisterQueryStats(expCtx, countRequest, nss);
|
||||||
expCtx, countRequest, ExtensionsCallbackNoop(), nss));
|
|
||||||
|
|
||||||
if (feature_flags::gFeatureFlagQueryStatsCountDistinct.isEnabled(
|
|
||||||
VersionContext::getDecoration(opCtx),
|
|
||||||
serverGlobalParams.featureCompatibility.acquireFCVSnapshot())) {
|
|
||||||
query_stats::registerRequest(opCtx, nss, [&]() {
|
|
||||||
return std::make_unique<query_stats::CountKey>(
|
|
||||||
expCtx,
|
|
||||||
*parsedFind,
|
|
||||||
countRequest.getLimit().has_value(),
|
|
||||||
countRequest.getSkip().has_value(),
|
|
||||||
countRequest.getReadConcern(),
|
|
||||||
countRequest.getMaxTimeMS().has_value());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: This must happen after query stats because query stats retain the
|
// Note: This must happen after query stats because query stats retain the
|
||||||
// originating command type for timeseries.
|
// originating command type for timeseries.
|
||||||
|
|
@ -300,7 +338,8 @@ public:
|
||||||
nss,
|
nss,
|
||||||
applyReadWriteConcern(opCtx,
|
applyReadWriteConcern(opCtx,
|
||||||
this,
|
this,
|
||||||
prepareCountForPassthrough(countRequest.toBSON(),
|
prepareCountForPassthrough(opCtx,
|
||||||
|
countRequest.toBSON(),
|
||||||
requestQueryStats)),
|
requestQueryStats)),
|
||||||
ReadPreferenceSetting::get(opCtx),
|
ReadPreferenceSetting::get(opCtx),
|
||||||
Shard::RetryPolicy::kIdempotent,
|
Shard::RetryPolicy::kIdempotent,
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
#include "mongo/db/query/query_settings/query_settings_service.h"
|
#include "mongo/db/query/query_settings/query_settings_service.h"
|
||||||
#include "mongo/db/query/query_shape/distinct_cmd_shape.h"
|
#include "mongo/db/query/query_shape/distinct_cmd_shape.h"
|
||||||
#include "mongo/db/query/query_shape/query_shape.h"
|
#include "mongo/db/query/query_shape/query_shape.h"
|
||||||
|
#include "mongo/db/query/query_shape/query_shape_hash.h"
|
||||||
#include "mongo/db/query/query_stats/distinct_key.h"
|
#include "mongo/db/query/query_stats/distinct_key.h"
|
||||||
#include "mongo/db/query/query_stats/query_stats.h"
|
#include "mongo/db/query/query_stats/query_stats.h"
|
||||||
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
||||||
|
|
@ -137,6 +138,11 @@ std::unique_ptr<CanonicalQuery> parseDistinctCmd(
|
||||||
"BSON field 'querySettings' is an unknown field",
|
"BSON field 'querySettings' is an unknown field",
|
||||||
!distinctCommand->getQuerySettings().has_value());
|
!distinctCommand->getQuerySettings().has_value());
|
||||||
|
|
||||||
|
// Forbid users from passing 'originalQueryShapeHash' explicitly.
|
||||||
|
uassert(10742700,
|
||||||
|
"BSON field 'originalQueryShapeHash' is an unknown field",
|
||||||
|
!distinctCommand->getOriginalQueryShapeHash().has_value());
|
||||||
|
|
||||||
auto expCtx = ExpressionContextBuilder{}
|
auto expCtx = ExpressionContextBuilder{}
|
||||||
.fromRequest(opCtx, *distinctCommand, defaultCollator)
|
.fromRequest(opCtx, *distinctCommand, defaultCollator)
|
||||||
.ns(nss)
|
.ns(nss)
|
||||||
|
|
@ -179,18 +185,37 @@ std::unique_ptr<CanonicalQuery> parseDistinctCmd(
|
||||||
std::move(parsedDistinct));
|
std::move(parsedDistinct));
|
||||||
}
|
}
|
||||||
|
|
||||||
BSONObj prepareDistinctForPassthrough(const BSONObj& cmd,
|
BSONObj prepareDistinctForPassthrough(
|
||||||
const query_settings::QuerySettings& qs,
|
const OperationContext* opCtx,
|
||||||
const bool requestQueryStats) {
|
const BSONObj& cmd,
|
||||||
|
const query_settings::QuerySettings& qs,
|
||||||
|
const bool requestQueryStats,
|
||||||
|
const boost::optional<query_shape::QueryShapeHash>& queryShapeHash) {
|
||||||
const auto qsBson = qs.toBSON();
|
const auto qsBson = qs.toBSON();
|
||||||
if (requestQueryStats || !qsBson.isEmpty()) {
|
if (requestQueryStats || !qsBson.isEmpty() || queryShapeHash) {
|
||||||
BSONObjBuilder bob(cmd);
|
BSONObjBuilder bob(cmd);
|
||||||
// Append distinct command with the query settings and includeQueryStatsMetrics if needed.
|
// Append distinct command with the query settings and includeQueryStatsMetrics if needed.
|
||||||
if (requestQueryStats) {
|
if (requestQueryStats) {
|
||||||
bob.append("includeQueryStatsMetrics", true);
|
bob.append(DistinctCommandRequest::kIncludeQueryStatsMetricsFieldName, true);
|
||||||
}
|
}
|
||||||
if (!qsBson.isEmpty()) {
|
if (!qsBson.isEmpty()) {
|
||||||
bob.append("querySettings", qsBson);
|
bob.append(DistinctCommandRequest::kQuerySettingsFieldName, qsBson);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass the queryShapeHash to the shards. We must validate that all participating shards can
|
||||||
|
// understand 'originalQueryShapeHash' and therefore check the feature flag. We use the last
|
||||||
|
// LTS when the FCV is uninitialized, even though distinct commands cannot execute during
|
||||||
|
// initial sync. This is because the feature is exclusively for observability enhancements
|
||||||
|
// and should only be applied when we are confident that the shard can correctly read this
|
||||||
|
// field, ensuring the query will not error.
|
||||||
|
if (feature_flags::gFeatureFlagOriginalQueryShapeHash
|
||||||
|
.isEnabledUseLastLTSFCVWhenUninitialized(
|
||||||
|
VersionContext::getDecoration(opCtx),
|
||||||
|
serverGlobalParams.featureCompatibility.acquireFCVSnapshot())) {
|
||||||
|
if (queryShapeHash) {
|
||||||
|
bob.append(DistinctCommandRequest::kOriginalQueryShapeHashFieldName,
|
||||||
|
queryShapeHash->toHexString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return CommandHelpers::filterCommandRequestForPassthrough(bob.done());
|
return CommandHelpers::filterCommandRequestForPassthrough(bob.done());
|
||||||
}
|
}
|
||||||
|
|
@ -427,9 +452,15 @@ public:
|
||||||
// We will decide if remote query stats metrics should be collected.
|
// We will decide if remote query stats metrics should be collected.
|
||||||
bool requestQueryStats =
|
bool requestQueryStats =
|
||||||
query_stats::shouldRequestRemoteMetrics(CurOp::get(opCtx)->debug());
|
query_stats::shouldRequestRemoteMetrics(CurOp::get(opCtx)->debug());
|
||||||
|
boost::optional<query_shape::QueryShapeHash> queryShapeHash =
|
||||||
|
CurOp::get(opCtx)->debug().getQueryShapeHash();
|
||||||
|
|
||||||
BSONObj distinctReadyForPassthrough = prepareDistinctForPassthrough(
|
BSONObj distinctReadyForPassthrough = prepareDistinctForPassthrough(
|
||||||
cmdObj, canonicalQuery->getExpCtx()->getQuerySettings(), requestQueryStats);
|
opCtx,
|
||||||
|
cmdObj,
|
||||||
|
canonicalQuery->getExpCtx()->getQuerySettings(),
|
||||||
|
requestQueryStats,
|
||||||
|
queryShapeHash);
|
||||||
|
|
||||||
|
|
||||||
const auto& cri = routingCtx.getCollectionRoutingInfo(nss);
|
const auto& cri = routingCtx.getCollectionRoutingInfo(nss);
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,10 @@ inline std::unique_ptr<FindCommandRequest> parseCmdObjectToFindCommandRequest(
|
||||||
"BSON field 'querySettings' is an unknown field",
|
"BSON field 'querySettings' is an unknown field",
|
||||||
!findCommand->getQuerySettings().has_value());
|
!findCommand->getQuerySettings().has_value());
|
||||||
|
|
||||||
|
uassert(10742703,
|
||||||
|
"BSON field 'originalQueryShapeHash' is an unknown field",
|
||||||
|
!findCommand->getOriginalQueryShapeHash().has_value());
|
||||||
|
|
||||||
uassert(ErrorCodes::InvalidNamespace,
|
uassert(ErrorCodes::InvalidNamespace,
|
||||||
"Cannot specify UUID to a mongos.",
|
"Cannot specify UUID to a mongos.",
|
||||||
!findCommand->getNamespaceOrUUID().isUUID());
|
!findCommand->getNamespaceOrUUID().isUUID());
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,22 @@ Document serializeForPassthrough(const boost::intrusive_ptr<ExpressionContext>&
|
||||||
req.setRawData(rawData);
|
req.setRawData(rawData);
|
||||||
aggregation_request_helper::addQuerySettingsToRequest(req, expCtx);
|
aggregation_request_helper::addQuerySettingsToRequest(req, expCtx);
|
||||||
|
|
||||||
|
// Pass the queryShapeHash to the shards. We must validate that all participating shards can
|
||||||
|
// understand 'originalQueryShapeHash' and therefore check the feature flag. We use the last
|
||||||
|
// LTS when the FCV is uninitialized, even though aggregates cannot execute during initial sync.
|
||||||
|
// This is because the feature is exclusively for observability enhancements and should only be
|
||||||
|
// applied when we are confident that the shard can correctly read this field, ensuring the
|
||||||
|
// query will not error.
|
||||||
|
if (!req.getExplain().has_value() &&
|
||||||
|
feature_flags::gFeatureFlagOriginalQueryShapeHash.isEnabledUseLastLTSFCVWhenUninitialized(
|
||||||
|
VersionContext::getDecoration(expCtx->getOperationContext()),
|
||||||
|
serverGlobalParams.featureCompatibility.acquireFCVSnapshot())) {
|
||||||
|
if (auto&& queryShapeHash =
|
||||||
|
CurOp::get(expCtx->getOperationContext())->debug().getQueryShapeHash()) {
|
||||||
|
req.setOriginalQueryShapeHash(queryShapeHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto cmdObj =
|
auto cmdObj =
|
||||||
isRawDataOperation(expCtx->getOperationContext()) && req.getNamespace() != executionNs
|
isRawDataOperation(expCtx->getOperationContext()) && req.getNamespace() != executionNs
|
||||||
? rewriteCommandForRawDataOperation<AggregateCommandRequest>(req.toBSON(),
|
? rewriteCommandForRawDataOperation<AggregateCommandRequest>(req.toBSON(),
|
||||||
|
|
@ -321,7 +337,7 @@ void performValidationChecks(const OperationContext* opCtx,
|
||||||
const AggregateCommandRequest& request,
|
const AggregateCommandRequest& request,
|
||||||
const LiteParsedPipeline& liteParsedPipeline) {
|
const LiteParsedPipeline& liteParsedPipeline) {
|
||||||
liteParsedPipeline.validate(opCtx);
|
liteParsedPipeline.validate(opCtx);
|
||||||
aggregation_request_helper::validateRequestForAPIVersion(opCtx, request);
|
aggregation_request_helper::validateRequestWithClient(opCtx, request);
|
||||||
aggregation_request_helper::validateRequestFromClusterQueryWithoutShardKey(request);
|
aggregation_request_helper::validateRequestFromClusterQueryWithoutShardKey(request);
|
||||||
|
|
||||||
uassert(51028, "Cannot specify exchange option to a router", !request.getExchange());
|
uassert(51028, "Cannot specify exchange option to a router", !request.getExchange());
|
||||||
|
|
@ -871,6 +887,8 @@ Status runAggregateImpl(OperationContext* opCtx,
|
||||||
auto status = [&](auto& expCtx) {
|
auto status = [&](auto& expCtx) {
|
||||||
bool requestQueryStatsFromRemotes = query_stats::shouldRequestRemoteMetrics(
|
bool requestQueryStatsFromRemotes = query_stats::shouldRequestRemoteMetrics(
|
||||||
CurOp::get(expCtx->getOperationContext())->debug());
|
CurOp::get(expCtx->getOperationContext())->debug());
|
||||||
|
boost::optional<query_shape::QueryShapeHash> queryShapeHash =
|
||||||
|
CurOp::get(opCtx)->debug().getQueryShapeHash();
|
||||||
try {
|
try {
|
||||||
switch (targeter.policy) {
|
switch (targeter.policy) {
|
||||||
case cluster_aggregation_planner::AggregationTargeter::TargetingPolicy::
|
case cluster_aggregation_planner::AggregationTargeter::TargetingPolicy::
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@
|
||||||
#include "mongo/db/query/query_settings/query_settings_service.h"
|
#include "mongo/db/query/query_settings/query_settings_service.h"
|
||||||
#include "mongo/db/query/query_shape/find_cmd_shape.h"
|
#include "mongo/db/query/query_shape/find_cmd_shape.h"
|
||||||
#include "mongo/db/query/query_shape/query_shape.h"
|
#include "mongo/db/query/query_shape/query_shape.h"
|
||||||
|
#include "mongo/db/query/query_shape/query_shape_hash.h"
|
||||||
#include "mongo/db/query/query_stats/find_key.h"
|
#include "mongo/db/query/query_stats/find_key.h"
|
||||||
#include "mongo/db/query/query_stats/query_stats.h"
|
#include "mongo/db/query/query_stats/query_stats.h"
|
||||||
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
#include "mongo/db/query/shard_key_diagnostic_printer.h"
|
||||||
|
|
@ -200,6 +201,20 @@ BSONObj makeFindCommandForShards(OperationContext* opCtx,
|
||||||
findCommand.setQuerySettings(query.getExpCtx()->getQuerySettings());
|
findCommand.setQuerySettings(query.getExpCtx()->getQuerySettings());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass the queryShapeHash to the shards. We must validate that all participating shards can
|
||||||
|
// understand 'originalQueryShapeHash' and therefore check the feature flag. We use the last LTS
|
||||||
|
// when the FCV is uninitialized, even though find commands cannot execute during initial sync.
|
||||||
|
// This is because the feature is exclusively for observability enhancements and should only be
|
||||||
|
// applied when we are confident that the shard can correctly read this field, ensuring the
|
||||||
|
// query will not error.
|
||||||
|
if (feature_flags::gFeatureFlagOriginalQueryShapeHash.isEnabledUseLastLTSFCVWhenUninitialized(
|
||||||
|
VersionContext::getDecoration(opCtx),
|
||||||
|
serverGlobalParams.featureCompatibility.acquireFCVSnapshot())) {
|
||||||
|
if (auto&& queryShapeHash = CurOp::get(opCtx)->debug().getQueryShapeHash()) {
|
||||||
|
findCommand.setOriginalQueryShapeHash(queryShapeHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Request metrics if necessary.
|
// Request metrics if necessary.
|
||||||
{
|
{
|
||||||
// We'll set includeQueryStatsMetrics if our configuration (e.g., feature flag, sample
|
// We'll set includeQueryStatsMetrics if our configuration (e.g., feature flag, sample
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue