Update Node_modules
This commit is contained in:
-368
@@ -1,368 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var copy = require('../connection/utils').copy,
|
||||
retrieveBSON = require('../connection/utils').retrieveBSON,
|
||||
KillCursor = require('../connection/commands').KillCursor,
|
||||
GetMore = require('../connection/commands').GetMore,
|
||||
Query = require('../connection/commands').Query,
|
||||
f = require('util').format,
|
||||
MongoError = require('../error').MongoError,
|
||||
getReadPreference = require('./shared').getReadPreference;
|
||||
|
||||
var BSON = retrieveBSON(),
|
||||
Long = BSON.Long;
|
||||
|
||||
var WireProtocol = function() {};
|
||||
|
||||
//
|
||||
// Execute a write operation
|
||||
var executeWrite = function(pool, bson, type, opsField, ns, ops, options, callback) {
|
||||
if (ops.length === 0) throw new MongoError('insert must contain at least one document');
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
options = options || {};
|
||||
}
|
||||
|
||||
// Split the ns up to get db and collection
|
||||
var p = ns.split('.');
|
||||
var d = p.shift();
|
||||
// Options
|
||||
var ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
|
||||
var writeConcern = options.writeConcern;
|
||||
|
||||
// return skeleton
|
||||
var writeCommand = {};
|
||||
writeCommand[type] = p.join('.');
|
||||
writeCommand[opsField] = ops;
|
||||
writeCommand.ordered = ordered;
|
||||
|
||||
// Did we specify a write concern
|
||||
if (writeConcern && Object.keys(writeConcern).length > 0) {
|
||||
writeCommand.writeConcern = writeConcern;
|
||||
}
|
||||
|
||||
// Do we have bypassDocumentValidation set, then enable it on the write command
|
||||
if (options.bypassDocumentValidation === true) {
|
||||
writeCommand.bypassDocumentValidation = options.bypassDocumentValidation;
|
||||
}
|
||||
|
||||
// Options object
|
||||
var opts = { command: true };
|
||||
if (typeof options.session !== 'undefined') opts.session = options.session;
|
||||
var queryOptions = { checkKeys: false, numberToSkip: 0, numberToReturn: 1 };
|
||||
if (type === 'insert') queryOptions.checkKeys = true;
|
||||
if (typeof options.checkKeys === 'boolean') queryOptions.checkKeys = options.checkKeys;
|
||||
// Ensure we support serialization of functions
|
||||
if (options.serializeFunctions) queryOptions.serializeFunctions = options.serializeFunctions;
|
||||
// Do not serialize the undefined fields
|
||||
if (options.ignoreUndefined) queryOptions.ignoreUndefined = options.ignoreUndefined;
|
||||
|
||||
try {
|
||||
// Create write command
|
||||
var cmd = new Query(bson, f('%s.$cmd', d), writeCommand, queryOptions);
|
||||
// Execute command
|
||||
pool.write(cmd, opts, callback);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Needs to support legacy mass insert as well as ordered/unordered legacy
|
||||
// emulation
|
||||
//
|
||||
WireProtocol.prototype.insert = function(pool, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'insert', 'documents', ns, ops, options, callback);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.update = function(pool, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'update', 'updates', ns, ops, options, callback);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.remove = function(pool, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'delete', 'deletes', ns, ops, options, callback);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.killCursor = function(bson, ns, cursorState, pool, callback) {
|
||||
var cursorId = cursorState.cursorId;
|
||||
// Create a kill cursor command
|
||||
var killCursor = new KillCursor(bson, ns, [cursorId]);
|
||||
|
||||
// Build killCursor options
|
||||
const options = {
|
||||
immediateRelease: true,
|
||||
noResponse: true
|
||||
};
|
||||
|
||||
if (typeof cursorState.session === 'object') {
|
||||
options.session = cursorState.session;
|
||||
}
|
||||
|
||||
// Execute the kill cursor command
|
||||
if (pool && pool.isConnected()) {
|
||||
try {
|
||||
pool.write(killCursor, options, callback);
|
||||
} catch (err) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(err, null);
|
||||
} else {
|
||||
console.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WireProtocol.prototype.getMore = function(
|
||||
bson,
|
||||
ns,
|
||||
cursorState,
|
||||
batchSize,
|
||||
raw,
|
||||
connection,
|
||||
options,
|
||||
callback
|
||||
) {
|
||||
// Create getMore command
|
||||
var getMore = new GetMore(bson, ns, cursorState.cursorId, { numberToReturn: batchSize });
|
||||
|
||||
// Query callback
|
||||
var queryCallback = function(err, result) {
|
||||
if (err) return callback(err);
|
||||
// Get the raw message
|
||||
var r = result.message;
|
||||
|
||||
// If we have a timed out query or a cursor that was killed
|
||||
if ((r.responseFlags & (1 << 0)) !== 0) {
|
||||
return callback(new MongoError('cursor does not exist, was killed or timed out'), null);
|
||||
}
|
||||
|
||||
// Ensure we have a Long valie cursor id
|
||||
var cursorId = typeof r.cursorId === 'number' ? Long.fromNumber(r.cursorId) : r.cursorId;
|
||||
|
||||
// Set all the values
|
||||
cursorState.documents = r.documents;
|
||||
cursorState.cursorId = cursorId;
|
||||
|
||||
// Return
|
||||
callback(null, null, r.connection);
|
||||
};
|
||||
|
||||
// Contains any query options
|
||||
var queryOptions = {};
|
||||
|
||||
// If we have a raw query decorate the function
|
||||
if (raw) {
|
||||
queryOptions.raw = raw;
|
||||
}
|
||||
|
||||
// Check if we need to promote longs
|
||||
if (typeof cursorState.promoteLongs === 'boolean') {
|
||||
queryOptions.promoteLongs = cursorState.promoteLongs;
|
||||
}
|
||||
|
||||
if (typeof cursorState.promoteValues === 'boolean') {
|
||||
queryOptions.promoteValues = cursorState.promoteValues;
|
||||
}
|
||||
|
||||
if (typeof cursorState.promoteBuffers === 'boolean') {
|
||||
queryOptions.promoteBuffers = cursorState.promoteBuffers;
|
||||
}
|
||||
|
||||
if (typeof cursorState.session === 'object') {
|
||||
queryOptions.session = cursorState.session;
|
||||
}
|
||||
|
||||
// Write out the getMore command
|
||||
connection.write(getMore, queryOptions, queryCallback);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.command = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Establish type of command
|
||||
if (cmd.find) {
|
||||
return setupClassicFind(bson, ns, cmd, cursorState, topology, options);
|
||||
} else if (cursorState.cursorId != null) {
|
||||
return;
|
||||
} else if (cmd) {
|
||||
return setupCommand(bson, ns, cmd, cursorState, topology, options);
|
||||
} else {
|
||||
throw new MongoError(f('command %s does not return a cursor', JSON.stringify(cmd)));
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Execute a find command
|
||||
var setupClassicFind = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Ensure we have at least some options
|
||||
options = options || {};
|
||||
// Get the readPreference
|
||||
var readPreference = getReadPreference(cmd, options);
|
||||
// Set the optional batchSize
|
||||
cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
|
||||
var numberToReturn = 0;
|
||||
|
||||
// Unpack the limit and batchSize values
|
||||
if (cursorState.limit === 0) {
|
||||
numberToReturn = cursorState.batchSize;
|
||||
} else if (
|
||||
cursorState.limit < 0 ||
|
||||
cursorState.limit < cursorState.batchSize ||
|
||||
(cursorState.limit > 0 && cursorState.batchSize === 0)
|
||||
) {
|
||||
numberToReturn = cursorState.limit;
|
||||
} else {
|
||||
numberToReturn = cursorState.batchSize;
|
||||
}
|
||||
|
||||
var numberToSkip = cursorState.skip || 0;
|
||||
// Build actual find command
|
||||
var findCmd = {};
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if (topology.type === 'mongos' && readPreference) {
|
||||
findCmd['$readPreference'] = readPreference.toJSON();
|
||||
}
|
||||
|
||||
// Add special modifiers to the query
|
||||
if (cmd.sort) findCmd['$orderby'] = cmd.sort;
|
||||
if (cmd.hint) findCmd['$hint'] = cmd.hint;
|
||||
if (cmd.snapshot) findCmd['$snapshot'] = cmd.snapshot;
|
||||
if (typeof cmd.returnKey !== 'undefined') findCmd['$returnKey'] = cmd.returnKey;
|
||||
if (cmd.maxScan) findCmd['$maxScan'] = cmd.maxScan;
|
||||
if (cmd.min) findCmd['$min'] = cmd.min;
|
||||
if (cmd.max) findCmd['$max'] = cmd.max;
|
||||
if (typeof cmd.showDiskLoc !== 'undefined') findCmd['$showDiskLoc'] = cmd.showDiskLoc;
|
||||
if (cmd.comment) findCmd['$comment'] = cmd.comment;
|
||||
if (cmd.maxTimeMS) findCmd['$maxTimeMS'] = cmd.maxTimeMS;
|
||||
|
||||
if (cmd.explain) {
|
||||
// nToReturn must be 0 (match all) or negative (match N and close cursor)
|
||||
// nToReturn > 0 will give explain results equivalent to limit(0)
|
||||
numberToReturn = -Math.abs(cmd.limit || 0);
|
||||
findCmd['$explain'] = true;
|
||||
}
|
||||
|
||||
// Add the query
|
||||
findCmd['$query'] = cmd.query;
|
||||
|
||||
// Throw on majority readConcern passed in
|
||||
if (cmd.readConcern && cmd.readConcern.level !== 'local') {
|
||||
throw new MongoError(
|
||||
f('server find command does not support a readConcern level of %s', cmd.readConcern.level)
|
||||
);
|
||||
}
|
||||
|
||||
// Remove readConcern, ensure no failing commands
|
||||
if (cmd.readConcern) {
|
||||
cmd = copy(cmd);
|
||||
delete cmd['readConcern'];
|
||||
}
|
||||
|
||||
// Serialize functions
|
||||
var serializeFunctions =
|
||||
typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
|
||||
var ignoreUndefined =
|
||||
typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, ns, findCmd, {
|
||||
numberToSkip: numberToSkip,
|
||||
numberToReturn: numberToReturn,
|
||||
pre32Limit: typeof cmd.limit !== 'undefined' ? cmd.limit : undefined,
|
||||
checkKeys: false,
|
||||
returnFieldSelector: cmd.fields,
|
||||
serializeFunctions: serializeFunctions,
|
||||
ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Set up the option bits for wire protocol
|
||||
if (typeof cmd.tailable === 'boolean') {
|
||||
query.tailable = cmd.tailable;
|
||||
}
|
||||
|
||||
if (typeof cmd.oplogReplay === 'boolean') {
|
||||
query.oplogReplay = cmd.oplogReplay;
|
||||
}
|
||||
|
||||
if (typeof cmd.noCursorTimeout === 'boolean') {
|
||||
query.noCursorTimeout = cmd.noCursorTimeout;
|
||||
}
|
||||
|
||||
if (typeof cmd.awaitData === 'boolean') {
|
||||
query.awaitData = cmd.awaitData;
|
||||
}
|
||||
|
||||
if (typeof cmd.partial === 'boolean') {
|
||||
query.partial = cmd.partial;
|
||||
}
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
};
|
||||
|
||||
//
|
||||
// Set up a command cursor
|
||||
var setupCommand = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Set empty options object
|
||||
options = options || {};
|
||||
// Get the readPreference
|
||||
var readPreference = getReadPreference(cmd, options);
|
||||
|
||||
// Final query
|
||||
var finalCmd = {};
|
||||
for (var name in cmd) {
|
||||
finalCmd[name] = cmd[name];
|
||||
}
|
||||
|
||||
// Build command namespace
|
||||
var parts = ns.split(/\./);
|
||||
|
||||
// Serialize functions
|
||||
var serializeFunctions =
|
||||
typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
|
||||
|
||||
var ignoreUndefined =
|
||||
typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
|
||||
|
||||
// Throw on majority readConcern passed in
|
||||
if (cmd.readConcern && cmd.readConcern.level !== 'local') {
|
||||
throw new MongoError(
|
||||
f(
|
||||
'server %s command does not support a readConcern level of %s',
|
||||
JSON.stringify(cmd),
|
||||
cmd.readConcern.level
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Remove readConcern, ensure no failing commands
|
||||
if (cmd.readConcern) delete cmd['readConcern'];
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if (topology.type === 'mongos' && readPreference && readPreference.preference !== 'primary') {
|
||||
finalCmd = {
|
||||
$query: finalCmd,
|
||||
$readPreference: readPreference.toJSON()
|
||||
};
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
var query = new Query(bson, f('%s.$cmd', parts.shift()), finalCmd, {
|
||||
numberToSkip: 0,
|
||||
numberToReturn: -1,
|
||||
checkKeys: false,
|
||||
serializeFunctions: serializeFunctions,
|
||||
ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
};
|
||||
|
||||
module.exports = WireProtocol;
|
||||
-682
@@ -1,682 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const Query = require('../connection/commands').Query;
|
||||
const retrieveBSON = require('../connection/utils').retrieveBSON;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
const getReadPreference = require('./shared').getReadPreference;
|
||||
const BSON = retrieveBSON();
|
||||
const Long = BSON.Long;
|
||||
const ReadPreference = require('../topologies/read_preference');
|
||||
const TxnState = require('../transactions').TxnState;
|
||||
|
||||
const WireProtocol = function() {};
|
||||
|
||||
function isTransactionCommand(command) {
|
||||
return !!(command.commitTransaction || command.abortTransaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally decorate a command with sessions specific keys
|
||||
*
|
||||
* @param {Object} command the command to decorate
|
||||
* @param {ClientSession} session the session tracking transaction state
|
||||
* @param {Object} [options] Optional settings passed to calling operation
|
||||
* @param {Function} [callback] Optional callback passed from calling operation
|
||||
* @return {MongoError|null} An error, if some error condition was met
|
||||
*/
|
||||
function decorateWithSessionsData(command, session, options) {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
// first apply non-transaction-specific sessions data
|
||||
const serverSession = session.serverSession;
|
||||
const inTransaction = session.inTransaction() || isTransactionCommand(command);
|
||||
const isRetryableWrite = options.willRetryWrite;
|
||||
|
||||
if (serverSession.txnNumber && (isRetryableWrite || inTransaction)) {
|
||||
command.txnNumber = BSON.Long.fromNumber(serverSession.txnNumber);
|
||||
}
|
||||
|
||||
// now attempt to apply transaction-specific sessions data
|
||||
if (!inTransaction) {
|
||||
if (session.transaction.state !== TxnState.NO_TRANSACTION) {
|
||||
session.transaction.transition(TxnState.NO_TRANSACTION);
|
||||
}
|
||||
|
||||
// for causal consistency
|
||||
if (session.supports.causalConsistency && session.operationTime) {
|
||||
command.readConcern = command.readConcern || {};
|
||||
Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.readPreference && !options.readPreference.equals(ReadPreference.primary)) {
|
||||
return new MongoError(
|
||||
`Read preference in a transaction must be primary, not: ${options.readPreference.mode}`
|
||||
);
|
||||
}
|
||||
|
||||
// `autocommit` must always be false to differentiate from retryable writes
|
||||
command.autocommit = false;
|
||||
|
||||
if (session.transaction.state === TxnState.STARTING_TRANSACTION) {
|
||||
session.transaction.transition(TxnState.TRANSACTION_IN_PROGRESS);
|
||||
command.startTransaction = true;
|
||||
|
||||
const readConcern =
|
||||
session.transaction.options.readConcern || session.clientOptions.readConcern;
|
||||
if (readConcern) {
|
||||
command.readConcern = readConcern;
|
||||
}
|
||||
|
||||
if (session.supports.causalConsistency && session.operationTime) {
|
||||
command.readConcern = command.readConcern || {};
|
||||
Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Execute a write operation
|
||||
function executeWrite(pool, bson, type, opsField, ns, ops, options, callback) {
|
||||
if (ops.length === 0) throw new MongoError('insert must contain at least one document');
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
options = options || {};
|
||||
}
|
||||
|
||||
// Split the ns up to get db and collection
|
||||
const p = ns.split('.');
|
||||
const d = p.shift();
|
||||
// Options
|
||||
const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
|
||||
const writeConcern = options.writeConcern;
|
||||
|
||||
// return skeleton
|
||||
const writeCommand = {};
|
||||
writeCommand[type] = p.join('.');
|
||||
writeCommand[opsField] = ops;
|
||||
writeCommand.ordered = ordered;
|
||||
|
||||
// Did we specify a write concern
|
||||
if (writeConcern && Object.keys(writeConcern).length > 0) {
|
||||
writeCommand.writeConcern = writeConcern;
|
||||
}
|
||||
|
||||
// If we have collation passed in
|
||||
if (options.collation) {
|
||||
for (let i = 0; i < writeCommand[opsField].length; i++) {
|
||||
if (!writeCommand[opsField][i].collation) {
|
||||
writeCommand[opsField][i].collation = options.collation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have bypassDocumentValidation set, then enable it on the write command
|
||||
if (options.bypassDocumentValidation === true) {
|
||||
writeCommand.bypassDocumentValidation = options.bypassDocumentValidation;
|
||||
}
|
||||
|
||||
// optionally decorate command with transactions data
|
||||
const err = decorateWithSessionsData(writeCommand, options.session, options, callback);
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
// Options object
|
||||
const opts = { command: true };
|
||||
if (typeof options.session !== 'undefined') opts.session = options.session;
|
||||
const queryOptions = { checkKeys: false, numberToSkip: 0, numberToReturn: 1 };
|
||||
if (type === 'insert') queryOptions.checkKeys = true;
|
||||
if (typeof options.checkKeys === 'boolean') queryOptions.checkKeys = options.checkKeys;
|
||||
|
||||
// Ensure we support serialization of functions
|
||||
if (options.serializeFunctions) queryOptions.serializeFunctions = options.serializeFunctions;
|
||||
// Do not serialize the undefined fields
|
||||
if (options.ignoreUndefined) queryOptions.ignoreUndefined = options.ignoreUndefined;
|
||||
|
||||
try {
|
||||
// Create write command
|
||||
const cmd = new Query(bson, `${d}.$cmd`, writeCommand, queryOptions);
|
||||
// Execute command
|
||||
pool.write(cmd, opts, callback);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Needs to support legacy mass insert as well as ordered/unordered legacy
|
||||
// emulation
|
||||
//
|
||||
WireProtocol.prototype.insert = function(pool, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'insert', 'documents', ns, ops, options, callback);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.update = function(pool, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'update', 'updates', ns, ops, options, callback);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.remove = function(pool, ns, bson, ops, options, callback) {
|
||||
executeWrite(pool, bson, 'delete', 'deletes', ns, ops, options, callback);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.killCursor = function(bson, ns, cursorState, pool, callback) {
|
||||
// Build command namespace
|
||||
const parts = ns.split(/\./);
|
||||
// Command namespace
|
||||
const commandns = `${parts.shift()}.$cmd`;
|
||||
const cursorId = cursorState.cursorId;
|
||||
// Create killCursor command
|
||||
const killcursorCmd = {
|
||||
killCursors: parts.join('.'),
|
||||
cursors: [cursorId]
|
||||
};
|
||||
|
||||
// Build Query object
|
||||
const query = new Query(bson, commandns, killcursorCmd, {
|
||||
numberToSkip: 0,
|
||||
numberToReturn: -1,
|
||||
checkKeys: false,
|
||||
returnFieldSelector: null
|
||||
});
|
||||
|
||||
// Kill cursor callback
|
||||
function killCursorCallback(err, result) {
|
||||
if (err) {
|
||||
if (typeof callback !== 'function') return;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Result
|
||||
const r = result.message;
|
||||
// If we have a timed out query or a cursor that was killed
|
||||
if ((r.responseFlags & (1 << 0)) !== 0) {
|
||||
if (typeof callback !== 'function') return;
|
||||
return callback(new MongoNetworkError('cursor killed or timed out'), null);
|
||||
}
|
||||
|
||||
if (!Array.isArray(r.documents) || r.documents.length === 0) {
|
||||
if (typeof callback !== 'function') return;
|
||||
return callback(
|
||||
new MongoError(`invalid killCursors result returned for cursor id ${cursorId}`)
|
||||
);
|
||||
}
|
||||
|
||||
// Return the result
|
||||
if (typeof callback === 'function') {
|
||||
callback(null, r.documents[0]);
|
||||
}
|
||||
}
|
||||
|
||||
const options = { command: true };
|
||||
if (typeof cursorState.session === 'object') {
|
||||
options.session = cursorState.session;
|
||||
}
|
||||
|
||||
// Execute the kill cursor command
|
||||
if (pool && pool.isConnected()) {
|
||||
try {
|
||||
pool.write(query, options, killCursorCallback);
|
||||
} catch (err) {
|
||||
killCursorCallback(err, null);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Callback
|
||||
if (typeof callback === 'function') callback(null, null);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.getMore = function(
|
||||
bson,
|
||||
ns,
|
||||
cursorState,
|
||||
batchSize,
|
||||
raw,
|
||||
connection,
|
||||
options,
|
||||
callback
|
||||
) {
|
||||
options = options || {};
|
||||
// Build command namespace
|
||||
const parts = ns.split(/\./);
|
||||
// Command namespace
|
||||
const commandns = `${parts.shift()}.$cmd`;
|
||||
|
||||
// Create getMore command
|
||||
const getMoreCmd = {
|
||||
getMore: cursorState.cursorId,
|
||||
collection: parts.join('.'),
|
||||
batchSize: Math.abs(batchSize)
|
||||
};
|
||||
|
||||
// optionally decorate command with transactions data
|
||||
const err = decorateWithSessionsData(getMoreCmd, options.session, options, callback);
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
if (cursorState.cmd.tailable && typeof cursorState.cmd.maxAwaitTimeMS === 'number') {
|
||||
getMoreCmd.maxTimeMS = cursorState.cmd.maxAwaitTimeMS;
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
const query = new Query(bson, commandns, getMoreCmd, {
|
||||
numberToSkip: 0,
|
||||
numberToReturn: -1,
|
||||
checkKeys: false,
|
||||
returnFieldSelector: null
|
||||
});
|
||||
|
||||
// Query callback
|
||||
function queryCallback(err, result) {
|
||||
if (err) return callback(err);
|
||||
// Get the raw message
|
||||
const r = result.message;
|
||||
|
||||
// If we have a timed out query or a cursor that was killed
|
||||
if ((r.responseFlags & (1 << 0)) !== 0) {
|
||||
return callback(new MongoNetworkError('cursor killed or timed out'), null);
|
||||
}
|
||||
|
||||
// Raw, return all the extracted documents
|
||||
if (raw) {
|
||||
cursorState.documents = r.documents;
|
||||
cursorState.cursorId = r.cursorId;
|
||||
return callback(null, r.documents);
|
||||
}
|
||||
|
||||
// We have an error detected
|
||||
if (r.documents[0].ok === 0) {
|
||||
return callback(new MongoError(r.documents[0]));
|
||||
}
|
||||
|
||||
// Ensure we have a Long valid cursor id
|
||||
const cursorId =
|
||||
typeof r.documents[0].cursor.id === 'number'
|
||||
? Long.fromNumber(r.documents[0].cursor.id)
|
||||
: r.documents[0].cursor.id;
|
||||
|
||||
// Set all the values
|
||||
cursorState.documents = r.documents[0].cursor.nextBatch;
|
||||
cursorState.cursorId = cursorId;
|
||||
|
||||
// Return the result
|
||||
callback(null, r.documents[0], r.connection);
|
||||
}
|
||||
|
||||
// Query options
|
||||
const queryOptions = { command: true };
|
||||
|
||||
// If we have a raw query decorate the function
|
||||
if (raw) {
|
||||
queryOptions.raw = raw;
|
||||
}
|
||||
|
||||
// Add the result field needed
|
||||
queryOptions.documentsReturnedIn = 'nextBatch';
|
||||
|
||||
// Check if we need to promote longs
|
||||
if (typeof cursorState.promoteLongs === 'boolean') {
|
||||
queryOptions.promoteLongs = cursorState.promoteLongs;
|
||||
}
|
||||
|
||||
if (typeof cursorState.promoteValues === 'boolean') {
|
||||
queryOptions.promoteValues = cursorState.promoteValues;
|
||||
}
|
||||
|
||||
if (typeof cursorState.promoteBuffers === 'boolean') {
|
||||
queryOptions.promoteBuffers = cursorState.promoteBuffers;
|
||||
}
|
||||
|
||||
if (typeof cursorState.session === 'object') {
|
||||
queryOptions.session = cursorState.session;
|
||||
}
|
||||
|
||||
// Write out the getMore command
|
||||
connection.write(query, queryOptions, queryCallback);
|
||||
};
|
||||
|
||||
WireProtocol.prototype.command = function(bson, ns, cmd, cursorState, topology, options) {
|
||||
options = options || {};
|
||||
// Check if this is a wire protocol command or not
|
||||
const wireProtocolCommand =
|
||||
typeof options.wireProtocolCommand === 'boolean' ? options.wireProtocolCommand : true;
|
||||
|
||||
// Establish type of command
|
||||
let query;
|
||||
if (cmd.find && wireProtocolCommand) {
|
||||
// Create the find command
|
||||
query = executeFindCommand(bson, ns, cmd, cursorState, topology, options);
|
||||
|
||||
// Mark the cmd as virtual
|
||||
cmd.virtual = false;
|
||||
// Signal the documents are in the firstBatch value
|
||||
query.documentsReturnedIn = 'firstBatch';
|
||||
} else if (cursorState.cursorId != null) {
|
||||
return;
|
||||
} else if (cmd) {
|
||||
query = setupCommand(bson, ns, cmd, cursorState, topology, options);
|
||||
} else {
|
||||
return new MongoError(`command ${JSON.stringify(cmd)} does not return a cursor`);
|
||||
}
|
||||
|
||||
if (query instanceof MongoError) {
|
||||
return query;
|
||||
}
|
||||
|
||||
// optionally decorate query with transaction data
|
||||
const err = decorateWithSessionsData(query.query, options.session, options);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
// // Command
|
||||
// {
|
||||
// find: ns
|
||||
// , query: <object>
|
||||
// , limit: <n>
|
||||
// , fields: <object>
|
||||
// , skip: <n>
|
||||
// , hint: <string>
|
||||
// , explain: <boolean>
|
||||
// , snapshot: <boolean>
|
||||
// , batchSize: <n>
|
||||
// , returnKey: <boolean>
|
||||
// , maxScan: <n>
|
||||
// , min: <n>
|
||||
// , max: <n>
|
||||
// , showDiskLoc: <boolean>
|
||||
// , comment: <string>
|
||||
// , maxTimeMS: <n>
|
||||
// , raw: <boolean>
|
||||
// , readPreference: <ReadPreference>
|
||||
// , tailable: <boolean>
|
||||
// , oplogReplay: <boolean>
|
||||
// , noCursorTimeout: <boolean>
|
||||
// , awaitdata: <boolean>
|
||||
// , exhaust: <boolean>
|
||||
// , partial: <boolean>
|
||||
// }
|
||||
|
||||
// FIND/GETMORE SPEC
|
||||
// {
|
||||
// “find”: <string>,
|
||||
// “filter”: { ... },
|
||||
// “sort”: { ... },
|
||||
// “projection”: { ... },
|
||||
// “hint”: { ... },
|
||||
// “skip”: <int>,
|
||||
// “limit”: <int>,
|
||||
// “batchSize”: <int>,
|
||||
// “singleBatch”: <bool>,
|
||||
// “comment”: <string>,
|
||||
// “maxScan”: <int>,
|
||||
// “maxTimeMS”: <int>,
|
||||
// “max”: { ... },
|
||||
// “min”: { ... },
|
||||
// “returnKey”: <bool>,
|
||||
// “showRecordId”: <bool>,
|
||||
// “snapshot”: <bool>,
|
||||
// “tailable”: <bool>,
|
||||
// “oplogReplay”: <bool>,
|
||||
// “noCursorTimeout”: <bool>,
|
||||
// “awaitData”: <bool>,
|
||||
// “partial”: <bool>,
|
||||
// “$readPreference”: { ... }
|
||||
// }
|
||||
|
||||
//
|
||||
// Execute a find command
|
||||
function executeFindCommand(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Ensure we have at least some options
|
||||
options = options || {};
|
||||
// Get the readPreference
|
||||
const readPreference = getReadPreference(cmd, options);
|
||||
|
||||
// Set the optional batchSize
|
||||
cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
|
||||
|
||||
// Build command namespace
|
||||
const parts = ns.split(/\./);
|
||||
// Command namespace
|
||||
const commandns = `${parts.shift()}.$cmd`;
|
||||
|
||||
// Build actual find command
|
||||
let findCmd = {
|
||||
find: parts.join('.')
|
||||
};
|
||||
|
||||
// I we provided a filter
|
||||
if (cmd.query) {
|
||||
// Check if the user is passing in the $query parameter
|
||||
if (cmd.query['$query']) {
|
||||
findCmd.filter = cmd.query['$query'];
|
||||
} else {
|
||||
findCmd.filter = cmd.query;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort value
|
||||
let sortValue = cmd.sort;
|
||||
|
||||
// Handle issue of sort being an Array
|
||||
if (Array.isArray(sortValue)) {
|
||||
const sortObject = {};
|
||||
|
||||
if (sortValue.length > 0 && !Array.isArray(sortValue[0])) {
|
||||
let sortDirection = sortValue[1];
|
||||
// Translate the sort order text
|
||||
if (sortDirection === 'asc') {
|
||||
sortDirection = 1;
|
||||
} else if (sortDirection === 'desc') {
|
||||
sortDirection = -1;
|
||||
}
|
||||
|
||||
// Set the sort order
|
||||
sortObject[sortValue[0]] = sortDirection;
|
||||
} else {
|
||||
for (var i = 0; i < sortValue.length; i++) {
|
||||
let sortDirection = sortValue[i][1];
|
||||
// Translate the sort order text
|
||||
if (sortDirection === 'asc') {
|
||||
sortDirection = 1;
|
||||
} else if (sortDirection === 'desc') {
|
||||
sortDirection = -1;
|
||||
}
|
||||
|
||||
// Set the sort order
|
||||
sortObject[sortValue[i][0]] = sortDirection;
|
||||
}
|
||||
}
|
||||
|
||||
sortValue = sortObject;
|
||||
}
|
||||
|
||||
// Add sort to command
|
||||
if (cmd.sort) findCmd.sort = sortValue;
|
||||
// Add a projection to the command
|
||||
if (cmd.fields) findCmd.projection = cmd.fields;
|
||||
// Add a hint to the command
|
||||
if (cmd.hint) findCmd.hint = cmd.hint;
|
||||
// Add a skip
|
||||
if (cmd.skip) findCmd.skip = cmd.skip;
|
||||
// Add a limit
|
||||
if (cmd.limit) findCmd.limit = cmd.limit;
|
||||
|
||||
// Check if we wish to have a singleBatch
|
||||
if (cmd.limit < 0) {
|
||||
findCmd.limit = Math.abs(cmd.limit);
|
||||
findCmd.singleBatch = true;
|
||||
}
|
||||
|
||||
// Add a batchSize
|
||||
if (typeof cmd.batchSize === 'number') {
|
||||
if (cmd.batchSize < 0) {
|
||||
if (cmd.limit !== 0 && Math.abs(cmd.batchSize) < Math.abs(cmd.limit)) {
|
||||
findCmd.limit = Math.abs(cmd.batchSize);
|
||||
}
|
||||
|
||||
findCmd.singleBatch = true;
|
||||
}
|
||||
|
||||
findCmd.batchSize = Math.abs(cmd.batchSize);
|
||||
}
|
||||
|
||||
// If we have comment set
|
||||
if (cmd.comment) findCmd.comment = cmd.comment;
|
||||
|
||||
// If we have maxScan
|
||||
if (cmd.maxScan) findCmd.maxScan = cmd.maxScan;
|
||||
|
||||
// If we have maxTimeMS set
|
||||
if (cmd.maxTimeMS) findCmd.maxTimeMS = cmd.maxTimeMS;
|
||||
|
||||
// If we have min
|
||||
if (cmd.min) findCmd.min = cmd.min;
|
||||
|
||||
// If we have max
|
||||
if (cmd.max) findCmd.max = cmd.max;
|
||||
|
||||
// If we have returnKey set
|
||||
findCmd.returnKey = cmd.returnKey ? cmd.returnKey : false;
|
||||
|
||||
// If we have showDiskLoc set
|
||||
findCmd.showRecordId = cmd.showDiskLoc ? cmd.showDiskLoc : false;
|
||||
|
||||
// If we have snapshot set
|
||||
if (cmd.snapshot) findCmd.snapshot = cmd.snapshot;
|
||||
|
||||
// If we have tailable set
|
||||
if (cmd.tailable) findCmd.tailable = cmd.tailable;
|
||||
|
||||
// If we have oplogReplay set
|
||||
if (cmd.oplogReplay) findCmd.oplogReplay = cmd.oplogReplay;
|
||||
|
||||
// If we have noCursorTimeout set
|
||||
if (cmd.noCursorTimeout) findCmd.noCursorTimeout = cmd.noCursorTimeout;
|
||||
|
||||
// If we have awaitData set
|
||||
if (cmd.awaitData) findCmd.awaitData = cmd.awaitData;
|
||||
if (cmd.awaitdata) findCmd.awaitData = cmd.awaitdata;
|
||||
|
||||
// If we have partial set
|
||||
if (cmd.partial) findCmd.partial = cmd.partial;
|
||||
|
||||
// If we have collation passed in
|
||||
if (cmd.collation) findCmd.collation = cmd.collation;
|
||||
|
||||
// If we have explain, we need to rewrite the find command
|
||||
// to wrap it in the explain command
|
||||
if (cmd.explain) {
|
||||
findCmd = {
|
||||
explain: findCmd
|
||||
};
|
||||
}
|
||||
|
||||
// Did we provide a readConcern
|
||||
if (cmd.readConcern) findCmd.readConcern = cmd.readConcern;
|
||||
|
||||
// Set up the serialize and ignoreUndefined fields
|
||||
const serializeFunctions =
|
||||
typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
|
||||
const ignoreUndefined =
|
||||
typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if (topology.type === 'mongos' && readPreference && readPreference.preference !== 'primary') {
|
||||
findCmd = {
|
||||
$query: findCmd,
|
||||
$readPreference: readPreference.toJSON()
|
||||
};
|
||||
}
|
||||
|
||||
// optionally decorate query with transaction data
|
||||
const err = decorateWithSessionsData(findCmd, options.session, options);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
const query = new Query(bson, commandns, findCmd, {
|
||||
numberToSkip: 0,
|
||||
numberToReturn: 1,
|
||||
checkKeys: false,
|
||||
returnFieldSelector: null,
|
||||
serializeFunctions: serializeFunctions,
|
||||
ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
}
|
||||
|
||||
//
|
||||
// Set up a command cursor
|
||||
function setupCommand(bson, ns, cmd, cursorState, topology, options) {
|
||||
// Set empty options object
|
||||
options = options || {};
|
||||
// Get the readPreference
|
||||
const readPreference = getReadPreference(cmd, options);
|
||||
|
||||
// Final query
|
||||
let finalCmd = {};
|
||||
for (let name in cmd) {
|
||||
finalCmd[name] = cmd[name];
|
||||
}
|
||||
|
||||
// Build command namespace
|
||||
const parts = ns.split(/\./);
|
||||
|
||||
// Serialize functions
|
||||
const serializeFunctions =
|
||||
typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
|
||||
|
||||
// Set up the serialize and ignoreUndefined fields
|
||||
const ignoreUndefined =
|
||||
typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
|
||||
|
||||
// We have a Mongos topology, check if we need to add a readPreference
|
||||
if (topology.type === 'mongos' && readPreference && readPreference.preference !== 'primary') {
|
||||
finalCmd = {
|
||||
$query: finalCmd,
|
||||
$readPreference: readPreference.toJSON()
|
||||
};
|
||||
}
|
||||
|
||||
// optionally decorate query with transaction data
|
||||
const err = decorateWithSessionsData(finalCmd, options.session, options);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// Build Query object
|
||||
const query = new Query(bson, `${parts.shift()}.$cmd`, finalCmd, {
|
||||
numberToSkip: 0,
|
||||
numberToReturn: -1,
|
||||
checkKeys: false,
|
||||
serializeFunctions: serializeFunctions,
|
||||
ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
// Set query flags
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
|
||||
// Return the query
|
||||
return query;
|
||||
}
|
||||
|
||||
module.exports = WireProtocol;
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
'use strict';
|
||||
|
||||
const Query = require('../connection/commands').Query;
|
||||
const Msg = require('../connection/msg').Msg;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const getReadPreference = require('./shared').getReadPreference;
|
||||
const isSharded = require('./shared').isSharded;
|
||||
const databaseNamespace = require('./shared').databaseNamespace;
|
||||
const isTransactionCommand = require('../transactions').isTransactionCommand;
|
||||
const applySession = require('../sessions').applySession;
|
||||
|
||||
function command(server, ns, cmd, options, callback) {
|
||||
if (typeof options === 'function') (callback = options), (options = {});
|
||||
options = options || {};
|
||||
|
||||
if (cmd == null) {
|
||||
return callback(new MongoError(`command ${JSON.stringify(cmd)} does not return a cursor`));
|
||||
}
|
||||
|
||||
const bson = server.s.bson;
|
||||
const pool = server.s.pool;
|
||||
const readPreference = getReadPreference(cmd, options);
|
||||
const shouldUseOpMsg = supportsOpMsg(server);
|
||||
const session = options.session;
|
||||
|
||||
let clusterTime = server.clusterTime;
|
||||
let finalCmd = Object.assign({}, cmd);
|
||||
if (hasSessionSupport(server) && session) {
|
||||
if (
|
||||
session.clusterTime &&
|
||||
session.clusterTime.clusterTime.greaterThan(clusterTime.clusterTime)
|
||||
) {
|
||||
clusterTime = session.clusterTime;
|
||||
}
|
||||
|
||||
const err = applySession(session, finalCmd, options);
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a known cluster time, gossip it
|
||||
if (clusterTime) {
|
||||
finalCmd.$clusterTime = clusterTime;
|
||||
}
|
||||
|
||||
if (
|
||||
isSharded(server) &&
|
||||
!shouldUseOpMsg &&
|
||||
readPreference &&
|
||||
readPreference.preference !== 'primary'
|
||||
) {
|
||||
finalCmd = {
|
||||
$query: finalCmd,
|
||||
$readPreference: readPreference.toJSON()
|
||||
};
|
||||
}
|
||||
|
||||
const commandOptions = Object.assign(
|
||||
{
|
||||
command: true,
|
||||
numberToSkip: 0,
|
||||
numberToReturn: -1,
|
||||
checkKeys: false
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
// This value is not overridable
|
||||
commandOptions.slaveOk = readPreference.slaveOk();
|
||||
|
||||
const cmdNs = `${databaseNamespace(ns)}.$cmd`;
|
||||
const message = shouldUseOpMsg
|
||||
? new Msg(bson, cmdNs, finalCmd, commandOptions)
|
||||
: new Query(bson, cmdNs, finalCmd, commandOptions);
|
||||
|
||||
const inTransaction = session && (session.inTransaction() || isTransactionCommand(finalCmd));
|
||||
const commandResponseHandler = inTransaction
|
||||
? function(err) {
|
||||
if (
|
||||
!cmd.commitTransaction &&
|
||||
err &&
|
||||
err instanceof MongoError &&
|
||||
err.hasErrorLabel('TransientTransactionError')
|
||||
) {
|
||||
session.transaction.unpinServer();
|
||||
}
|
||||
|
||||
return callback.apply(null, arguments);
|
||||
}
|
||||
: callback;
|
||||
|
||||
try {
|
||||
pool.write(message, commandOptions, commandResponseHandler);
|
||||
} catch (err) {
|
||||
commandResponseHandler(err);
|
||||
}
|
||||
}
|
||||
|
||||
function hasSessionSupport(topology) {
|
||||
if (topology == null) return false;
|
||||
if (topology.description) {
|
||||
return topology.description.maxWireVersion >= 6;
|
||||
}
|
||||
|
||||
return topology.ismaster == null ? false : topology.ismaster.maxWireVersion >= 6;
|
||||
}
|
||||
|
||||
function supportsOpMsg(topologyOrServer) {
|
||||
const description = topologyOrServer.ismaster
|
||||
? topologyOrServer.ismaster
|
||||
: topologyOrServer.description;
|
||||
|
||||
if (description == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return description.maxWireVersion >= 6 && description.__nodejs_mock_server__ == null;
|
||||
}
|
||||
|
||||
module.exports = command;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const MIN_SUPPORTED_SERVER_VERSION = '2.6';
|
||||
const MAX_SUPPORTED_SERVER_VERSION = '4.2';
|
||||
const MIN_SUPPORTED_WIRE_VERSION = 2;
|
||||
const MAX_SUPPORTED_WIRE_VERSION = 8;
|
||||
|
||||
module.exports = {
|
||||
MIN_SUPPORTED_SERVER_VERSION,
|
||||
MAX_SUPPORTED_SERVER_VERSION,
|
||||
MIN_SUPPORTED_WIRE_VERSION,
|
||||
MAX_SUPPORTED_WIRE_VERSION
|
||||
};
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
'use strict';
|
||||
|
||||
const GetMore = require('../connection/commands').GetMore;
|
||||
const retrieveBSON = require('../connection/utils').retrieveBSON;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
const BSON = retrieveBSON();
|
||||
const Long = BSON.Long;
|
||||
const collectionNamespace = require('./shared').collectionNamespace;
|
||||
const maxWireVersion = require('../utils').maxWireVersion;
|
||||
const applyCommonQueryOptions = require('./shared').applyCommonQueryOptions;
|
||||
const command = require('./command');
|
||||
|
||||
function getMore(server, ns, cursorState, batchSize, options, callback) {
|
||||
options = options || {};
|
||||
|
||||
const wireVersion = maxWireVersion(server);
|
||||
function queryCallback(err, result) {
|
||||
if (err) return callback(err);
|
||||
const response = result.message;
|
||||
|
||||
// If we have a timed out query or a cursor that was killed
|
||||
if (response.cursorNotFound) {
|
||||
return callback(new MongoNetworkError('cursor killed or timed out'), null);
|
||||
}
|
||||
|
||||
if (wireVersion < 4) {
|
||||
const cursorId =
|
||||
typeof response.cursorId === 'number'
|
||||
? Long.fromNumber(response.cursorId)
|
||||
: response.cursorId;
|
||||
|
||||
cursorState.documents = response.documents;
|
||||
cursorState.cursorId = cursorId;
|
||||
|
||||
callback(null, null, response.connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have an error detected
|
||||
if (response.documents[0].ok === 0) {
|
||||
return callback(new MongoError(response.documents[0]));
|
||||
}
|
||||
|
||||
// Ensure we have a Long valid cursor id
|
||||
const cursorId =
|
||||
typeof response.documents[0].cursor.id === 'number'
|
||||
? Long.fromNumber(response.documents[0].cursor.id)
|
||||
: response.documents[0].cursor.id;
|
||||
|
||||
cursorState.documents = response.documents[0].cursor.nextBatch;
|
||||
cursorState.cursorId = cursorId;
|
||||
|
||||
callback(null, response.documents[0], response.connection);
|
||||
}
|
||||
|
||||
if (wireVersion < 4) {
|
||||
const bson = server.s.bson;
|
||||
const getMoreOp = new GetMore(bson, ns, cursorState.cursorId, { numberToReturn: batchSize });
|
||||
const queryOptions = applyCommonQueryOptions({}, cursorState);
|
||||
server.s.pool.write(getMoreOp, queryOptions, queryCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
const getMoreCmd = {
|
||||
getMore: cursorState.cursorId,
|
||||
collection: collectionNamespace(ns),
|
||||
batchSize: Math.abs(batchSize)
|
||||
};
|
||||
|
||||
if (cursorState.cmd.tailable && typeof cursorState.cmd.maxAwaitTimeMS === 'number') {
|
||||
getMoreCmd.maxTimeMS = cursorState.cmd.maxAwaitTimeMS;
|
||||
}
|
||||
|
||||
const commandOptions = Object.assign(
|
||||
{
|
||||
returnFieldSelector: null,
|
||||
documentsReturnedIn: 'nextBatch'
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
command(server, ns, getMoreCmd, commandOptions, queryCallback);
|
||||
}
|
||||
|
||||
module.exports = getMore;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
const writeCommand = require('./write_command');
|
||||
|
||||
module.exports = {
|
||||
insert: function insert(server, ns, ops, options, callback) {
|
||||
writeCommand(server, 'insert', 'documents', ns, ops, options, callback);
|
||||
},
|
||||
update: function update(server, ns, ops, options, callback) {
|
||||
writeCommand(server, 'update', 'updates', ns, ops, options, callback);
|
||||
},
|
||||
remove: function remove(server, ns, ops, options, callback) {
|
||||
writeCommand(server, 'delete', 'deletes', ns, ops, options, callback);
|
||||
},
|
||||
killCursors: require('./kill_cursors'),
|
||||
getMore: require('./get_more'),
|
||||
query: require('./query'),
|
||||
command: require('./command')
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const KillCursor = require('../connection/commands').KillCursor;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
const collectionNamespace = require('./shared').collectionNamespace;
|
||||
const maxWireVersion = require('../utils').maxWireVersion;
|
||||
const command = require('./command');
|
||||
|
||||
function killCursors(server, ns, cursorState, callback) {
|
||||
callback = typeof callback === 'function' ? callback : () => {};
|
||||
const cursorId = cursorState.cursorId;
|
||||
|
||||
if (maxWireVersion(server) < 4) {
|
||||
const bson = server.s.bson;
|
||||
const pool = server.s.pool;
|
||||
const killCursor = new KillCursor(bson, ns, [cursorId]);
|
||||
const options = {
|
||||
immediateRelease: true,
|
||||
noResponse: true
|
||||
};
|
||||
|
||||
if (typeof cursorState.session === 'object') {
|
||||
options.session = cursorState.session;
|
||||
}
|
||||
|
||||
if (pool && pool.isConnected()) {
|
||||
try {
|
||||
pool.write(killCursor, options, callback);
|
||||
} catch (err) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(err, null);
|
||||
} else {
|
||||
console.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const killCursorCmd = {
|
||||
killCursors: collectionNamespace(ns),
|
||||
cursors: [cursorId]
|
||||
};
|
||||
|
||||
const options = {};
|
||||
if (typeof cursorState.session === 'object') options.session = cursorState.session;
|
||||
|
||||
command(server, ns, killCursorCmd, options, (err, result) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const response = result.message;
|
||||
if (response.cursorNotFound) {
|
||||
return callback(new MongoNetworkError('cursor killed or timed out'), null);
|
||||
}
|
||||
|
||||
if (!Array.isArray(response.documents) || response.documents.length === 0) {
|
||||
return callback(
|
||||
new MongoError(`invalid killCursors result returned for cursor id ${cursorId}`)
|
||||
);
|
||||
}
|
||||
|
||||
callback(null, response.documents[0]);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = killCursors;
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
'use strict';
|
||||
|
||||
const Query = require('../connection/commands').Query;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const getReadPreference = require('./shared').getReadPreference;
|
||||
const collectionNamespace = require('./shared').collectionNamespace;
|
||||
const isSharded = require('./shared').isSharded;
|
||||
const maxWireVersion = require('../utils').maxWireVersion;
|
||||
const applyCommonQueryOptions = require('./shared').applyCommonQueryOptions;
|
||||
const command = require('./command');
|
||||
|
||||
function query(server, ns, cmd, cursorState, options, callback) {
|
||||
options = options || {};
|
||||
if (cursorState.cursorId != null) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
if (cmd == null) {
|
||||
return callback(new MongoError(`command ${JSON.stringify(cmd)} does not return a cursor`));
|
||||
}
|
||||
|
||||
if (maxWireVersion(server) < 4) {
|
||||
const query = prepareLegacyFindQuery(server, ns, cmd, cursorState, options);
|
||||
const queryOptions = applyCommonQueryOptions({}, cursorState);
|
||||
if (typeof query.documentsReturnedIn === 'string') {
|
||||
queryOptions.documentsReturnedIn = query.documentsReturnedIn;
|
||||
}
|
||||
|
||||
server.s.pool.write(query, queryOptions, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
const readPreference = getReadPreference(cmd, options);
|
||||
const findCmd = prepareFindCommand(server, ns, cmd, cursorState, options);
|
||||
|
||||
// NOTE: This actually modifies the passed in cmd, and our code _depends_ on this
|
||||
// side-effect. Change this ASAP
|
||||
cmd.virtual = false;
|
||||
|
||||
const commandOptions = Object.assign(
|
||||
{
|
||||
documentsReturnedIn: 'firstBatch',
|
||||
numberToReturn: 1,
|
||||
slaveOk: readPreference.slaveOk()
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
if (cmd.readPreference) commandOptions.readPreference = readPreference;
|
||||
command(server, ns, findCmd, commandOptions, callback);
|
||||
}
|
||||
|
||||
function prepareFindCommand(server, ns, cmd, cursorState) {
|
||||
cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
|
||||
let findCmd = {
|
||||
find: collectionNamespace(ns)
|
||||
};
|
||||
|
||||
if (cmd.query) {
|
||||
if (cmd.query['$query']) {
|
||||
findCmd.filter = cmd.query['$query'];
|
||||
} else {
|
||||
findCmd.filter = cmd.query;
|
||||
}
|
||||
}
|
||||
|
||||
let sortValue = cmd.sort;
|
||||
if (Array.isArray(sortValue)) {
|
||||
const sortObject = {};
|
||||
|
||||
if (sortValue.length > 0 && !Array.isArray(sortValue[0])) {
|
||||
let sortDirection = sortValue[1];
|
||||
if (sortDirection === 'asc') {
|
||||
sortDirection = 1;
|
||||
} else if (sortDirection === 'desc') {
|
||||
sortDirection = -1;
|
||||
}
|
||||
|
||||
sortObject[sortValue[0]] = sortDirection;
|
||||
} else {
|
||||
for (let i = 0; i < sortValue.length; i++) {
|
||||
let sortDirection = sortValue[i][1];
|
||||
if (sortDirection === 'asc') {
|
||||
sortDirection = 1;
|
||||
} else if (sortDirection === 'desc') {
|
||||
sortDirection = -1;
|
||||
}
|
||||
|
||||
sortObject[sortValue[i][0]] = sortDirection;
|
||||
}
|
||||
}
|
||||
|
||||
sortValue = sortObject;
|
||||
}
|
||||
|
||||
if (cmd.sort) findCmd.sort = sortValue;
|
||||
if (cmd.fields) findCmd.projection = cmd.fields;
|
||||
if (cmd.hint) findCmd.hint = cmd.hint;
|
||||
if (cmd.skip) findCmd.skip = cmd.skip;
|
||||
if (cmd.limit) findCmd.limit = cmd.limit;
|
||||
if (cmd.limit < 0) {
|
||||
findCmd.limit = Math.abs(cmd.limit);
|
||||
findCmd.singleBatch = true;
|
||||
}
|
||||
|
||||
if (typeof cmd.batchSize === 'number') {
|
||||
if (cmd.batchSize < 0) {
|
||||
if (cmd.limit !== 0 && Math.abs(cmd.batchSize) < Math.abs(cmd.limit)) {
|
||||
findCmd.limit = Math.abs(cmd.batchSize);
|
||||
}
|
||||
|
||||
findCmd.singleBatch = true;
|
||||
}
|
||||
|
||||
findCmd.batchSize = Math.abs(cmd.batchSize);
|
||||
}
|
||||
|
||||
if (cmd.comment) findCmd.comment = cmd.comment;
|
||||
if (cmd.maxScan) findCmd.maxScan = cmd.maxScan;
|
||||
if (cmd.maxTimeMS) findCmd.maxTimeMS = cmd.maxTimeMS;
|
||||
if (cmd.min) findCmd.min = cmd.min;
|
||||
if (cmd.max) findCmd.max = cmd.max;
|
||||
findCmd.returnKey = cmd.returnKey ? cmd.returnKey : false;
|
||||
findCmd.showRecordId = cmd.showDiskLoc ? cmd.showDiskLoc : false;
|
||||
if (cmd.snapshot) findCmd.snapshot = cmd.snapshot;
|
||||
if (cmd.tailable) findCmd.tailable = cmd.tailable;
|
||||
if (cmd.oplogReplay) findCmd.oplogReplay = cmd.oplogReplay;
|
||||
if (cmd.noCursorTimeout) findCmd.noCursorTimeout = cmd.noCursorTimeout;
|
||||
if (cmd.awaitData) findCmd.awaitData = cmd.awaitData;
|
||||
if (cmd.awaitdata) findCmd.awaitData = cmd.awaitdata;
|
||||
if (cmd.partial) findCmd.partial = cmd.partial;
|
||||
if (cmd.collation) findCmd.collation = cmd.collation;
|
||||
if (cmd.readConcern) findCmd.readConcern = cmd.readConcern;
|
||||
|
||||
// If we have explain, we need to rewrite the find command
|
||||
// to wrap it in the explain command
|
||||
if (cmd.explain) {
|
||||
findCmd = {
|
||||
explain: findCmd
|
||||
};
|
||||
}
|
||||
|
||||
return findCmd;
|
||||
}
|
||||
|
||||
function prepareLegacyFindQuery(server, ns, cmd, cursorState, options) {
|
||||
options = options || {};
|
||||
const bson = server.s.bson;
|
||||
const readPreference = getReadPreference(cmd, options);
|
||||
cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
|
||||
|
||||
let numberToReturn = 0;
|
||||
if (
|
||||
cursorState.limit < 0 ||
|
||||
(cursorState.limit !== 0 && cursorState.limit < cursorState.batchSize) ||
|
||||
(cursorState.limit > 0 && cursorState.batchSize === 0)
|
||||
) {
|
||||
numberToReturn = cursorState.limit;
|
||||
} else {
|
||||
numberToReturn = cursorState.batchSize;
|
||||
}
|
||||
|
||||
const numberToSkip = cursorState.skip || 0;
|
||||
|
||||
const findCmd = {};
|
||||
if (isSharded(server) && readPreference) {
|
||||
findCmd['$readPreference'] = readPreference.toJSON();
|
||||
}
|
||||
|
||||
if (cmd.sort) findCmd['$orderby'] = cmd.sort;
|
||||
if (cmd.hint) findCmd['$hint'] = cmd.hint;
|
||||
if (cmd.snapshot) findCmd['$snapshot'] = cmd.snapshot;
|
||||
if (typeof cmd.returnKey !== 'undefined') findCmd['$returnKey'] = cmd.returnKey;
|
||||
if (cmd.maxScan) findCmd['$maxScan'] = cmd.maxScan;
|
||||
if (cmd.min) findCmd['$min'] = cmd.min;
|
||||
if (cmd.max) findCmd['$max'] = cmd.max;
|
||||
if (typeof cmd.showDiskLoc !== 'undefined') findCmd['$showDiskLoc'] = cmd.showDiskLoc;
|
||||
if (cmd.comment) findCmd['$comment'] = cmd.comment;
|
||||
if (cmd.maxTimeMS) findCmd['$maxTimeMS'] = cmd.maxTimeMS;
|
||||
if (cmd.explain) {
|
||||
// nToReturn must be 0 (match all) or negative (match N and close cursor)
|
||||
// nToReturn > 0 will give explain results equivalent to limit(0)
|
||||
numberToReturn = -Math.abs(cmd.limit || 0);
|
||||
findCmd['$explain'] = true;
|
||||
}
|
||||
|
||||
findCmd['$query'] = cmd.query;
|
||||
if (cmd.readConcern && cmd.readConcern.level !== 'local') {
|
||||
throw new MongoError(
|
||||
`server find command does not support a readConcern level of ${cmd.readConcern.level}`
|
||||
);
|
||||
}
|
||||
|
||||
if (cmd.readConcern) {
|
||||
cmd = Object.assign({}, cmd);
|
||||
delete cmd['readConcern'];
|
||||
}
|
||||
|
||||
const serializeFunctions =
|
||||
typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
|
||||
const ignoreUndefined =
|
||||
typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
|
||||
|
||||
const query = new Query(bson, ns, findCmd, {
|
||||
numberToSkip: numberToSkip,
|
||||
numberToReturn: numberToReturn,
|
||||
pre32Limit: typeof cmd.limit !== 'undefined' ? cmd.limit : undefined,
|
||||
checkKeys: false,
|
||||
returnFieldSelector: cmd.fields,
|
||||
serializeFunctions: serializeFunctions,
|
||||
ignoreUndefined: ignoreUndefined
|
||||
});
|
||||
|
||||
if (typeof cmd.tailable === 'boolean') query.tailable = cmd.tailable;
|
||||
if (typeof cmd.oplogReplay === 'boolean') query.oplogReplay = cmd.oplogReplay;
|
||||
if (typeof cmd.noCursorTimeout === 'boolean') query.noCursorTimeout = cmd.noCursorTimeout;
|
||||
if (typeof cmd.awaitData === 'boolean') query.awaitData = cmd.awaitData;
|
||||
if (typeof cmd.partial === 'boolean') query.partial = cmd.partial;
|
||||
|
||||
query.slaveOk = readPreference.slaveOk();
|
||||
return query;
|
||||
}
|
||||
|
||||
module.exports = query;
|
||||
+68
-8
@@ -1,9 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var ReadPreference = require('../topologies/read_preference'),
|
||||
MongoError = require('../error').MongoError;
|
||||
const ReadPreference = require('../topologies/read_preference');
|
||||
const MongoError = require('../error').MongoError;
|
||||
const ServerType = require('../sdam/server_description').ServerType;
|
||||
const TopologyDescription = require('../sdam/topology_description').TopologyDescription;
|
||||
|
||||
var MESSAGE_HEADER_SIZE = 16;
|
||||
const MESSAGE_HEADER_SIZE = 16;
|
||||
const COMPRESSION_DETAILS_SIZE = 9; // originalOpcode + uncompressedSize, compressorID
|
||||
|
||||
// OPCODE Numbers
|
||||
// Defined at https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#request-opcodes
|
||||
@@ -15,7 +18,8 @@ var opcodes = {
|
||||
OP_GETMORE: 2005,
|
||||
OP_DELETE: 2006,
|
||||
OP_KILL_CURSORS: 2007,
|
||||
OP_COMPRESSED: 2012
|
||||
OP_COMPRESSED: 2012,
|
||||
OP_MSG: 2013
|
||||
};
|
||||
|
||||
var getReadPreference = function(cmd, options) {
|
||||
@@ -47,9 +51,65 @@ var parseHeader = function(message) {
|
||||
};
|
||||
};
|
||||
|
||||
function applyCommonQueryOptions(queryOptions, options) {
|
||||
Object.assign(queryOptions, {
|
||||
raw: typeof options.raw === 'boolean' ? options.raw : false,
|
||||
promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
|
||||
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
|
||||
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
|
||||
monitoring: typeof options.monitoring === 'boolean' ? options.monitoring : false,
|
||||
fullResult: typeof options.fullResult === 'boolean' ? options.fullResult : false
|
||||
});
|
||||
|
||||
if (typeof options.socketTimeout === 'number') {
|
||||
queryOptions.socketTimeout = options.socketTimeout;
|
||||
}
|
||||
|
||||
if (options.session) {
|
||||
queryOptions.session = options.session;
|
||||
}
|
||||
|
||||
if (typeof options.documentsReturnedIn === 'string') {
|
||||
queryOptions.documentsReturnedIn = options.documentsReturnedIn;
|
||||
}
|
||||
|
||||
return queryOptions;
|
||||
}
|
||||
|
||||
function isSharded(topologyOrServer) {
|
||||
if (topologyOrServer.type === 'mongos') return true;
|
||||
if (topologyOrServer.description && topologyOrServer.description.type === ServerType.Mongos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: This is incredibly inefficient, and should be removed once command construction
|
||||
// happens based on `Server` not `Topology`.
|
||||
if (topologyOrServer.description && topologyOrServer.description instanceof TopologyDescription) {
|
||||
const servers = Array.from(topologyOrServer.description.servers.values());
|
||||
return servers.some(server => server.type === ServerType.Mongos);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function databaseNamespace(ns) {
|
||||
return ns.split('.')[0];
|
||||
}
|
||||
function collectionNamespace(ns) {
|
||||
return ns
|
||||
.split('.')
|
||||
.slice(1)
|
||||
.join('.');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getReadPreference: getReadPreference,
|
||||
MESSAGE_HEADER_SIZE: MESSAGE_HEADER_SIZE,
|
||||
opcodes: opcodes,
|
||||
parseHeader: parseHeader
|
||||
getReadPreference,
|
||||
MESSAGE_HEADER_SIZE,
|
||||
COMPRESSION_DETAILS_SIZE,
|
||||
opcodes,
|
||||
parseHeader,
|
||||
applyCommonQueryOptions,
|
||||
isSharded,
|
||||
databaseNamespace,
|
||||
collectionNamespace
|
||||
};
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
const MongoError = require('../error').MongoError;
|
||||
const collectionNamespace = require('./shared').collectionNamespace;
|
||||
const command = require('./command');
|
||||
|
||||
function writeCommand(server, type, opsField, ns, ops, options, callback) {
|
||||
if (ops.length === 0) throw new MongoError(`${type} must contain at least one document`);
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
|
||||
const writeConcern = options.writeConcern;
|
||||
|
||||
const writeCommand = {};
|
||||
writeCommand[type] = collectionNamespace(ns);
|
||||
writeCommand[opsField] = ops;
|
||||
writeCommand.ordered = ordered;
|
||||
|
||||
if (writeConcern && Object.keys(writeConcern).length > 0) {
|
||||
writeCommand.writeConcern = writeConcern;
|
||||
}
|
||||
|
||||
if (options.collation) {
|
||||
for (let i = 0; i < writeCommand[opsField].length; i++) {
|
||||
if (!writeCommand[opsField][i].collation) {
|
||||
writeCommand[opsField][i].collation = options.collation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.bypassDocumentValidation === true) {
|
||||
writeCommand.bypassDocumentValidation = options.bypassDocumentValidation;
|
||||
}
|
||||
|
||||
const commandOptions = Object.assign(
|
||||
{
|
||||
checkKeys: type === 'insert',
|
||||
numberToReturn: 1
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
command(server, ns, writeCommand, commandOptions, callback);
|
||||
}
|
||||
|
||||
module.exports = writeCommand;
|
||||
Reference in New Issue
Block a user