You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1103 lines
34 KiB
1103 lines
34 KiB
'use strict';
|
|
|
|
var inherits = require('util').inherits,
|
|
f = require('util').format,
|
|
EventEmitter = require('events').EventEmitter,
|
|
ReadPreference = require('./read_preference'),
|
|
Logger = require('../connection/logger'),
|
|
debugOptions = require('../connection/utils').debugOptions,
|
|
retrieveBSON = require('../connection/utils').retrieveBSON,
|
|
Pool = require('../connection/pool'),
|
|
Query = require('../connection/commands').Query,
|
|
MongoError = require('../error').MongoError,
|
|
MongoNetworkError = require('../error').MongoNetworkError,
|
|
TwoSixWireProtocolSupport = require('../wireprotocol/2_6_support'),
|
|
ThreeTwoWireProtocolSupport = require('../wireprotocol/3_2_support'),
|
|
BasicCursor = require('../cursor'),
|
|
sdam = require('./shared'),
|
|
createClientInfo = require('./shared').createClientInfo,
|
|
createCompressionInfo = require('./shared').createCompressionInfo,
|
|
resolveClusterTime = require('./shared').resolveClusterTime,
|
|
SessionMixins = require('./shared').SessionMixins;
|
|
|
|
// Used for filtering out fields for loggin
|
|
var debugFields = [
|
|
'reconnect',
|
|
'reconnectTries',
|
|
'reconnectInterval',
|
|
'emitError',
|
|
'cursorFactory',
|
|
'host',
|
|
'port',
|
|
'size',
|
|
'keepAlive',
|
|
'keepAliveInitialDelay',
|
|
'noDelay',
|
|
'connectionTimeout',
|
|
'checkServerIdentity',
|
|
'socketTimeout',
|
|
'singleBufferSerializtion',
|
|
'ssl',
|
|
'ca',
|
|
'crl',
|
|
'cert',
|
|
'key',
|
|
'rejectUnauthorized',
|
|
'promoteLongs',
|
|
'promoteValues',
|
|
'promoteBuffers',
|
|
'servername'
|
|
];
|
|
|
|
// Server instance id
|
|
var id = 0;
|
|
var serverAccounting = false;
|
|
var servers = {};
|
|
var BSON = retrieveBSON();
|
|
|
|
/**
|
|
* Creates a new Server instance
|
|
* @class
|
|
* @param {boolean} [options.reconnect=true] Server will attempt to reconnect on loss of connection
|
|
* @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
|
|
* @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
|
|
* @param {number} [options.monitoring=true] Enable the server state monitoring (calling ismaster at monitoringInterval)
|
|
* @param {number} [options.monitoringInterval=5000] The interval of calling ismaster when monitoring is enabled.
|
|
* @param {Cursor} [options.cursorFactory=Cursor] The cursor factory class used for all query cursors
|
|
* @param {string} options.host The server host
|
|
* @param {number} options.port The server port
|
|
* @param {number} [options.size=5] Server connection pool size
|
|
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
|
* @param {number} [options.keepAliveInitialDelay=300000] Initial delay before TCP keep alive enabled
|
|
* @param {boolean} [options.noDelay=true] TCP Connection no delay
|
|
* @param {number} [options.connectionTimeout=30000] TCP Connection timeout setting
|
|
* @param {number} [options.socketTimeout=360000] TCP Socket timeout setting
|
|
* @param {boolean} [options.ssl=false] Use SSL for connection
|
|
* @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
|
|
* @param {Buffer} [options.ca] SSL Certificate store binary buffer
|
|
* @param {Buffer} [options.crl] SSL Certificate revocation store binary buffer
|
|
* @param {Buffer} [options.cert] SSL Certificate binary buffer
|
|
* @param {Buffer} [options.key] SSL Key file binary buffer
|
|
* @param {string} [options.passphrase] SSL Certificate pass phrase
|
|
* @param {boolean} [options.rejectUnauthorized=true] Reject unauthorized server certificates
|
|
* @param {string} [options.servername=null] String containing the server name requested via TLS SNI.
|
|
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
|
|
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
|
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
|
|
* @param {string} [options.appname=null] Application name, passed in on ismaster call and logged in mongod server logs. Maximum size 128 bytes.
|
|
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
|
|
* @return {Server} A cursor instance
|
|
* @fires Server#connect
|
|
* @fires Server#close
|
|
* @fires Server#error
|
|
* @fires Server#timeout
|
|
* @fires Server#parseError
|
|
* @fires Server#reconnect
|
|
* @fires Server#reconnectFailed
|
|
* @fires Server#serverHeartbeatStarted
|
|
* @fires Server#serverHeartbeatSucceeded
|
|
* @fires Server#serverHeartbeatFailed
|
|
* @fires Server#topologyOpening
|
|
* @fires Server#topologyClosed
|
|
* @fires Server#topologyDescriptionChanged
|
|
* @property {string} type the topology type.
|
|
* @property {string} parserType the parser type used (c++ or js).
|
|
*/
|
|
var Server = function(options) {
|
|
options = options || {};
|
|
|
|
// Add event listener
|
|
EventEmitter.call(this);
|
|
|
|
// Server instance id
|
|
this.id = id++;
|
|
|
|
// Internal state
|
|
this.s = {
|
|
// Options
|
|
options: options,
|
|
// Logger
|
|
logger: Logger('Server', options),
|
|
// Factory overrides
|
|
Cursor: options.cursorFactory || BasicCursor,
|
|
// BSON instance
|
|
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
|
|
]),
|
|
// Pool
|
|
pool: null,
|
|
// Disconnect handler
|
|
disconnectHandler: options.disconnectHandler,
|
|
// Monitor thread (keeps the connection alive)
|
|
monitoring: typeof options.monitoring === 'boolean' ? options.monitoring : true,
|
|
// Is the server in a topology
|
|
inTopology: !!options.parent,
|
|
// Monitoring timeout
|
|
monitoringInterval:
|
|
typeof options.monitoringInterval === 'number' ? options.monitoringInterval : 5000,
|
|
// Topology id
|
|
topologyId: -1,
|
|
compression: { compressors: createCompressionInfo(options) },
|
|
// Optional parent topology
|
|
parent: options.parent
|
|
};
|
|
|
|
// If this is a single deployment we need to track the clusterTime here
|
|
if (!this.s.parent) {
|
|
this.s.clusterTime = null;
|
|
}
|
|
|
|
// Curent ismaster
|
|
this.ismaster = null;
|
|
// Current ping time
|
|
this.lastIsMasterMS = -1;
|
|
// The monitoringProcessId
|
|
this.monitoringProcessId = null;
|
|
// Initial connection
|
|
this.initialConnect = true;
|
|
// Wire protocol handler, default to oldest known protocol handler
|
|
// this gets changed when the first ismaster is called.
|
|
this.wireProtocolHandler = new TwoSixWireProtocolSupport();
|
|
// Default type
|
|
this._type = 'server';
|
|
// Set the client info
|
|
this.clientInfo = createClientInfo(options);
|
|
|
|
// Max Stalleness values
|
|
// last time we updated the ismaster state
|
|
this.lastUpdateTime = 0;
|
|
// Last write time
|
|
this.lastWriteDate = 0;
|
|
// Stalleness
|
|
this.staleness = 0;
|
|
};
|
|
|
|
inherits(Server, EventEmitter);
|
|
Object.assign(Server.prototype, SessionMixins);
|
|
|
|
Object.defineProperty(Server.prototype, 'type', {
|
|
enumerable: true,
|
|
get: function() {
|
|
return this._type;
|
|
}
|
|
});
|
|
|
|
Object.defineProperty(Server.prototype, 'parserType', {
|
|
enumerable: true,
|
|
get: function() {
|
|
return BSON.native ? 'c++' : 'js';
|
|
}
|
|
});
|
|
|
|
Object.defineProperty(Server.prototype, 'logicalSessionTimeoutMinutes', {
|
|
enumerable: true,
|
|
get: function() {
|
|
if (!this.ismaster) return null;
|
|
return this.ismaster.logicalSessionTimeoutMinutes || null;
|
|
}
|
|
});
|
|
|
|
// In single server deployments we track the clusterTime directly on the topology, however
|
|
// in Mongos and ReplSet deployments we instead need to delegate the clusterTime up to the
|
|
// tracking objects so we can ensure we are gossiping the maximum time received from the
|
|
// server.
|
|
Object.defineProperty(Server.prototype, 'clusterTime', {
|
|
enumerable: true,
|
|
set: function(clusterTime) {
|
|
const settings = this.s.parent ? this.s.parent : this.s;
|
|
resolveClusterTime(settings, clusterTime);
|
|
},
|
|
get: function() {
|
|
const settings = this.s.parent ? this.s.parent : this.s;
|
|
return settings.clusterTime || null;
|
|
}
|
|
});
|
|
|
|
Server.enableServerAccounting = function() {
|
|
serverAccounting = true;
|
|
servers = {};
|
|
};
|
|
|
|
Server.disableServerAccounting = function() {
|
|
serverAccounting = false;
|
|
};
|
|
|
|
Server.servers = function() {
|
|
return servers;
|
|
};
|
|
|
|
Object.defineProperty(Server.prototype, 'name', {
|
|
enumerable: true,
|
|
get: function() {
|
|
return this.s.options.host + ':' + this.s.options.port;
|
|
}
|
|
});
|
|
|
|
function isSupportedServer(response) {
|
|
return response && typeof response.maxWireVersion === 'number' && response.maxWireVersion >= 2;
|
|
}
|
|
|
|
function configureWireProtocolHandler(self, ismaster) {
|
|
// 3.2 wire protocol handler
|
|
if (ismaster.maxWireVersion >= 4) {
|
|
return new ThreeTwoWireProtocolSupport(new TwoSixWireProtocolSupport());
|
|
}
|
|
|
|
// default to 2.6 wire protocol handler
|
|
return new TwoSixWireProtocolSupport();
|
|
}
|
|
|
|
function disconnectHandler(self, type, ns, cmd, options, callback) {
|
|
// Topology is not connected, save the call in the provided store to be
|
|
// Executed at some point when the handler deems it's reconnected
|
|
if (
|
|
!self.s.pool.isConnected() &&
|
|
self.s.options.reconnect &&
|
|
self.s.disconnectHandler != null &&
|
|
!options.monitoring
|
|
) {
|
|
self.s.disconnectHandler.add(type, ns, cmd, options, callback);
|
|
return true;
|
|
}
|
|
|
|
// If we have no connection error
|
|
if (!self.s.pool.isConnected()) {
|
|
callback(new MongoError(f('no connection available to server %s', self.name)));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function monitoringProcess(self) {
|
|
return function() {
|
|
// Pool was destroyed do not continue process
|
|
if (self.s.pool.isDestroyed()) return;
|
|
// Emit monitoring Process event
|
|
self.emit('monitoring', self);
|
|
// Perform ismaster call
|
|
// Query options
|
|
var queryOptions = { numberToSkip: 0, numberToReturn: -1, checkKeys: false, slaveOk: true };
|
|
// Create a query instance
|
|
var query = new Query(self.s.bson, 'admin.$cmd', { ismaster: true }, queryOptions);
|
|
// Get start time
|
|
var start = new Date().getTime();
|
|
|
|
// Execute the ismaster query
|
|
self.s.pool.write(
|
|
query,
|
|
{
|
|
socketTimeout:
|
|
typeof self.s.options.connectionTimeout !== 'number'
|
|
? 2000
|
|
: self.s.options.connectionTimeout,
|
|
monitoring: true
|
|
},
|
|
function(err, result) {
|
|
// Set initial lastIsMasterMS
|
|
self.lastIsMasterMS = new Date().getTime() - start;
|
|
if (self.s.pool.isDestroyed()) return;
|
|
// Update the ismaster view if we have a result
|
|
if (result) {
|
|
self.ismaster = result.result;
|
|
}
|
|
// Re-schedule the monitoring process
|
|
self.monitoringProcessId = setTimeout(monitoringProcess(self), self.s.monitoringInterval);
|
|
}
|
|
);
|
|
};
|
|
}
|
|
|
|
var eventHandler = function(self, event) {
|
|
return function(err) {
|
|
// Log information of received information if in info mode
|
|
if (self.s.logger.isInfo()) {
|
|
var object = err instanceof MongoError ? JSON.stringify(err) : {};
|
|
self.s.logger.info(
|
|
f('server %s fired event %s out with message %s', self.name, event, object)
|
|
);
|
|
}
|
|
|
|
// Handle connect event
|
|
if (event === 'connect') {
|
|
// Issue an ismaster command at connect
|
|
// Query options
|
|
var queryOptions = { numberToSkip: 0, numberToReturn: -1, checkKeys: false, slaveOk: true };
|
|
// Create a query instance
|
|
var compressors =
|
|
self.s.compression && self.s.compression.compressors ? self.s.compression.compressors : [];
|
|
var query = new Query(
|
|
self.s.bson,
|
|
'admin.$cmd',
|
|
{ ismaster: true, client: self.clientInfo, compression: compressors },
|
|
queryOptions
|
|
);
|
|
// Get start time
|
|
var start = new Date().getTime();
|
|
// Execute the ismaster query
|
|
self.s.pool.write(
|
|
query,
|
|
{
|
|
socketTimeout: self.s.options.connectionTimeout || 2000
|
|
},
|
|
function(err, result) {
|
|
// Set initial lastIsMasterMS
|
|
self.lastIsMasterMS = new Date().getTime() - start;
|
|
if (err) {
|
|
self.destroy();
|
|
return self.emit('error', err);
|
|
}
|
|
|
|
if (!isSupportedServer(result.result)) {
|
|
self.destroy();
|
|
const latestSupportedVersion = '2.6';
|
|
const message =
|
|
'Server at ' +
|
|
self.s.options.host +
|
|
':' +
|
|
self.s.options.port +
|
|
' reports wire version ' +
|
|
(result.result.maxWireVersion || 0) +
|
|
', but this version of Node.js Driver requires at least 2 (MongoDB' +
|
|
latestSupportedVersion +
|
|
').';
|
|
return self.emit('error', new MongoError(message), self);
|
|
}
|
|
|
|
// Determine whether the server is instructing us to use a compressor
|
|
if (result.result && result.result.compression) {
|
|
for (var i = 0; i < self.s.compression.compressors.length; i++) {
|
|
if (result.result.compression.indexOf(self.s.compression.compressors[i]) > -1) {
|
|
self.s.pool.options.agreedCompressor = self.s.compression.compressors[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (self.s.compression.zlibCompressionLevel) {
|
|
self.s.pool.options.zlibCompressionLevel = self.s.compression.zlibCompressionLevel;
|
|
}
|
|
}
|
|
|
|
// Ensure no error emitted after initial connect when reconnecting
|
|
self.initialConnect = false;
|
|
// Save the ismaster
|
|
self.ismaster = result.result;
|
|
|
|
// It's a proxy change the type so
|
|
// the wireprotocol will send $readPreference
|
|
if (self.ismaster.msg === 'isdbgrid') {
|
|
self._type = 'mongos';
|
|
}
|
|
// Add the correct wire protocol handler
|
|
self.wireProtocolHandler = configureWireProtocolHandler(self, self.ismaster);
|
|
// Have we defined self monitoring
|
|
if (self.s.monitoring) {
|
|
self.monitoringProcessId = setTimeout(
|
|
monitoringProcess(self),
|
|
self.s.monitoringInterval
|
|
);
|
|
}
|
|
|
|
// Emit server description changed if something listening
|
|
sdam.emitServerDescriptionChanged(self, {
|
|
address: self.name,
|
|
arbiters: [],
|
|
hosts: [],
|
|
passives: [],
|
|
type: sdam.getTopologyType(self)
|
|
});
|
|
|
|
if (!self.s.inTopology) {
|
|
// Emit topology description changed if something listening
|
|
sdam.emitTopologyDescriptionChanged(self, {
|
|
topologyType: 'Single',
|
|
servers: [
|
|
{
|
|
address: self.name,
|
|
arbiters: [],
|
|
hosts: [],
|
|
passives: [],
|
|
type: sdam.getTopologyType(self)
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
// Log the ismaster if available
|
|
if (self.s.logger.isInfo()) {
|
|
self.s.logger.info(
|
|
f('server %s connected with ismaster [%s]', self.name, JSON.stringify(self.ismaster))
|
|
);
|
|
}
|
|
|
|
// Emit connect
|
|
self.emit('connect', self);
|
|
}
|
|
);
|
|
} else if (
|
|
event === 'error' ||
|
|
event === 'parseError' ||
|
|
event === 'close' ||
|
|
event === 'timeout' ||
|
|
event === 'reconnect' ||
|
|
event === 'attemptReconnect' ||
|
|
'reconnectFailed'
|
|
) {
|
|
// Remove server instance from accounting
|
|
if (
|
|
serverAccounting &&
|
|
['close', 'timeout', 'error', 'parseError', 'reconnectFailed'].indexOf(event) !== -1
|
|
) {
|
|
// Emit toplogy opening event if not in topology
|
|
if (!self.s.inTopology) {
|
|
self.emit('topologyOpening', { topologyId: self.id });
|
|
}
|
|
|
|
delete servers[self.id];
|
|
}
|
|
|
|
if (event === 'close') {
|
|
// Closing emits a server description changed event going to unknown.
|
|
sdam.emitServerDescriptionChanged(self, {
|
|
address: self.name,
|
|
arbiters: [],
|
|
hosts: [],
|
|
passives: [],
|
|
type: 'Unknown'
|
|
});
|
|
}
|
|
|
|
// Reconnect failed return error
|
|
if (event === 'reconnectFailed') {
|
|
self.emit('reconnectFailed', err);
|
|
// Emit error if any listeners
|
|
if (self.listeners('error').length > 0) {
|
|
self.emit('error', err);
|
|
}
|
|
// Terminate
|
|
return;
|
|
}
|
|
|
|
// On first connect fail
|
|
if (
|
|
self.s.pool.state === 'disconnected' &&
|
|
self.initialConnect &&
|
|
['close', 'timeout', 'error', 'parseError'].indexOf(event) !== -1
|
|
) {
|
|
self.initialConnect = false;
|
|
return self.emit(
|
|
'error',
|
|
new MongoNetworkError(
|
|
f('failed to connect to server [%s] on first connect [%s]', self.name, err)
|
|
)
|
|
);
|
|
}
|
|
|
|
// Reconnect event, emit the server
|
|
if (event === 'reconnect') {
|
|
// Reconnecting emits a server description changed event going from unknown to the
|
|
// current server type.
|
|
sdam.emitServerDescriptionChanged(self, {
|
|
address: self.name,
|
|
arbiters: [],
|
|
hosts: [],
|
|
passives: [],
|
|
type: sdam.getTopologyType(self)
|
|
});
|
|
return self.emit(event, self);
|
|
}
|
|
|
|
// Emit the event
|
|
self.emit(event, err);
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Initiate server connect
|
|
* @method
|
|
* @param {array} [options.auth=null] Array of auth options to apply on connect
|
|
*/
|
|
Server.prototype.connect = function(options) {
|
|
var self = this;
|
|
options = options || {};
|
|
|
|
// Set the connections
|
|
if (serverAccounting) servers[this.id] = this;
|
|
|
|
// Do not allow connect to be called on anything that's not disconnected
|
|
if (self.s.pool && !self.s.pool.isDisconnected() && !self.s.pool.isDestroyed()) {
|
|
throw new MongoError(f('server instance in invalid state %s', self.s.pool.state));
|
|
}
|
|
|
|
// Create a pool
|
|
self.s.pool = new Pool(this, Object.assign(self.s.options, options, { bson: this.s.bson }));
|
|
|
|
// Set up listeners
|
|
self.s.pool.on('close', eventHandler(self, 'close'));
|
|
self.s.pool.on('error', eventHandler(self, 'error'));
|
|
self.s.pool.on('timeout', eventHandler(self, 'timeout'));
|
|
self.s.pool.on('parseError', eventHandler(self, 'parseError'));
|
|
self.s.pool.on('connect', eventHandler(self, 'connect'));
|
|
self.s.pool.on('reconnect', eventHandler(self, 'reconnect'));
|
|
self.s.pool.on('reconnectFailed', eventHandler(self, 'reconnectFailed'));
|
|
|
|
// Emit toplogy opening event if not in topology
|
|
if (!self.s.inTopology) {
|
|
this.emit('topologyOpening', { topologyId: self.id });
|
|
}
|
|
|
|
// Emit opening server event
|
|
self.emit('serverOpening', {
|
|
topologyId: self.s.topologyId !== -1 ? self.s.topologyId : self.id,
|
|
address: self.name
|
|
});
|
|
|
|
// Connect with optional auth settings
|
|
if (options.auth) {
|
|
self.s.pool.connect.apply(self.s.pool, options.auth);
|
|
} else {
|
|
self.s.pool.connect();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the server description
|
|
* @method
|
|
* @return {object}
|
|
*/
|
|
Server.prototype.getDescription = function() {
|
|
var ismaster = this.ismaster || {};
|
|
var description = {
|
|
type: sdam.getTopologyType(this),
|
|
address: this.name
|
|
};
|
|
|
|
// Add fields if available
|
|
if (ismaster.hosts) description.hosts = ismaster.hosts;
|
|
if (ismaster.arbiters) description.arbiters = ismaster.arbiters;
|
|
if (ismaster.passives) description.passives = ismaster.passives;
|
|
if (ismaster.setName) description.setName = ismaster.setName;
|
|
return description;
|
|
};
|
|
|
|
/**
|
|
* Returns the last known ismaster document for this server
|
|
* @method
|
|
* @return {object}
|
|
*/
|
|
Server.prototype.lastIsMaster = function() {
|
|
return this.ismaster;
|
|
};
|
|
|
|
/**
|
|
* Unref all connections belong to this server
|
|
* @method
|
|
*/
|
|
Server.prototype.unref = function() {
|
|
this.s.pool.unref();
|
|
};
|
|
|
|
/**
|
|
* Figure out if the server is connected
|
|
* @method
|
|
* @return {boolean}
|
|
*/
|
|
Server.prototype.isConnected = function() {
|
|
if (!this.s.pool) return false;
|
|
return this.s.pool.isConnected();
|
|
};
|
|
|
|
/**
|
|
* Figure out if the server instance was destroyed by calling destroy
|
|
* @method
|
|
* @return {boolean}
|
|
*/
|
|
Server.prototype.isDestroyed = function() {
|
|
if (!this.s.pool) return false;
|
|
return this.s.pool.isDestroyed();
|
|
};
|
|
|
|
function basicWriteValidations(self) {
|
|
if (!self.s.pool) return new MongoError('server instance is not connected');
|
|
if (self.s.pool.isDestroyed()) return new MongoError('server instance pool was destroyed');
|
|
}
|
|
|
|
function basicReadValidations(self, options) {
|
|
basicWriteValidations(self, options);
|
|
|
|
if (options.readPreference && !(options.readPreference instanceof ReadPreference)) {
|
|
throw new Error('readPreference must be an instance of ReadPreference');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a command
|
|
* @method
|
|
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
* @param {object} cmd The command hash
|
|
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
|
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
* @param {Boolean} [options.checkKeys=false] Specify if the bson parser should validate keys.
|
|
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
* @param {Boolean} [options.fullResult=false] Return the full envelope instead of just the result document.
|
|
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
* @param {opResultCallback} callback A callback function
|
|
*/
|
|
Server.prototype.command = function(ns, cmd, options, callback) {
|
|
var self = this;
|
|
if (typeof options === 'function') {
|
|
(callback = options), (options = {}), (options = options || {});
|
|
}
|
|
|
|
var result = basicReadValidations(self, options);
|
|
if (result) return callback(result);
|
|
|
|
// Clone the options
|
|
options = Object.assign({}, options, { wireProtocolCommand: false });
|
|
|
|
// Debug log
|
|
if (self.s.logger.isDebug())
|
|
self.s.logger.debug(
|
|
f(
|
|
'executing command [%s] against %s',
|
|
JSON.stringify({
|
|
ns: ns,
|
|
cmd: cmd,
|
|
options: debugOptions(debugFields, options)
|
|
}),
|
|
self.name
|
|
)
|
|
);
|
|
|
|
// If we are not connected or have a disconnectHandler specified
|
|
if (disconnectHandler(self, 'command', ns, cmd, options, callback)) return;
|
|
|
|
// Check if we have collation support
|
|
if (this.ismaster && this.ismaster.maxWireVersion < 5 && cmd.collation) {
|
|
return callback(new MongoError(f('server %s does not support collation', this.name)));
|
|
}
|
|
|
|
// Are we executing against a specific topology
|
|
var topology = options.topology || {};
|
|
// Create the query object
|
|
var query = self.wireProtocolHandler.command(self.s.bson, ns, cmd, {}, topology, options);
|
|
// Set slave OK of the query
|
|
query.slaveOk = options.readPreference ? options.readPreference.slaveOk() : false;
|
|
|
|
// Write options
|
|
var writeOptions = {
|
|
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,
|
|
command: true,
|
|
monitoring: typeof options.monitoring === 'boolean' ? options.monitoring : false,
|
|
fullResult: typeof options.fullResult === 'boolean' ? options.fullResult : false,
|
|
requestId: query.requestId,
|
|
socketTimeout: typeof options.socketTimeout === 'number' ? options.socketTimeout : null,
|
|
session: options.session || null
|
|
};
|
|
|
|
// Write the operation to the pool
|
|
self.s.pool.write(query, writeOptions, callback);
|
|
};
|
|
|
|
/**
|
|
* Insert one or more documents
|
|
* @method
|
|
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
* @param {array} ops An array of documents to insert
|
|
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
|
* @param {object} [options.writeConcern={}] Write concern for the operation
|
|
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
* @param {opResultCallback} callback A callback function
|
|
*/
|
|
Server.prototype.insert = function(ns, ops, options, callback) {
|
|
var self = this;
|
|
if (typeof options === 'function') {
|
|
(callback = options), (options = {}), (options = options || {});
|
|
}
|
|
|
|
var result = basicWriteValidations(self, options);
|
|
if (result) return callback(result);
|
|
|
|
// If we are not connected or have a disconnectHandler specified
|
|
if (disconnectHandler(self, 'insert', ns, ops, options, callback)) return;
|
|
|
|
// Setup the docs as an array
|
|
ops = Array.isArray(ops) ? ops : [ops];
|
|
|
|
// Execute write
|
|
return self.wireProtocolHandler.insert(
|
|
self.s.pool,
|
|
self.ismaster,
|
|
ns,
|
|
self.s.bson,
|
|
ops,
|
|
options,
|
|
callback
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Perform one or more update operations
|
|
* @method
|
|
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
* @param {array} ops An array of updates
|
|
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
|
* @param {object} [options.writeConcern={}] Write concern for the operation
|
|
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
* @param {opResultCallback} callback A callback function
|
|
*/
|
|
Server.prototype.update = function(ns, ops, options, callback) {
|
|
var self = this;
|
|
if (typeof options === 'function') {
|
|
(callback = options), (options = {}), (options = options || {});
|
|
}
|
|
|
|
var result = basicWriteValidations(self, options);
|
|
if (result) return callback(result);
|
|
|
|
// If we are not connected or have a disconnectHandler specified
|
|
if (disconnectHandler(self, 'update', ns, ops, options, callback)) return;
|
|
|
|
// Check if we have collation support
|
|
if (this.ismaster && this.ismaster.maxWireVersion < 5 && options.collation) {
|
|
return callback(new MongoError(f('server %s does not support collation', this.name)));
|
|
}
|
|
|
|
// Setup the docs as an array
|
|
ops = Array.isArray(ops) ? ops : [ops];
|
|
// Execute write
|
|
return self.wireProtocolHandler.update(
|
|
self.s.pool,
|
|
self.ismaster,
|
|
ns,
|
|
self.s.bson,
|
|
ops,
|
|
options,
|
|
callback
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Perform one or more remove operations
|
|
* @method
|
|
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
* @param {array} ops An array of removes
|
|
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
|
* @param {object} [options.writeConcern={}] Write concern for the operation
|
|
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
* @param {opResultCallback} callback A callback function
|
|
*/
|
|
Server.prototype.remove = function(ns, ops, options, callback) {
|
|
var self = this;
|
|
if (typeof options === 'function') {
|
|
(callback = options), (options = {}), (options = options || {});
|
|
}
|
|
|
|
var result = basicWriteValidations(self, options);
|
|
if (result) return callback(result);
|
|
|
|
// If we are not connected or have a disconnectHandler specified
|
|
if (disconnectHandler(self, 'remove', ns, ops, options, callback)) return;
|
|
|
|
// Check if we have collation support
|
|
if (this.ismaster && this.ismaster.maxWireVersion < 5 && options.collation) {
|
|
return callback(new MongoError(f('server %s does not support collation', this.name)));
|
|
}
|
|
|
|
// Setup the docs as an array
|
|
ops = Array.isArray(ops) ? ops : [ops];
|
|
// Execute write
|
|
return self.wireProtocolHandler.remove(
|
|
self.s.pool,
|
|
self.ismaster,
|
|
ns,
|
|
self.s.bson,
|
|
ops,
|
|
options,
|
|
callback
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Get a new cursor
|
|
* @method
|
|
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
* @param {object|Long} cmd Can be either a command returning a cursor or a cursorId
|
|
* @param {object} [options] Options for the cursor
|
|
* @param {object} [options.batchSize=0] Batchsize for the operation
|
|
* @param {array} [options.documents=[]] Initial documents list for cursor
|
|
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
|
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
* @param {object} [options.topology] The internal topology of the created cursor
|
|
* @returns {Cursor}
|
|
*/
|
|
Server.prototype.cursor = function(ns, cmd, options) {
|
|
options = options || {};
|
|
const topology = options.topology || this;
|
|
|
|
// Set up final cursor type
|
|
var FinalCursor = options.cursorFactory || this.s.Cursor;
|
|
|
|
// Return the cursor
|
|
return new FinalCursor(this.s.bson, ns, cmd, options, topology, this.s.options);
|
|
};
|
|
|
|
/**
|
|
* Logout from a database
|
|
* @method
|
|
* @param {string} db The db we are logging out from
|
|
* @param {authResultCallback} callback A callback function
|
|
*/
|
|
Server.prototype.logout = function(dbName, callback) {
|
|
this.s.pool.logout(dbName, callback);
|
|
};
|
|
|
|
/**
|
|
* Authenticate using a specified mechanism
|
|
* @method
|
|
* @param {string} mechanism The Auth mechanism we are invoking
|
|
* @param {string} db The db we are invoking the mechanism against
|
|
* @param {...object} param Parameters for the specific mechanism
|
|
* @param {authResultCallback} callback A callback function
|
|
*/
|
|
Server.prototype.auth = function(mechanism, db) {
|
|
var self = this;
|
|
|
|
// If we have the default mechanism we pick mechanism based on the wire
|
|
// protocol max version. If it's >= 3 then scram-sha1 otherwise mongodb-cr
|
|
if (mechanism === 'default' && self.ismaster && self.ismaster.maxWireVersion >= 3) {
|
|
mechanism = 'scram-sha-1';
|
|
} else if (mechanism === 'default') {
|
|
mechanism = 'mongocr';
|
|
}
|
|
|
|
// Slice all the arguments off
|
|
var args = Array.prototype.slice.call(arguments, 0);
|
|
// Set the mechanism
|
|
args[0] = mechanism;
|
|
// Get the callback
|
|
var callback = args[args.length - 1];
|
|
|
|
// If we are not connected or have a disconnectHandler specified
|
|
if (disconnectHandler(self, 'auth', db, args, {}, callback)) {
|
|
return;
|
|
}
|
|
|
|
// Do not authenticate if we are an arbiter
|
|
if (this.lastIsMaster() && this.lastIsMaster().arbiterOnly) {
|
|
return callback(null, true);
|
|
}
|
|
|
|
// Apply the arguments to the pool
|
|
self.s.pool.auth.apply(self.s.pool, args);
|
|
};
|
|
|
|
/**
|
|
* Compare two server instances
|
|
* @method
|
|
* @param {Server} server Server to compare equality against
|
|
* @return {boolean}
|
|
*/
|
|
Server.prototype.equals = function(server) {
|
|
if (typeof server === 'string') return this.name.toLowerCase() === server.toLowerCase();
|
|
if (server.name) return this.name.toLowerCase() === server.name.toLowerCase();
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* All raw connections
|
|
* @method
|
|
* @return {Connection[]}
|
|
*/
|
|
Server.prototype.connections = function() {
|
|
return this.s.pool.allConnections();
|
|
};
|
|
|
|
/**
|
|
* Get server
|
|
* @method
|
|
* @return {Server}
|
|
*/
|
|
Server.prototype.getServer = function() {
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Get connection
|
|
* @method
|
|
* @return {Connection}
|
|
*/
|
|
Server.prototype.getConnection = function() {
|
|
return this.s.pool.get();
|
|
};
|
|
|
|
var listeners = ['close', 'error', 'timeout', 'parseError', 'connect'];
|
|
|
|
/**
|
|
* Destroy the server connection
|
|
* @method
|
|
* @param {boolean} [options.emitClose=false] Emit close event on destroy
|
|
* @param {boolean} [options.emitDestroy=false] Emit destroy event on destroy
|
|
* @param {boolean} [options.force=false] Force destroy the pool
|
|
*/
|
|
Server.prototype.destroy = function(options) {
|
|
options = options || {};
|
|
var self = this;
|
|
|
|
// Set the connections
|
|
if (serverAccounting) delete servers[this.id];
|
|
|
|
// Destroy the monitoring process if any
|
|
if (this.monitoringProcessId) {
|
|
clearTimeout(this.monitoringProcessId);
|
|
}
|
|
|
|
// No pool, return
|
|
if (!self.s.pool) return;
|
|
|
|
// Emit close event
|
|
if (options.emitClose) {
|
|
self.emit('close', self);
|
|
}
|
|
|
|
// Emit destroy event
|
|
if (options.emitDestroy) {
|
|
self.emit('destroy', self);
|
|
}
|
|
|
|
// Remove all listeners
|
|
listeners.forEach(function(event) {
|
|
self.s.pool.removeAllListeners(event);
|
|
});
|
|
|
|
// Emit opening server event
|
|
if (self.listeners('serverClosed').length > 0)
|
|
self.emit('serverClosed', {
|
|
topologyId: self.s.topologyId !== -1 ? self.s.topologyId : self.id,
|
|
address: self.name
|
|
});
|
|
|
|
// Emit toplogy opening event if not in topology
|
|
if (self.listeners('topologyClosed').length > 0 && !self.s.inTopology) {
|
|
self.emit('topologyClosed', { topologyId: self.id });
|
|
}
|
|
|
|
if (self.s.logger.isDebug()) {
|
|
self.s.logger.debug(f('destroy called on server %s', self.name));
|
|
}
|
|
|
|
// Destroy the pool
|
|
this.s.pool.destroy(options.force);
|
|
};
|
|
|
|
/**
|
|
* A server connect event, used to verify that the connection is up and running
|
|
*
|
|
* @event Server#connect
|
|
* @type {Server}
|
|
*/
|
|
|
|
/**
|
|
* A server reconnect event, used to verify that the server topology has reconnected
|
|
*
|
|
* @event Server#reconnect
|
|
* @type {Server}
|
|
*/
|
|
|
|
/**
|
|
* A server opening SDAM monitoring event
|
|
*
|
|
* @event Server#serverOpening
|
|
* @type {object}
|
|
*/
|
|
|
|
/**
|
|
* A server closed SDAM monitoring event
|
|
*
|
|
* @event Server#serverClosed
|
|
* @type {object}
|
|
*/
|
|
|
|
/**
|
|
* A server description SDAM change monitoring event
|
|
*
|
|
* @event Server#serverDescriptionChanged
|
|
* @type {object}
|
|
*/
|
|
|
|
/**
|
|
* A topology open SDAM event
|
|
*
|
|
* @event Server#topologyOpening
|
|
* @type {object}
|
|
*/
|
|
|
|
/**
|
|
* A topology closed SDAM event
|
|
*
|
|
* @event Server#topologyClosed
|
|
* @type {object}
|
|
*/
|
|
|
|
/**
|
|
* A topology structure SDAM change event
|
|
*
|
|
* @event Server#topologyDescriptionChanged
|
|
* @type {object}
|
|
*/
|
|
|
|
/**
|
|
* Server reconnect failed
|
|
*
|
|
* @event Server#reconnectFailed
|
|
* @type {Error}
|
|
*/
|
|
|
|
/**
|
|
* Server connection pool closed
|
|
*
|
|
* @event Server#close
|
|
* @type {object}
|
|
*/
|
|
|
|
/**
|
|
* Server connection pool caused an error
|
|
*
|
|
* @event Server#error
|
|
* @type {Error}
|
|
*/
|
|
|
|
/**
|
|
* Server destroyed was called
|
|
*
|
|
* @event Server#destroy
|
|
* @type {Server}
|
|
*/
|
|
|
|
module.exports = Server;
|