Skip to content

Commit a38a340

Browse files
kevin-cherkauerEvergreen Agent
authored andcommitted
SERVER-82641 Only materialize result doc once in buildUnwind()
1 parent a23fbbe commit a38a340

File tree

1 file changed

+128
-70
lines changed

1 file changed

+128
-70
lines changed

src/mongo/db/query/sbe_stage_builder.cpp

Lines changed: 128 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,106 +2315,164 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
23152315
true /* participateInTrialRunTracking */);
23162316

23172317
//
2318-
// If needed, build project parent node to add the unwind output array index to the result doc.
2318+
// Build project parent node to add the unwind and/or index outputs to the result doc. Since
2319+
// docs are immutable in SBE, doing this the simpler way via separate ProjectStages for each
2320+
// output leads to an extra result doc copy if both unwind and index get projected. To avoid
2321+
// this, we build a single ProjectStage that handles all possible combinations of needed
2322+
// projections. This is simplified slightly by the fact that we know whether the index output
2323+
// was requested or not, so we can wire only the relevant combinations.
23192324
//
23202325

2321-
// Variables whose values are to be projected into the result document (as wrapped SlotIds).
2326+
// Paths in result document to project to.
2327+
std::vector<std::string> fieldPaths;
2328+
2329+
// Variables whose values are to be projected into the result document.
23222330
std::vector<ProjectNode> projectionNodes;
23232331

