@@ -649,6 +649,9 @@ string optionString(size_t options) {
649649 case QueryPlannerParams::GENERATE_PER_COLUMN_FILTERS:
650650 ss << " GENERATE_PER_COLUMN_FILTERS " ;
651651 break ;
652+ case QueryPlannerParams::STRICT_NO_TABLE_SCAN:
653+ ss << " STRICT_NO_TABLE_SCAN " ;
654+ break ;
652655 case QueryPlannerParams::DEFAULT:
653656 MONGO_UNREACHABLE;
654657 break ;
@@ -1082,13 +1085,42 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> singleSolution(
10821085 return {std::move (out)};
10831086}
10841087
1085- bool canTableScan (const QueryPlannerParams& params) {
1086- return !(params.options & QueryPlannerParams::NO_TABLE_SCAN);
1088+ // If no table scan option is set the planner may not return any plan containing a collection scan.
1089+ // Yet clusteredIdxScans are still allowed as they are not a full collection scan but a bounded
1090+ // collection scan.
1091+ bool noTableScan (const QueryPlannerParams& params) {
1092+ return (params.options & QueryPlannerParams::NO_TABLE_SCAN);
1093+ }
1094+
1095+ // Used internally if the planner should also avoid retruning a plan containing a clusteredIDX scan.
1096+ bool noTableAndClusteredIDXScan (const QueryPlannerParams& params) {
1097+ return (params.options & QueryPlannerParams::STRICT_NO_TABLE_SCAN);
1098+ }
1099+
1100+ bool isClusteredScan (QuerySolutionNode* node) {
1101+ if (node->getType () == STAGE_COLLSCAN) {
1102+ auto collectionScanSolnNode = dynamic_cast <CollectionScanNode*>(node);
1103+ return (collectionScanSolnNode->doClusteredCollectionScanClassic () ||
1104+ collectionScanSolnNode->doClusteredCollectionScanSbe ());
1105+ }
1106+ return false ;
1107+ }
1108+
1109+ // Check if this is a real coll scan or a hidden ClusteredIDX scan.
1110+ bool isColusteredIDXScanSoln (QuerySolution* collscanSoln) {
1111+ if (collscanSoln->root ()->getType () == STAGE_SHARDING_FILTER) {
1112+ auto child = collscanSoln->root ()->children .begin ();
1113+ return isClusteredScan (child->get ());
1114+ }
1115+ if (collscanSoln->root ()->getType () == STAGE_COLLSCAN) {
1116+ return isClusteredScan (collscanSoln->root ());
1117+ }
1118+ return false ;
10871119}
10881120
10891121StatusWith<std::vector<std::unique_ptr<QuerySolution>>> attemptCollectionScan (
10901122 const CanonicalQuery& query, bool isTailable, const QueryPlannerParams& params) {
1091- if (! canTableScan (params)) {
1123+ if (noTableScan (params)) {
10921124 return Status (ErrorCodes::NoQueryExecutionPlans,
10931125 " not allowed to output a collection scan because 'notablescan' is enabled" );
10941126 }
@@ -1689,9 +1721,9 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
16891721
16901722 // No indexed plans? We must provide a collscan if possible or else we can't run the query.
16911723 bool collScanRequired = 0 == out.size ();
1692- if (collScanRequired && ! canTableScan (params)) {
1724+ if (collScanRequired && noTableAndClusteredIDXScan (params)) {
16931725 return Status (ErrorCodes::NoQueryExecutionPlans,
1694- " No indexed plans available, and running with 'notablescan'" );
1726+ " No indexed plans available, and running with 'notablescan' 1 " );
16951727 }
16961728
16971729 bool clusteredCollection = params.clusteredInfo .has_value ();
@@ -1706,6 +1738,7 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
17061738 return Status (ErrorCodes::NoQueryExecutionPlans, " No query solutions" );
17071739 }
17081740
1741+ bool isClusteredIDXScan = false ;
17091742 if (possibleToCollscan && (collscanRequested || collScanRequired || clusteredCollection)) {
17101743 boost::optional<int > clusteredScanDirection =
17111744 QueryPlannerCommon::determineClusteredScanDirection (query, params);
@@ -1715,7 +1748,7 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
17151748 return Status (ErrorCodes::NoQueryExecutionPlans,
17161749 " Failed to build collection scan soln" );
17171750 }
1718-
1751+ isClusteredIDXScan = isColusteredIDXScanSoln (collscanSoln. get ());
17191752 // We consider collection scan in the following cases:
17201753 // 1. collScanRequested - specifically requested by caller.
17211754 // 2. collScanRequired - there are no other possible plans, so we fallback to full scan.
@@ -1736,8 +1769,15 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
17361769 out.push_back (std::move (collscanSoln));
17371770 }
17381771 }
1772+ // Make sure to respect the notablescan option. A clustered IDX scan is allowed even under a
1773+ // NOTABLE option. Only in the case of a strict NOTABLE scan option a clustered IDX scan is not
1774+ // allowed. This option is used in mongoS for shardPruning.
1775+ invariant (out.size () > 0 );
1776+ if (collScanRequired && noTableScan (params) && !isClusteredIDXScan) {
1777+ return Status (ErrorCodes::NoQueryExecutionPlans,
1778+ " No indexed plans available, and running with 'notablescan' 2" );
1779+ }
17391780
1740- invariant (!out.empty ());
17411781 return {std::move (out)};
17421782} // QueryPlanner::plan
17431783
0 commit comments