summaryrefslogtreecommitdiff
path: root/jstests/libs/override_methods/implicitly_wrap_pipelines_in_facets.js
blob: 0d4e0158b3ed4cd54ad14a1f010e6bcd63486738 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
 * Loading this file overrides Mongo.prototype.runCommand() with a function that wraps any
 * aggregate command's pipeline inside a $facet stage, then appends an $unwind stage. This will
 * yield the same results, but stress the logic of the $facet stage.
 */
(function() {
'use strict';

// Set the batch size of the $facet stage's buffer to be lower. This will further stress the
// batching logic, since most pipelines will fall below the default size of 100MB.
assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryFacetBufferSizeBytes: 1000}));

// Save a reference to the original runCommand method in the IIFE's scope.
// This scoping allows the original method to be called by the override below.
var originalRunCommand = Mongo.prototype.runCommand;

Mongo.prototype.runCommand = function(dbName, cmdObj, options) {
    // Skip wrapping the pipeline in a $facet stage if it's not an aggregation, or if it's
    // possibly an invalid one without a pipeline.
    if (typeof cmdObj !== 'object' || cmdObj === null || !cmdObj.hasOwnProperty('aggregate') ||
        !cmdObj.hasOwnProperty('pipeline') || !Array.isArray(cmdObj.pipeline)) {
        return originalRunCommand.apply(this, arguments);
    }

    var originalPipeline = cmdObj.pipeline;

    const stagesDisallowedInsideFacet = [
        '$changeStream',
        '$collStats',
        '$facet',
        '$geoNear',
        '$indexStats',
        '$merge',
        '$out',
    ];
    for (let stageSpec of originalPipeline) {
        // Skip wrapping the pipeline in a $facet stage if it has an invalid stage
        // specification.
        if (typeof stageSpec !== 'object' || stageSpec === null) {
            print('Not wrapping invalid pipeline in a $facet stage');
            return originalRunCommand.apply(this, arguments);
        }

        if (stageSpec.hasOwnProperty('$match') && typeof stageSpec.$match === 'object' &&
            stageSpec.$match !== null) {
            if (stageSpec.$match.hasOwnProperty('$text')) {
                // A $text search is disallowed within a $facet stage.
                print('Not wrapping $text in a $facet stage');
                return originalRunCommand.apply(this, arguments);
            }
            if (Object.keys(stageSpec.$match).length === 0) {
                // Skip wrapping an empty $match stage, since it can be optimized out, resulting
                // in an empty pipeline which is disallowed within a $facet stage.
                print('Not wrapping empty $match in a $facet stage');
                return originalRunCommand.apply(this, arguments);
            }
        }

        // Skip wrapping the pipeline in a $facet stage if it contains a stage disallowed inside
        // a $facet.
        for (let disallowedStage of stagesDisallowedInsideFacet) {
            if (stageSpec.hasOwnProperty(disallowedStage)) {
                print('Not wrapping ' + disallowedStage + ' in a $facet stage');
                return originalRunCommand.apply(this, arguments);
            }
        }
    }

    cmdObj.pipeline = [
        {$facet: {originalPipeline: originalPipeline, extraPipeline: [{$count: "count"}]}},
        {$unwind: '$originalPipeline'},
        {$replaceRoot: {newRoot: '$originalPipeline'}},
    ];
    return originalRunCommand.apply(this, arguments);
};
}());