[DOCS-12036] Aggregation examples don't work due to missing cursor option Created: 06/Sep/18  Updated: 12/Sep/18  Resolved: 10/Sep/18

Status: Closed
Project: Documentation
Component/s: None
Affects Version/s: 3.6, 4.0.0
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Ori Avtalion Assignee: Unassigned
Resolution: Cannot Reproduce Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
is related to DOCS-12044 Aggregation lookup example is not wor... Closed
is related to DOCS-12045 $graphLookup examples should include ... Closed
Participants:
Days since reply: 5 years, 22 weeks ago

 Description   

Description

MongoDB 3.6 had a breaking compatibility change: "If you run the aggregate command, you must include either the cursor option or the explain option."

Many examples don't appear to have been updated, leaving users to puzzle out what's wrong.

For example, in the documentation for the $unwind operator, the command:

db.inventory.aggregate( [ { $unwind : "$sizes" } ] )

fails with the error:

aggregate failed: {
{{ "ok": 0,}}
{{ "errmsg": "The 'cursor' option is required, except for aggregate}}
with the explain argument",
{{ "code": 9,}}
{{ "codeName": "FailedToParse"}}
}

Scope of changes

Impact to Other Docs

MVP (Work and Date)

Resources (Scope or Design Docs, Invision, etc.)



 Comments   
Comment by Kay Kim (Inactive) [ 12/Sep/18 ]

:thumbsup:

Comment by Ori Avtalion [ 12/Sep/18 ]

Sorry, this is my mistake. I had mongo-hacker installed, which had its own version of the aggregate function. I will report a bug there.

Comment by Kay Kim (Inactive) [ 10/Sep/18 ]

Closing as cannot reproduce using the newly downloaded mongo shell for 3.6 and 4.0.
Can view the mongo shell code for aggregate which adds the cursor option:

 
> db.inventory.aggregate( [ { $unwind: "$sizes" } ] )
{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
> db.inventory.aggregate
function (pipeline, aggregateOptions) {
    if (!(pipeline instanceof Array)) {
        // Support legacy varargs form. Also handles db.foo.aggregate().
        pipeline = Array.from(arguments);
        aggregateOptions = {};
    } else if (aggregateOptions === undefined) {
        aggregateOptions = {};
    }
 
    const cmdObj = this._makeCommand("aggregate", {pipeline: pipeline});
 
    return this._db._runAggregate(cmdObj, aggregateOptions);
}
> db._runAggregate
function (cmdObj, aggregateOptions) {
        assert(cmdObj.pipeline instanceof Array, "cmdObj must contain a 'pipeline' array");
        assert(cmdObj.aggregate !== undefined, "cmdObj must contain 'aggregate' field");
        assert(aggregateOptions === undefined || aggregateOptions instanceof Object,
               "'aggregateOptions' argument must be an object");
 
        // Make a copy of the initial command object, i.e. {aggregate: x, pipeline: [...]}.
        cmdObj = Object.extend({}, cmdObj);
 
        // Make a copy of the aggregation options.
        let optcpy = Object.extend({}, (aggregateOptions || {}));
 
        if ('batchSize' in optcpy) {
            if (optcpy.cursor == null) {
                optcpy.cursor = {};
            }
 
            optcpy.cursor.batchSize = optcpy['batchSize'];
            delete optcpy['batchSize'];
        } else if ('useCursor' in optcpy) {
            if (optcpy.cursor == null) {
                optcpy.cursor = {};
            }
 
            delete optcpy['useCursor'];
        }
 
        const maxAwaitTimeMS = optcpy.maxAwaitTimeMS;
        delete optcpy.maxAwaitTimeMS;
 
        // Reassign the cleaned-up options.
        aggregateOptions = optcpy;
 
        // Add the options to the command object.
        Object.extend(cmdObj, aggregateOptions);
 
        if (!('cursor' in cmdObj)) {
            cmdObj.cursor = {};
        }
 
        const pipeline = cmdObj.pipeline;
 
        // Check whether the pipeline has an $out stage. If not, we may run on a Secondary and
        // should attach a readPreference.
        const hasOutStage =
            pipeline.length >= 1 && pipeline[pipeline.length - 1].hasOwnProperty("$out");
 
        const doAgg = function(cmdObj) {
            return hasOutStage ? this.runCommand(cmdObj) : this.runReadCommand(cmdObj);
        }.bind(this);
 
        const res = doAgg(cmdObj);
 
        if (!res.ok && (res.code == 17020 || res.errmsg == "unrecognized field \"cursor") &&
            !("cursor" in aggregateOptions)) {
            // If the command failed because cursors aren't supported and the user didn't explicitly
            // request a cursor, try again without requesting a cursor.
            delete cmdObj.cursor;
 
            res = doAgg(cmdObj);
 
            if ('result' in res && !("cursor" in res)) {
                // convert old-style output to cursor-style output
                res.cursor = {ns: '', id: NumberLong(0)};
                res.cursor.firstBatch = res.result;
                delete res.result;
            }
        }
 
        assert.commandWorked(res, "aggregate failed");
 
        if ("cursor" in res) {
            let batchSizeValue = undefined;
 
            if (cmdObj["cursor"]["batchSize"] > 0) {
                batchSizeValue = cmdObj["cursor"]["batchSize"];
            }
 
            return new DBCommandCursor(this, res, batchSizeValue, maxAwaitTimeMS);
        }
 
        return res;
    }

Comment by Kay Kim (Inactive) [ 06/Sep/18 ]

Hi salty-horse – thanks for filing this ticket.
The example use the mongo shell helper method – which returns a cursor (starting from 2.6)

Using the shell helper:

> db.inventory.aggregate( [ { $unwind: "$sizes" } ] )
{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }

However, if you are using the command directly, then, you would get that error:

> db.runCommand( { aggregate: "inventory", pipeline: [ { $unwind: "$sizes" } ] } )
{
	"ok" : 0,
	"errmsg" : "The 'cursor' option is required, except for aggregate with the explain argument",
	"code" : 9,
	"codeName" : "FailedToParse"
}

Because the helper method already returns a cursor, you might find that more intuitive to use and most of our examples use the shell helper.

If you are using the helper method, could you verify your mongo shell version as well as your server version? These should print when you first open a mongo shell:

MongoDB shell version v3.6.4
connecting to: mongodb://127.0.0.1:27017/
MongoDB server version: 3.6.4

Generated at Thu Feb 08 08:04:14 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.