diff options
Diffstat (limited to 'jstests/core/views/views_aggregation.js')
-rw-r--r-- | jstests/core/views/views_aggregation.js | 454 |
1 files changed, 225 insertions, 229 deletions
diff --git a/jstests/core/views/views_aggregation.js b/jstests/core/views/views_aggregation.js index 2b7f78f319f..fb78211307f 100644 --- a/jstests/core/views/views_aggregation.js +++ b/jstests/core/views/views_aggregation.js @@ -4,205 +4,206 @@ * requires_non_retryable_commands] */ (function() { - "use strict"; - - // For assertMergeFailsForAllModesWithCode. - load("jstests/aggregation/extras/merge_helpers.js"); - load("jstests/aggregation/extras/utils.js"); // For arrayEq, assertErrorCode, and - // orderedArrayEq. - - let viewsDB = db.getSiblingDB("views_aggregation"); - assert.commandWorked(viewsDB.dropDatabase()); - - // Helper functions. - let assertAggResultEq = function(collection, pipeline, expected, ordered) { - let coll = viewsDB.getCollection(collection); - let arr = coll.aggregate(pipeline).toArray(); - let success = (typeof(ordered) === "undefined" || !ordered) ? arrayEq(arr, expected) - : orderedArrayEq(arr, expected); - assert(success, tojson({got: arr, expected: expected})); - }; - let byPopulation = function(a, b) { - if (a.pop < b.pop) - return -1; - else if (a.pop > b.pop) - return 1; - else - return 0; - }; - - // Populate a collection with some test data. - let allDocuments = []; - allDocuments.push({_id: "New York", state: "NY", pop: 7}); - allDocuments.push({_id: "Newark", state: "NJ", pop: 3}); - allDocuments.push({_id: "Palo Alto", state: "CA", pop: 10}); - allDocuments.push({_id: "San Francisco", state: "CA", pop: 4}); - allDocuments.push({_id: "Trenton", state: "NJ", pop: 5}); - - let coll = viewsDB.coll; - let bulk = coll.initializeUnorderedBulkOp(); - allDocuments.forEach(function(doc) { - bulk.insert(doc); +"use strict"; + +// For assertMergeFailsForAllModesWithCode. +load("jstests/aggregation/extras/merge_helpers.js"); +load("jstests/aggregation/extras/utils.js"); // For arrayEq, assertErrorCode, and + // orderedArrayEq. + +let viewsDB = db.getSiblingDB("views_aggregation"); +assert.commandWorked(viewsDB.dropDatabase()); + +// Helper functions. +let assertAggResultEq = function(collection, pipeline, expected, ordered) { + let coll = viewsDB.getCollection(collection); + let arr = coll.aggregate(pipeline).toArray(); + let success = (typeof (ordered) === "undefined" || !ordered) ? arrayEq(arr, expected) + : orderedArrayEq(arr, expected); + assert(success, tojson({got: arr, expected: expected})); +}; +let byPopulation = function(a, b) { + if (a.pop < b.pop) + return -1; + else if (a.pop > b.pop) + return 1; + else + return 0; +}; + +// Populate a collection with some test data. +let allDocuments = []; +allDocuments.push({_id: "New York", state: "NY", pop: 7}); +allDocuments.push({_id: "Newark", state: "NJ", pop: 3}); +allDocuments.push({_id: "Palo Alto", state: "CA", pop: 10}); +allDocuments.push({_id: "San Francisco", state: "CA", pop: 4}); +allDocuments.push({_id: "Trenton", state: "NJ", pop: 5}); + +let coll = viewsDB.coll; +let bulk = coll.initializeUnorderedBulkOp(); +allDocuments.forEach(function(doc) { + bulk.insert(doc); +}); +assert.writeOK(bulk.execute()); + +// Create views on the data. +assert.commandWorked(viewsDB.runCommand({create: "emptyPipelineView", viewOn: "coll"})); +assert.commandWorked( + viewsDB.runCommand({create: "identityView", viewOn: "coll", pipeline: [{$match: {}}]})); +assert.commandWorked(viewsDB.runCommand( + {create: "noIdView", viewOn: "coll", pipeline: [{$project: {_id: 0, state: 1, pop: 1}}]})); +assert.commandWorked(viewsDB.runCommand({ + create: "popSortedView", + viewOn: "identityView", + pipeline: [{$match: {pop: {$gte: 0}}}, {$sort: {pop: 1}}] +})); + +// Find all documents with empty aggregations. +assertAggResultEq("emptyPipelineView", [], allDocuments); +assertAggResultEq("identityView", [], allDocuments); +assertAggResultEq("identityView", [{$match: {}}], allDocuments); + +// Filter documents on a view with $match. +assertAggResultEq( + "popSortedView", [{$match: {state: "NY"}}], [{_id: "New York", state: "NY", pop: 7}]); + +// An aggregation still works on a view that strips _id. +assertAggResultEq("noIdView", [{$match: {state: "NY"}}], [{state: "NY", pop: 7}]); + +// Aggregations work on views that sort. +const doOrderedSort = true; +assertAggResultEq("popSortedView", [], allDocuments.sort(byPopulation), doOrderedSort); +assertAggResultEq("popSortedView", [{$limit: 1}, {$project: {_id: 1}}], [{_id: "Palo Alto"}]); + +// Test that the $out stage errors when writing to a view namespace. +assertErrorCode(coll, [{$out: "emptyPipelineView"}], ErrorCodes.CommandNotSupportedOnView); + +// Test that the $merge stage errors when writing to a view namespace. +assertMergeFailsForAllModesWithCode({ + source: viewsDB.coll, + target: viewsDB.emptyPipelineView, + errorCodes: [ErrorCodes.CommandNotSupportedOnView] +}); + +// Test that the $merge stage errors when writing to a view namespace in a foreign database. +let foreignDB = db.getSiblingDB("views_aggregation_foreign"); +foreignDB.view.drop(); +assert.commandWorked(foreignDB.createView("view", "coll", [])); + +assertMergeFailsForAllModesWithCode({ + source: viewsDB.coll, + target: foreignDB.view, + errorCodes: [ErrorCodes.CommandNotSupportedOnView] +}); + +// Test that an aggregate on a view propagates the 'bypassDocumentValidation' option. +const validatedCollName = "collectionWithValidator"; +viewsDB[validatedCollName].drop(); +assert.commandWorked( + viewsDB.createCollection(validatedCollName, {validator: {illegalField: {$exists: false}}})); + +viewsDB.invalidDocs.drop(); +viewsDB.invalidDocsView.drop(); +assert.writeOK(viewsDB.invalidDocs.insert({illegalField: "present"})); +assert.commandWorked(viewsDB.createView("invalidDocsView", "invalidDocs", [])); + +assert.commandWorked( + viewsDB.runCommand({ + aggregate: "invalidDocsView", + pipeline: [{$out: validatedCollName}], + cursor: {}, + bypassDocumentValidation: true + }), + "Expected $out insertions to succeed since 'bypassDocumentValidation' was specified"); + +// Test that an aggregate on a view propagates the 'allowDiskUse' option. +const extSortLimit = 100 * 1024 * 1024; +const largeStrSize = 10 * 1024 * 1024; +const largeStr = new Array(largeStrSize).join('x'); +viewsDB.largeColl.drop(); +for (let i = 0; i <= extSortLimit / largeStrSize; ++i) { + assert.writeOK(viewsDB.largeColl.insert({x: i, largeStr: largeStr})); +} +assertErrorCode(viewsDB.largeColl, + [{$sort: {x: -1}}], + 16819, + "Expected in-memory sort to fail due to excessive memory usage"); +viewsDB.largeView.drop(); +assert.commandWorked(viewsDB.createView("largeView", "largeColl", [])); +assertErrorCode(viewsDB.largeView, + [{$sort: {x: -1}}], + 16819, + "Expected in-memory sort to fail due to excessive memory usage"); + +assert.commandWorked( + viewsDB.runCommand( + {aggregate: "largeView", pipeline: [{$sort: {x: -1}}], cursor: {}, allowDiskUse: true}), + "Expected aggregate to succeed since 'allowDiskUse' was specified"); + +// Test explain modes on a view. +let explainPlan = assert.commandWorked( + viewsDB.popSortedView.explain("queryPlanner").aggregate([{$limit: 1}, {$match: {pop: 3}}])); +assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); +assert(!explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); + +explainPlan = assert.commandWorked( + viewsDB.popSortedView.explain("executionStats").aggregate([{$limit: 1}, {$match: {pop: 3}}])); +assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); +assert(explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); +assert.eq(explainPlan.stages[0].$cursor.executionStats.nReturned, 5); +assert(!explainPlan.stages[0].$cursor.executionStats.hasOwnProperty("allPlansExecution")); + +explainPlan = assert.commandWorked(viewsDB.popSortedView.explain("allPlansExecution") + .aggregate([{$limit: 1}, {$match: {pop: 3}}])); +assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); +assert(explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); +assert.eq(explainPlan.stages[0].$cursor.executionStats.nReturned, 5); +assert(explainPlan.stages[0].$cursor.executionStats.hasOwnProperty("allPlansExecution")); + +// Passing a value of true for the explain option to the aggregation command, without using the +// shell explain helper, should continue to work. +explainPlan = assert.commandWorked( + viewsDB.popSortedView.aggregate([{$limit: 1}, {$match: {pop: 3}}], {explain: true})); +assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); +assert(!explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); + +// Test allPlansExecution explain mode on the base collection. +explainPlan = assert.commandWorked( + viewsDB.coll.explain("allPlansExecution").aggregate([{$limit: 1}, {$match: {pop: 3}}])); +assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); +assert(explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); +assert.eq(explainPlan.stages[0].$cursor.executionStats.nReturned, 1); +assert(explainPlan.stages[0].$cursor.executionStats.hasOwnProperty("allPlansExecution")); + +// The explain:true option should not work when paired with the explain shell helper. +assert.throws(function() { + viewsDB.popSortedView.explain("executionStats").aggregate([{$limit: 1}, {$match: {pop: 3}}], { + explain: true }); - assert.writeOK(bulk.execute()); - - // Create views on the data. - assert.commandWorked(viewsDB.runCommand({create: "emptyPipelineView", viewOn: "coll"})); - assert.commandWorked( - viewsDB.runCommand({create: "identityView", viewOn: "coll", pipeline: [{$match: {}}]})); - assert.commandWorked(viewsDB.runCommand( - {create: "noIdView", viewOn: "coll", pipeline: [{$project: {_id: 0, state: 1, pop: 1}}]})); - assert.commandWorked(viewsDB.runCommand({ - create: "popSortedView", - viewOn: "identityView", - pipeline: [{$match: {pop: {$gte: 0}}}, {$sort: {pop: 1}}] - })); - - // Find all documents with empty aggregations. - assertAggResultEq("emptyPipelineView", [], allDocuments); - assertAggResultEq("identityView", [], allDocuments); - assertAggResultEq("identityView", [{$match: {}}], allDocuments); - - // Filter documents on a view with $match. - assertAggResultEq( - "popSortedView", [{$match: {state: "NY"}}], [{_id: "New York", state: "NY", pop: 7}]); - - // An aggregation still works on a view that strips _id. - assertAggResultEq("noIdView", [{$match: {state: "NY"}}], [{state: "NY", pop: 7}]); - - // Aggregations work on views that sort. - const doOrderedSort = true; - assertAggResultEq("popSortedView", [], allDocuments.sort(byPopulation), doOrderedSort); - assertAggResultEq("popSortedView", [{$limit: 1}, {$project: {_id: 1}}], [{_id: "Palo Alto"}]); - - // Test that the $out stage errors when writing to a view namespace. - assertErrorCode(coll, [{$out: "emptyPipelineView"}], ErrorCodes.CommandNotSupportedOnView); - - // Test that the $merge stage errors when writing to a view namespace. - assertMergeFailsForAllModesWithCode({ - source: viewsDB.coll, - target: viewsDB.emptyPipelineView, - errorCodes: [ErrorCodes.CommandNotSupportedOnView] - }); - - // Test that the $merge stage errors when writing to a view namespace in a foreign database. - let foreignDB = db.getSiblingDB("views_aggregation_foreign"); - foreignDB.view.drop(); - assert.commandWorked(foreignDB.createView("view", "coll", [])); - - assertMergeFailsForAllModesWithCode({ - source: viewsDB.coll, - target: foreignDB.view, - errorCodes: [ErrorCodes.CommandNotSupportedOnView] - }); - - // Test that an aggregate on a view propagates the 'bypassDocumentValidation' option. - const validatedCollName = "collectionWithValidator"; - viewsDB[validatedCollName].drop(); - assert.commandWorked( - viewsDB.createCollection(validatedCollName, {validator: {illegalField: {$exists: false}}})); - - viewsDB.invalidDocs.drop(); - viewsDB.invalidDocsView.drop(); - assert.writeOK(viewsDB.invalidDocs.insert({illegalField: "present"})); - assert.commandWorked(viewsDB.createView("invalidDocsView", "invalidDocs", [])); - - assert.commandWorked( - viewsDB.runCommand({ - aggregate: "invalidDocsView", - pipeline: [{$out: validatedCollName}], - cursor: {}, - bypassDocumentValidation: true - }), - "Expected $out insertions to succeed since 'bypassDocumentValidation' was specified"); - - // Test that an aggregate on a view propagates the 'allowDiskUse' option. - const extSortLimit = 100 * 1024 * 1024; - const largeStrSize = 10 * 1024 * 1024; - const largeStr = new Array(largeStrSize).join('x'); - viewsDB.largeColl.drop(); - for (let i = 0; i <= extSortLimit / largeStrSize; ++i) { - assert.writeOK(viewsDB.largeColl.insert({x: i, largeStr: largeStr})); - } - assertErrorCode(viewsDB.largeColl, - [{$sort: {x: -1}}], - 16819, - "Expected in-memory sort to fail due to excessive memory usage"); - viewsDB.largeView.drop(); - assert.commandWorked(viewsDB.createView("largeView", "largeColl", [])); - assertErrorCode(viewsDB.largeView, - [{$sort: {x: -1}}], - 16819, - "Expected in-memory sort to fail due to excessive memory usage"); - - assert.commandWorked( - viewsDB.runCommand( - {aggregate: "largeView", pipeline: [{$sort: {x: -1}}], cursor: {}, allowDiskUse: true}), - "Expected aggregate to succeed since 'allowDiskUse' was specified"); - - // Test explain modes on a view. - let explainPlan = assert.commandWorked( - viewsDB.popSortedView.explain("queryPlanner").aggregate([{$limit: 1}, {$match: {pop: 3}}])); - assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); - assert(!explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); - - explainPlan = assert.commandWorked(viewsDB.popSortedView.explain("executionStats") - .aggregate([{$limit: 1}, {$match: {pop: 3}}])); - assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); - assert(explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); - assert.eq(explainPlan.stages[0].$cursor.executionStats.nReturned, 5); - assert(!explainPlan.stages[0].$cursor.executionStats.hasOwnProperty("allPlansExecution")); - - explainPlan = assert.commandWorked(viewsDB.popSortedView.explain("allPlansExecution") - .aggregate([{$limit: 1}, {$match: {pop: 3}}])); - assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); - assert(explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); - assert.eq(explainPlan.stages[0].$cursor.executionStats.nReturned, 5); - assert(explainPlan.stages[0].$cursor.executionStats.hasOwnProperty("allPlansExecution")); - - // Passing a value of true for the explain option to the aggregation command, without using the - // shell explain helper, should continue to work. - explainPlan = assert.commandWorked( - viewsDB.popSortedView.aggregate([{$limit: 1}, {$match: {pop: 3}}], {explain: true})); - assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); - assert(!explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); - - // Test allPlansExecution explain mode on the base collection. - explainPlan = assert.commandWorked( - viewsDB.coll.explain("allPlansExecution").aggregate([{$limit: 1}, {$match: {pop: 3}}])); - assert.eq(explainPlan.stages[0].$cursor.queryPlanner.namespace, "views_aggregation.coll"); - assert(explainPlan.stages[0].$cursor.hasOwnProperty("executionStats")); - assert.eq(explainPlan.stages[0].$cursor.executionStats.nReturned, 1); - assert(explainPlan.stages[0].$cursor.executionStats.hasOwnProperty("allPlansExecution")); - - // The explain:true option should not work when paired with the explain shell helper. - assert.throws(function() { - viewsDB.popSortedView.explain("executionStats") - .aggregate([{$limit: 1}, {$match: {pop: 3}}], {explain: true}); - }); - - // The remaining tests involve $lookup and $graphLookup. We cannot lookup into sharded - // collections, so skip these tests if running in a sharded configuration. - let isMasterResponse = assert.commandWorked(viewsDB.runCommand("isMaster")); - const isMongos = (isMasterResponse.msg === "isdbgrid"); - if (isMongos) { - jsTest.log("Tests are being run on a mongos; skipping all $lookup and $graphLookup tests."); - return; - } - - // Test that the $lookup stage resolves the view namespace referenced in the 'from' field. - assertAggResultEq( - coll.getName(), - [ - {$match: {_id: "New York"}}, - {$lookup: {from: "identityView", localField: "_id", foreignField: "_id", as: "matched"}}, - {$unwind: "$matched"}, - {$project: {_id: 1, matchedId: "$matched._id"}} - ], - [{_id: "New York", matchedId: "New York"}]); +}); + +// The remaining tests involve $lookup and $graphLookup. We cannot lookup into sharded +// collections, so skip these tests if running in a sharded configuration. +let isMasterResponse = assert.commandWorked(viewsDB.runCommand("isMaster")); +const isMongos = (isMasterResponse.msg === "isdbgrid"); +if (isMongos) { + jsTest.log("Tests are being run on a mongos; skipping all $lookup and $graphLookup tests."); + return; +} + +// Test that the $lookup stage resolves the view namespace referenced in the 'from' field. +assertAggResultEq( + coll.getName(), + [ + {$match: {_id: "New York"}}, + {$lookup: {from: "identityView", localField: "_id", foreignField: "_id", as: "matched"}}, + {$unwind: "$matched"}, + {$project: {_id: 1, matchedId: "$matched._id"}} + ], + [{_id: "New York", matchedId: "New York"}]); - // Test that the $graphLookup stage resolves the view namespace referenced in the 'from' field. - assertAggResultEq(coll.getName(), +// Test that the $graphLookup stage resolves the view namespace referenced in the 'from' field. +assertAggResultEq(coll.getName(), [ {$match: {_id: "New York"}}, { @@ -219,22 +220,19 @@ ], [{_id: "New York", matchedId: "New York"}]); - // Test that the $lookup stage resolves the view namespace referenced in the 'from' field of - // another $lookup stage nested inside of it. - assert.commandWorked(viewsDB.runCommand({ - create: "viewWithLookupInside", - viewOn: coll.getName(), - pipeline: [ - { - $lookup: - {from: "identityView", localField: "_id", foreignField: "_id", as: "matched"} - }, - {$unwind: "$matched"}, - {$project: {_id: 1, matchedId: "$matched._id"}} - ] - })); - - assertAggResultEq( +// Test that the $lookup stage resolves the view namespace referenced in the 'from' field of +// another $lookup stage nested inside of it. +assert.commandWorked(viewsDB.runCommand({ + create: "viewWithLookupInside", + viewOn: coll.getName(), + pipeline: [ + {$lookup: {from: "identityView", localField: "_id", foreignField: "_id", as: "matched"}}, + {$unwind: "$matched"}, + {$project: {_id: 1, matchedId: "$matched._id"}} + ] +})); + +assertAggResultEq( coll.getName(), [ {$match: {_id: "New York"}}, @@ -251,9 +249,9 @@ ], [{_id: "New York", matchedId1: "New York", matchedId2: "New York"}]); - // Test that the $graphLookup stage resolves the view namespace referenced in the 'from' field - // of a $lookup stage nested inside of it. - let graphLookupPipeline = [ +// Test that the $graphLookup stage resolves the view namespace referenced in the 'from' field +// of a $lookup stage nested inside of it. +let graphLookupPipeline = [ {$match: {_id: "New York"}}, { $graphLookup: { @@ -268,13 +266,13 @@ {$project: {_id: 1, matchedId1: "$matched._id", matchedId2: "$matched.matchedId"}} ]; - assertAggResultEq(coll.getName(), - graphLookupPipeline, - [{_id: "New York", matchedId1: "New York", matchedId2: "New York"}]); +assertAggResultEq(coll.getName(), + graphLookupPipeline, + [{_id: "New York", matchedId1: "New York", matchedId2: "New York"}]); - // Test that the $lookup stage on a view with a nested $lookup on a different view resolves the - // view namespaces referenced in their respective 'from' fields. - assertAggResultEq( +// Test that the $lookup stage on a view with a nested $lookup on a different view resolves the +// view namespaces referenced in their respective 'from' fields. +assertAggResultEq( coll.getName(), [ {$match: {_id: "Trenton"}}, @@ -307,11 +305,9 @@ }] }]); - // Test that the $facet stage resolves the view namespace referenced in the 'from' field of a - // $lookup stage nested inside of a $graphLookup stage. - assertAggResultEq( - coll.getName(), - [{$facet: {nested: graphLookupPipeline}}], - [{nested: [{_id: "New York", matchedId1: "New York", matchedId2: "New York"}]}]); - +// Test that the $facet stage resolves the view namespace referenced in the 'from' field of a +// $lookup stage nested inside of a $graphLookup stage. +assertAggResultEq(coll.getName(), + [{$facet: {nested: graphLookupPipeline}}], + [{nested: [{_id: "New York", matchedId1: "New York", matchedId2: "New York"}]}]); }()); |