This commit is contained in:
Jonasz Bigda
2023-03-25 21:51:42 +01:00
parent 0db1d5117e
commit b332e9ceb0
1044 changed files with 37502 additions and 63938 deletions

View File

@@ -6,6 +6,7 @@ const defineAspects = require('./operation').defineAspects;
const crypto = require('crypto');
const handleCallback = require('../utils').handleCallback;
const toError = require('../utils').toError;
const emitWarning = require('../utils').emitWarning;
class AddUserOperation extends CommandOperation {
constructor(db, username, password, options) {
@@ -22,12 +23,14 @@ class AddUserOperation extends CommandOperation {
const options = this.options;
// Get additional values
let roles = Array.isArray(options.roles) ? options.roles : [];
let roles = [];
if (Array.isArray(options.roles)) roles = options.roles;
if (typeof options.roles === 'string') roles = [options.roles];
// If not roles defined print deprecated message
// TODO: handle deprecation properly
if (roles.length === 0) {
console.log('Creating a user without roles is deprecated in MongoDB >= 2.6');
emitWarning('Creating a user without roles is deprecated in MongoDB >= 2.6');
}
// Check the db name and add roles if needed

View File

@@ -39,7 +39,7 @@ function validateCollection(admin, collectionName, options, callback) {
// Decorate command with extra options
for (let i = 0; i < keys.length; i++) {
if (options.hasOwnProperty(keys[i]) && keys[i] !== 'session') {
if (Object.prototype.hasOwnProperty.call(options, keys[i]) && keys[i] !== 'session') {
command[keys[i]] = options[keys[i]];
}
}

View File

@@ -37,10 +37,8 @@ class AggregateOperation extends CommandOperationV2 {
this.readPreference = ReadPreference.primary;
}
if (options.explain && (this.readConcern || this.writeConcern)) {
throw new MongoError(
'"explain" cannot be used on an aggregate call with readConcern/writeConcern'
);
if (this.explain && this.writeConcern) {
throw new MongoError('"explain" cannot be used on an aggregate call with writeConcern');
}
if (options.cursor != null && typeof options.cursor !== 'object') {
@@ -83,9 +81,8 @@ class AggregateOperation extends CommandOperationV2 {
command.hint = options.hint;
}
if (options.explain) {
if (this.explain) {
options.full = false;
command.explain = options.explain;
}
command.cursor = options.cursor || {};
@@ -100,7 +97,8 @@ class AggregateOperation extends CommandOperationV2 {
defineAspects(AggregateOperation, [
Aspect.READ_OPERATION,
Aspect.RETRYABLE,
Aspect.EXECUTE_WITH_SELECTION
Aspect.EXECUTE_WITH_SELECTION,
Aspect.EXPLAINABLE
]);
module.exports = AggregateOperation;

View File

@@ -70,31 +70,6 @@ class BulkWriteOperation extends OperationBase {
return callback(err, null);
}
r.insertedCount = r.nInserted;
r.matchedCount = r.nMatched;
r.modifiedCount = r.nModified || 0;
r.deletedCount = r.nRemoved;
r.upsertedCount = r.getUpsertedIds().length;
r.upsertedIds = {};
r.insertedIds = {};
// Update the n
r.n = r.insertedCount;
// Inserted documents
const inserted = r.getInsertedIds();
// Map inserted ids
for (let i = 0; i < inserted.length; i++) {
r.insertedIds[inserted[i].index] = inserted[i]._id;
}
// Upserted documents
const upserted = r.getUpsertedIds();
// Map upserted ids
for (let i = 0; i < upserted.length; i++) {
r.upsertedIds[upserted[i].index] = upserted[i]._id;
}
// Return the results
callback(null, r);
});

View File

@@ -8,13 +8,11 @@ const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
const ensureIndexDb = require('./db_ops').ensureIndex;
const evaluate = require('./db_ops').evaluate;
const executeCommand = require('./db_ops').executeCommand;
const resolveReadPreference = require('../utils').resolveReadPreference;
const handleCallback = require('../utils').handleCallback;
const indexInformationDb = require('./db_ops').indexInformation;
const Long = require('../core').BSON.Long;
const MongoError = require('../core').MongoError;
const ReadPreference = require('../core').ReadPreference;
const toError = require('../utils').toError;
const insertDocuments = require('./common_functions').insertDocuments;
const updateDocuments = require('./common_functions').updateDocuments;
@@ -52,24 +50,6 @@ const updateDocuments = require('./common_functions').updateDocuments;
const groupFunction =
'function () {\nvar c = db[ns].find(condition);\nvar map = new Map();\nvar reduce_function = reduce;\n\nwhile (c.hasNext()) {\nvar obj = c.next();\nvar key = {};\n\nfor (var i = 0, len = keys.length; i < len; ++i) {\nvar k = keys[i];\nkey[k] = obj[k];\n}\n\nvar aggObj = map.get(key);\n\nif (aggObj == null) {\nvar newObj = Object.extend({}, key);\naggObj = Object.extend(newObj, initial);\nmap.put(key, aggObj);\n}\n\nreduce_function(obj, aggObj);\n}\n\nreturn { "result": map.values() };\n}';
// Check the update operation to ensure it has atomic operators.
function checkForAtomicOperators(update) {
if (Array.isArray(update)) {
return update.reduce((err, u) => err || checkForAtomicOperators(u), null);
}
const keys = Object.keys(update);
// same errors as the server would give for update doc lacking atomic operators
if (keys.length === 0) {
return toError('The update operation document must contain at least one atomic operator.');
}
if (keys[0][0] !== '$') {
return toError('the update operation document must contain atomic operators.');
}
}
/**
* Create an index on the db and collection.
*
@@ -188,7 +168,7 @@ function group(coll, keys, condition, initial, reduce, finalize, command, option
options = Object.assign({}, options);
// Ensure we have the right read preference inheritance
options.readPreference = resolveReadPreference(coll, options);
options.readPreference = ReadPreference.resolve(coll, options);
// Do we have a readConcern specified
decorateWithReadConcern(selector, coll, options);
@@ -361,7 +341,6 @@ function save(coll, doc, options, callback) {
}
module.exports = {
checkForAtomicOperators,
createIndex,
createIndexes,
ensureIndex,

View File

@@ -7,8 +7,8 @@ const debugOptions = require('../utils').debugOptions;
const handleCallback = require('../utils').handleCallback;
const MongoError = require('../core').MongoError;
const ReadPreference = require('../core').ReadPreference;
const resolveReadPreference = require('../utils').resolveReadPreference;
const MongoDBNamespace = require('../utils').MongoDBNamespace;
const extractCommand = require('../command_utils').extractCommand;
const debugFields = [
'authSource',
@@ -22,6 +22,7 @@ const debugFields = [
'promoteLongs',
'promoteValues',
'promoteBuffers',
'bsonRegExp',
'bufferMaxEntries',
'numberOfRetries',
'retryMiliSeconds',
@@ -38,9 +39,9 @@ class CommandOperation extends OperationBase {
if (!this.hasAspect(Aspect.WRITE_OPERATION)) {
if (collection != null) {
this.options.readPreference = resolveReadPreference(collection, options);
this.options.readPreference = ReadPreference.resolve(collection, options);
} else {
this.options.readPreference = resolveReadPreference(db, options);
this.options.readPreference = ReadPreference.resolve(db, options);
}
} else {
if (collection != null) {
@@ -96,9 +97,10 @@ class CommandOperation extends OperationBase {
// Debug information
if (db.s.logger.isDebug()) {
const extractedCommand = extractCommand(command);
db.s.logger.debug(
`executing command ${JSON.stringify(
command
extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : command
)} against ${dbName}.$cmd with options [${JSON.stringify(
debugOptions(debugFields, options)
)}]`

View File

@@ -2,12 +2,14 @@
const Aspect = require('./operation').Aspect;
const OperationBase = require('./operation').OperationBase;
const resolveReadPreference = require('../utils').resolveReadPreference;
const ReadPreference = require('../core').ReadPreference;
const ReadConcern = require('../read_concern');
const WriteConcern = require('../write_concern');
const maxWireVersion = require('../core/utils').maxWireVersion;
const decorateWithExplain = require('../utils').decorateWithExplain;
const commandSupportsReadConcern = require('../core/sessions').commandSupportsReadConcern;
const MongoError = require('../error').MongoError;
const MongoError = require('../core/error').MongoError;
const extractCommand = require('../command_utils').extractCommand;
const SUPPORTS_WRITE_CONCERN_AND_COLLATION = 5;
@@ -16,10 +18,12 @@ class CommandOperationV2 extends OperationBase {
super(options);
this.ns = parent.s.namespace.withCollection('$cmd');
this.readPreference = resolveReadPreference(parent, this.options);
this.readConcern = resolveReadConcern(parent, this.options);
this.writeConcern = resolveWriteConcern(parent, this.options);
this.explain = false;
const propertyProvider = this.hasAspect(Aspect.NO_INHERIT_OPTIONS) ? undefined : parent;
this.readPreference = this.hasAspect(Aspect.WRITE_OPERATION)
? ReadPreference.primary
: ReadPreference.resolve(propertyProvider, this.options);
this.readConcern = resolveReadConcern(propertyProvider, this.options);
this.writeConcern = resolveWriteConcern(propertyProvider, this.options);
if (operationOptions && typeof operationOptions.fullResponse === 'boolean') {
this.fullResponse = true;
@@ -76,8 +80,22 @@ class CommandOperationV2 extends OperationBase {
cmd.comment = options.comment;
}
if (this.hasAspect(Aspect.EXPLAINABLE) && this.explain) {
if (serverWireVersion < 6 && cmd.aggregate) {
// Prior to 3.6, with aggregate, verbosity is ignored, and we must pass in "explain: true"
cmd.explain = true;
} else {
cmd = decorateWithExplain(cmd, this.explain);
}
}
if (this.logger && this.logger.isDebug()) {
this.logger.debug(`executing command ${JSON.stringify(cmd)} against ${this.ns}`);
const extractedCommand = extractCommand(cmd);
this.logger.debug(
`executing command ${JSON.stringify(
extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : cmd
)} against ${this.ns}`
);
}
server.command(this.ns.toString(), cmd, this.options, (err, result) => {
@@ -97,11 +115,11 @@ class CommandOperationV2 extends OperationBase {
}
function resolveWriteConcern(parent, options) {
return WriteConcern.fromOptions(options) || parent.writeConcern;
return WriteConcern.fromOptions(options) || (parent && parent.writeConcern);
}
function resolveReadConcern(parent, options) {
return ReadConcern.fromOptions(options) || parent.readConcern;
return ReadConcern.fromOptions(options) || (parent && parent.readConcern);
}
module.exports = CommandOperationV2;

View File

@@ -11,6 +11,7 @@ const MongoError = require('../core').MongoError;
const ReadPreference = require('../core').ReadPreference;
const toError = require('../utils').toError;
const CursorState = require('../core/cursor').CursorState;
const maxWireVersion = require('../core/utils').maxWireVersion;
/**
* Build the count command.
@@ -57,14 +58,6 @@ function buildCountCommand(collectionOrCursor, query, options) {
return cmd;
}
function deleteCallback(err, r, callback) {
if (callback == null) return;
if (err && callback) return callback(err);
if (r == null) return callback(null, { result: { ok: 1 } });
r.deletedCount = r.result.n;
if (callback) callback(null, r);
}
/**
* Find and update a document.
*
@@ -297,6 +290,9 @@ function removeDocuments(coll, selector, options, callback) {
} else if (finalOptions.retryWrites) {
finalOptions.retryWrites = false;
}
if (options.hint) {
op.hint = options.hint;
}
// Have we specified collation
try {
@@ -305,6 +301,12 @@ function removeDocuments(coll, selector, options, callback) {
return callback(err, null);
}
if (options.explain !== undefined && maxWireVersion(coll.s.topology) < 3) {
return callback
? callback(new MongoError(`server does not support explain on remove`))
: undefined;
}
// Execute the remove
coll.s.topology.remove(coll.s.namespace, [op], finalOptions, (err, result) => {
if (callback == null) return;
@@ -366,6 +368,12 @@ function updateDocuments(coll, selector, document, options, callback) {
return callback(err, null);
}
if (options.explain !== undefined && maxWireVersion(coll.s.topology) < 3) {
return callback
? callback(new MongoError(`server does not support explain on update`))
: undefined;
}
// Update options
coll.s.topology.update(coll.s.namespace, [op], finalOptions, (err, result) => {
if (callback == null) return;
@@ -379,31 +387,13 @@ function updateDocuments(coll, selector, document, options, callback) {
});
}
function updateCallback(err, r, callback) {
if (callback == null) return;
if (err) return callback(err);
if (r == null) return callback(null, { result: { ok: 1 } });
r.modifiedCount = r.result.nModified != null ? r.result.nModified : r.result.n;
r.upsertedId =
Array.isArray(r.result.upserted) && r.result.upserted.length > 0
? r.result.upserted[0] // FIXME(major): should be `r.result.upserted[0]._id`
: null;
r.upsertedCount =
Array.isArray(r.result.upserted) && r.result.upserted.length ? r.result.upserted.length : 0;
r.matchedCount =
Array.isArray(r.result.upserted) && r.result.upserted.length > 0 ? 0 : r.result.n;
callback(null, r);
}
module.exports = {
buildCountCommand,
deleteCallback,
findAndModify,
indexInformation,
nextObject,
prepareDocs,
insertDocuments,
removeDocuments,
updateDocuments,
updateCallback
updateDocuments
};

View File

@@ -13,8 +13,9 @@ const ReplSet = require('../topologies/replset');
const Server = require('../topologies/server');
const ServerSessionPool = require('../core').Sessions.ServerSessionPool;
const emitDeprecationWarning = require('../utils').emitDeprecationWarning;
const emitWarningOnce = require('../utils').emitWarningOnce;
const fs = require('fs');
const BSON = require('../core/connection/utils').retrieveBSON();
const WriteConcern = require('../write_concern');
const CMAP_EVENT_NAMES = require('../cmap/events').CMAP_EVENT_NAMES;
let client;
@@ -33,9 +34,11 @@ const legacyParse = deprecate(
const AUTH_MECHANISM_INTERNAL_MAP = {
DEFAULT: 'default',
'MONGODB-CR': 'mongocr',
PLAIN: 'plain',
GSSAPI: 'gssapi',
'MONGODB-CR': 'mongocr',
'MONGODB-X509': 'x509',
'MONGODB-AWS': 'mongodb-aws',
'SCRAM-SHA-1': 'scram-sha-1',
'SCRAM-SHA-256': 'scram-sha-256'
};
@@ -66,12 +69,13 @@ const monitoringEvents = [
const VALID_AUTH_MECHANISMS = new Set([
'DEFAULT',
'MONGODB-CR',
'PLAIN',
'GSSAPI',
'MONGODB-CR',
'MONGODB-X509',
'MONGODB-AWS',
'SCRAM-SHA-1',
'SCRAM-SHA-256',
'GSSAPI'
'SCRAM-SHA-256'
]);
const validOptionNames = [
@@ -102,6 +106,7 @@ const validOptionNames = [
'w',
'wtimeout',
'j',
'writeConcern',
'forceServerObjectId',
'serializeFunctions',
'ignoreUndefined',
@@ -117,6 +122,7 @@ const validOptionNames = [
'promoteValues',
'promoteBuffers',
'promoteLongs',
'bsonRegExp',
'domainsEnabled',
'checkServerIdentity',
'validateOptions',
@@ -132,6 +138,7 @@ const validOptionNames = [
'auto_reconnect',
'minSize',
'monitorCommands',
'serverApi',
'retryWrites',
'retryReads',
'useNewUrlParser',
@@ -151,6 +158,8 @@ const validOptionNames = [
'tlsCertificateKeyFilePassword',
'minHeartbeatFrequencyMS',
'heartbeatFrequencyMS',
'directConnection',
'appName',
// CMAP options
'maxPoolSize',
@@ -175,12 +184,12 @@ function validOptions(options) {
if (options.validateOptions) {
return new MongoError(`option ${name} is not supported`);
} else {
console.warn(`the options [${name}] is not supported`);
emitWarningOnce(`the options [${name}] is not supported`);
}
}
if (legacyOptionNames.indexOf(name) !== -1) {
console.warn(
emitWarningOnce(
`the server/replset/mongos/db options are deprecated, ` +
`all their options are supported at the top level of the options object [${validOptionNames}]`
);
@@ -250,9 +259,6 @@ function resolveTLSOptions(options) {
});
}
const emitDeprecationForNonUnifiedTopology = deprecate(() => {},
'current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. ' + 'To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.');
function connect(mongoClient, url, options, callback) {
options = Object.assign({}, options);
@@ -285,7 +291,7 @@ function connect(mongoClient, url, options, callback) {
const _finalOptions = createUnifiedOptions(object, options);
// Check if we have connection and socket timeout set
if (_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 360000;
if (_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 0;
if (_finalOptions.connectTimeoutMS == null) _finalOptions.connectTimeoutMS = 10000;
if (_finalOptions.retryWrites == null) _finalOptions.retryWrites = true;
if (_finalOptions.useRecoveryToken == null) _finalOptions.useRecoveryToken = true;
@@ -295,18 +301,16 @@ function connect(mongoClient, url, options, callback) {
delete _finalOptions.db_options.auth;
}
// `journal` should be translated to `j` for the driver
if (_finalOptions.journal != null) {
_finalOptions.j = _finalOptions.journal;
_finalOptions.journal = undefined;
}
// resolve tls options if needed
resolveTLSOptions(_finalOptions);
// Store the merged options object
mongoClient.s.options = _finalOptions;
// Apply read and write concern from parsed url
mongoClient.s.readPreference = ReadPreference.fromOptions(_finalOptions);
mongoClient.s.writeConcern = WriteConcern.fromOptions(_finalOptions);
// Failure modes
if (object.servers.length === 0) {
return callback(new Error('connection string must contain at least one seed host'));
@@ -330,7 +334,9 @@ function connect(mongoClient, url, options, callback) {
return createTopology(mongoClient, 'unified', _finalOptions, connectCallback);
}
emitDeprecationForNonUnifiedTopology();
emitWarningOnce(
'Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.'
);
// Do we have a replicaset then skip discovery and go straight to connectivity
if (_finalOptions.replicaSet || _finalOptions.rs_name) {
@@ -491,58 +497,9 @@ function createTopology(mongoClient, topologyType, options, callback) {
// determine CSFLE support
if (options.autoEncryption != null) {
let AutoEncrypter;
try {
require.resolve('mongodb-client-encryption');
} catch (err) {
callback(
new MongoError(
'Auto-encryption requested, but the module is not installed. Please add `mongodb-client-encryption` as a dependency of your project'
)
);
return;
}
try {
let mongodbClientEncryption = require('mongodb-client-encryption');
if (typeof mongodbClientEncryption.extension !== 'function') {
callback(
new MongoError(
'loaded version of `mongodb-client-encryption` does not have property `extension`. Please make sure you are loading the correct version of `mongodb-client-encryption`'
)
);
}
AutoEncrypter = mongodbClientEncryption.extension(require('../../index')).AutoEncrypter;
} catch (err) {
callback(err);
return;
}
const mongoCryptOptions = Object.assign(
{
bson:
options.bson ||
new BSON([
BSON.Binary,
BSON.Code,
BSON.DBRef,
BSON.Decimal128,
BSON.Double,
BSON.Int32,
BSON.Long,
BSON.Map,
BSON.MaxKey,
BSON.MinKey,
BSON.ObjectId,
BSON.BSONRegExp,
BSON.Symbol,
BSON.Timestamp
])
},
options.autoEncryption
);
options.autoEncrypter = new AutoEncrypter(mongoClient, mongoCryptOptions);
const Encrypter = require('../encrypter').Encrypter;
options.encrypter = new Encrypter(mongoClient, options);
options.autoEncrypter = options.encrypter.autoEncrypter;
}
// Create the topology
@@ -580,7 +537,10 @@ function createTopology(mongoClient, topologyType, options, callback) {
return;
}
callback(undefined, topology);
options.encrypter.connectInternalClient(error => {
if (error) return callback(error);
callback(undefined, topology);
});
});
});
@@ -611,9 +571,12 @@ function createUnifiedOptions(finalOptions, options) {
'mongos_options'
];
const noMerge = ['readconcern', 'compression', 'autoencryption'];
const skip = ['w', 'wtimeout', 'j', 'journal', 'fsync', 'writeconcern'];
for (const name in options) {
if (noMerge.indexOf(name.toLowerCase()) !== -1) {
if (skip.indexOf(name.toLowerCase()) !== -1) {
continue;
} else if (noMerge.indexOf(name.toLowerCase()) !== -1) {
finalOptions[name] = options[name];
} else if (childOptions.indexOf(name.toLowerCase()) !== -1) {
finalOptions = mergeOptions(finalOptions, options[name], false);
@@ -631,6 +594,14 @@ function createUnifiedOptions(finalOptions, options) {
}
}
// Handle write concern keys separately, since `options` may have the keys at the top level or
// under `options.writeConcern`. The final merged keys will be under `finalOptions.writeConcern`.
// This way, `fromOptions` will warn once if `options` is using deprecated write concern options
const optionsWriteConcern = WriteConcern.fromOptions(options);
if (optionsWriteConcern) {
finalOptions.writeConcern = Object.assign({}, finalOptions.writeConcern, optionsWriteConcern);
}
return finalOptions;
}
@@ -644,6 +615,7 @@ function generateCredentials(client, username, password, options) {
// authMechanism
const authMechanismRaw = options.authMechanism || 'DEFAULT';
const authMechanism = authMechanismRaw.toUpperCase();
const mechanismProperties = options.authMechanismProperties;
if (!VALID_AUTH_MECHANISMS.has(authMechanism)) {
throw MongoError.create({
@@ -652,18 +624,9 @@ function generateCredentials(client, username, password, options) {
});
}
if (authMechanism === 'GSSAPI') {
return new MongoCredentials({
mechanism: process.platform === 'win32' ? 'sspi' : 'gssapi',
mechanismProperties: options,
source,
username,
password
});
}
return new MongoCredentials({
mechanism: AUTH_MECHANISM_INTERNAL_MAP[authMechanism],
mechanismProperties,
source,
username,
password
@@ -763,12 +726,24 @@ function transformUrlOptions(_object) {
if (object.wTimeoutMS) {
object.wtimeout = object.wTimeoutMS;
object.wTimeoutMS = undefined;
}
if (_object.srvHost) {
object.srvHost = _object.srvHost;
}
// Any write concern options from the URL will be top-level, so we manually
// move them options under `object.writeConcern` to avoid warnings later
const wcKeys = ['w', 'wtimeout', 'j', 'journal', 'fsync'];
for (const key of wcKeys) {
if (object[key] !== undefined) {
if (object.writeConcern === undefined) object.writeConcern = {};
object.writeConcern[key] = object[key];
object[key] = undefined;
}
}
return object;
}
@@ -791,7 +766,7 @@ function translateOptions(options, translationOptions) {
}
// Set the socket and connection timeouts
if (options.socketTimeoutMS == null) options.socketTimeoutMS = 360000;
if (options.socketTimeoutMS == null) options.socketTimeoutMS = 0;
if (options.connectTimeoutMS == null) options.connectTimeoutMS = 10000;
if (!translationOptions.createServers) {

View File

@@ -4,13 +4,11 @@ const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const CommandOperation = require('./command');
const applyWriteConcern = require('../utils').applyWriteConcern;
const handleCallback = require('../utils').handleCallback;
const loadCollection = require('../dynamic_loaders').loadCollection;
const MongoError = require('../core').MongoError;
const ReadPreference = require('../core').ReadPreference;
// Filter out any write concern options
const illegalCommandFields = [
const ILLEGAL_COMMAND_FIELDS = new Set([
'w',
'wtimeout',
'j',
@@ -24,12 +22,11 @@ const illegalCommandFields = [
'session',
'readConcern',
'writeConcern'
];
]);
class CreateCollectionOperation extends CommandOperation {
constructor(db, name, options) {
super(db, options);
this.name = name;
}
@@ -37,14 +34,12 @@ class CreateCollectionOperation extends CommandOperation {
const name = this.name;
const options = this.options;
// Create collection command
const cmd = { create: name };
// Add all optional parameters
for (let n in options) {
if (
options[n] != null &&
typeof options[n] !== 'function' &&
illegalCommandFields.indexOf(n) === -1
!ILLEGAL_COMMAND_FIELDS.has(n)
) {
cmd[n] = options[n];
}
@@ -57,61 +52,51 @@ class CreateCollectionOperation extends CommandOperation {
const db = this.db;
const name = this.name;
const options = this.options;
const Collection = loadCollection();
let Collection = loadCollection();
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed()) {
return callback(new MongoError('topology was destroyed'));
}
let listCollectionOptions = Object.assign({}, options, { nameOnly: true });
let listCollectionOptions = Object.assign({ nameOnly: true, strict: false }, options);
listCollectionOptions = applyWriteConcern(listCollectionOptions, { db }, listCollectionOptions);
// Check if we have the name
db.listCollections({ name }, listCollectionOptions)
.setReadPreference(ReadPreference.PRIMARY)
.toArray((err, collections) => {
if (err != null) return handleCallback(callback, err, null);
if (collections.length > 0 && listCollectionOptions.strict) {
return handleCallback(
callback,
MongoError.create({
message: `Collection ${name} already exists. Currently in strict mode.`,
driver: true
}),
null
);
} else if (collections.length > 0) {
try {
return handleCallback(
callback,
null,
new Collection(db, db.s.topology, db.databaseName, name, db.s.pkFactory, options)
);
} catch (err) {
return handleCallback(callback, err);
}
}
function done(err) {
if (err) {
return callback(err);
}
// Execute command
super.execute(err => {
if (err) return handleCallback(callback, err);
try {
callback(
null,
new Collection(db, db.s.topology, db.databaseName, name, db.s.pkFactory, options)
);
} catch (err) {
callback(err);
}
}
try {
return handleCallback(
callback,
null,
new Collection(db, db.s.topology, db.databaseName, name, db.s.pkFactory, options)
);
} catch (err) {
return handleCallback(callback, err);
const strictMode = listCollectionOptions.strict;
if (strictMode) {
db.listCollections({ name }, listCollectionOptions)
.setReadPreference(ReadPreference.PRIMARY)
.toArray((err, collections) => {
if (err) {
return callback(err);
}
if (collections.length > 0) {
return callback(
new MongoError(`Collection ${name} already exists. Currently in strict mode.`)
);
}
super.execute(done);
});
});
return;
}
// otherwise just execute the command
super.execute(done);
}
}
defineAspects(CreateCollectionOperation, Aspect.WRITE_OPERATION);
module.exports = CreateCollectionOperation;

View File

@@ -1,92 +0,0 @@
'use strict';
const Aspect = require('./operation').Aspect;
const CommandOperation = require('./command');
const defineAspects = require('./operation').defineAspects;
const handleCallback = require('../utils').handleCallback;
const MongoError = require('../core').MongoError;
const parseIndexOptions = require('../utils').parseIndexOptions;
const keysToOmit = new Set([
'name',
'key',
'writeConcern',
'w',
'wtimeout',
'j',
'fsync',
'readPreference',
'session'
]);
class CreateIndexOperation extends CommandOperation {
constructor(db, name, fieldOrSpec, options) {
super(db, options);
// Build the index
const indexParameters = parseIndexOptions(fieldOrSpec);
// Generate the index name
const indexName = typeof options.name === 'string' ? options.name : indexParameters.name;
// Set up the index
const indexesObject = { name: indexName, key: indexParameters.fieldHash };
this.name = name;
this.fieldOrSpec = fieldOrSpec;
this.indexes = indexesObject;
}
_buildCommand() {
const options = this.options;
const name = this.name;
const indexes = this.indexes;
// merge all the options
for (let optionName in options) {
if (!keysToOmit.has(optionName)) {
indexes[optionName] = options[optionName];
}
}
// Create command, apply write concern to command
const cmd = { createIndexes: name, indexes: [indexes] };
return cmd;
}
execute(callback) {
const db = this.db;
const options = this.options;
const indexes = this.indexes;
// Get capabilities
const capabilities = db.s.topology.capabilities();
// Did the user pass in a collation, check if our write server supports it
if (options.collation && capabilities && !capabilities.commandsTakeCollation) {
// Create a new error
const error = new MongoError('server/primary/mongos does not support collation');
error.code = 67;
// Return the error
return callback(error);
}
// Ensure we have a callback
if (options.writeConcern && typeof callback !== 'function') {
throw MongoError.create({
message: 'Cannot use a writeConcern without a provided callback',
driver: true
});
}
// Attempt to run using createIndexes command
super.execute((err, result) => {
if (err == null) return handleCallback(callback, err, indexes.name);
return handleCallback(callback, err, result);
});
}
}
defineAspects(CreateIndexOperation, Aspect.WRITE_OPERATION);
module.exports = CreateIndexOperation;

View File

@@ -2,60 +2,136 @@
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const OperationBase = require('./operation').OperationBase;
const executeCommand = require('./db_ops').executeCommand;
const CommandOperationV2 = require('./command_v2');
const MongoError = require('../core').MongoError;
const ReadPreference = require('../core').ReadPreference;
const parseIndexOptions = require('../utils').parseIndexOptions;
const maxWireVersion = require('../core/utils').maxWireVersion;
class CreateIndexesOperation extends OperationBase {
constructor(collection, indexSpecs, options) {
super(options);
const VALID_INDEX_OPTIONS = new Set([
'background',
'unique',
'name',
'partialFilterExpression',
'sparse',
'expireAfterSeconds',
'storageEngine',
'collation',
// text indexes
'weights',
'default_language',
'language_override',
'textIndexVersion',
// 2d-sphere indexes
'2dsphereIndexVersion',
// 2d indexes
'bits',
'min',
'max',
// geoHaystack Indexes
'bucketSize',
// wildcard indexes
'wildcardProjection'
]);
class CreateIndexesOperation extends CommandOperationV2 {
/**
* @ignore
*/
constructor(parent, collection, indexes, options) {
super(parent, options);
this.collection = collection;
this.indexSpecs = indexSpecs;
// createIndex can be called with a variety of styles:
// coll.createIndex('a');
// coll.createIndex({ a: 1 });
// coll.createIndex([['a', 1]]);
// createIndexes is always called with an array of index spec objects
if (!Array.isArray(indexes) || Array.isArray(indexes[0])) {
this.onlyReturnNameOfCreatedIndex = true;
// TODO: remove in v4 (breaking change); make createIndex return full response as createIndexes does
const indexParameters = parseIndexOptions(indexes);
// Generate the index name
const name = typeof options.name === 'string' ? options.name : indexParameters.name;
// Set up the index
const indexSpec = { name, key: indexParameters.fieldHash };
// merge valid index options into the index spec
for (let optionName in options) {
if (VALID_INDEX_OPTIONS.has(optionName)) {
indexSpec[optionName] = options[optionName];
}
}
this.indexes = [indexSpec];
return;
}
this.indexes = indexes;
}
execute(callback) {
const coll = this.collection;
const indexSpecs = this.indexSpecs;
let options = this.options;
/**
* @ignore
*/
execute(server, callback) {
const options = this.options;
const indexes = this.indexes;
const capabilities = coll.s.topology.capabilities();
const serverWireVersion = maxWireVersion(server);
// Ensure we generate the correct name if the parameter is not set
for (let i = 0; i < indexSpecs.length; i++) {
if (indexSpecs[i].name == null) {
for (let i = 0; i < indexes.length; i++) {
// Did the user pass in a collation, check if our write server supports it
if (indexes[i].collation && serverWireVersion < 5) {
callback(
new MongoError(
`Server ${server.name}, which reports wire version ${serverWireVersion}, does not support collation`
)
);
return;
}
if (indexes[i].name == null) {
const keys = [];
// Did the user pass in a collation, check if our write server supports it
if (indexSpecs[i].collation && capabilities && !capabilities.commandsTakeCollation) {
return callback(new MongoError('server/primary/mongos does not support collation'));
}
for (let name in indexSpecs[i].key) {
keys.push(`${name}_${indexSpecs[i].key[name]}`);
for (let name in indexes[i].key) {
keys.push(`${name}_${indexes[i].key[name]}`);
}
// Set the name
indexSpecs[i].name = keys.join('_');
indexes[i].name = keys.join('_');
}
}
options = Object.assign({}, options, { readPreference: ReadPreference.PRIMARY });
const cmd = { createIndexes: this.collection, indexes };
// Execute the index
executeCommand(
coll.s.db,
{
createIndexes: coll.collectionName,
indexes: indexSpecs
},
options,
callback
);
if (options.commitQuorum != null) {
if (serverWireVersion < 9) {
callback(
new MongoError('`commitQuorum` option for `createIndexes` not supported on servers < 4.4')
);
return;
}
cmd.commitQuorum = options.commitQuorum;
}
// collation is set on each index, it should not be defined at the root
this.options.collation = undefined;
super.executeCommand(server, cmd, (err, result) => {
if (err) {
callback(err);
return;
}
callback(null, this.onlyReturnNameOfCreatedIndex ? indexes[0].name : result);
});
}
}
defineAspects(CreateIndexesOperation, Aspect.WRITE_OPERATION);
defineAspects(CreateIndexesOperation, [Aspect.WRITE_OPERATION, Aspect.EXECUTE_WITH_SELECTION]);
module.exports = CreateIndexesOperation;

View File

@@ -1,14 +1,15 @@
'use strict';
const MONGODB_ERROR_CODES = require('../error_codes').MONGODB_ERROR_CODES;
const applyWriteConcern = require('../utils').applyWriteConcern;
const Code = require('../core').BSON.Code;
const resolveReadPreference = require('../utils').resolveReadPreference;
const debugOptions = require('../utils').debugOptions;
const handleCallback = require('../utils').handleCallback;
const MongoError = require('../core').MongoError;
const parseIndexOptions = require('../utils').parseIndexOptions;
const ReadPreference = require('../core').ReadPreference;
const toError = require('../utils').toError;
const extractCommand = require('../command_utils').extractCommand;
const CONSTANTS = require('../constants');
const MongoDBNamespace = require('../utils').MongoDBNamespace;
@@ -24,6 +25,7 @@ const debugFields = [
'promoteLongs',
'promoteValues',
'promoteBuffers',
'bsonRegExp',
'bufferMaxEntries',
'numberOfRetries',
'retryMiliSeconds',
@@ -74,12 +76,12 @@ function createIndex(db, name, fieldOrSpec, options, callback) {
* 197 = 'InvalidIndexSpecificationOption' (`_id` with `background: true`)
*/
if (
err.code === 67 ||
err.code === 11000 ||
err.code === 85 ||
err.code === 86 ||
err.code === 11600 ||
err.code === 197
err.code === MONGODB_ERROR_CODES.CannotCreateIndex ||
err.code === MONGODB_ERROR_CODES.DuplicateKey ||
err.code === MONGODB_ERROR_CODES.IndexOptionsConflict ||
err.code === MONGODB_ERROR_CODES.IndexKeySpecsConflict ||
err.code === MONGODB_ERROR_CODES.InterruptedAtShutdown ||
err.code === MONGODB_ERROR_CODES.InvalidIndexSpecificationOption
) {
return handleCallback(callback, err, result);
}
@@ -146,7 +148,9 @@ function ensureIndex(db, name, fieldOrSpec, options, callback) {
// Check if the index already exists
indexInformation(db, name, finalOptions, (err, indexInformation) => {
if (err != null && err.code !== 26) return handleCallback(callback, err, null);
if (err != null && err.code !== MONGODB_ERROR_CODES.NamespaceNotFound) {
return handleCallback(callback, err, null);
}
// If the index does not exist, create it
if (indexInformation == null || !indexInformation[index_name]) {
createIndex(db, name, fieldOrSpec, options, callback);
@@ -225,17 +229,19 @@ function executeCommand(db, command, options, callback) {
const dbName = options.dbName || options.authdb || db.databaseName;
// Convert the readPreference if its not a write
options.readPreference = resolveReadPreference(db, options);
options.readPreference = ReadPreference.resolve(db, options);
// Debug information
if (db.s.logger.isDebug())
if (db.s.logger.isDebug()) {
const extractedCommand = extractCommand(command);
db.s.logger.debug(
`executing command ${JSON.stringify(
command
extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : command
)} against ${dbName}.$cmd with options [${JSON.stringify(
debugOptions(debugFields, options)
)}]`
);
}
// Execute command
db.s.topology.command(db.s.namespace.withCollection('$cmd'), command, options, (err, result) => {

View File

@@ -1,8 +1,9 @@
'use strict';
const OperationBase = require('./operation').OperationBase;
const deleteCallback = require('./common_functions').deleteCallback;
const removeDocuments = require('./common_functions').removeDocuments;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
class DeleteManyOperation extends OperationBase {
constructor(collection, filter, options) {
@@ -18,8 +19,20 @@ class DeleteManyOperation extends OperationBase {
const options = this.options;
options.single = false;
removeDocuments(coll, filter, options, (err, r) => deleteCallback(err, r, callback));
removeDocuments(coll, filter, options, (err, r) => {
if (callback == null) return;
if (err && callback) return callback(err);
if (r == null) return callback(null, { result: { ok: 1 } });
// If an explain operation was executed, don't process the server results
if (this.explain) return callback(undefined, r.result);
r.deletedCount = r.result.n;
callback(null, r);
});
}
}
defineAspects(DeleteManyOperation, [Aspect.EXPLAINABLE]);
module.exports = DeleteManyOperation;

View File

@@ -1,8 +1,9 @@
'use strict';
const OperationBase = require('./operation').OperationBase;
const deleteCallback = require('./common_functions').deleteCallback;
const removeDocuments = require('./common_functions').removeDocuments;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
class DeleteOneOperation extends OperationBase {
constructor(collection, filter, options) {
@@ -18,8 +19,20 @@ class DeleteOneOperation extends OperationBase {
const options = this.options;
options.single = true;
removeDocuments(coll, filter, options, (err, r) => deleteCallback(err, r, callback));
removeDocuments(coll, filter, options, (err, r) => {
if (callback == null) return;
if (err && callback) return callback(err);
if (r == null) return callback(null, { result: { ok: 1 } });
// If an explain operation was executed, don't process the server results
if (this.explain) return callback(undefined, r.result);
r.deletedCount = r.result.n;
callback(null, r);
});
}
}
defineAspects(DeleteOneOperation, [Aspect.EXPLAINABLE]);
module.exports = DeleteOneOperation;

View File

@@ -5,6 +5,8 @@ const defineAspects = require('./operation').defineAspects;
const CommandOperationV2 = require('./command_v2');
const decorateWithCollation = require('../utils').decorateWithCollation;
const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
const maxWireVersion = require('../core/utils').maxWireVersion;
const MongoError = require('../error').MongoError;
/**
* Return a list of distinct values for the given key across a collection.
@@ -65,13 +67,18 @@ class DistinctOperation extends CommandOperationV2 {
return callback(err, null);
}
if (this.explain && maxWireVersion(server) < 4) {
callback(new MongoError(`server does not support explain on distinct`));
return;
}
super.executeCommand(server, cmd, (err, result) => {
if (err) {
callback(err);
return;
}
callback(null, this.options.full ? result : result.values);
callback(null, this.options.full || this.explain ? result : result.values);
});
}
}
@@ -79,7 +86,8 @@ class DistinctOperation extends CommandOperationV2 {
defineAspects(DistinctOperation, [
Aspect.READ_OPERATION,
Aspect.RETRYABLE,
Aspect.EXECUTE_WITH_SELECTION
Aspect.EXECUTE_WITH_SELECTION,
Aspect.EXPLAINABLE
]);
module.exports = DistinctOperation;

View File

@@ -1,43 +1,72 @@
'use strict';
const MONGODB_ERROR_CODES = require('../error_codes').MONGODB_ERROR_CODES;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const CommandOperationV2 = require('./command_v2');
const maxWireVersion = require('../core/utils').maxWireVersion;
const CountDocumentsOperation = require('./count_documents');
class EstimatedDocumentCountOperation extends CommandOperationV2 {
constructor(collection, query, options) {
if (typeof options === 'undefined') {
options = query;
query = undefined;
}
constructor(collection, options) {
super(collection, options);
this.collection = collection;
this.collectionName = collection.s.namespace.collection;
if (query) {
this.query = query;
}
}
execute(server, callback) {
const options = this.options;
const cmd = { count: this.collectionName };
if (maxWireVersion(server) < 12) {
return this.executeLegacy(server, callback);
}
// if the user specifies a filter, use a CountDocumentsOperation instead
if (this.options.query) {
const op = new CountDocumentsOperation(this.collection, this.options.query, this.options);
return op.execute(server, callback);
}
const pipeline = [{ $collStats: { count: {} } }, { $group: { _id: 1, n: { $sum: '$count' } } }];
const cmd = { aggregate: this.collectionName, pipeline, cursor: {} };
if (this.query) {
cmd.query = this.query;
if (typeof this.options.maxTimeMS === 'number') {
cmd.maxTimeMS = this.options.maxTimeMS;
}
super.executeCommand(server, cmd, (err, response) => {
if (err && err.code !== MONGODB_ERROR_CODES.NamespaceNotFound) {
callback(err);
return;
}
callback(
undefined,
(response &&
response.cursor &&
response.cursor.firstBatch &&
response.cursor.firstBatch[0].n) ||
0
);
});
}
executeLegacy(server, callback) {
const cmd = { count: this.collectionName };
const options = this.options;
if (options.query) {
cmd.query = options.query;
}
if (options.hint) {
cmd.hint = options.hint;
}
if (typeof options.maxTimeMS === 'number') {
cmd.maxTimeMS = options.maxTimeMS;
}
if (typeof options.skip === 'number') {
cmd.skip = options.skip;
}
if (typeof options.limit === 'number') {
cmd.limit = options.limit;
}
if (options.hint) {
cmd.hint = options.hint;
}
super.executeCommand(server, cmd, (err, response) => {
if (err) {
callback(err);

View File

@@ -1,5 +1,6 @@
'use strict';
const maybePromise = require('../utils').maybePromise;
const MongoError = require('../core/error').MongoError;
const Aspect = require('./operation').Aspect;
const OperationBase = require('./operation').OperationBase;
@@ -21,7 +22,7 @@ const isUnifiedTopology = require('../core/utils').isUnifiedTopology;
* @param {Operation} operation The operation to execute
* @param {function} callback The command result callback
*/
function executeOperation(topology, operation, callback) {
function executeOperation(topology, operation, cb) {
if (topology == null) {
throw new TypeError('This method requires a valid topology instance');
}
@@ -30,64 +31,57 @@ function executeOperation(topology, operation, callback) {
throw new TypeError('This method requires a valid operation instance');
}
if (isUnifiedTopology(topology) && topology.shouldCheckForSessionSupport()) {
return selectServerForSessionSupport(topology, operation, callback);
}
const Promise = topology.s.promiseLibrary;
// The driver sessions spec mandates that we implicitly create sessions for operations
// that are not explicitly provided with a session.
let session, owner;
if (topology.hasSessionSupport()) {
if (operation.session == null) {
owner = Symbol();
session = topology.startSession({ owner });
operation.session = session;
} else if (operation.session.hasEnded) {
throw new MongoError('Use of expired sessions is not permitted');
return maybePromise(topology, cb, callback => {
if (isUnifiedTopology(topology) && topology.shouldCheckForSessionSupport()) {
// Recursive call to executeOperation after a server selection
return selectServerForSessionSupport(topology, operation, callback);
}
}
let result;
if (typeof callback !== 'function') {
result = new Promise((resolve, reject) => {
callback = (err, res) => {
if (err) return reject(err);
resolve(res);
};
});
}
function executeCallback(err, result) {
if (session && session.owner === owner) {
session.endSession();
if (operation.session === session) {
operation.clearSession();
// The driver sessions spec mandates that we implicitly create sessions for operations
// that are not explicitly provided with a session.
let session, owner;
if (topology.hasSessionSupport()) {
if (operation.session == null) {
owner = Symbol();
session = topology.startSession({ owner });
operation.session = session;
} else if (operation.session.hasEnded) {
return callback(new MongoError('Use of expired sessions is not permitted'));
}
} else if (operation.session) {
// If the user passed an explicit session and we are still, after server selection,
// trying to run against a topology that doesn't support sessions we error out.
return callback(new MongoError('Current topology does not support sessions'));
}
callback(err, result);
}
try {
if (operation.hasAspect(Aspect.EXECUTE_WITH_SELECTION)) {
executeWithServerSelection(topology, operation, executeCallback);
} else {
operation.execute(executeCallback);
}
} catch (e) {
if (session && session.owner === owner) {
session.endSession();
if (operation.session === session) {
operation.clearSession();
function executeCallback(err, result) {
if (session && session.owner === owner) {
session.endSession();
if (operation.session === session) {
operation.clearSession();
}
}
callback(err, result);
}
throw e;
}
try {
if (operation.hasAspect(Aspect.EXECUTE_WITH_SELECTION)) {
executeWithServerSelection(topology, operation, executeCallback);
} else {
operation.execute(executeCallback);
}
} catch (error) {
if (session && session.owner === owner) {
session.endSession();
if (operation.session === session) {
operation.clearSession();
}
}
return result;
callback(error);
}
});
}
function supportsRetryableReads(server) {
@@ -139,7 +133,6 @@ function executeWithServerSelection(topology, operation, callback) {
callback(err, null);
return;
}
const shouldRetryReads =
topology.s.options.retryReads !== false &&
operation.session &&
@@ -156,31 +149,16 @@ function executeWithServerSelection(topology, operation, callback) {
});
}
// TODO: This is only supported for unified topology, it should go away once
// we remove support for legacy topology types.
// The Unified Topology runs serverSelection before executing every operation
// Session support is determined by the result of a monitoring check triggered by this selection
function selectServerForSessionSupport(topology, operation, callback) {
const Promise = topology.s.promiseLibrary;
let result;
if (typeof callback !== 'function') {
result = new Promise((resolve, reject) => {
callback = (err, result) => {
if (err) return reject(err);
resolve(result);
};
});
}
topology.selectServer(ReadPreference.primaryPreferred, err => {
if (err) {
callback(err);
return;
return callback(err);
}
executeOperation(topology, operation, callback);
});
return result;
}
module.exports = executeOperation;

View File

@@ -3,7 +3,9 @@
const OperationBase = require('./operation').OperationBase;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const resolveReadPreference = require('../utils').resolveReadPreference;
const ReadPreference = require('../core').ReadPreference;
const maxWireVersion = require('../core/utils').maxWireVersion;
const MongoError = require('../core/error').MongoError;
class FindOperation extends OperationBase {
constructor(collection, ns, command, options) {
@@ -11,16 +13,28 @@ class FindOperation extends OperationBase {
this.ns = ns;
this.cmd = command;
this.readPreference = resolveReadPreference(collection, this.options);
this.readPreference = ReadPreference.resolve(collection, this.options);
}
execute(server, callback) {
// copied from `CommandOperationV2`, to be subclassed in the future
this.server = server;
const cursorState = this.cursorState || {};
// updates readPreference if setReadPreference was called on the cursor
this.readPreference = ReadPreference.resolve(this, this.options);
if (typeof this.cmd.allowDiskUse !== 'undefined' && maxWireVersion(server) < 4) {
callback(new MongoError('The `allowDiskUse` option is not supported on MongoDB < 3.2'));
return;
}
if (this.explain) {
// We need to manually ensure explain is in the options.
this.options.explain = this.explain.verbosity;
}
// TOOD: use `MongoDBNamespace` through and through
const cursorState = this.cursorState || {};
server.query(this.ns.toString(), this.cmd, cursorState, this.options, callback);
}
}
@@ -28,7 +42,8 @@ class FindOperation extends OperationBase {
defineAspects(FindOperation, [
Aspect.READ_OPERATION,
Aspect.RETRYABLE,
Aspect.EXECUTE_WITH_SELECTION
Aspect.EXECUTE_WITH_SELECTION,
Aspect.EXPLAINABLE
]);
module.exports = FindOperation;

View File

@@ -8,6 +8,11 @@ const executeCommand = require('./db_ops').executeCommand;
const formattedOrderClause = require('../utils').formattedOrderClause;
const handleCallback = require('../utils').handleCallback;
const ReadPreference = require('../core').ReadPreference;
const maxWireVersion = require('../core/utils').maxWireVersion;
const MongoError = require('../error').MongoError;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const decorateWithExplain = require('../utils').decorateWithExplain;
class FindAndModifyOperation extends OperationBase {
constructor(collection, query, sort, doc, options) {
@@ -27,7 +32,7 @@ class FindAndModifyOperation extends OperationBase {
let options = this.options;
// Create findAndModify command object
const queryObject = {
let queryObject = {
findAndModify: coll.collectionName,
query: query
};
@@ -86,6 +91,29 @@ class FindAndModifyOperation extends OperationBase {
return callback(err, null);
}
if (options.hint) {
// TODO: once this method becomes a CommandOperationV2 we will have the server
// in place to check.
const unacknowledgedWrite = options.writeConcern && options.writeConcern.w === 0;
if (unacknowledgedWrite || maxWireVersion(coll.s.topology) < 8) {
callback(
new MongoError('The current topology does not support a hint on findAndModify commands')
);
return;
}
queryObject.hint = options.hint;
}
if (this.explain) {
if (maxWireVersion(coll.s.topology) < 4) {
callback(new MongoError(`server does not support explain on findAndModify`));
return;
}
queryObject = decorateWithExplain(queryObject, this.explain);
}
// Execute the command
executeCommand(coll.s.db, queryObject, options, (err, result) => {
if (err) return handleCallback(callback, err, null);
@@ -95,4 +123,6 @@ class FindAndModifyOperation extends OperationBase {
}
}
defineAspects(FindAndModifyOperation, [Aspect.EXPLAINABLE]);
module.exports = FindAndModifyOperation;

View File

@@ -3,6 +3,8 @@
const handleCallback = require('../utils').handleCallback;
const OperationBase = require('./operation').OperationBase;
const toError = require('../utils').toError;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
class FindOneOperation extends OperationBase {
constructor(collection, query, options) {
@@ -34,4 +36,6 @@ class FindOneOperation extends OperationBase {
}
}
defineAspects(FindOneOperation, [Aspect.EXPLAINABLE]);
module.exports = FindOneOperation;

View File

@@ -9,6 +9,11 @@ class FindOneAndDeleteOperation extends FindAndModifyOperation {
finalOptions.fields = options.projection;
finalOptions.remove = true;
// Basic validation
if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}
super(collection, filter, finalOptions.sort, null, finalOptions);
}
}

View File

@@ -1,15 +1,34 @@
'use strict';
const MongoError = require('../core').MongoError;
const FindAndModifyOperation = require('./find_and_modify');
const hasAtomicOperators = require('../utils').hasAtomicOperators;
class FindOneAndReplaceOperation extends FindAndModifyOperation {
constructor(collection, filter, replacement, options) {
if ('returnDocument' in options && 'returnOriginal' in options) {
throw new MongoError(
'findOneAndReplace option returnOriginal is deprecated in favor of returnDocument and cannot be combined'
);
}
// Final options
const finalOptions = Object.assign({}, options);
finalOptions.fields = options.projection;
finalOptions.update = true;
finalOptions.new = options.returnOriginal !== void 0 ? !options.returnOriginal : false;
finalOptions.upsert = options.upsert !== void 0 ? !!options.upsert : false;
finalOptions.new = options.returnDocument === 'after' || options.returnOriginal === false;
finalOptions.upsert = options.upsert === true;
if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}
if (replacement == null || typeof replacement !== 'object') {
throw new TypeError('Replacement parameter must be an object');
}
if (hasAtomicOperators(replacement)) {
throw new TypeError('Replacement document must not contain atomic operators');
}
super(collection, filter, finalOptions.sort, replacement, finalOptions);
}

View File

@@ -1,16 +1,34 @@
'use strict';
const MongoError = require('../core').MongoError;
const FindAndModifyOperation = require('./find_and_modify');
const hasAtomicOperators = require('../utils').hasAtomicOperators;
class FindOneAndUpdateOperation extends FindAndModifyOperation {
constructor(collection, filter, update, options) {
if ('returnDocument' in options && 'returnOriginal' in options) {
throw new MongoError(
'findOneAndUpdate option returnOriginal is deprecated in favor of returnDocument and cannot be combined'
);
}
// Final options
const finalOptions = Object.assign({}, options);
finalOptions.fields = options.projection;
finalOptions.update = true;
finalOptions.new =
typeof options.returnOriginal === 'boolean' ? !options.returnOriginal : false;
finalOptions.upsert = typeof options.upsert === 'boolean' ? options.upsert : false;
finalOptions.new = options.returnDocument === 'after' || options.returnOriginal === false;
finalOptions.upsert = options.upsert === true;
if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}
if (update == null || typeof update !== 'object') {
throw new TypeError('Update parameter must be an object');
}
if (!hasAtomicOperators(update)) {
throw new TypeError('Update document requires atomic operators');
}
super(collection, filter, finalOptions.sort, update, finalOptions);
}

View File

@@ -7,7 +7,7 @@ const decorateCommand = require('../utils').decorateCommand;
const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
const executeCommand = require('./db_ops').executeCommand;
const handleCallback = require('../utils').handleCallback;
const resolveReadPreference = require('../utils').resolveReadPreference;
const ReadPreference = require('../core').ReadPreference;
const toError = require('../utils').toError;
/**
@@ -58,7 +58,7 @@ class GeoHaystackSearchOperation extends OperationBase {
options = Object.assign({}, options);
// Ensure we have the right read preference inheritance
options.readPreference = resolveReadPreference(coll, options);
options.readPreference = ReadPreference.resolve(coll, options);
// Do we have a readConcern specified
decorateWithReadConcern(commandObject, coll, options);

View File

@@ -30,11 +30,7 @@ class InsertManyOperation extends OperationBase {
docs = prepareDocs(coll, docs, options);
// Generate the bulk write operations
const operations = [
{
insertMany: docs
}
];
const operations = docs.map(document => ({ insertOne: { document } }));
const bulkWriteOperation = new BulkWriteOperation(coll, operations, options);

View File

@@ -9,10 +9,16 @@ const handleCallback = require('../utils').handleCallback;
const isObject = require('../utils').isObject;
const loadDb = require('../dynamic_loaders').loadDb;
const OperationBase = require('./operation').OperationBase;
const resolveReadPreference = require('../utils').resolveReadPreference;
const ReadPreference = require('../core').ReadPreference;
const toError = require('../utils').toError;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const decorateWithExplain = require('../utils').decorateWithExplain;
const maxWireVersion = require('../core/utils').maxWireVersion;
const MongoError = require('../error').MongoError;
const exclusionList = [
'explain',
'readPreference',
'session',
'bypassDocumentValidation',
@@ -59,8 +65,8 @@ class MapReduceOperation extends OperationBase {
const reduce = this.reduce;
let options = this.options;
const mapCommandHash = {
mapreduce: coll.collectionName,
let mapCommandHash = {
mapReduce: coll.collectionName,
map: map,
reduce: reduce
};
@@ -80,7 +86,7 @@ class MapReduceOperation extends OperationBase {
options = Object.assign({}, options);
// Ensure we have the right read preference inheritance
options.readPreference = resolveReadPreference(coll, options);
options.readPreference = ReadPreference.resolve(coll, options);
// If we have a read preference and inline is not set as output fail hard
if (
@@ -110,6 +116,14 @@ class MapReduceOperation extends OperationBase {
return callback(err, null);
}
if (this.explain) {
if (maxWireVersion(coll.s.topology) < 9) {
callback(new MongoError(`server does not support explain on mapReduce`));
return;
}
mapCommandHash = decorateWithExplain(mapCommandHash, this.explain);
}
// Execute command
executeCommand(coll.s.db, mapCommandHash, options, (err, result) => {
if (err) return handleCallback(callback, err);
@@ -118,6 +132,9 @@ class MapReduceOperation extends OperationBase {
return handleCallback(callback, toError(result));
}
// If an explain operation was executed, don't process the server results
if (this.explain) return callback(undefined, result);
// Create statistics value
const stats = {};
if (result.timeMillis) stats['processtime'] = result.timeMillis;
@@ -187,4 +204,6 @@ function processScope(scope) {
return new_scope;
}
defineAspects(MapReduceOperation, [Aspect.EXPLAINABLE]);
module.exports = MapReduceOperation;

View File

@@ -1,10 +1,15 @@
'use strict';
const Explain = require('../explain').Explain;
const MongoError = require('../core/error').MongoError;
const Aspect = {
READ_OPERATION: Symbol('READ_OPERATION'),
WRITE_OPERATION: Symbol('WRITE_OPERATION'),
RETRYABLE: Symbol('RETRYABLE'),
EXECUTE_WITH_SELECTION: Symbol('EXECUTE_WITH_SELECTION')
EXECUTE_WITH_SELECTION: Symbol('EXECUTE_WITH_SELECTION'),
NO_INHERIT_OPTIONS: Symbol('NO_INHERIT_OPTIONS'),
EXPLAINABLE: Symbol('EXPLAINABLE')
};
/**
@@ -16,6 +21,12 @@ const Aspect = {
class OperationBase {
constructor(options) {
this.options = Object.assign({}, options);
if (this.hasAspect(Aspect.EXPLAINABLE)) {
this.explain = Explain.fromOptions(options);
} else if (this.options.explain !== undefined) {
throw new MongoError(`explain is not supported on this command`);
}
}
hasAspect(aspect) {

View File

@@ -1,28 +1,33 @@
'use strict';
const CommandOperation = require('./command');
const handleCallback = require('../utils').handleCallback;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const CommandOperationV2 = require('./command_v2');
const serverType = require('../core/sdam/common').serverType;
const ServerType = require('../core/sdam/common').ServerType;
const MongoError = require('../core').MongoError;
class ReIndexOperation extends CommandOperation {
class ReIndexOperation extends CommandOperationV2 {
constructor(collection, options) {
super(collection.s.db, options, collection);
super(collection, options);
this.collectionName = collection.collectionName;
}
_buildCommand() {
const collection = this.collection;
const cmd = { reIndex: collection.collectionName };
return cmd;
}
execute(callback) {
super.execute((err, result) => {
if (callback == null) return;
if (err) return handleCallback(callback, err, null);
handleCallback(callback, null, result.ok ? true : false);
execute(server, callback) {
if (serverType(server) !== ServerType.Standalone) {
callback(new MongoError(`reIndex can only be executed on standalone servers.`));
return;
}
super.executeCommand(server, { reIndex: this.collectionName }, (err, result) => {
if (err) {
callback(err);
return;
}
callback(null, !!result.ok);
});
}
}
defineAspects(ReIndexOperation, [Aspect.EXECUTE_WITH_SELECTION]);
module.exports = ReIndexOperation;

View File

@@ -2,27 +2,34 @@
const OperationBase = require('./operation').OperationBase;
const updateDocuments = require('./common_functions').updateDocuments;
const hasAtomicOperators = require('../utils').hasAtomicOperators;
class ReplaceOneOperation extends OperationBase {
constructor(collection, filter, doc, options) {
constructor(collection, filter, replacement, options) {
super(options);
if (hasAtomicOperators(replacement)) {
throw new TypeError('Replacement document must not contain atomic operators');
}
this.collection = collection;
this.filter = filter;
this.doc = doc;
this.replacement = replacement;
}
execute(callback) {
const coll = this.collection;
const filter = this.filter;
const doc = this.doc;
const replacement = this.replacement;
const options = this.options;
// Set single document update
options.multi = false;
// Execute update
updateDocuments(coll, filter, doc, options, (err, r) => replaceCallback(err, r, doc, callback));
updateDocuments(coll, filter, replacement, options, (err, r) =>
replaceCallback(err, r, replacement, callback)
);
}
}

View File

@@ -1,13 +1,19 @@
'use strict';
const OperationBase = require('./operation').OperationBase;
const updateCallback = require('./common_functions').updateCallback;
const updateDocuments = require('./common_functions').updateDocuments;
const hasAtomicOperators = require('../utils').hasAtomicOperators;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
class UpdateManyOperation extends OperationBase {
constructor(collection, filter, update, options) {
super(options);
if (!hasAtomicOperators(update)) {
throw new TypeError('Update document requires atomic operators');
}
this.collection = collection;
this.filter = filter;
this.update = update;
@@ -22,8 +28,28 @@ class UpdateManyOperation extends OperationBase {
// Set single document update
options.multi = true;
// Execute update
updateDocuments(coll, filter, update, options, (err, r) => updateCallback(err, r, callback));
updateDocuments(coll, filter, update, options, (err, r) => {
if (callback == null) return;
if (err) return callback(err);
if (r == null) return callback(null, { result: { ok: 1 } });
// If an explain operation was executed, don't process the server results
if (this.explain) return callback(undefined, r.result);
r.modifiedCount = r.result.nModified != null ? r.result.nModified : r.result.n;
r.upsertedId =
Array.isArray(r.result.upserted) && r.result.upserted.length > 0
? r.result.upserted[0] // FIXME(major): should be `r.result.upserted[0]._id`
: null;
r.upsertedCount =
Array.isArray(r.result.upserted) && r.result.upserted.length ? r.result.upserted.length : 0;
r.matchedCount =
Array.isArray(r.result.upserted) && r.result.upserted.length > 0 ? 0 : r.result.n;
callback(null, r);
});
}
}
defineAspects(UpdateManyOperation, [Aspect.EXPLAINABLE]);
module.exports = UpdateManyOperation;

View File

@@ -2,11 +2,18 @@
const OperationBase = require('./operation').OperationBase;
const updateDocuments = require('./common_functions').updateDocuments;
const hasAtomicOperators = require('../utils').hasAtomicOperators;
const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
class UpdateOneOperation extends OperationBase {
constructor(collection, filter, update, options) {
super(options);
if (!hasAtomicOperators(update)) {
throw new TypeError('Update document requires atomic operators');
}
this.collection = collection;
this.filter = filter;
this.update = update;
@@ -21,24 +28,28 @@ class UpdateOneOperation extends OperationBase {
// Set single document update
options.multi = false;
// Execute update
updateDocuments(coll, filter, update, options, (err, r) => updateCallback(err, r, callback));
updateDocuments(coll, filter, update, options, (err, r) => {
if (callback == null) return;
if (err) return callback(err);
if (r == null) return callback(null, { result: { ok: 1 } });
// If an explain operation was executed, don't process the server results
if (this.explain) return callback(undefined, r.result);
r.modifiedCount = r.result.nModified != null ? r.result.nModified : r.result.n;
r.upsertedId =
Array.isArray(r.result.upserted) && r.result.upserted.length > 0
? r.result.upserted[0] // FIXME(major): should be `r.result.upserted[0]._id`
: null;
r.upsertedCount =
Array.isArray(r.result.upserted) && r.result.upserted.length ? r.result.upserted.length : 0;
r.matchedCount =
Array.isArray(r.result.upserted) && r.result.upserted.length > 0 ? 0 : r.result.n;
callback(null, r);
});
}
}
function updateCallback(err, r, callback) {
if (callback == null) return;
if (err) return callback(err);
if (r == null) return callback(null, { result: { ok: 1 } });
r.modifiedCount = r.result.nModified != null ? r.result.nModified : r.result.n;
r.upsertedId =
Array.isArray(r.result.upserted) && r.result.upserted.length > 0
? r.result.upserted[0] // FIXME(major): should be `r.result.upserted[0]._id`
: null;
r.upsertedCount =
Array.isArray(r.result.upserted) && r.result.upserted.length ? r.result.upserted.length : 0;
r.matchedCount =
Array.isArray(r.result.upserted) && r.result.upserted.length > 0 ? 0 : r.result.n;
callback(null, r);
}
defineAspects(UpdateOneOperation, [Aspect.EXPLAINABLE]);
module.exports = UpdateOneOperation;

View File

@@ -8,14 +8,13 @@ class ValidateCollectionOperation extends CommandOperation {
let command = { validate: collectionName };
const keys = Object.keys(options);
for (let i = 0; i < keys.length; i++) {
if (options.hasOwnProperty(keys[i]) && keys[i] !== 'session') {
if (Object.prototype.hasOwnProperty.call(options, keys[i]) && keys[i] !== 'session') {
command[keys[i]] = options[keys[i]];
}
}
super(admin.s.db, options, null, command);
this.collectionName;
this.collectionName = collectionName;
}
execute(callback) {