Skip to content

Commit ccbec96

Browse files
HanaPearlmanEvergreen Agent
authored andcommitted
SERVER-82121: Ignore empty hints in CQF
1 parent 981902d commit ccbec96

File tree

2 files changed

+53
-30
lines changed

2 files changed

+53
-30
lines changed

jstests/cqf/optimizer/index_hints.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ assert.commandWorked(t.insert({_id: 2, b: 2, a: [2]}));
1919
assert.commandWorked(t.insert({_id: 3, b: 3, a: 2}));
2020
assert.commandWorked(t.insert({_id: 4, b: 4, a: [1, 3]}));
2121

22+
{
23+
// Empty hint is ignored when there are no relevant indexes.
24+
let res = t.explain("executionStats").find({a: 2}).hint({}).finish();
25+
assertValueOnPlanPath("PhysicalScan", res, "child.child.nodeType");
26+
27+
res = t.explain("executionStats").aggregate([{$match: {a: 2}}], {hint: {}});
28+
assertValueOnPlanPath("PhysicalScan", res, "child.child.nodeType");
29+
}
30+
2231
assert.commandWorked(t.createIndex({a: 1}));
2332
assert.commandWorked(t.createIndex({b: 1}));
2433

@@ -28,6 +37,15 @@ assert.commandWorked(t.createIndex({b: 1}));
2837
assertValueOnPlanPath("PhysicalScan", res, "child.child.nodeType");
2938
}
3039

