diff --git a/lib/collection.js b/lib/collection.js index e1d9accbf7..e4a926e6b0 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -1,6 +1,7 @@ 'use strict'; const deprecate = require('util').deprecate; +const deprecateOptions = require('./utils').deprecateOptions; const checkCollectionName = require('./utils').checkCollectionName; const ObjectID = require('mongodb-core').BSON.ObjectID; const AggregationCursor = require('./aggregation_cursor'); @@ -237,7 +238,7 @@ Object.defineProperty(Collection.prototype, 'hint', { } }); -const DEPRECATED_FIND_OPTIONS = ['maxScan', 'snapshot']; +const DEPRECATED_FIND_OPTIONS = ['maxScan', 'fields', 'snapshot']; /** * Creates a cursor for a query that can be used to iterate over results from MongoDB @@ -273,163 +274,163 @@ const DEPRECATED_FIND_OPTIONS = ['maxScan', 'snapshot']; * @throws {MongoError} * @return {Cursor} */ -Collection.prototype.find = function(query, options, callback) { - if (typeof callback === 'object') { - // TODO(MAJOR): throw in the future - console.warn('Third parameter to `find()` must be a callback or undefined'); - } - - let selector = query; - // figuring out arguments - if (typeof callback !== 'function') { - if (typeof options === 'function') { - callback = options; - options = undefined; - } else if (options == null) { - callback = typeof selector === 'function' ? selector : undefined; - selector = typeof selector === 'object' ? selector : undefined; +Collection.prototype.find = deprecateOptions( + { + name: 'collection.find', + deprecatedOptions: DEPRECATED_FIND_OPTIONS, + optionsIndex: 1 + }, + function(query, options, callback) { + if (typeof callback === 'object') { + // TODO(MAJOR): throw in the future + console.warn('Third parameter to `find()` must be a callback or undefined'); } - } - // Ensure selector is not null - selector = selector == null ? {} : selector; - // Validate correctness off the selector - const object = selector; - if (Buffer.isBuffer(object)) { - const object_size = object[0] | (object[1] << 8) | (object[2] << 16) | (object[3] << 24); - if (object_size !== object.length) { - const error = new Error( - 'query selector raw message size does not match message header size [' + - object.length + - '] != [' + - object_size + - ']' - ); - error.name = 'MongoError'; - throw error; + let selector = query; + // figuring out arguments + if (typeof callback !== 'function') { + if (typeof options === 'function') { + callback = options; + options = undefined; + } else if (options == null) { + callback = typeof selector === 'function' ? selector : undefined; + selector = typeof selector === 'object' ? selector : undefined; + } } - } - // Check special case where we are using an objectId - if (selector != null && selector._bsontype === 'ObjectID') { - selector = { _id: selector }; - } + // Ensure selector is not null + selector = selector == null ? {} : selector; + // Validate correctness off the selector + const object = selector; + if (Buffer.isBuffer(object)) { + const object_size = object[0] | (object[1] << 8) | (object[2] << 16) | (object[3] << 24); + if (object_size !== object.length) { + const error = new Error( + 'query selector raw message size does not match message header size [' + + object.length + + '] != [' + + object_size + + ']' + ); + error.name = 'MongoError'; + throw error; + } + } - if (!options) options = {}; + // Check special case where we are using an objectId + if (selector != null && selector._bsontype === 'ObjectID') { + selector = { _id: selector }; + } - let projection = options.projection || options.fields; + if (!options) options = {}; - if (projection && !Buffer.isBuffer(projection) && Array.isArray(projection)) { - projection = projection.length - ? projection.reduce((result, field) => { - result[field] = 1; - return result; - }, {}) - : { _id: 1 }; - } + let projection = options.projection || options.fields; - // Make a shallow copy of options - let newOptions = Object.assign({}, options); - - // Make a shallow copy of the collection options - for (let key in this.s.options) { - if (mergeKeys.indexOf(key) !== -1) { - newOptions[key] = this.s.options[key]; + if (projection && !Buffer.isBuffer(projection) && Array.isArray(projection)) { + projection = projection.length + ? projection.reduce((result, field) => { + result[field] = 1; + return result; + }, {}) + : { _id: 1 }; } - } - - // Unpack options - newOptions.skip = options.skip ? options.skip : 0; - newOptions.limit = options.limit ? options.limit : 0; - newOptions.raw = typeof options.raw === 'boolean' ? options.raw : this.s.raw; - newOptions.hint = options.hint != null ? normalizeHintField(options.hint) : this.s.collectionHint; - newOptions.timeout = typeof options.timeout === 'undefined' ? undefined : options.timeout; - // // If we have overridden slaveOk otherwise use the default db setting - newOptions.slaveOk = options.slaveOk != null ? options.slaveOk : this.s.db.slaveOk; - - // Add read preference if needed - newOptions.readPreference = resolveReadPreference(newOptions, { - db: this.s.db, - collection: this - }); - // Set slave ok to true if read preference different from primary - if ( - newOptions.readPreference != null && - (newOptions.readPreference !== 'primary' || newOptions.readPreference.mode !== 'primary') - ) { - newOptions.slaveOk = true; - } + // Make a shallow copy of options + let newOptions = Object.assign({}, options); - // Ensure the query is an object - if (selector != null && typeof selector !== 'object') { - throw MongoError.create({ message: 'query selector must be an object', driver: true }); - } + // Make a shallow copy of the collection options + for (let key in this.s.options) { + if (mergeKeys.indexOf(key) !== -1) { + newOptions[key] = this.s.options[key]; + } + } - // Build the find command - const findCommand = { - find: this.s.namespace, - limit: newOptions.limit, - skip: newOptions.skip, - query: selector - }; + // Unpack options + newOptions.skip = options.skip ? options.skip : 0; + newOptions.limit = options.limit ? options.limit : 0; + newOptions.raw = typeof options.raw === 'boolean' ? options.raw : this.s.raw; + newOptions.hint = + options.hint != null ? normalizeHintField(options.hint) : this.s.collectionHint; + newOptions.timeout = typeof options.timeout === 'undefined' ? undefined : options.timeout; + // // If we have overridden slaveOk otherwise use the default db setting + newOptions.slaveOk = options.slaveOk != null ? options.slaveOk : this.s.db.slaveOk; + + // Add read preference if needed + newOptions.readPreference = resolveReadPreference(newOptions, { + db: this.s.db, + collection: this + }); - // Ensure we use the right await data option - if (typeof newOptions.awaitdata === 'boolean') { - newOptions.awaitData = newOptions.awaitdata; - } + // Set slave ok to true if read preference different from primary + if ( + newOptions.readPreference != null && + (newOptions.readPreference !== 'primary' || newOptions.readPreference.mode !== 'primary') + ) { + newOptions.slaveOk = true; + } - // Translate to new command option noCursorTimeout - if (typeof newOptions.timeout === 'boolean') newOptions.noCursorTimeout = newOptions.timeout; + // Ensure the query is an object + if (selector != null && typeof selector !== 'object') { + throw MongoError.create({ message: 'query selector must be an object', driver: true }); + } - // Merge in options to command - for (let name in newOptions) { - if (newOptions[name] != null && name !== 'session') { - findCommand[name] = newOptions[name]; + // Build the find command + const findCommand = { + find: this.s.namespace, + limit: newOptions.limit, + skip: newOptions.skip, + query: selector + }; + + // Ensure we use the right await data option + if (typeof newOptions.awaitdata === 'boolean') { + newOptions.awaitData = newOptions.awaitdata; } - } - DEPRECATED_FIND_OPTIONS.forEach(deprecatedOption => { - if (findCommand[deprecatedOption]) { - console.warn( - `Find option ${deprecatedOption} is deprecated, and will be removed in a later version` - ); + // Translate to new command option noCursorTimeout + if (typeof newOptions.timeout === 'boolean') newOptions.noCursorTimeout = newOptions.timeout; + + // Merge in options to command + for (let name in newOptions) { + if (newOptions[name] != null && name !== 'session') { + findCommand[name] = newOptions[name]; + } } - }); - if (projection) findCommand.fields = projection; + if (projection) findCommand.fields = projection; - // Add db object to the new options - newOptions.db = this.s.db; + // Add db object to the new options + newOptions.db = this.s.db; - // Add the promise library - newOptions.promiseLibrary = this.s.promiseLibrary; + // Add the promise library + newOptions.promiseLibrary = this.s.promiseLibrary; - // Set raw if available at collection level - if (newOptions.raw == null && typeof this.s.raw === 'boolean') newOptions.raw = this.s.raw; - // Set promoteLongs if available at collection level - if (newOptions.promoteLongs == null && typeof this.s.promoteLongs === 'boolean') - newOptions.promoteLongs = this.s.promoteLongs; - if (newOptions.promoteValues == null && typeof this.s.promoteValues === 'boolean') - newOptions.promoteValues = this.s.promoteValues; - if (newOptions.promoteBuffers == null && typeof this.s.promoteBuffers === 'boolean') - newOptions.promoteBuffers = this.s.promoteBuffers; + // Set raw if available at collection level + if (newOptions.raw == null && typeof this.s.raw === 'boolean') newOptions.raw = this.s.raw; + // Set promoteLongs if available at collection level + if (newOptions.promoteLongs == null && typeof this.s.promoteLongs === 'boolean') + newOptions.promoteLongs = this.s.promoteLongs; + if (newOptions.promoteValues == null && typeof this.s.promoteValues === 'boolean') + newOptions.promoteValues = this.s.promoteValues; + if (newOptions.promoteBuffers == null && typeof this.s.promoteBuffers === 'boolean') + newOptions.promoteBuffers = this.s.promoteBuffers; - // Sort options - if (findCommand.sort) { - findCommand.sort = formattedOrderClause(findCommand.sort); - } + // Sort options + if (findCommand.sort) { + findCommand.sort = formattedOrderClause(findCommand.sort); + } - // Set the readConcern - decorateWithReadConcern(findCommand, this, options); + // Set the readConcern + decorateWithReadConcern(findCommand, this, options); - // Decorate find command with collation options - decorateWithCollation(findCommand, this, options); + // Decorate find command with collation options + decorateWithCollation(findCommand, this, options); - const cursor = this.s.topology.cursor(this.s.namespace, findCommand, newOptions); + const cursor = this.s.topology.cursor(this.s.namespace, findCommand, newOptions); - return typeof callback === 'function' ? handleCallback(callback, null, cursor) : cursor; -}; + return typeof callback === 'function' ? handleCallback(callback, null, cursor) : cursor; + } +); /** * Inserts a single document into MongoDB. If documents passed in do not contain the **_id** field, @@ -662,7 +663,7 @@ Collection.prototype.bulkWrite = function(operations, options, callback) { * @return {Promise} returns Promise if no callback passed * @deprecated Use insertOne, insertMany or bulkWrite */ -Collection.prototype.insert = function(docs, options, callback) { +Collection.prototype.insert = deprecate(function(docs, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || { ordered: false }; docs = !Array.isArray(docs) ? [docs] : docs; @@ -672,7 +673,7 @@ Collection.prototype.insert = function(docs, options, callback) { } return this.insertMany(docs, options, callback); -}; +}, 'collection.insert is deprecated. Use insertOne, insertMany or bulkWrite instead.'); /** * @typedef {Object} Collection~updateWriteOpResult @@ -816,7 +817,7 @@ Collection.prototype.updateMany = function(filter, update, options, callback) { * @return {Promise} returns Promise if no callback passed * @deprecated use updateOne, updateMany or bulkWrite */ -Collection.prototype.update = function(selector, document, options, callback) { +Collection.prototype.update = deprecate(function(selector, document, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -833,7 +834,7 @@ Collection.prototype.update = function(selector, document, options, callback) { options, callback ]); -}; +}, 'collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead.'); /** * @typedef {Object} Collection~deleteWriteOpResult @@ -919,7 +920,7 @@ Collection.prototype.removeMany = Collection.prototype.deleteMany; * @return {Promise} returns Promise if no callback passed * @deprecated use deleteOne, deleteMany or bulkWrite */ -Collection.prototype.remove = function(selector, options, callback) { +Collection.prototype.remove = deprecate(function(selector, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -930,7 +931,7 @@ Collection.prototype.remove = function(selector, options, callback) { } return executeOperation(this.s.topology, removeDocuments, [this, selector, options, callback]); -}; +}, 'collection.remove is deprecated. Use deleteOne, deleteMany, or bulkWrite instead.'); /** * Save a document. Simple full document replacement function. Not recommended for efficiency, use atomic @@ -946,7 +947,7 @@ Collection.prototype.remove = function(selector, options, callback) { * @return {Promise} returns Promise if no callback passed * @deprecated use insertOne, insertMany, updateOne or updateMany */ -Collection.prototype.save = function(doc, options, callback) { +Collection.prototype.save = deprecate(function(doc, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -957,7 +958,7 @@ Collection.prototype.save = function(doc, options, callback) { } return executeOperation(this.s.topology, save, [this, doc, options, callback]); -}; +}, 'collection.save is deprecated. Use insertOne, insertMany, updateOne, or updateMany instead.'); /** * The callback format for results @@ -1007,19 +1008,26 @@ Collection.prototype.save = function(doc, options, callback) { * @param {Collection~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ -Collection.prototype.findOne = function(query, options, callback) { - if (typeof callback === 'object') { - // TODO(MAJOR): throw in the future - console.warn('Third parameter to `findOne()` must be a callback or undefined'); - } +Collection.prototype.findOne = deprecateOptions( + { + name: 'collection.find', + deprecatedOptions: DEPRECATED_FIND_OPTIONS, + optionsIndex: 1 + }, + function(query, options, callback) { + if (typeof callback === 'object') { + // TODO(MAJOR): throw in the future + console.warn('Third parameter to `findOne()` must be a callback or undefined'); + } - if (typeof query === 'function') (callback = query), (query = {}), (options = {}); - if (typeof options === 'function') (callback = options), (options = {}); - query = query || {}; - options = options || {}; + if (typeof query === 'function') (callback = query), (query = {}), (options = {}); + if (typeof options === 'function') (callback = options), (options = {}); + query = query || {}; + options = options || {}; - return executeOperation(this.s.topology, findOne, [this, query, options, callback]); -}; + return executeOperation(this.s.topology, findOne, [this, query, options, callback]); + } +); /** * The callback format for the collection method, must be used if strict is specified @@ -1197,7 +1205,10 @@ Collection.prototype.dropIndexes = function(options, callback) { * @param {Collection~resultCallback} callback The command result callback * @return {Promise} returns Promise if no [callback] passed */ -Collection.prototype.dropAllIndexes = Collection.prototype.dropIndexes; +Collection.prototype.dropAllIndexes = deprecate( + Collection.prototype.dropIndexes, + 'collection.dropAllIndexes is deprecated. Use dropIndexes instead.' +); /** * Reindex all indexes on the collection @@ -1290,12 +1301,12 @@ Collection.prototype.listIndexes = function(options) { * @param {Collection~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ -Collection.prototype.ensureIndex = function(fieldOrSpec, options, callback) { +Collection.prototype.ensureIndex = deprecate(function(fieldOrSpec, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; return executeOperation(this.s.topology, ensureIndex, [this, fieldOrSpec, options, callback]); -}; +}, 'collection.ensureIndex is deprecated. Use createIndexes instead.'); /** * Checks if one or more indexes exist on the collection, fails on first non-existing index @@ -1634,7 +1645,7 @@ Collection.prototype.findOneAndUpdate = function(filter, update, options, callba * @return {Promise} returns Promise if no callback passed * @deprecated use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead */ -Collection.prototype.findAndModify = function(query, sort, doc, options, callback) { +Collection.prototype.findAndModify = deprecate(function(query, sort, doc, options, callback) { const args = Array.prototype.slice.call(arguments, 1); callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; sort = args.length ? args.shift() || [] : []; @@ -1654,7 +1665,7 @@ Collection.prototype.findAndModify = function(query, sort, doc, options, callbac options, callback ]); -}; +}, 'collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead.'); /** * Find and remove a document. @@ -1670,14 +1681,14 @@ Collection.prototype.findAndModify = function(query, sort, doc, options, callbac * @return {Promise} returns Promise if no callback passed * @deprecated use findOneAndDelete instead */ -Collection.prototype.findAndRemove = function(query, sort, options, callback) { +Collection.prototype.findAndRemove = deprecate(function(query, sort, options, callback) { const args = Array.prototype.slice.call(arguments, 1); callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; sort = args.length ? args.shift() || [] : []; options = args.length ? args.shift() || {} : {}; return executeOperation(this.s.topology, findAndRemove, [this, query, sort, options, callback]); -}; +}, 'collection.findAndRemove is deprecated. Use findOneAndDelete instead.'); /** * Execute an aggregation framework pipeline against the collection, needs MongoDB >= 2.2 @@ -1933,9 +1944,9 @@ Collection.prototype.geoHaystackSearch = function(x, y, options, callback) { * @param {ClientSession} [options.session] optional session to use for this operation * @param {Collection~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed - * @deprecated MongoDB 3.6 or higher will no longer support the group command. We recommend rewriting using the aggregation framework. + * @deprecated MongoDB 3.6 or higher no longer supports the group command. We recommend rewriting using the aggregation framework. */ -Collection.prototype.group = function( +Collection.prototype.group = deprecate(function( keys, condition, initial, @@ -1989,7 +2000,8 @@ Collection.prototype.group = function( options, callback ]); -}; +}, +'MongoDB 3.6 or higher no longer supports the group command. We recommend rewriting using the aggregation framework.'); /** * Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection. diff --git a/lib/cursor.js b/lib/cursor.js index 559dc305b4..d7cd06493f 100644 --- a/lib/cursor.js +++ b/lib/cursor.js @@ -699,14 +699,14 @@ Cursor.prototype.skip = function(value) { * @throws {MongoError} * @return {null} */ -Cursor.prototype.each = function(callback) { +Cursor.prototype.each = deprecate(function(callback) { // Rewind cursor state this.rewind(); // Set current cursor to INIT this.s.state = Cursor.INIT; // Run the query each(this, callback); -}; +}, 'Cursor.each is deprecated. Use Cursor.forEach instead.'); /** * The callback format for the forEach iterator method diff --git a/lib/db.js b/lib/db.js index c652f6e109..478989638e 100644 --- a/lib/db.js +++ b/lib/db.js @@ -17,6 +17,8 @@ const executeOperation = require('./utils').executeOperation; const applyWriteConcern = require('./utils').applyWriteConcern; const resolveReadPreference = require('./utils').resolveReadPreference; const ChangeStream = require('./change_stream'); +const deprecate = require('util').deprecate; +const deprecateOptions = require('./utils').deprecateOptions; // Operations const addUser = require('./operations/db_ops').addUser; @@ -414,17 +416,20 @@ Db.prototype.collection = function(name, options, callback) { * @param {Db~collectionResultCallback} [callback] The results callback * @return {Promise} returns Promise if no callback passed */ -Db.prototype.createCollection = function(name, options, callback) { - if (typeof options === 'function') (callback = options), (options = {}); - options = options || {}; - options.promiseLibrary = options.promiseLibrary || this.s.promiseLibrary; - - if (options.autoIndexId !== undefined) { - console.warn('the autoIndexId option is deprecated and will be removed in a future release'); +Db.prototype.createCollection = deprecateOptions( + { + name: 'Db.createCollection', + deprecatedOptions: ['autoIndexId'], + optionsIndex: 1 + }, + function(name, options, callback) { + if (typeof options === 'function') (callback = options), (options = {}); + options = options || {}; + options.promiseLibrary = options.promiseLibrary || this.s.promiseLibrary; + + return executeOperation(this.s.topology, createCollection, [this, name, options, callback]); } - - return executeOperation(this.s.topology, createCollection, [this, name, options, callback]); -}; +); /** * Get all the db statistics. @@ -549,14 +554,14 @@ Db.prototype.listCollections = function(filter, options) { * @deprecated Eval is deprecated on MongoDB 3.2 and forward * @return {Promise} returns Promise if no callback passed */ -Db.prototype.eval = function(code, parameters, options, callback) { +Db.prototype.eval = deprecate(function(code, parameters, options, callback) { const args = Array.prototype.slice.call(arguments, 1); callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; parameters = args.length ? args.shift() : parameters; options = args.length ? args.shift() || {} : {}; return executeOperation(this.s.topology, evaluate, [this, code, parameters, options, callback]); -}; +}, 'Db.eval is deprecated as of MongoDB version 3.2'); /** * Rename a collection. @@ -739,7 +744,7 @@ Db.prototype.createIndex = function(name, fieldOrSpec, options, callback) { * @param {Db~resultCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed */ -Db.prototype.ensureIndex = function(name, fieldOrSpec, options, callback) { +Db.prototype.ensureIndex = deprecate(function(name, fieldOrSpec, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -750,7 +755,7 @@ Db.prototype.ensureIndex = function(name, fieldOrSpec, options, callback) { options, callback ]); -}; +}, 'Db.ensureIndex is deprecated as of MongoDB version 3.0 / driver version 2.0'); Db.prototype.addChild = function(db) { if (this.s.parentDb) return this.s.parentDb.addChild(db); @@ -823,12 +828,12 @@ Db.prototype.setProfilingLevel = function(level, options, callback) { * @return {Promise} returns Promise if no callback passed * @deprecated Query the system.profile collection directly. */ -Db.prototype.profilingInfo = function(options, callback) { +Db.prototype.profilingInfo = deprecate(function(options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; return executeOperation(this.s.topology, profilingInfo, [this, options, callback]); -}; +}, 'Db.profilingInfo is deprecated. Query the system.profile collection directly.'); /** * Retrieve the current profiling Level for MongoDB