summaryrefslogtreecommitdiff
path: root/jstests/libs/override_methods/implicitly_wrap_pipelines_in_facets.js
blob: 84da15b1b8f3cb526435f811567b89bec81e189f (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;

        if (originalPipeline.length === 0) {
            // Empty pipelines are disallowed within a $facet stage.
            print('Not wrapping empty pipeline in a $facet stage');
            return originalRunCommand.apply(this, arguments);
        }

        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}},
            {$unwind: '$originalPipeline'},
            {$replaceRoot: {newRoot: '$originalPipeline'}},
        ];
        return originalRunCommand.apply(this, arguments);
    };
}());