40+
{
41+
// Empty hint is ignored when there are relevant indexes that are not preferable.
42+
let res = t.explain("executionStats").find({a: 2}).hint({}).finish();
43+
assertValueOnPlanPath("PhysicalScan", res, "child.child.nodeType");
44+
45+
res = t.explain("executionStats").aggregate([{$match: {a: 2}}], {hint: {}});
46+
assertValueOnPlanPath("PhysicalScan", res, "child.child.nodeType");
47+
}
48+
3149
{
3250
let res = t.explain("executionStats").find({a: 2}).hint({a: 1}).finish();
3351
assertValueOnPlanPath("IndexScan", res, "child.leftChild.nodeType");
@@ -64,6 +82,15 @@ for (let i = 0; i < 100; i++) {
6482
assertValueOnPlanPath("IndexScan", res, "child.leftChild.nodeType");
6583
}
6684

85+
{
86+
// Empty hint is ignored when there are relevant indexes that are preferable.
87+
let res = t.explain("executionStats").find({a: 2}).hint({}).finish();
88+
assertValueOnPlanPath("IndexScan", res, "child.leftChild.nodeType");
89+
90+
res = t.explain("executionStats").aggregate([{$match: {a: 2}}], {hint: {}});
91+
assertValueOnPlanPath("IndexScan", res, "child.leftChild.nodeType");
92+
}
93+
6794
{
6895
let res = t.explain("executionStats").find({a: 2}).hint({a: 1}).finish();
6996
assertValueOnPlanPath("IndexScan", res, "child.leftChild.nodeType");

src/mongo/db/query/cqf_get_executor.cpp

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -158,28 +158,28 @@ static std::pair<IndexDefinitions, MultikeynessTrie> buildIndexSpecsOptimizer(
158158

159159
std::pair<IndexDefinitions, MultikeynessTrie> result;
160160
std::string indexHintName;
161-
bool skipAllIndexes = false;
161+
162+
// True if the query has a $natural hint, indicating that we must use a collection scan.
163+
bool hasNaturalHint = false;
164+
162165
if (indexHint) {
163166
const BSONElement element = indexHint->firstElement();
164167
const StringData fieldName = element.fieldNameStringData();
165-
if (fieldName == "$natural"_sd) {
168+
if (fieldName == query_request_helper::kNaturalSortField) {
166169
// Do not add indexes.
167-
skipAllIndexes = true;
170+
hasNaturalHint = true;
168171
} else if (fieldName == "$hint"_sd && element.type() == BSONType::String) {
169172
indexHintName = element.valueStringData().toString();
170173
}
171174

172-
disableScan = !skipAllIndexes;
175+
disableScan = !hasNaturalHint;
173176
}
174177

175178
const IndexCatalog& indexCatalog = *collection->getIndexCatalog();
176179

177180
auto indexIterator =
178181
indexCatalog.getIndexIterator(opCtx, IndexCatalog::InclusionPolicy::kReady);
179182

180-
const bool queryHasNaturalHint = indexHint && !indexHint->isEmpty() &&
181-
indexHint->firstElementFieldNameStringData() == query_request_helper::kNaturalSortField;
182-
183183
while (indexIterator->more()) {
184184
const IndexCatalogEntry& catalogEntry = *indexIterator->next();
185185
const IndexDescriptor& descriptor = *catalogEntry.descriptor();
@@ -196,32 +196,25 @@ static std::pair<IndexDefinitions, MultikeynessTrie> buildIndexSpecsOptimizer(
196196
continue;
197197
}
198198

199-
// If there is a $natural hint, we should not assert here as we will not use the index.
200-
const bool isSpecialIndex =
201-
descriptor.infoObj().hasField(IndexDescriptor::kExpireAfterSecondsFieldName) ||
199+
// Check for special indexes. We do not want to try to build index metadata for a special
200+
// index (since we do not support those yet in CQF) but we should allow the query to go
201+
// through CQF if there is a $natural hint.
202+
if (descriptor.infoObj().hasField(IndexDescriptor::kExpireAfterSecondsFieldName) ||
202203
descriptor.isSparse() || descriptor.getIndexType() != IndexType::INDEX_BTREE ||
203-
!descriptor.collation().isEmpty();
204-
if (!queryHasNaturalHint && isSpecialIndex) {
205-
uasserted(ErrorCodes::InternalErrorNotSupported, "Unsupported index type");
206-
}
207-
208-
// We do not want to try to build index metadata for a special index (since we do not
209-
// support those yet in CQF) but we should allow the query to go through CQF if there is a
210-
// $natural hint.
211-
if (queryHasNaturalHint && isSpecialIndex) {
204+
!descriptor.collation().isEmpty()) {
205+
uassert(
206+
ErrorCodes::InternalErrorNotSupported, "Unsupported index type", hasNaturalHint);
212207
continue;
213208
}
214209

215210
if (indexHint) {
216211
if (indexHintName.empty()) {
217-
if (!SimpleBSONObjComparator::kInstance.evaluate(descriptor.keyPattern() ==
218-
*indexHint)) {
219-
// Index key pattern does not match hint.
220-
skipIndex = true;
221-
}
222-
} else if (indexHintName != descriptor.indexName()) {
223-
// Index name does not match hint.
224-
skipIndex = true;
212+
// Index hint is a key pattern. Check if it matches this descriptor's key pattern.
213+
skipIndex = SimpleBSONObjComparator::kInstance.evaluate(descriptor.keyPattern() !=
214+
*indexHint);
215+
} else {
216+
// Index hint is an index name. Check if it matches the descriptor's name.
217+
skipIndex = indexHintName != descriptor.indexName();
225218
}
226219
}
227220

@@ -331,7 +324,7 @@ static std::pair<IndexDefinitions, MultikeynessTrie> buildIndexSpecsOptimizer(
331324
}
332325
}
333326
// For now we assume distribution is Centralized.
334-
if (!skipIndex && !skipAllIndexes) {
327+
if (!skipIndex && !hasNaturalHint) {
335328
result.first.emplace(descriptor.indexName(), std::move(indexDef));
336329
}
337330
}
@@ -864,7 +857,10 @@ boost::optional<ExecParams> getSBEExecutorViaCascadesOptimizer(
864857

865858
const auto& collection = collections.getMainCollection();
866859

867-
validateCommandOptions(canonicalQuery, collection, indexHint, involvedCollections);
860+
const boost::optional<BSONObj>& hint =
861+
(indexHint && !indexHint->isEmpty() ? indexHint : boost::none);
862+
863+
validateCommandOptions(canonicalQuery, collection, hint, involvedCollections);
868864

869865
const bool requireRID = canonicalQuery ? canonicalQuery->getForceGenerateRecordId() : false;
870866
const bool collectionExists = static_cast<bool>(collection);
@@ -882,7 +878,7 @@ boost::optional<ExecParams> getSBEExecutorViaCascadesOptimizer(
882878
collection,
883879
involvedCollections,
884880
nss,
885-
indexHint,
881+
hint,
886882
scanProjName,
887883
uuidStr,
888884
scanDefName,

0 commit comments

Comments
 (0)