ExportXMLWordPrintableJSON

    • Type: Bug
    • Resolution: Unresolved
    • Priority: Major - P3
    • None
    • Affects Version/s: 8.3.11
    • Component/s: Query Execution
    • None
    • Query Execution
    • ALL
    • Hide

      Deployment: single-node replica set, default configuration (internalQueryFrameworkControl is trySbeRestricted by default). The script switches the parameter with setParameter and leaves it at the default.

      db.c.insertMany([
          {_id: 1, g: "k", v: MaxKey()},
          {_id: 2, g: "k", v: MinKey()},
      ]);
      const pipeline = [{$group: {_id: "$g", mx: {$max: "$v"}, mn: {$min: "$v"}}}, {$project: {mx: 1, mn: 1, t: {$type: "$mx"}}}];
      
      // The same aggregation with the classic engine forced, then with the default engine selection.
      for (const mode of ["forceClassicEngine", "trySbeRestricted"]) {
          db.adminCommand({setParameter: 1, internalQueryFrameworkControl: mode});
          const e = db.c.explain().aggregate(pipeline);
          const qp = e.queryPlanner || e.stages[0].$cursor.queryPlanner;
          const plan = [];
          for (let s = qp.winningPlan.queryPlan || qp.winningPlan; s; s = s.inputStage) plan.push(s.stage);
          const rest = (e.stages || []).slice(1).map(s => Object.keys(s)[0]);
          print(`// ${mode}: explainVersion ${e.explainVersion}, plan ${plan.join(" <- ")}` + (rest.length ? `, then ${rest.join(", ")}` : ""));
          printjson(db.c.aggregate(pipeline).toArray());
      }
      

      Output:

      // forceClassicEngine: explainVersion 1, plan PROJECTION_SIMPLE <- COLLSCAN, then $group, $project
      [
        {
          _id: 'k',
          mx: MaxKey(),
          mn: MinKey(),
          t: 'maxKey'
        }
      ]
      // trySbeRestricted: explainVersion 2, plan GROUP <- COLLSCAN, then $project
      [
        {
          _id: 'k',
          mx: {
            '': MaxKey()
          },
          mn: {
            '': MinKey()
          },
          t: 'object'
        }
      ]
      
      Show
      Deployment: single-node replica set, default configuration ( internalQueryFrameworkControl is trySbeRestricted by default). The script switches the parameter with setParameter and leaves it at the default. db.c.insertMany([ {_id: 1, g: "k" , v: MaxKey()}, {_id: 2, g: "k" , v: MinKey()}, ]); const pipeline = [{$group: {_id: "$g" , mx: {$max: "$v" }, mn: {$min: "$v" }}}, {$project: {mx: 1, mn: 1, t: {$type: "$mx" }}}]; // The same aggregation with the classic engine forced, then with the default engine selection. for ( const mode of [ "forceClassicEngine" , "trySbeRestricted" ]) { db.adminCommand({setParameter: 1, internalQueryFrameworkControl: mode}); const e = db.c.explain().aggregate(pipeline); const qp = e.queryPlanner || e.stages[0].$cursor.queryPlanner; const plan = []; for (let s = qp.winningPlan.queryPlan || qp.winningPlan; s; s = s.inputStage) plan.push(s.stage); const rest = (e.stages || []).slice(1).map(s => Object .keys(s)[0]); print( ` // ${mode}: explainVersion ${e.explainVersion}, plan ${plan.join( " <- " )}` + (rest.length ? `, then ${rest.join( ", " )}` : "")); printjson(db.c.aggregate(pipeline).toArray()); } Output: // forceClassicEngine: explainVersion 1, plan PROJECTION_SIMPLE <- COLLSCAN, then $group, $project [ { _id: 'k' , mx: MaxKey(), mn: MinKey(), t: 'maxKey' } ] // trySbeRestricted: explainVersion 2, plan GROUP <- COLLSCAN, then $project [ { _id: 'k' , mx: { '': MaxKey() }, mn: { '': MinKey() }, t: 'object' } ]
    • QE 2026-10-12
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      With the default engine selection, when $group runs in SBE and produces MinKey or MaxKey, and a later stage runs in the classic engine (here $project), the value arrives as an object with a single empty-named field holding MinKey/MaxKey instead of MinKey/MaxKey itself. With forceClassicEngine the same pipeline returns MinKey/MaxKey.

      • classic engine: internalQueryFrameworkControl: forceClassicEngine. Check: the explain line in the output shows explainVersion 1, with $group and $project after $cursor.
      • SBE prefix + classic suffix: default internalQueryFrameworkControl: trySbeRestricted: $group is pushed into SBE, $project stays in the classic engine. Check: the explain line shows explainVersion 2 with GROUP in the SBE plan, then $project.

      Expected: mx: MaxKey(), mn: MinKey() and t: 'maxKey' with both engines (the classic engine's result).
      Actual: Default (trySbeRestricted): mx and mn are objects with one empty-named field holding MaxKey / MinKey, and $type reports 'object'.

      convertToValue, which turns SBE values into a classic Document where the SBE prefix hands results to classic stages, maps MinKey/MaxKey to Value(kMinBSONKey) / Value(kMaxBSONKey). Those constants are BSONObj (an object with one empty-named MinKey/MaxKey field), so the result is an object value instead of Value(MINKEY) / Value(MAXKEY). When SBE returns results straight to the client (convertToBsonObj) the value is correct, so only pipelines with a classic suffix are affected. master has the same code.

      Found with AI assistance.

            Assignee:
            Serhii Lysenko
            Reporter:
            r33s3n6 N/A (EXT)
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: