This commit is contained in:
2019-01-25 01:39:48 +01:00
commit 41ac3fb32a
3269 changed files with 396937 additions and 0 deletions

View File

@@ -0,0 +1,267 @@
'use strict';
const retrieveBSON = require('../connection/utils').retrieveBSON;
const KillCursor = require('../connection/commands').KillCursor;
const GetMore = require('../connection/commands').GetMore;
const Query = require('../connection/commands').Query;
const MongoError = require('../error').MongoError;
const getReadPreference = require('./shared').getReadPreference;
const applyCommonQueryOptions = require('./shared').applyCommonQueryOptions;
const isMongos = require('./shared').isMongos;
const databaseNamespace = require('./shared').databaseNamespace;
const collectionNamespace = require('./shared').collectionNamespace;
const BSON = retrieveBSON();
const Long = BSON.Long;
class WireProtocol {
insert(server, ns, ops, options, callback) {
executeWrite(this, server, 'insert', 'documents', ns, ops, options, callback);
}
update(server, ns, ops, options, callback) {
executeWrite(this, server, 'update', 'updates', ns, ops, options, callback);
}
remove(server, ns, ops, options, callback) {
executeWrite(this, server, 'delete', 'deletes', ns, ops, options, callback);
}
killCursor(server, ns, cursorState, callback) {
const bson = server.s.bson;
const pool = server.s.pool;
const cursorId = cursorState.cursorId;
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);
}
}
}
}
getMore(server, ns, cursorState, batchSize, options, callback) {
const bson = server.s.bson;
const getMore = new GetMore(bson, ns, cursorState.cursorId, { numberToReturn: batchSize });
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 MongoError('Cursor does not exist, was killed, or timed out'), null);
}
const cursorId =
typeof response.cursorId === 'number'
? Long.fromNumber(response.cursorId)
: response.cursorId;
cursorState.documents = response.documents;
cursorState.cursorId = cursorId;
callback(null, null, response.connection);
}
const queryOptions = applyCommonQueryOptions({}, cursorState);
server.s.pool.write(getMore, queryOptions, queryCallback);
}
query(server, ns, cmd, cursorState, options, callback) {
if (cursorState.cursorId != null) {
return;
}
const query = setupClassicFind(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);
}
command(server, ns, cmd, options, callback) {
if (cmd == null) {
return callback(new MongoError(`command ${JSON.stringify(cmd)} does not return a cursor`));
}
options = options || {};
const bson = server.s.bson;
const pool = server.s.pool;
const readPreference = getReadPreference(cmd, options);
let finalCmd = Object.assign({}, cmd);
if (finalCmd.readConcern) {
if (finalCmd.readConcern.level !== 'local') {
return callback(
new MongoError(
`server ${JSON.stringify(finalCmd)} command does not support a readConcern level of ${
finalCmd.readConcern.level
}`
)
);
}
delete finalCmd['readConcern'];
}
if (isMongos(server) && 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();
try {
const query = new Query(bson, `${databaseNamespace(ns)}.$cmd`, finalCmd, commandOptions);
pool.write(query, commandOptions, callback);
} catch (err) {
callback(err);
}
}
}
function executeWrite(handler, server, 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 || {};
}
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.bypassDocumentValidation === true) {
writeCommand.bypassDocumentValidation = options.bypassDocumentValidation;
}
const commandOptions = Object.assign(
{
checkKeys: type === 'insert',
numberToReturn: 1
},
options
);
handler.command(server, ns, writeCommand, commandOptions, callback);
}
function setupClassicFind(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) {
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;
}
const numberToSkip = cursorState.skip || 0;
const findCmd = {};
if (isMongos(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 = WireProtocol;

View File

@@ -0,0 +1,397 @@
'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 isMongos = require('./shared').isMongos;
const databaseNamespace = require('./shared').databaseNamespace;
const collectionNamespace = require('./shared').collectionNamespace;
class WireProtocol {
insert(server, ns, ops, options, callback) {
executeWrite(this, server, 'insert', 'documents', ns, ops, options, callback);
}
update(server, ns, ops, options, callback) {
executeWrite(this, server, 'update', 'updates', ns, ops, options, callback);
}
remove(server, ns, ops, options, callback) {
executeWrite(this, server, 'delete', 'deletes', ns, ops, options, callback);
}
killCursor(server, ns, cursorState, callback) {
callback = typeof callback === 'function' ? callback : () => {};
const cursorId = cursorState.cursorId;
const killCursorCmd = {
killCursors: collectionNamespace(ns),
cursors: [cursorId]
};
const options = {};
if (typeof cursorState.session === 'object') options.session = cursorState.session;
this.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]);
});
}
getMore(server, ns, cursorState, batchSize, options, callback) {
options = options || {};
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;
}
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);
}
// Raw, return all the extracted documents
if (cursorState.raw) {
cursorState.documents = response.documents;
cursorState.cursorId = response.cursorId;
return callback(null, response.documents);
}
// 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);
}
const commandOptions = Object.assign(
{
returnFieldSelector: null,
documentsReturnedIn: 'nextBatch'
},
options
);
this.command(server, ns, getMoreCmd, commandOptions, queryCallback);
}
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`));
}
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;
this.command(server, ns, findCmd, commandOptions, callback);
}
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);
let finalCmd = Object.assign({}, cmd);
if (isMongos(server) && readPreference && readPreference.preference !== 'primary') {
finalCmd = {
$query: finalCmd,
$readPreference: readPreference.toJSON()
};
}
const err = decorateWithSessionsData(finalCmd, options.session, options);
if (err) {
return callback(err);
}
const commandOptions = Object.assign(
{
command: true,
numberToSkip: 0,
numberToReturn: -1,
checkKeys: false
},
options
);
// This value is not overridable
commandOptions.slaveOk = readPreference.slaveOk();
try {
const query = new Query(bson, `${databaseNamespace(ns)}.$cmd`, finalCmd, commandOptions);
pool.write(query, commandOptions, callback);
} catch (err) {
callback(err);
}
}
}
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 });
}
}
}
function executeWrite(handler, server, 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 || {};
}
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
);
handler.command(server, ns, writeCommand, 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;
}
module.exports = WireProtocol;

View File

@@ -0,0 +1,73 @@
'use strict';
var Snappy = require('../connection/utils').retrieveSnappy(),
zlib = require('zlib');
var compressorIDs = {
snappy: 1,
zlib: 2
};
var uncompressibleCommands = [
'ismaster',
'saslStart',
'saslContinue',
'getnonce',
'authenticate',
'createUser',
'updateUser',
'copydbSaslStart',
'copydbgetnonce',
'copydb'
];
// Facilitate compressing a message using an agreed compressor
var compress = function(self, dataToBeCompressed, callback) {
switch (self.options.agreedCompressor) {
case 'snappy':
Snappy.compress(dataToBeCompressed, callback);
break;
case 'zlib':
// Determine zlibCompressionLevel
var zlibOptions = {};
if (self.options.zlibCompressionLevel) {
zlibOptions.level = self.options.zlibCompressionLevel;
}
zlib.deflate(dataToBeCompressed, zlibOptions, callback);
break;
default:
throw new Error(
'Attempt to compress message using unknown compressor "' +
self.options.agreedCompressor +
'".'
);
}
};
// Decompress a message using the given compressor
var decompress = function(compressorID, compressedData, callback) {
if (compressorID < 0 || compressorID > compressorIDs.length) {
throw new Error(
'Server sent message compressed using an unsupported compressor. (Received compressor ID ' +
compressorID +
')'
);
}
switch (compressorID) {
case compressorIDs.snappy:
Snappy.uncompress(compressedData, callback);
break;
case compressorIDs.zlib:
zlib.inflate(compressedData, callback);
break;
default:
callback(null, compressedData);
}
};
module.exports = {
compressorIDs: compressorIDs,
uncompressibleCommands: uncompressibleCommands,
compress: compress,
decompress: decompress
};

101
node_modules/mongodb-core/lib/wireprotocol/shared.js generated vendored Normal file
View File

@@ -0,0 +1,101 @@
'use strict';
var ReadPreference = require('../topologies/read_preference'),
MongoError = require('../error').MongoError;
var MESSAGE_HEADER_SIZE = 16;
// OPCODE Numbers
// Defined at https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#request-opcodes
var opcodes = {
OP_REPLY: 1,
OP_UPDATE: 2001,
OP_INSERT: 2002,
OP_QUERY: 2004,
OP_GETMORE: 2005,
OP_DELETE: 2006,
OP_KILL_CURSORS: 2007,
OP_COMPRESSED: 2012
};
var getReadPreference = function(cmd, options) {
// Default to command version of the readPreference
var readPreference = cmd.readPreference || new ReadPreference('primary');
// If we have an option readPreference override the command one
if (options.readPreference) {
readPreference = options.readPreference;
}
if (typeof readPreference === 'string') {
readPreference = new ReadPreference(readPreference);
}
if (!(readPreference instanceof ReadPreference)) {
throw new MongoError('read preference must be a ReadPreference instance');
}
return readPreference;
};
// Parses the header of a wire protocol message
var parseHeader = function(message) {
return {
length: message.readInt32LE(0),
requestId: message.readInt32LE(4),
responseTo: message.readInt32LE(8),
opCode: message.readInt32LE(12)
};
};
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 isMongos(server) {
if (server.type === 'mongos') return true;
if (server.parent && server.parent.type === 'mongos') return true;
// NOTE: handle unified topology
return false;
}
function databaseNamespace(ns) {
return ns.split('.')[0];
}
function collectionNamespace(ns) {
return ns
.split('.')
.slice(1)
.join('.');
}
module.exports = {
getReadPreference,
MESSAGE_HEADER_SIZE,
opcodes,
parseHeader,
applyCommonQueryOptions,
isMongos,
databaseNamespace,
collectionNamespace
};