@@ -2315,106 +2315,164 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
2315
2315
true /* participateInTrialRunTracking */ );
2316
2316
2317
2317
//
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.
2319
2324
//
2320
2325
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.
2322
2330
std::vector<ProjectNode> projectionNodes;
2323
2331
2324
- TypedSlot resultSlot1;
2332
+ // The projection expression that adds the index and/or unwind values to the result doc.
2333
+ TypedExpression finalProjectExpr;
2334
+
2325
2335
if (un->indexPath ) {
2326
- projectionNodes. emplace_back (SbExpr{arrayIndexSlot});
2336
+ // "includeArrayIndex" option (Cases 1-3). The index is always projected in these.
2327
2337
2328
2338
// 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.
2330
2339
if (reqs.has ({PlanStageSlots::SlotType::kField , un->indexPath ->fullPath ()})) {
2331
2340
outputs.set (PlanStageSlots::OwnedSlotName{PlanStageSlots::SlotType::kField ,
2332
2341
un->indexPath ->fullPath ()},
2333
2342
arrayIndexSlot);
2334
2343
}
2335
2344
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 (
2338
2383
_state,
2339
2384
projection_ast::ProjectType::kAddition ,
2340
- {un-> indexPath -> fullPath ()} ,
2385
+ std::move (fieldPaths) ,
2341
2386
std::move (projectionNodes),
2342
2387
SbExpr{childResultSlot.slotId }); // current result doc: updated by the projection
2343
2388
2344
- // Create a projection expression to force project a Null array index to the result doc.
2389
+ // Case 3: index Null //////////////////////////////////////////////////////////////////////
2390
+ fieldPaths.clear ();
2345
2391
projectionNodes.clear ();
2392
+
2393
+ // Index output
2394
+ fieldPaths.emplace_back (un->indexPath ->fullPath ());
2346
2395
projectionNodes.emplace_back (SbExpr{makeConstant (sbe::value::TypeTags::Null, 0 )});
2347
- SbExpr indexNullProjectExpr = generateProjection (
2396
+
2397
+ SbExpr indexNullProjExpr = generateProjection (
2348
2398
_state,
2349
2399
projection_ast::ProjectType::kAddition ,
2350
- {un-> indexPath -> fullPath ()} ,
2400
+ std::move (fieldPaths) ,
2351
2401
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 ])))
2370
2431
.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 ));
2377
2432
} 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.
2382
2434
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});
2386
2442
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 };
2411
2470
stage = makeProjectStage (std::move (stage),
2412
2471
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 ));
2417
2474
2475
+ outputs.set (kResult , resultSlot);
2418
2476
return {std::move (stage), std::move (outputs)};
2419
2477
} // buildUnwind
2420
2478
0 commit comments