From b7835f1b066d3ca04def0320535ba49838f7326f Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Wed, 4 Jan 2023 09:58:29 -0500 Subject: [PATCH 1/9] feat: remove fullResponse property of commandoperationoptions --- src/collection.ts | 79 ---- src/index.ts | 6 - src/operations/add_user.ts | 2 +- src/operations/map_reduce.ts | 250 ----------- src/sessions.ts | 2 +- src/utils.ts | 17 +- .../client_side_encryption.spec.test.ts | 6 +- .../crud/document_validation.test.js | 65 --- test/integration/crud/explain.test.js | 64 --- .../node-specific/auto_connect.test.ts | 18 - .../node-specific/mapreduce.test.js | 392 ------------------ .../node-specific/operation_examples.test.ts | 319 -------------- .../read_write_concern.spec.test.js | 8 +- .../read-write-concern/readconcern.test.js | 45 -- .../server-selection/readpreference.test.js | 126 ------ test/tools/spec-runner/index.js | 14 +- .../community/collection/mapReduce.test-d.ts | 24 -- test/types/mongodb.test-d.ts | 1 - test/unit/assorted/collations.test.js | 38 +- test/unit/assorted/write_concern.test.js | 18 +- test/unit/collection.test.ts | 63 --- 21 files changed, 22 insertions(+), 1535 deletions(-) delete mode 100644 src/operations/map_reduce.ts delete mode 100644 test/integration/node-specific/mapreduce.test.js delete mode 100644 test/types/community/collection/mapReduce.test-d.ts diff --git a/src/collection.ts b/src/collection.ts index 5897587dd77..30dbabfcb07 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -68,12 +68,6 @@ import { InsertOneResult } from './operations/insert'; import { IsCappedOperation } from './operations/is_capped'; -import { - MapFunction, - MapReduceOperation, - MapReduceOptions, - ReduceFunction -} from './operations/map_reduce'; import type { Hint, OperationOptions } from './operations/operation'; import { OptionsOperation } from './operations/options_operation'; import { RenameOperation, RenameOptions } from './operations/rename'; @@ -1524,79 +1518,6 @@ export class Collection { return new ChangeStream(this, pipeline, resolveOptions(this, options)); } - /** - * Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection. - * - * @deprecated collection.mapReduce is deprecated. Use the aggregation pipeline instead. Visit https://docs.mongodb.com/manual/reference/map-reduce-to-aggregation-pipeline for more information on how to translate map-reduce operations to the aggregation pipeline. - * @param map - The mapping function. - * @param reduce - The reduce function. - * @param options - Optional settings for the command - * @param callback - An optional callback, a Promise will be returned if none is provided - */ - mapReduce( - map: string | MapFunction, - reduce: string | ReduceFunction - ): Promise; - mapReduce( - map: string | MapFunction, - reduce: string | ReduceFunction, - options: MapReduceOptions - ): Promise; - /** @deprecated Callbacks are deprecated and will be removed in the next major version. See [mongodb-legacy](https://github.com/mongodb-js/nodejs-mongodb-legacy) for migration assistance */ - mapReduce( - map: string | MapFunction, - reduce: string | ReduceFunction, - callback: Callback - ): void; - /** @deprecated Callbacks are deprecated and will be removed in the next major version. See [mongodb-legacy](https://github.com/mongodb-js/nodejs-mongodb-legacy) for migration assistance */ - mapReduce( - map: string | MapFunction, - reduce: string | ReduceFunction, - options: MapReduceOptions, - callback: Callback - ): void; - mapReduce( - map: string | MapFunction, - reduce: string | ReduceFunction, - options?: MapReduceOptions | Callback, - callback?: Callback - ): Promise | void { - emitWarningOnce( - 'collection.mapReduce is deprecated. Use the aggregation pipeline instead. Visit https://docs.mongodb.com/manual/reference/map-reduce-to-aggregation-pipeline for more information on how to translate map-reduce operations to the aggregation pipeline.' - ); - if ('function' === typeof options) (callback = options), (options = {}); - // Out must always be defined (make sure we don't break weirdly on pre 1.8+ servers) - // TODO NODE-3339: Figure out if this is still necessary given we no longer officially support pre-1.8 - if (options?.out == null) { - throw new MongoInvalidArgumentError( - 'Option "out" must be defined, see mongodb docs for possible values' - ); - } - - if ('function' === typeof map) { - map = map.toString(); - } - - if ('function' === typeof reduce) { - reduce = reduce.toString(); - } - - if ('function' === typeof options.finalize) { - options.finalize = options.finalize.toString(); - } - - return executeOperation( - this.s.db.s.client, - new MapReduceOperation( - this as TODO_NODE_3286, - map, - reduce, - resolveOptions(this, options) as TODO_NODE_3286 - ), - callback - ); - } - /** * Initiate an Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order. * diff --git a/src/index.ts b/src/index.ts index a29d31089bb..0d0718b43b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -385,12 +385,6 @@ export type { export type { InsertManyResult, InsertOneOptions, InsertOneResult } from './operations/insert'; export type { CollectionInfo, ListCollectionsOptions } from './operations/list_collections'; export type { ListDatabasesOptions, ListDatabasesResult } from './operations/list_databases'; -export type { - FinalizeFunction, - MapFunction, - MapReduceOptions, - ReduceFunction -} from './operations/map_reduce'; export type { AbstractOperation, Hint, OperationOptions } from './operations/operation'; export type { ProfilingLevelOptions } from './operations/profiling_level'; export type { RemoveUserOptions } from './operations/remove_user'; diff --git a/src/operations/add_user.ts b/src/operations/add_user.ts index be5982a42f6..dceb3d25650 100644 --- a/src/operations/add_user.ts +++ b/src/operations/add_user.ts @@ -57,7 +57,7 @@ export class AddUserOperation extends CommandOperation { // Error out if digestPassword set // v5 removed the digestPassword option from AddUserOptions but we still want to throw // an error when digestPassword is provided. - if ('digestPassword' in options && options.digestPassword != null) { + if ((options as any).digestPassword != null) { return callback( new MongoInvalidArgumentError( 'Option "digestPassword" not supported via addUser, use db.command(...) instead' diff --git a/src/operations/map_reduce.ts b/src/operations/map_reduce.ts deleted file mode 100644 index f3134f45adb..00000000000 --- a/src/operations/map_reduce.ts +++ /dev/null @@ -1,250 +0,0 @@ -import type { ObjectId } from '../bson'; -import { Code, Document } from '../bson'; -import type { Collection } from '../collection'; -import { MongoCompatibilityError, MongoServerError } from '../error'; -import { ReadPreference, ReadPreferenceMode } from '../read_preference'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; -import type { Sort } from '../sort'; -import { - applyWriteConcern, - Callback, - decorateWithCollation, - decorateWithReadConcern, - isObject, - maxWireVersion -} from '../utils'; -import { CommandOperation, CommandOperationOptions } from './command'; -import { Aspect, defineAspects } from './operation'; - -const exclusionList = [ - 'explain', - 'readPreference', - 'readConcern', - 'session', - 'bypassDocumentValidation', - 'writeConcern', - 'raw', - 'fieldsAsRaw', - 'promoteLongs', - 'promoteValues', - 'promoteBuffers', - 'bsonRegExp', - 'serializeFunctions', - 'ignoreUndefined', - 'enableUtf8Validation', - 'scope' // this option is reformatted thus exclude the original -]; - -/** @public */ -export type MapFunction = (this: TSchema) => void; -/** @public */ -export type ReduceFunction = (key: TKey, values: TValue[]) => TValue; -/** @public */ -export type FinalizeFunction = ( - key: TKey, - reducedValue: TValue -) => TValue; - -/** @public */ -export interface MapReduceOptions - extends CommandOperationOptions { - /** Sets the output target for the map reduce job. */ - out?: 'inline' | { inline: 1 } | { replace: string } | { merge: string } | { reduce: string }; - /** Query filter object. */ - query?: Document; - /** Sorts the input objects using this key. Useful for optimization, like sorting by the emit key for fewer reduces. */ - sort?: Sort; - /** Number of objects to return from collection. */ - limit?: number; - /** Keep temporary data. */ - keeptemp?: boolean; - /** Finalize function. */ - finalize?: FinalizeFunction | string; - /** Can pass in variables that can be access from map/reduce/finalize. */ - scope?: Document; - /** It is possible to make the execution stay in JS. Provided in MongoDB \> 2.0.X. */ - jsMode?: boolean; - /** Provide statistics on job execution time. */ - verbose?: boolean; - /** Allow driver to bypass schema validation in MongoDB 3.2 or higher. */ - bypassDocumentValidation?: boolean; -} - -interface MapReduceStats { - processtime?: number; - counts?: number; - timing?: number; -} - -/** - * Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection. - * @internal - */ -export class MapReduceOperation extends CommandOperation { - override options: MapReduceOptions; - collection: Collection; - /** The mapping function. */ - map: MapFunction | string; - /** The reduce function. */ - reduce: ReduceFunction | string; - - /** - * Constructs a MapReduce operation. - * - * @param collection - Collection instance. - * @param map - The mapping function. - * @param reduce - The reduce function. - * @param options - Optional settings. See Collection.prototype.mapReduce for a list of options. - */ - constructor( - collection: Collection, - map: MapFunction | string, - reduce: ReduceFunction | string, - options?: MapReduceOptions - ) { - super(collection, options); - - this.options = options ?? {}; - this.collection = collection; - this.map = map; - this.reduce = reduce; - } - - override execute( - server: Server, - session: ClientSession | undefined, - callback: Callback - ): void { - const coll = this.collection; - const map = this.map; - const reduce = this.reduce; - let options = this.options; - - const mapCommandHash: Document = { - mapReduce: coll.collectionName, - map: map, - reduce: reduce - }; - - if (options.scope) { - mapCommandHash.scope = processScope(options.scope); - } - - // Add any other options passed in - for (const n in options) { - // Only include if not in exclusion list - if (exclusionList.indexOf(n) === -1) { - mapCommandHash[n] = (options as any)[n]; - } - } - - options = Object.assign({}, options); - - // If we have a read preference and inline is not set as output fail hard - if ( - this.readPreference.mode === ReadPreferenceMode.primary && - options.out && - (options.out as any).inline !== 1 && - options.out !== 'inline' - ) { - // Force readPreference to primary - options.readPreference = ReadPreference.primary; - // Decorate command with writeConcern if supported - applyWriteConcern(mapCommandHash, { db: coll.s.db, collection: coll }, options); - } else { - decorateWithReadConcern(mapCommandHash, coll, options); - } - - // Is bypassDocumentValidation specified - if (options.bypassDocumentValidation === true) { - mapCommandHash.bypassDocumentValidation = options.bypassDocumentValidation; - } - - // Have we specified collation - try { - decorateWithCollation(mapCommandHash, coll, options); - } catch (err) { - return callback(err); - } - - if (this.explain && maxWireVersion(server) < 9) { - callback( - new MongoCompatibilityError(`Server ${server.name} does not support explain on mapReduce`) - ); - return; - } - - // Execute command - super.executeCommand(server, session, mapCommandHash, (err, result) => { - if (err) return callback(err); - // Check if we have an error - if (1 !== result.ok || result.err || result.errmsg) { - return callback(new MongoServerError(result)); - } - - // If an explain option was executed, don't process the server results - if (this.explain) return callback(undefined, result); - - // Create statistics value - const stats: MapReduceStats = {}; - if (result.timeMillis) stats['processtime'] = result.timeMillis; - if (result.counts) stats['counts'] = result.counts; - if (result.timing) stats['timing'] = result.timing; - - // invoked with inline? - if (result.results) { - // If we wish for no verbosity - if (options['verbose'] == null || !options['verbose']) { - return callback(undefined, result.results); - } - - return callback(undefined, { results: result.results, stats: stats }); - } - - // The returned collection - let collection = null; - - // If we have an object it's a different db - if (result.result != null && typeof result.result === 'object') { - const doc = result.result; - // Return a collection from another db - collection = coll.s.db.s.client.db(doc.db, coll.s.db.s.options).collection(doc.collection); - } else { - // Create a collection object that wraps the result collection - collection = coll.s.db.collection(result.result); - } - - // If we wish for no verbosity - if (options['verbose'] == null || !options['verbose']) { - return callback(err, collection); - } - - // Return stats as third set of values - callback(err, { collection, stats }); - }); - } -} - -/** Functions that are passed as scope args must be converted to Code instances. */ -function processScope(scope: Document | ObjectId) { - if (!isObject(scope) || (scope as any)._bsontype === 'ObjectID') { - return scope; - } - - const newScope: Document = {}; - - for (const key of Object.keys(scope)) { - if ('function' === typeof (scope as Document)[key]) { - newScope[key] = new Code(String((scope as Document)[key])); - } else if ((scope as Document)[key]._bsontype === 'Code') { - newScope[key] = (scope as Document)[key]; - } else { - newScope[key] = processScope((scope as Document)[key]); - } - } - - return newScope; -} - -defineAspects(MapReduceOperation, [Aspect.EXPLAINABLE]); diff --git a/src/sessions.ts b/src/sessions.ts index 1774f7c7806..7bc090c72d1 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -1007,7 +1007,7 @@ export function applySession( if ( session.supports.causalConsistency && session.operationTime && - commandSupportsReadConcern(command, options) + commandSupportsReadConcern(command) ) { command.readConcern = command.readConcern || {}; Object.assign(command.readConcern, { afterClusterTime: session.operationTime }); diff --git a/src/utils.ts b/src/utils.ts index f6027c8e58b..dbb376efb65 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1328,21 +1328,8 @@ export function shuffle(sequence: Iterable, limit = 0): Array { // TODO: this should be codified in command construction // @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.rst#read-concern -export function commandSupportsReadConcern(command: Document, options?: Document): boolean { - if (command.aggregate || command.count || command.distinct || command.find || command.geoNear) { - return true; - } - - if ( - command.mapReduce && - options && - options.out && - (options.out.inline === 1 || options.out === 'inline') - ) { - return true; - } - - return false; +export function commandSupportsReadConcern(command: Document): boolean { + return command.aggregate || command.count || command.distinct || command.find || command.geoNear; } /** A utility function to get the instance of mongodb-client-encryption, if it exists. */ diff --git a/test/integration/client-side-encryption/client_side_encryption.spec.test.ts b/test/integration/client-side-encryption/client_side_encryption.spec.test.ts index f727e975462..292355c9f50 100644 --- a/test/integration/client-side-encryption/client_side_encryption.spec.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.spec.test.ts @@ -61,7 +61,11 @@ const skippedAuthTests = [ const skippedNoAuthTests = ['getMore with encryption', 'operation fails with maxWireVersion < 8']; const SKIPPED_TESTS = new Set([ - ...(isAuthEnabled ? skippedAuthTests.concat(skippedNoAuthTests) : skippedNoAuthTests) + ...(isAuthEnabled ? skippedAuthTests.concat(skippedNoAuthTests) : skippedNoAuthTests), + [ + // the node driver does not have a mapReduce helper + 'mapReduce deterministic encryption (unsupported)' + ] ]); const isServerless = !!process.env.SERVERLESS; diff --git a/test/integration/crud/document_validation.test.js b/test/integration/crud/document_validation.test.js index 13157aefd3c..e17f93d8225 100644 --- a/test/integration/crud/document_validation.test.js +++ b/test/integration/crud/document_validation.test.js @@ -342,69 +342,4 @@ describe('Document Validation', function () { }); } }); - - it('should correctly bypass validation for mapReduce using out', { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { - requires: { - mongodb: '>=3.1.7', - topology: ['single', 'replicaset', 'sharded'] - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - - // Get collection - var col = db.collection('createValidationCollectionOut'); - - // Drop the collection - col.drop(function () { - // Create a collection with a validator - db.createCollection( - 'createValidationCollectionOut', - { validator: { a: { $exists: true } } }, - function (err) { - expect(err).to.not.exist; - - // Get write concern - var writeConcern = configuration.writeConcernMax(); - writeConcern.bypassDocumentValidation = true; - - // Insert documents - col.insertMany( - [{ user_id: 1 }, { user_id: 2 }], - { bypassDocumentValidation: true }, - function (err) { - expect(err).to.not.exist; - - // String functions - var map = 'function() { emit(this.user_id, 1); }'; - var reduce = 'function(k,vals) { return 1; }'; - - col.mapReduce( - map, - reduce, - { - out: { replace: 'createValidationCollectionOut' }, - bypassDocumentValidation: true - }, - function (err) { - expect(err).to.not.exist; - - client.close(done); - } - ); - } - ); - } - ); - }); - }); - } - }); }); diff --git a/test/integration/crud/explain.test.js b/test/integration/crud/explain.test.js index f250ed5bf99..2496ddf118a 100644 --- a/test/integration/crud/explain.test.js +++ b/test/integration/crud/explain.test.js @@ -222,38 +222,6 @@ describe('Explain', function () { } }); - it('should honor boolean explain with mapReduce', { - metadata: { - requires: { - mongodb: '>=4.4' - } - }, - test: function (done) { - var db = client.db('shouldHonorBooleanExplainWithMapReduce'); - var collection = db.collection('test'); - - collection.insertMany([{ user_id: 1 }, { user_id: 2 }], (err, res) => { - expect(err).to.not.exist; - expect(res).to.exist; - - var map = 'function() { emit(this.user_id, 1); }'; - var reduce = 'function(k,vals) { return 1; }'; - - collection.mapReduce( - map, - reduce, - { out: { replace: 'tempCollection' }, explain: true }, - (err, explanation) => { - expect(err).to.not.exist; - expect(explanation).to.exist; - expect(explanation).property('stages').to.exist; - done(); - } - ); - }); - } - }); - it('should use allPlansExecution as true explain verbosity', { metadata: { requires: { @@ -439,38 +407,6 @@ describe('Explain', function () { } }); - it('should honor string explain with mapReduce', { - metadata: { - requires: { - mongodb: '>=4.4' - } - }, - test: function (done) { - var db = client.db('shouldHonorStringExplainWithMapReduce'); - var collection = db.collection('test'); - - collection.insertMany([{ user_id: 1 }, { user_id: 2 }], (err, res) => { - expect(err).to.not.exist; - expect(res).to.exist; - - var map = 'function() { emit(this.user_id, 1); }'; - var reduce = 'function(k,vals) { return 1; }'; - - collection.mapReduce( - map, - reduce, - { out: { replace: 'tempCollection' }, explain: 'executionStats' }, - (err, explanation) => { - expect(err).to.not.exist; - expect(explanation).to.exist; - expect(explanation).property('stages').to.exist; - done(); - } - ); - }); - } - }); - it('should honor boolean explain with find', async () => { const db = client.db('shouldHonorBooleanExplainWithFind'); const collection = db.collection('test'); diff --git a/test/integration/node-specific/auto_connect.test.ts b/test/integration/node-specific/auto_connect.test.ts index 74d55208f83..d15e9df3f0c 100644 --- a/test/integration/node-specific/auto_connect.test.ts +++ b/test/integration/node-specific/auto_connect.test.ts @@ -515,24 +515,6 @@ describe('When executing an operation for the first time', () => { }); }); - describe(`#mapReduce()`, () => { - it('should connect the client', async () => { - const c = client.db().collection('test'); - await c.mapReduce( - function () { - // @ts-expect-error: mapReduce is deprecated - emit(this.a, [0]); - }, - function (a, b) { - // @ts-expect-error: mapReduce is deprecated - return Array.sum(b); - }, - { out: 'inline' } - ); - expect(client).to.have.property('topology').that.is.instanceOf(Topology); - }); - }); - describe(`#options()`, () => { it('should connect the client', async () => { const c = client.db().collection('test'); diff --git a/test/integration/node-specific/mapreduce.test.js b/test/integration/node-specific/mapreduce.test.js deleted file mode 100644 index 5f95e4a013e..00000000000 --- a/test/integration/node-specific/mapreduce.test.js +++ /dev/null @@ -1,392 +0,0 @@ -'use strict'; -const { assert: test, setupDatabase } = require('../shared'); -const { Code } = require('../../../src'); -const { expect } = require('chai'); - -describe('MapReduce', function () { - before(function () { - return setupDatabase(this.configuration, ['outputCollectionDb']); - }); - - /** - * Mapreduce tests - */ - it('shouldPerformMapReduceWithStringFunctions', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.createCollection('test_map_reduce', function (err, collection) { - collection.insert( - [{ user_id: 1 }, { user_id: 2 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - - // String functions - var map = 'function() { emit(this.user_id, 1); }'; - var reduce = 'function(k,vals) { return 1; }'; - - collection.mapReduce( - map, - reduce, - { out: { replace: 'tempCollection' } }, - function (err, collection) { - collection.findOne({ _id: 1 }, function (err, result) { - test.equal(1, result.value); - - collection.findOne({ _id: 2 }, function (err, result) { - test.equal(1, result.value); - client.close(done); - }); - }); - } - ); - } - ); - }); - }); - } - }); - - /** - * Mapreduce tests - */ - it('shouldForceMapReduceError', { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { - requires: { - mongodb: '>1.7.6', - topology: ['single', 'replicaset', 'sharded'] - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - expect(err).to.not.exist; - var db = client.db(configuration.db); - db.createCollection('should_force_map_reduce_error', function (err, collection) { - collection.insert( - [{ user_id: 1 }, { user_id: 2 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - // String functions - var map = 'function() { emiddft(this.user_id, 1); }'; - var reduce = 'function(k,vals) { return 1; }'; - - collection.mapReduce(map, reduce, { out: { inline: 1 } }, function (err) { - test.ok(err != null); - client.close(done); - }); - } - ); - }); - }); - } - }); - - it('shouldPerformMapReduceWithParametersBeingFunctions', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.createCollection( - 'test_map_reduce_with_functions_as_arguments', - function (err, collection) { - expect(err).to.not.exist; - collection.insert( - [{ user_id: 1 }, { user_id: 2 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - - // String functions - var map = function () { - emit(this.user_id, 1); // eslint-disable-line - }; - var reduce = function () { - return 1; - }; - - collection.mapReduce( - map, - reduce, - { out: { replace: 'tempCollection' } }, - function (err, collection) { - collection.findOne({ _id: 1 }, function (err, result) { - test.equal(1, result.value); - - collection.findOne({ _id: 2 }, function (err, result) { - test.equal(1, result.value); - client.close(done); - }); - }); - } - ); - } - ); - } - ); - }); - } - }); - - it('shouldPerformMapReduceWithCodeObjects', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.createCollection('test_map_reduce_with_code_objects', function (err, collection) { - collection.insert( - [{ user_id: 1 }, { user_id: 2 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - // String functions - var map = new Code('function() { emit(this.user_id, 1); }'); - var reduce = new Code('function(k,vals) { return 1; }'); - - collection.mapReduce( - map, - reduce, - { out: { replace: 'tempCollection' } }, - function (err, collection) { - collection.findOne({ _id: 1 }, function (err, result) { - test.equal(1, result.value); - - collection.findOne({ _id: 2 }, function (err, result) { - test.equal(1, result.value); - client.close(done); - }); - }); - } - ); - } - ); - }); - }); - } - }); - - it('shouldPerformMapReduceWithOptions', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.createCollection('test_map_reduce_with_options', function (err, collection) { - collection.insert( - [{ user_id: 1 }, { user_id: 2 }, { user_id: 3 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - // String functions - var map = new Code('function() { emit(this.user_id, 1); }'); - var reduce = new Code('function(k,vals) { return 1; }'); - - collection.mapReduce( - map, - reduce, - { out: { replace: 'tempCollection' }, query: { user_id: { $gt: 1 } } }, - function (err, collection) { - collection.count(function (err, count) { - test.equal(2, count); - - collection.findOne({ _id: 2 }, function (err, result) { - test.equal(1, result.value); - - collection.findOne({ _id: 3 }, function (err, result) { - test.equal(1, result.value); - client.close(done); - }); - }); - }); - } - ); - } - ); - }); - }); - } - }); - - it('shouldHandleMapReduceErrors', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.createCollection('test_map_reduce_error', function (err, collection) { - collection.insert( - [{ user_id: 1 }, { user_id: 2 }, { user_id: 3 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - // String functions - var map = new Code("function() { throw 'error'; }"); - var reduce = new Code("function(k,vals) { throw 'error'; }"); - - collection.mapReduce( - map, - reduce, - { out: { inline: 1 }, query: { user_id: { $gt: 1 } } }, - function (err) { - test.ok(err != null); - client.close(done); - } - ); - } - ); - }); - }); - } - }); - - it('shouldSaveDataToDifferentDbFromMapreduce', { - metadata: { - requires: { - topology: ['single', 'replicaset', 'sharded'], - mongodb: '>= 3.4' - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - const outDb = client.db('outputCollectionDb'); - - // Create a test collection - db.createCollection('test_map_reduce_functions', function (err, collection) { - // create the output collection - outDb.createCollection('tempCollection', err => { - expect(err).to.not.exist; - - // Insert some documents to perform map reduce over - collection.insert( - [{ user_id: 1 }, { user_id: 2 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - // Map function - var map = function () { - emit(this.user_id, 1); // eslint-disable-line - }; - // Reduce function - var reduce = function () { - return 1; - }; - - // Perform the map reduce - collection.mapReduce( - map, - reduce, - { out: { replace: 'test_map_reduce_functions_temp', db: 'outputCollectionDb' } }, - function (err, collection) { - expect(err).to.not.exist; - - // Mapreduce returns the temporary collection with the results - collection.findOne({ _id: 1 }, function (err, result) { - expect(err).to.not.exist; - test.equal(1, result.value); - - collection.findOne({ _id: 2 }, function (err, result) { - expect(err).to.not.exist; - test.equal(1, result.value); - - client.close(done); - }); - }); - } - ); - } - ); - }); - }); - }); - } - }); - - /** - * Mapreduce tests - */ - it.skip('shouldPerformMapReduceWithScopeContainingFunction', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded'] } - }, - - test: function (done) { - var util = { - times_one_hundred: function (x) { - return x * 100; - } - }; - - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.createCollection('test_map_reduce', function (err, collection) { - collection.insert( - [{ user_id: 1 }, { user_id: 2 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - // String functions - var map = 'function() { emit(this.user_id, util.times_one_hundred(this.user_id)); }'; - var reduce = 'function(k,vals) { return vals[0]; }'; - - // Before MapReduce - test.equal(200, util.times_one_hundred(2)); - - collection.mapReduce( - map, - reduce, - { scope: { util: util }, out: { replace: 'test_map_reduce_temp' } }, - function (err, collection) { - // After MapReduce - test.equal(200, util.times_one_hundred(2)); - - collection.findOne({ _id: 2 }, function (err, result) { - // During MapReduce - test.equal(200, result.value); - - client.close(done); - }); - } - ); - } - ); - }); - }); - } - }); -}); diff --git a/test/integration/node-specific/operation_examples.test.ts b/test/integration/node-specific/operation_examples.test.ts index 59667ec385a..cae2230f43e 100644 --- a/test/integration/node-specific/operation_examples.test.ts +++ b/test/integration/node-specific/operation_examples.test.ts @@ -1080,325 +1080,6 @@ describe('Operations', function () { } }); - /** - * A simple map reduce example using a Promise. - * - * example-class Collection - * example-method mapReduce - */ - it('shouldPerformSimpleMapReduceFunctionsWithPromises', { - metadata: { requires: { topology: ['single'] } }, - - test: function () { - const configuration = this.configuration; - const client = configuration.newClient({ w: 0 }, { maxPoolSize: 1 }); - - /* eslint-disable */ - - return client.connect().then(function (client) { - var db = client.db(configuration.db); - // LINE var MongoClient = require('mongodb').MongoClient, - // LINE test = require('assert'); - // LINE const client = new MongoClient('mongodb://localhost:27017/test'); - // LINE client.connect().then(() => { - // LINE var db = client.db('test); - // REPLACE configuration.writeConcernMax() WITH {w:1} - // REMOVE-LINE done(); - // BEGIN - - // Create a test collection - var collection = db.collection('test_map_reduce_functions_with_promise'); - - // Insert some documents to perform map reduce over - return collection - .insertMany([{ user_id: 1 }, { user_id: 2 }], { writeConcern: { w: 1 } }) - .then(function () { - // Map function - var map = function () { - emit(this.user_id, 1); - }; - - // Reduce function - var reduce = function (k, vals) { - return 1; - }; - - // Perform the map reduce - return collection.mapReduce(map, reduce, { out: { replace: 'tempCollection' } }); - }) - .then(function (reducedCollection) { - // Mapreduce returns the temporary collection with the results - return reducedCollection.findOne({ _id: 1 }).then(function (result) { - expect(result.value).to.equal(1); - return reducedCollection; - }); - }) - .then(function (reducedCollection) { - return reducedCollection.findOne({ _id: 2 }); - }) - .then(function (result) { - expect(result.value).to.equal(1); - return client.close(); - }); - }); - // END - - /* eslint-enable */ - } - }); - - /** - * A simple map reduce example using the inline output type on MongoDB gte 1.7.6 returning the statistics using a Promise. - * - * example-class Collection - * example-method mapReduce - */ - it('shouldPerformMapReduceFunctionInlineWithPromises', { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { requires: { mongodb: '>1.7.6', topology: ['single'] } }, - - test: function () { - const configuration = this.configuration; - const client = configuration.newClient({ w: 0 }, { maxPoolSize: 1 }); - - return client.connect().then(function (client) { - const db = client.db(configuration.db); - // LINE var MongoClient = require('mongodb').MongoClient, - // LINE test = require('assert'); - // LINE const client = new MongoClient('mongodb://localhost:27017/test'); - // LINE client.connect().then(() => { - // LINE var db = client.db('test); - // REPLACE configuration.writeConcernMax() WITH {w:1} - // REMOVE-LINE done(); - // BEGIN - - // Create a test collection - const collection = db.collection('test_map_reduce_functions_inline_with_promise'); - - /* eslint-disable */ - // Map function - var map = function () { - emit(this.user_id, 1); - }; - - // Reduce function - var reduce = function (k, vals) { - return 1; - }; - /* eslint-enable */ - - // Insert some test documents - return collection - .insertMany([{ user_id: 1 }, { user_id: 2 }], { writeConcern: { w: 1 } }) - .then(function () { - // Execute map reduce and return results inline - return collection.mapReduce(map, reduce, { out: { inline: 1 }, verbose: true }); - }) - .then(function (result) { - expect(result.results.length).to.equal(2); - expect(result.stats != null).to.exist; - - return collection.mapReduce(map, reduce, { - out: { replace: 'mapreduce_integration_test' }, - verbose: true - }); - }) - .then(function (result) { - expect(result.stats != null).to.exist; - return client.close(); - }); - }); - // END - } - }); - - /** - * Mapreduce using a provided scope containing a javascript function executed using a Promise. - * - * example-class Collection - * example-method mapReduce - */ - it('shouldPerformMapReduceWithContextWithPromises', { - metadata: { requires: { topology: ['single'] } }, - - test: function () { - const configuration = this.configuration; - const client = configuration.newClient({ maxPoolSize: 1 }); - - return client.connect().then(function (client) { - const db = client.db(configuration.db); - // LINE var MongoClient = require('mongodb').MongoClient, - // LINE Code = require('mongodb').Code, - // LINE test = require('assert'); - // LINE const client = new MongoClient('mongodb://localhost:27017/test'); - // LINE client.connect().then(() => { - // LINE var db = client.db('test); - // REPLACE configuration.writeConcernMax() WITH {w:1} - // REMOVE-LINE done(); - // BEGIN - - // Create a test collection - const collection = db.collection('test_map_reduce_functions_scope_with_promise'); - - /* eslint-disable */ - // Map function - var map = function () { - emit(fn(this.timestamp.getYear()), 1); - }; - - // Reduce function - var reduce = function (k, v) { - var count = 0; - for (var i = 0; i < v.length; i++) { - count += v[i]; - } - return count; - }; - - // Javascript function available in the map reduce scope - var t = function (val) { - return val + 1; - }; - - // Execute the map reduce with the custom scope - var o = {}; - o.scope = { fn: new Code(t.toString()) }; - o.out = { replace: 'replacethiscollection' }; - /* eslint-enable */ - - // Insert some test documents - return collection - .insertMany( - [ - { user_id: 1, timestamp: new Date() }, - { user_id: 2, timestamp: new Date() } - ], - { writeConcern: { w: 1 } } - ) - .then(function () { - return collection.mapReduce(map, reduce, o); - }) - .then(function (outCollection) { - // Find all entries in the map-reduce collection - return outCollection.find().toArray(); - }) - .then(function (results) { - expect(results[0].value).to.equal(2); - - // mapReduce with scope containing plain function - const o = {}; - o.scope = { fn: t }; - o.out = { replace: 'replacethiscollection' }; - - return collection.mapReduce(map, reduce, o); - }) - .then(function (outCollection) { - // Find all entries in the map-reduce collection - return outCollection.find().toArray(); - }) - .then(function (results) { - expect(results[0].value).to.equal(2); - return client.close(); - }); - }); - // END - } - }); - - /** - * Mapreduce using a scope containing javascript objects with functions using a Promise. - * - * example-class Collection - * example-method mapReduce - */ - it.skip('shouldPerformMapReduceInContextObjectsWithPromises', { - metadata: { requires: { topology: ['single'] } }, - - test: function () { - const configuration = this.configuration; - const client = configuration.newClient({ w: 0 }, { maxPoolSize: 1 }); - - return client.connect().then(function (client) { - const db = client.db(configuration.db); - // LINE var MongoClient = require('mongodb').MongoClient, - // LINE Code = require('mongodb').Code, - // LINE test = require('assert'); - // LINE const client = new MongoClient('mongodb://localhost:27017/test'); - // LINE client.connect().then(() => { - // LINE var db = client.db('test); - // REPLACE configuration.writeConcernMax() WITH {w:1} - // REMOVE-LINE done(); - // BEGIN - - // Create a test collection - const collection = db.collection('test_map_reduce_functions_scope_objects_with_promise'); - - /* eslint-disable */ - // Map function - var map = function () { - emit(obj.fn(this.timestamp.getYear()), 1); - }; - - // Reduce function - var reduce = function (k, v) { - var count = 0; - for (var i = 0; i < v.length; i++) { - count += v[i]; - } - return count; - }; - - // Javascript function available in the map reduce scope - var t = function (val) { - return val + 1; - }; - - // Execute the map reduce with the custom scope containing objects - var o = {}; - o.scope = { obj: { fn: new Code(t.toString()) } }; - o.out = { replace: 'replacethiscollection' }; - /* eslint-enable */ - - // Insert some test documents - return collection - .insertMany( - [ - { user_id: 1, timestamp: new Date() }, - { user_id: 2, timestamp: new Date() } - ], - { writeConcern: { w: 1 } } - ) - .then(function () { - return collection.mapReduce(map, reduce, o); - }) - .then(function (outCollection) { - // Find all entries in the map-reduce collection - return outCollection.find().toArray(); - }) - .then(function (results) { - expect(results[0].value).to.equal(2); - - // mapReduce with scope containing plain function - const o = {}; - o.scope = { obj: { fn: t } }; - o.out = { replace: 'replacethiscollection' }; - - return collection.mapReduce(map, reduce, o); - }) - .then(function (outCollection) { - // Find all entries in the map-reduce collection - return outCollection.find().toArray(); - }) - .then(function (results) { - expect(results[0].value).to.equal(2); - return client.close(); - }); - }); - // END - } - }); - /** * Example of retrieving a collections indexes using a Promise. * diff --git a/test/integration/read-write-concern/read_write_concern.spec.test.js b/test/integration/read-write-concern/read_write_concern.spec.test.js index 0cb09cc1a74..948a7bbc7db 100644 --- a/test/integration/read-write-concern/read_write_concern.spec.test.js +++ b/test/integration/read-write-concern/read_write_concern.spec.test.js @@ -13,6 +13,12 @@ describe('Read Write Concern spec tests', function () { return testContext.setup(this.configuration); }); - generateTopologyTests(testSuites, testContext); + generateTopologyTests(testSuites, testContext, ({ description }) => { + if (description === 'MapReduce omits default write concern') { + // The node driver does not have a mapReduce collection helper + return false; + } + return true; + }); }); }); diff --git a/test/integration/read-write-concern/readconcern.test.js b/test/integration/read-write-concern/readconcern.test.js index fa42a47b192..081fafdbfea 100644 --- a/test/integration/read-write-concern/readconcern.test.js +++ b/test/integration/read-write-concern/readconcern.test.js @@ -343,51 +343,6 @@ describe('ReadConcern', function () { } }); - it('Should set majority readConcern mapReduce command but be ignored', { - metadata: { requires: { topology: 'replicaset', mongodb: '>= 3.2' } }, - - test: function (done) { - const started = []; - const succeeded = []; - // Get a new instance - const configuration = this.configuration; - client = configuration.newClient( - { w: 1 }, - { maxPoolSize: 1, readConcern: { level: 'majority' }, monitorCommands: true } - ); - - client.connect((err, client) => { - expect(err).to.not.exist; - - const db = client.db(configuration.db); - expect(db.readConcern).to.deep.equal({ level: 'majority' }); - - // Get the collection - const collection = db.collection('test_map_reduce_read_concern'); - collection.insertMany( - [{ user_id: 1 }, { user_id: 2 }], - configuration.writeConcernMax(), - err => { - expect(err).to.not.exist; - // String functions - const map = 'function() { emit(this.user_id, 1); }'; - const reduce = 'function(k,vals) { return 1; }'; - - // Listen to apm events - client.on('commandStarted', filterForCommands('mapReduce', started)); - client.on('commandSucceeded', filterForCommands('mapReduce', succeeded)); - - // Execute mapReduce - collection.mapReduce(map, reduce, { out: { replace: 'tempCollection' } }, err => { - expect(err).to.not.exist; - validateTestResults(started, succeeded, 'mapReduce'); - done(); - }); - } - ); - }); - } - }); it('Should set local readConcern on db level when using createCollection method', { metadata: { requires: { topology: 'replicaset', mongodb: '>= 3.2' } }, diff --git a/test/integration/server-selection/readpreference.test.js b/test/integration/server-selection/readpreference.test.js index 1dc1d1a260f..d5667a1fc42 100644 --- a/test/integration/server-selection/readpreference.test.js +++ b/test/integration/server-selection/readpreference.test.js @@ -59,132 +59,6 @@ describe('ReadPreference', function () { } }); - it('Should correctly apply collection level read Preference to mapReduce', { - metadata: { requires: { mongodb: '>=2.6.0', topology: ['single', 'ssl'] } }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - expect(err).to.not.exist; - // Set read preference - var collection = db.collection('read_pref_1', { - readPreference: ReadPreference.SECONDARY_PREFERRED - }); - // Save checkout function - var command = client.topology.command; - // Set up our checker method - client.topology.command = function () { - var args = Array.prototype.slice.call(arguments, 0); - if (args[0] === 'integration_tests.$cmd') { - test.equal(ReadPreference.SECONDARY_PREFERRED, args[2].readPreference.mode); - } - - return command.apply(db.s.topology, args); - }; - - // Map function - var map = function () { - emit(this.user_id, 1); // eslint-disable-line - }; - // Reduce function - var reduce = function (/* k, vals */) { - return 1; - }; - - // Perform the map reduce - collection.mapReduce(map, reduce, { out: { inline: 1 } }, function (/* err */) { - // expect(err).to.not.exist; - - client.topology.command = command; - - client.close(done); - }); - }); - } - }); - - it( - 'Should correctly apply collection level read Preference to mapReduce backward compatibility', - { - metadata: { requires: { mongodb: '>=2.6.0', topology: ['single', 'ssl'] } }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - expect(err).to.not.exist; - // Set read preference - var collection = db.collection('read_pref_1', { - readPreference: ReadPreference.SECONDARY_PREFERRED - }); - // Save checkout function - var command = client.topology.command; - // Set up our checker method - client.topology.command = function () { - var args = Array.prototype.slice.call(arguments, 0); - if (args[0] === 'integration_tests.$cmd') { - test.equal(ReadPreference.SECONDARY_PREFERRED, args[2].readPreference.mode); - } - - return command.apply(db.s.topology, args); - }; - - // Map function - var map = function () { - emit(this.user_id, 1); // eslint-disable-line - }; - - // Reduce function - var reduce = function (/* k, vals */) { - return 1; - }; - - // Perform the map reduce - collection.mapReduce(map, reduce, { out: 'inline' }, function (/* err */) { - // expect(err).to.not.exist; - client.topology.command = command; - client.close(done); - }); - }); - } - } - ); - - it('Should fail due to not using mapReduce inline with read preference', { - metadata: { requires: { mongodb: '>=2.6.0', topology: ['single', 'ssl'] } }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - expect(err).to.not.exist; - // Set read preference - var collection = db.collection('read_pref_1', { - readPreference: ReadPreference.SECONDARY_PREFERRED - }); - // Map function - var map = function () { - emit(this.user_id, 1); // eslint-disable-line - }; - - // Reduce function - var reduce = function (/* k, vals */) { - return 1; - }; - - // Perform the map reduce - collection.mapReduce(map, reduce, { out: { append: 'test' } }, function (err) { - test.notEqual(err, null); - client.close(done); - }); - }); - } - }); - it('Should correctly apply collection level read Preference to aggregate', { metadata: { requires: { mongodb: '>=2.6.0', topology: ['single', 'ssl'] } }, diff --git a/test/tools/spec-runner/index.js b/test/tools/spec-runner/index.js index 3107cdeec18..d240f377636 100644 --- a/test/tools/spec-runner/index.js +++ b/test/tools/spec-runner/index.js @@ -699,13 +699,11 @@ const kOperations = new Map([ ], [ 'mapReduce', + // eslint-disable-next-line no-unused-vars (operation, collection, context /*, options */) => { - const args = operation.arguments; - const map = args.map; - const reduce = args.reduce; - const options = { session: maybeSession(operation, context) }; - if (args.out) options.out = args.out; - return collection.mapReduce(map, reduce, options); + throw new Error( + 'The Node driver no longer supports a MapReduce helper. This operation should never be run.' + ); } ], [ @@ -815,10 +813,6 @@ function testOperation(operation, obj, context, options) { return args.unshift(operation.arguments[key]); } - if ((key === 'map' || key === 'reduce') && operationName === 'mapReduce') { - return args.unshift(operation.arguments[key]); - } - if (key === 'command') return args.unshift(operation.arguments[key]); if (key === 'requests') return args.unshift(extractBulkRequests(operation.arguments[key])); diff --git a/test/types/community/collection/mapReduce.test-d.ts b/test/types/community/collection/mapReduce.test-d.ts deleted file mode 100644 index ce097ef7049..00000000000 --- a/test/types/community/collection/mapReduce.test-d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { MongoClient } from '../../../mongodb'; - -// Declare emit function to be called inside map function -declare function emit(key: any, value: any): void; - -interface TestMapReduceSchema { - cust_id: string; - amount: number; - status: string; -} - -function testCollectionMapFunction(this: TestMapReduceSchema) { - emit(this.cust_id, this.amount); -} - -function testCollectionReduceFunction(key: string, values: any[]): number { - return values.length; -} - -const client = new MongoClient(''); -client - .db('test') - .collection('test-mapReduce-collection') - .mapReduce(testCollectionMapFunction, testCollectionReduceFunction); diff --git a/test/types/mongodb.test-d.ts b/test/types/mongodb.test-d.ts index 2987d1b8c1c..73b2b8b58b2 100644 --- a/test/types/mongodb.test-d.ts +++ b/test/types/mongodb.test-d.ts @@ -11,7 +11,6 @@ expectDeprecated(Collection.prototype.insert); expectDeprecated(Collection.prototype.update); expectDeprecated(Collection.prototype.remove); expectDeprecated(Collection.prototype.count); -expectDeprecated(Collection.prototype.mapReduce); expectDeprecated(FindCursor.prototype.count); expectNotDeprecated(MongoDBDriver.ObjectId); diff --git a/test/unit/assorted/collations.test.js b/test/unit/assorted/collations.test.js index d3cf6fb0976..5ddd2374547 100644 --- a/test/unit/assorted/collations.test.js +++ b/test/unit/assorted/collations.test.js @@ -1,7 +1,7 @@ 'use strict'; const mock = require('../../tools/mongodb-mock/index'); const { expect } = require('chai'); -const { Long, Code } = require('../../../src'); +const { Long } = require('../../../src'); const { isHello } = require('../../mongodb'); const { MongoClient } = require('../../../src'); @@ -107,42 +107,6 @@ describe('Collation', function () { }); }); - it('Successfully pass through collation to mapReduce command', () => { - const client = new MongoClient(`mongodb://${testContext.server.uri()}/test`); - const primary = [Object.assign({}, mock.HELLO)]; - - let commandResult; - testContext.server.setMessageHandler(request => { - var doc = request.document; - if (isHello(doc)) { - request.reply(primary[0]); - } else if (doc.mapReduce) { - commandResult = doc; - request.reply({ ok: 1, result: 'tempCollection' }); - } else if (doc.endSessions) { - request.reply({ ok: 1 }); - } - }); - - return client.connect().then(() => { - const db = client.db('collation_db'); - const map = new Code('function() { emit(this.user_id, 1); }'); - const reduce = new Code('function(k,vals) { return 1; }'); - - return db - .collection('test') - .mapReduce(map, reduce, { - out: { replace: 'tempCollection' }, - collation: { caseLevel: true } - }) - .then(() => { - expect(commandResult).to.have.property('collation'); - expect(commandResult.collation).to.eql({ caseLevel: true }); - return client.close(); - }); - }); - }); - it('Successfully pass through collation to remove command', () => { const client = new MongoClient(`mongodb://${testContext.server.uri()}/test`); const primary = [Object.assign({}, mock.HELLO)]; diff --git a/test/unit/assorted/write_concern.test.js b/test/unit/assorted/write_concern.test.js index dc04a1dd81d..b9f89ade8d3 100644 --- a/test/unit/assorted/write_concern.test.js +++ b/test/unit/assorted/write_concern.test.js @@ -1,7 +1,7 @@ 'use strict'; const mock = require('../../tools/mongodb-mock/index'); const { expect } = require('chai'); -const { ObjectId, Code, MongoClient } = require('../../../src'); +const { ObjectId, MongoClient } = require('../../../src'); const { LEGACY_HELLO_COMMAND } = require('../../mongodb'); const { isHello } = require('../../mongodb'); @@ -101,9 +101,6 @@ function writeConcernTest(command, testFn) { case 'aggregate': t.decorateResponse({ cursor: { id: 0, firstBatch: [], ns: 'write_concern_db' } }); break; - case 'mapReduce': - t.decorateResponse({ result: 'tempCollection' }); - break; } await t.run(command, async (client, db) => { await testFn(db, Object.assign({}, TEST_OPTIONS)); @@ -160,19 +157,6 @@ describe('Command Write Concern', function () { db.collection('test').dropIndexes(writeConcernTestOptions) )); - it('successfully pass through writeConcern to mapReduce command', () => - writeConcernTest('mapReduce', function (db, writeConcernTestOptions) { - const map = new Code('function() { emit(this.user_id, 1); }'); - const reduce = new Code('function(k,vals) { return 1; }'); - return db - .collection('test') - .mapReduce( - map, - reduce, - Object.assign({ out: { replace: 'tempCollection' } }, writeConcernTestOptions) - ); - })); - it('successfully pass through writeConcern to createUser command', () => writeConcernTest('createUser', (db, writeConcernTestOptions) => db.admin().addUser('kay:kay', 'abc123', writeConcernTestOptions) diff --git a/test/unit/collection.test.ts b/test/unit/collection.test.ts index 9a60d63cf2f..432fdd593b9 100644 --- a/test/unit/collection.test.ts +++ b/test/unit/collection.test.ts @@ -173,69 +173,6 @@ describe('Collection', function () { }); }); - context('#mapReduce', () => { - function testMapReduce(config, done) { - const client = new MongoClient(`mongodb://${server.uri()}/test`); - let close = e => { - close = () => null; - client.close(() => done(e)); - }; - - server.setMessageHandler(request => { - const doc = request.document; - if (doc.mapReduce) { - try { - expect(doc.bypassDocumentValidation).equal(config.expected); - request.reply({ - results: 't', - ok: 1 - }); - } catch (e) { - close(e); - } - } - - if (isHello(doc)) { - request.reply(Object.assign({}, HELLO)); - } else if (doc.endSessions) { - request.reply({ ok: 1 }); - } - }); - - client.connect(function (err, client) { - expect(err).to.not.exist; - const db = client.db('test'); - const collection = db.collection('test_c'); - - const options = { - out: 'test_c', - bypassDocumentValidation: config.actual - }; - - collection.mapReduce( - function map() { - return null; - }, - function reduce() { - return null; - }, - options as any, - e => { - close(e); - } - ); - }); - } - // map reduce - it('should only set bypass document validation if strictly true in mapReduce', function (done) { - testMapReduce({ expected: true, actual: true }, done); - }); - - it('should not set bypass document validation if not strictly true in mapReduce', function (done) { - testMapReduce({ expected: undefined, actual: false }, done); - }); - }); - context('#bulkWrite', () => { function testBulkWrite(config, done) { const client = new MongoClient(`mongodb://${server.uri()}/test`); From 111df067290cb56c5e29a609b27f65d1f8d2cc29 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Thu, 5 Jan 2023 16:11:17 -0500 Subject: [PATCH 2/9] chore - fix merge - to squash: --- src/operations/add_user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations/add_user.ts b/src/operations/add_user.ts index dceb3d25650..be5982a42f6 100644 --- a/src/operations/add_user.ts +++ b/src/operations/add_user.ts @@ -57,7 +57,7 @@ export class AddUserOperation extends CommandOperation { // Error out if digestPassword set // v5 removed the digestPassword option from AddUserOptions but we still want to throw // an error when digestPassword is provided. - if ((options as any).digestPassword != null) { + if ('digestPassword' in options && options.digestPassword != null) { return callback( new MongoInvalidArgumentError( 'Option "digestPassword" not supported via addUser, use db.command(...) instead' From 1f191bd73162dd8447f5ed9b59885a92509e14f9 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Thu, 5 Jan 2023 16:29:23 -0500 Subject: [PATCH 3/9] chore: add doc to filter predicate --- test/tools/spec-runner/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/tools/spec-runner/index.js b/test/tools/spec-runner/index.js index d240f377636..42d979bed36 100644 --- a/test/tools/spec-runner/index.js +++ b/test/tools/spec-runner/index.js @@ -141,6 +141,9 @@ function legacyRunOnToRunOnRequirement(runOn) { return runOnRequirement; } +/** + * @param {((suite: Mocha.Test) => boolean)?} filter a function that returns true for any tests that should run, false otherwise. + */ function generateTopologyTests(testSuites, testContext, filter) { for (const testSuite of testSuites) { let runOn = testSuite.runOn; From 1952d16ff7cc295a41059df902b48197f5b97a25 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Thu, 5 Jan 2023 16:45:13 -0500 Subject: [PATCH 4/9] chore: skip csfle test --- .../client-side-encryption/client_side_encryption.spec.test.ts | 2 +- test/tools/spec-runner/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/client-side-encryption/client_side_encryption.spec.test.ts b/test/integration/client-side-encryption/client_side_encryption.spec.test.ts index 292355c9f50..ab57182b38b 100644 --- a/test/integration/client-side-encryption/client_side_encryption.spec.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.spec.test.ts @@ -62,7 +62,7 @@ const skippedNoAuthTests = ['getMore with encryption', 'operation fails with max const SKIPPED_TESTS = new Set([ ...(isAuthEnabled ? skippedAuthTests.concat(skippedNoAuthTests) : skippedNoAuthTests), - [ + ...[ // the node driver does not have a mapReduce helper 'mapReduce deterministic encryption (unsupported)' ] diff --git a/test/tools/spec-runner/index.js b/test/tools/spec-runner/index.js index 42d979bed36..9cb09bafe50 100644 --- a/test/tools/spec-runner/index.js +++ b/test/tools/spec-runner/index.js @@ -142,7 +142,7 @@ function legacyRunOnToRunOnRequirement(runOn) { } /** - * @param {((suite: Mocha.Test) => boolean)?} filter a function that returns true for any tests that should run, false otherwise. + * @param {((test: { description: string }) => boolean)?} filter a function that returns true for any tests that should run, false otherwise. */ function generateTopologyTests(testSuites, testContext, filter) { for (const testSuite of testSuites) { From 90825f72c95b845deebd3f86461bfa1286a54dad Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Thu, 5 Jan 2023 16:49:13 -0500 Subject: [PATCH 5/9] docs: add upgrade notes for mapReduce --- etc/notes/CHANGES_5.0.0.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/etc/notes/CHANGES_5.0.0.md b/etc/notes/CHANGES_5.0.0.md index 11fb49d7a50..56d7abf4df9 100644 --- a/etc/notes/CHANGES_5.0.0.md +++ b/etc/notes/CHANGES_5.0.0.md @@ -16,6 +16,15 @@ The following is a detailed collection of the changes in the major v5 release of ## Changes +### `Collection.mapReduce()` helper removed + +The `mapReduce` helper has been removed from the `Collection` class. The `mapReduce` operation has been +deprecated in favor of the aggregation pipeline since MongoDB server version 5.0. It is recommended +to migrate code that uses `Collection.mapReduce` to use the aggregation pipeline. + +If the `mapReduce` command must be used, the `Db.command()` helper can be used to run the raw +`mapReduce` command. + ### `AddUserOptions.digestPassword` removed The `digestPassword` option has been removed from the add user helper. From a246179b842fa052774d610fffa1dc6c8f606454 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Fri, 6 Jan 2023 13:28:28 -0500 Subject: [PATCH 6/9] fix: add back read concern application logic --- etc/notes/CHANGES_5.0.0.md | 15 +++++++++++++++ src/sessions.ts | 2 +- src/utils.ts | 19 ++++++++++++++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/etc/notes/CHANGES_5.0.0.md b/etc/notes/CHANGES_5.0.0.md index 56d7abf4df9..77ddfc87925 100644 --- a/etc/notes/CHANGES_5.0.0.md +++ b/etc/notes/CHANGES_5.0.0.md @@ -25,6 +25,21 @@ to migrate code that uses `Collection.mapReduce` to use the aggregation pipeline If the `mapReduce` command must be used, the `Db.command()` helper can be used to run the raw `mapReduce` command. +```typescript +const command = { + mapReduce: 'my-collection', + map: 'function() { emit(this.user_id, 1); }', + reduce: 'function(k,vals) { return 1; }', + out: 'inline', + readConcern: 'majority' +} + +await db.command(command); +``` + +**Note** When using the `Db.command()` helper, all `mapReduce` options should be specified +on the raw command object and should not be passed through the options object. + ### `AddUserOptions.digestPassword` removed The `digestPassword` option has been removed from the add user helper. diff --git a/src/sessions.ts b/src/sessions.ts index 7bc090c72d1..1774f7c7806 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -1007,7 +1007,7 @@ export function applySession( if ( session.supports.causalConsistency && session.operationTime && - commandSupportsReadConcern(command) + commandSupportsReadConcern(command, options) ) { command.readConcern = command.readConcern || {}; Object.assign(command.readConcern, { afterClusterTime: session.operationTime }); diff --git a/src/utils.ts b/src/utils.ts index dbb376efb65..61fc08ef7a0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1326,10 +1326,23 @@ export function shuffle(sequence: Iterable, limit = 0): Array { return limit % items.length === 0 ? items : items.slice(lowerBound); } -// TODO: this should be codified in command construction +// TODO(NODE-4936): read concern eligibility for commands should be codified in command construction // @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.rst#read-concern -export function commandSupportsReadConcern(command: Document): boolean { - return command.aggregate || command.count || command.distinct || command.find || command.geoNear; +export function commandSupportsReadConcern(command: Document, options?: Document): boolean { + if (command.aggregate || command.count || command.distinct || command.find || command.geoNear) { + return true; + } + + if ( + command.mapReduce && + options && + options.out && + (options.out.inline === 1 || options.out === 'inline') + ) { + return true; + } + + return false; } /** A utility function to get the instance of mongodb-client-encryption, if it exists. */ From d6eefce2b9b8c5ee34f245ba34ba59f5691fdaf9 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Mon, 9 Jan 2023 16:33:46 -0500 Subject: [PATCH 7/9] chore: update migration guide --- etc/notes/CHANGES_5.0.0.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/etc/notes/CHANGES_5.0.0.md b/etc/notes/CHANGES_5.0.0.md index 77ddfc87925..889c4f5fec6 100644 --- a/etc/notes/CHANGES_5.0.0.md +++ b/etc/notes/CHANGES_5.0.0.md @@ -20,12 +20,25 @@ The following is a detailed collection of the changes in the major v5 release of The `mapReduce` helper has been removed from the `Collection` class. The `mapReduce` operation has been deprecated in favor of the aggregation pipeline since MongoDB server version 5.0. It is recommended -to migrate code that uses `Collection.mapReduce` to use the aggregation pipeline. +to migrate code that uses `Collection.mapReduce` to use the aggregation pipeline (see [Map-Reduce to Aggregation Pipeline](https://www.mongodb.com/docs/manual/reference/map-reduce-to-aggregation-pipeline/)). If the `mapReduce` command must be used, the `Db.command()` helper can be used to run the raw `mapReduce` command. ```typescript +// using the Collection.mapReduce helper in <4.x drivers +const collection = db.collection('my-collection'); + +await collection.mapReduce( + function() { emit(this.user_id, 1); }, + function(, vals) { return 1 }, + { + out: 'inline', + readConcern: 'majority' + } +) + +// manually running the command using `db.command()` const command = { mapReduce: 'my-collection', map: 'function() { emit(this.user_id, 1); }', From db7bc784221543512a546b6eeddf246e019661de Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Tue, 10 Jan 2023 09:29:17 -0500 Subject: [PATCH 8/9] fix lint --- test/mongodb.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mongodb.ts b/test/mongodb.ts index ec2ad646880..cc037bb625a 100644 --- a/test/mongodb.ts +++ b/test/mongodb.ts @@ -95,7 +95,6 @@ export * from '../src/operations/is_capped'; export * from '../src/operations/kill_cursors'; export * from '../src/operations/list_collections'; export * from '../src/operations/list_databases'; -export * from '../src/operations/map_reduce'; export * from '../src/operations/operation'; export * from '../src/operations/options_operation'; export * from '../src/operations/profiling_level'; From 852329daa39dd7470dc78ad846032982539847ae Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Tue, 10 Jan 2023 16:52:02 -0500 Subject: [PATCH 9/9] Update etc/notes/CHANGES_5.0.0.md Co-authored-by: Neal Beeken --- etc/notes/CHANGES_5.0.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/notes/CHANGES_5.0.0.md b/etc/notes/CHANGES_5.0.0.md index 889c4f5fec6..39ca37c9691 100644 --- a/etc/notes/CHANGES_5.0.0.md +++ b/etc/notes/CHANGES_5.0.0.md @@ -31,7 +31,7 @@ const collection = db.collection('my-collection'); await collection.mapReduce( function() { emit(this.user_id, 1); }, - function(, vals) { return 1 }, + function(k, vals) { return 1 }, { out: 'inline', readConcern: 'majority'