2324-
TypedSlot resultSlot1;
2332+
// The projection expression that adds the index and/or unwind values to the result doc.
2333+
TypedExpression finalProjectExpr;
2334+
23252335
if (un->indexPath) {
2326-
projectionNodes.emplace_back(SbExpr{arrayIndexSlot});
2336+
// "includeArrayIndex" option (Cases 1-3). The index is always projected in these.
23272337

23282338
// If our parent wants the array index field, set our outputs to point it to that slot.
2329-
// Otherwise clearNonRequiredSlots() will have cleared this field, so we don't need to.
23302339
if (reqs.has({PlanStageSlots::SlotType::kField, un->indexPath->fullPath()})) {
23312340
outputs.set(PlanStageSlots::OwnedSlotName{PlanStageSlots::SlotType::kField,
23322341
un->indexPath->fullPath()},
23332342
arrayIndexSlot);
23342343
}
23352344

2336-
// Create a projection expression to project the array index to the result document.
2337-
SbExpr indexProjectExpr = generateProjection(
2345+
// Case 1: index Null, unwind val //////////////////////////////////////////////////////////
2346+
// We need two copies of the Case 1 expression as it is used twice, but the copy constructor
2347+
// is deleted so we are forced to std::move it.
2348+
SbExpr indexNullUnwindValProjExpr[2];
2349+
2350+
for (int copy = 0; copy < 2; ++copy) {
2351+
fieldPaths.clear();
2352+
projectionNodes.clear();
2353+
2354+
// Index output
2355+
fieldPaths.emplace_back(un->indexPath->fullPath());
2356+
projectionNodes.emplace_back(SbExpr{makeConstant(sbe::value::TypeTags::Null, 0)});
2357+
2358+
// Unwind output
2359+
fieldPaths.emplace_back(fp.fullPath());
2360+
projectionNodes.emplace_back(SbExpr{unwindSlot});
2361+
2362+
indexNullUnwindValProjExpr[copy] = generateProjection(
2363+
_state,
2364+
projection_ast::ProjectType::kAddition,
2365+
std::move(fieldPaths),
2366+
std::move(projectionNodes),
2367+
SbExpr{childResultSlot.slotId}); // current result doc: updated by the projection
2368+
}
2369+
2370+
// Case 2: index val, unwind val ///////////////////////////////////////////////////////////
2371+
fieldPaths.clear();
2372+
projectionNodes.clear();
2373+
2374+
// Index output
2375+
fieldPaths.emplace_back(un->indexPath->fullPath());
2376+
projectionNodes.emplace_back(SbExpr{arrayIndexSlot});
2377+
2378+
// Unwind output
2379+
fieldPaths.emplace_back(fp.fullPath());
2380+
projectionNodes.emplace_back(SbExpr{unwindSlot});
2381+
2382+
SbExpr indexValUnwindValProjExpr = generateProjection(
23382383
_state,
23392384
projection_ast::ProjectType::kAddition,
2340-
{un->indexPath->fullPath()},
2385+
std::move(fieldPaths),
23412386
std::move(projectionNodes),
23422387
SbExpr{childResultSlot.slotId}); // current result doc: updated by the projection
23432388

2344-
// Create a projection expression to force project a Null array index to the result doc.
2389+
// Case 3: index Null //////////////////////////////////////////////////////////////////////
2390+
fieldPaths.clear();
23452391
projectionNodes.clear();
2392+
2393+
// Index output
2394+
fieldPaths.emplace_back(un->indexPath->fullPath());
23462395
projectionNodes.emplace_back(SbExpr{makeConstant(sbe::value::TypeTags::Null, 0)});
2347-
SbExpr indexNullProjectExpr = generateProjection(
2396+
2397+
SbExpr indexNullProjExpr = generateProjection(
23482398
_state,
23492399
projection_ast::ProjectType::kAddition,
2350-
{un->indexPath->fullPath()},
2400+
std::move(fieldPaths),
23512401
std::move(projectionNodes),
2352-
SbExpr{childResultSlot.slotId}); // current result doc: updated by the projection
2353-
2354-
// Wrap 'indexProjectExpr' and 'indexNullProjectExpr' in a conditional to handle quirky MQL
2355-
// edge cases.
2356-
SbExprBuilder ifIndexBuilder{_state};
2357-
TypedExpression ifIndexProjectExpr =
2358-
ifIndexBuilder
2359-
.makeIf(
2360-
/* if */ ifIndexBuilder.makeBinaryOp(
2361-
sbe::EPrimBinary::logicOr,
2362-
ifIndexBuilder.makeFunction("isNull",
2363-
ifIndexBuilder.makeVariable(arrayIndexSlot)),
2364-
ifIndexBuilder.makeBinaryOp(
2365-
sbe::EPrimBinary::greaterEq,
2366-
ifIndexBuilder.makeVariable(arrayIndexSlot),
2367-
makeConstant(sbe::value::TypeTags::NumberInt64, 0))),
2368-
/* then project index */ std::move(indexProjectExpr),
2369-
/* else project Null */ std::move(indexNullProjectExpr))
2402+
SbExpr{childResultSlot.slotId}); // current result document: updated by the projection
2403+
2404+
// Wrap the above projection subexpressions in conditionals that correctly handle quirky MQL
2405+
// edge cases:
2406+
// if isNull(index)
2407+
// then if exists(unwind)
2408+
// then project {Null, unwind}
2409+
// else project {Null, }
2410+
// else if index >= 0
2411+
// then project {index, unwind}
2412+
// else project {Null, unwind}
2413+
SbExprBuilder bldI{_state};
2414+
finalProjectExpr =
2415+
/* outer if */ bldI
2416+
.makeIf(bldI.makeFunction("isNull", bldI.makeVariable(arrayIndexSlot)),
2417+
/* outer then */
2418+
bldI.makeIf(
2419+
/* inner1 if */ bldI.makeFunction(
2420+
"exists", sbe::makeE<sbe::EVariable>(unwindSlot)),
2421+
/* inner1 then */ std::move(indexNullUnwindValProjExpr[0]),
2422+
/* inner1 else */ std::move(indexNullProjExpr)),
2423+
/* outer else */
2424+
bldI.makeIf(
2425+
/* inner2 if */ bldI.makeBinaryOp(
2426+
sbe::EPrimBinary::greaterEq,
2427+
bldI.makeVariable(arrayIndexSlot),
2428+
makeConstant(sbe::value::TypeTags::NumberInt64, 0)),
2429+
/* inner2 then */ std::move(indexValUnwindValProjExpr),
2430+
/* inner2 else */ std::move(indexNullUnwindValProjExpr[1])))
23702431
.extractExpr(_state);
2371-
2372-
resultSlot1 = TypedSlot{_slotIdGenerator.generate(), TypeSignature::kObjectType};
2373-
stage = makeProjectStage(std::move(stage),
2374-
un->nodeId(),
2375-
resultSlot1.slotId, // output result document
2376-
std::move(ifIndexProjectExpr.expr));
23772432
} else {
2378-
// The "includeArrayIndex" option was not specified, so we do not wire the stage to project
2379-
// it, and 'resultSlot1' is just an alias of the prior 'childResultSlot'.
2380-
resultSlot1 = childResultSlot;
2381-
}
2433+
// No "includeArrayIndex" option (Cases 4-5). The index is never projected in these.
23822434

2383-
//
2384-
// Build project (grand)parent node to add the unwound value to the result doc.
2385-
//
2435+
// Case 4: unwind val //////////////////////////////////////////////////////////////////////
2436+
fieldPaths.clear();
2437+
projectionNodes.clear();
2438+
2439+
// Unwind output
2440+
fieldPaths.emplace_back(fp.fullPath());
2441+
projectionNodes.emplace_back(SbExpr{unwindSlot});
23862442

2387-
// Create a projection expression to project the unwind value to the result document.
2388-
projectionNodes.clear();
2389-
projectionNodes.emplace_back(SbExpr{unwindSlot});
2390-
SbExpr unwindProjectExpr = generateProjection(
2391-
_state,
2392-
projection_ast::ProjectType::kAddition,
2393-
{fp.fullPath()},
2394-
std::move(projectionNodes),
2395-
SbExpr{resultSlot1.slotId}); // current result document: updated by the projection
2396-
2397-
// Wrap 'unwindProjectExpr' in a conditional that correctly handles the quirky MQL edge cases.
2398-
// If the unwind field was not an array, we avoid projecting the Nothing value to the result as
2399-
// this would incorrectly create the dotted path above that value in the result document.
2400-
SbExprBuilder ifUnwindBuilder{_state};
2401-
TypedExpression ifUnwindProjectExpr =
2402-
ifUnwindBuilder
2403-
.makeIf(
2404-
/* if */ ifUnwindBuilder.makeFunction("isNull",
2405-
ifUnwindBuilder.makeVariable(arrayIndexSlot)),
2406-
/* then no-op */ ifUnwindBuilder.makeVariable(resultSlot1.slotId),
2407-
/* else project */ std::move(unwindProjectExpr))
2408-
.extractExpr(_state);
2409-
2410-
TypedSlot resultSlot2 = TypedSlot{_slotIdGenerator.generate(), TypeSignature::kObjectType};
2443+
SbExpr unwindValProjExpr = generateProjection(
2444+
_state,
2445+
projection_ast::ProjectType::kAddition,
2446+
std::move(fieldPaths),
2447+
std::move(projectionNodes),
2448+
SbExpr{childResultSlot.slotId}); // current result document: updated by the projection
2449+
2450+
// Case 5: NO-OP - original doc ////////////////////////////////////////////////////////////
2451+
// Does not need a generateProjection() call as it will be handled in the wrapper logic.
2452+
2453+
// Wrap 'unwindValProjExpr' in a conditional that correctly handles the quirky MQL edge
2454+
// cases. If the unwind field was not an array (indicated by 'arrayIndexSlot' containing
2455+
// Null), we avoid projecting its value to the result, as if it is Nothing (instead of a
2456+
// singleton) this would incorrectly create the dotted path above that value in the result
2457+
// document. We don't need to project in the singleton case either as the result doc already
2458+
// has that singleton at the unwind field location.
2459+
SbExprBuilder bldU{_state};
2460+
finalProjectExpr =
2461+
bldU.makeIf(
2462+
/* if */ bldU.makeFunction("isNull", bldU.makeVariable(arrayIndexSlot)),
2463+
/* then no-op */ bldU.makeVariable(childResultSlot.slotId),
2464+
/* else project */ std::move(unwindValProjExpr))
2465+
.extractExpr(_state);
2466+
} // else no "includeArrayIndex"
2467+
2468+
// Create the ProjectStage that adds the output(s) to the result doc via 'finalProjectExpr'.
2469+
TypedSlot resultSlot = TypedSlot{_slotIdGenerator.generate(), TypeSignature::kObjectType};
24112470
stage = makeProjectStage(std::move(stage),
24122471
un->nodeId(),
2413-
resultSlot2.slotId, // output result document
2414-
std::move(ifUnwindProjectExpr.expr));
2415-
2416-
outputs.set(kResult, resultSlot2);
2472+
resultSlot.slotId, // output result document
2473+
std::move(finalProjectExpr.expr));
24172474

2475+
outputs.set(kResult, resultSlot);
24182476
return {std::move(stage), std::move(outputs)};
24192477
} // buildUnwind
24202478

0 commit comments

Comments
 (0)