database and ajax test added

This commit is contained in:
2019-02-01 14:06:44 +01:00
parent b5eb4c2b79
commit f3c33a35cd
562 changed files with 154325 additions and 16 deletions
+62
View File
@@ -0,0 +1,62 @@
'use strict';
const executeCommand = require('./db_ops').executeCommand;
const executeDbAdminCommand = require('./db_ops').executeDbAdminCommand;
/**
* Get ReplicaSet status
*
* @param {Admin} a collection instance.
* @param {Object} [options] Optional settings. See Admin.prototype.replSetGetStatus for a list of options.
* @param {Admin~resultCallback} [callback] The command result callback.
*/
function replSetGetStatus(admin, options, callback) {
executeDbAdminCommand(admin.s.db, { replSetGetStatus: 1 }, options, callback);
}
/**
* Retrieve this db's server status.
*
* @param {Admin} a collection instance.
* @param {Object} [options] Optional settings. See Admin.prototype.serverStatus for a list of options.
* @param {Admin~resultCallback} [callback] The command result callback
*/
function serverStatus(admin, options, callback) {
executeDbAdminCommand(admin.s.db, { serverStatus: 1 }, options, callback);
}
/**
* Validate an existing collection
*
* @param {Admin} a collection instance.
* @param {string} collectionName The name of the collection to validate.
* @param {Object} [options] Optional settings. See Admin.prototype.validateCollection for a list of options.
* @param {Admin~resultCallback} [callback] The command result callback.
*/
function validateCollection(admin, collectionName, options, callback) {
const command = { validate: collectionName };
const keys = Object.keys(options);
// Decorate command with extra options
for (let i = 0; i < keys.length; i++) {
if (options.hasOwnProperty(keys[i]) && keys[i] !== 'session') {
command[keys[i]] = options[keys[i]];
}
}
executeCommand(admin.s.db, command, options, (err, doc) => {
if (err != null) return callback(err, null);
if (doc.ok === 0) return callback(new Error('Error with validate command'), null);
if (doc.result != null && doc.result.constructor !== String)
return callback(new Error('Error with validation data'), null);
if (doc.result != null && doc.result.match(/exception|corrupt/) != null)
return callback(new Error('Error: invalid collection ' + collectionName), null);
if (doc.valid != null && !doc.valid)
return callback(new Error('Error: invalid collection ' + collectionName), null);
return callback(null, doc);
});
}
module.exports = { replSetGetStatus, serverStatus, validateCollection };
File diff suppressed because it is too large Load Diff
+242
View File
@@ -0,0 +1,242 @@
'use strict';
const buildCountCommand = require('./collection_ops').buildCountCommand;
const formattedOrderClause = require('../utils').formattedOrderClause;
const handleCallback = require('../utils').handleCallback;
const MongoError = require('mongodb-core').MongoError;
const push = Array.prototype.push;
/**
* Get the count of documents for this cursor.
*
* @method
* @param {Cursor} cursor The Cursor instance on which to count.
* @param {boolean} [applySkipLimit=true] Specifies whether the count command apply limit and skip settings should be applied on the cursor or in the provided options.
* @param {object} [options] Optional settings. See Cursor.prototype.count for a list of options.
* @param {Cursor~countResultCallback} [callback] The result callback.
*/
function count(cursor, applySkipLimit, opts, callback) {
if (applySkipLimit) {
if (typeof cursor.cursorSkip() === 'number') opts.skip = cursor.cursorSkip();
if (typeof cursor.cursorLimit() === 'number') opts.limit = cursor.cursorLimit();
}
// Ensure we have the right read preference inheritance
if (opts.readPreference) {
cursor.setReadPreference(opts.readPreference);
}
if (
typeof opts.maxTimeMS !== 'number' &&
cursor.s.cmd &&
typeof cursor.s.cmd.maxTimeMS === 'number'
) {
opts.maxTimeMS = cursor.s.cmd.maxTimeMS;
}
let options = {};
options.skip = opts.skip;
options.limit = opts.limit;
options.hint = opts.hint;
options.maxTimeMS = opts.maxTimeMS;
// Command
const delimiter = cursor.s.ns.indexOf('.');
options.collectionName = cursor.s.ns.substr(delimiter + 1);
let command;
try {
command = buildCountCommand(cursor, cursor.s.cmd.query, options);
} catch (err) {
return callback(err);
}
// Set cursor server to the same as the topology
cursor.server = cursor.topology.s.coreTopology;
// Execute the command
cursor.s.topology.command(
`${cursor.s.ns.substr(0, delimiter)}.$cmd`,
command,
cursor.s.options,
(err, result) => {
callback(err, result ? result.result.n : null);
}
);
}
/**
* Iterates over all the documents for this cursor. See Cursor.prototype.each for more information.
*
* @method
* @deprecated
* @param {Cursor} cursor The Cursor instance on which to run.
* @param {Cursor~resultCallback} callback The result callback.
*/
function each(cursor, callback) {
const Cursor = require('../cursor');
if (!callback) throw MongoError.create({ message: 'callback is mandatory', driver: true });
if (cursor.isNotified()) return;
if (cursor.s.state === Cursor.CLOSED || cursor.isDead()) {
return handleCallback(
callback,
MongoError.create({ message: 'Cursor is closed', driver: true })
);
}
if (cursor.s.state === Cursor.INIT) cursor.s.state = Cursor.OPEN;
// Define function to avoid global scope escape
let fn = null;
// Trampoline all the entries
if (cursor.bufferedCount() > 0) {
while ((fn = loop(cursor, callback))) fn(cursor, callback);
each(cursor, callback);
} else {
cursor.next((err, item) => {
if (err) return handleCallback(callback, err);
if (item == null) {
return cursor.close({ skipKillCursors: true }, () => handleCallback(callback, null, null));
}
if (handleCallback(callback, null, item) === false) return;
each(cursor, callback);
});
}
}
/**
* Check if there is any document still available in the cursor.
*
* @method
* @param {Cursor} cursor The Cursor instance on which to run.
* @param {Cursor~resultCallback} [callback] The result callback.
*/
function hasNext(cursor, callback) {
const Cursor = require('../cursor');
if (cursor.s.currentDoc) {
return callback(null, true);
}
if (cursor.isNotified()) {
return callback(null, false);
}
nextObject(cursor, (err, doc) => {
if (err) return callback(err, null);
if (cursor.s.state === Cursor.CLOSED || cursor.isDead()) return callback(null, false);
if (!doc) return callback(null, false);
cursor.s.currentDoc = doc;
callback(null, true);
});
}
// Trampoline emptying the number of retrieved items
// without incurring a nextTick operation
function loop(cursor, callback) {
// No more items we are done
if (cursor.bufferedCount() === 0) return;
// Get the next document
cursor._next(callback);
// Loop
return loop;
}
/**
* Get the next available document from the cursor. Returns null if no more documents are available.
*
* @method
* @param {Cursor} cursor The Cursor instance from which to get the next document.
* @param {Cursor~resultCallback} [callback] The result callback.
*/
function next(cursor, callback) {
// Return the currentDoc if someone called hasNext first
if (cursor.s.currentDoc) {
const doc = cursor.s.currentDoc;
cursor.s.currentDoc = null;
return callback(null, doc);
}
// Return the next object
nextObject(cursor, callback);
}
// Get the next available document from the cursor, returns null if no more documents are available.
function nextObject(cursor, callback) {
const Cursor = require('../cursor');
if (cursor.s.state === Cursor.CLOSED || (cursor.isDead && cursor.isDead()))
return handleCallback(
callback,
MongoError.create({ message: 'Cursor is closed', driver: true })
);
if (cursor.s.state === Cursor.INIT && cursor.s.cmd.sort) {
try {
cursor.s.cmd.sort = formattedOrderClause(cursor.s.cmd.sort);
} catch (err) {
return handleCallback(callback, err);
}
}
// Get the next object
cursor._next((err, doc) => {
cursor.s.state = Cursor.OPEN;
if (err) return handleCallback(callback, err);
handleCallback(callback, null, doc);
});
}
/**
* Returns an array of documents. See Cursor.prototype.toArray for more information.
*
* @method
* @param {Cursor} cursor The Cursor instance from which to get the next document.
* @param {Cursor~toArrayResultCallback} [callback] The result callback.
*/
function toArray(cursor, callback) {
const Cursor = require('../cursor');
const items = [];
// Reset cursor
cursor.rewind();
cursor.s.state = Cursor.INIT;
// Fetch all the documents
const fetchDocs = () => {
cursor._next((err, doc) => {
if (err) {
return cursor._endSession
? cursor._endSession(() => handleCallback(callback, err))
: handleCallback(callback, err);
}
if (doc == null) {
return cursor.close({ skipKillCursors: true }, () => handleCallback(callback, null, items));
}
// Add doc to items
items.push(doc);
// Get all buffered objects
if (cursor.bufferedCount() > 0) {
let docs = cursor.readBufferedDocuments(cursor.bufferedCount());
// Transform the doc if transform method added
if (cursor.s.transforms && typeof cursor.s.transforms.doc === 'function') {
docs = docs.map(cursor.s.transforms.doc);
}
push.apply(items, docs);
}
// Attempt a fetch
fetchDocs();
});
};
fetchDocs();
}
module.exports = { count, each, hasNext, next, toArray };
+986
View File
@@ -0,0 +1,986 @@
'use strict';
const applyWriteConcern = require('../utils').applyWriteConcern;
const Code = require('mongodb-core').BSON.Code;
const resolveReadPreference = require('../utils').resolveReadPreference;
const crypto = require('crypto');
const debugOptions = require('../utils').debugOptions;
const handleCallback = require('../utils').handleCallback;
const MongoError = require('mongodb-core').MongoError;
const parseIndexOptions = require('../utils').parseIndexOptions;
const ReadPreference = require('mongodb-core').ReadPreference;
const toError = require('../utils').toError;
const CONSTANTS = require('../constants');
const count = require('./collection_ops').count;
const findOne = require('./collection_ops').findOne;
const remove = require('./collection_ops').remove;
const updateOne = require('./collection_ops').updateOne;
const debugFields = [
'authSource',
'w',
'wtimeout',
'j',
'native_parser',
'forceServerObjectId',
'serializeFunctions',
'raw',
'promoteLongs',
'promoteValues',
'promoteBuffers',
'bufferMaxEntries',
'numberOfRetries',
'retryMiliSeconds',
'readPreference',
'pkFactory',
'parentDb',
'promiseLibrary',
'noListener'
];
// Filter out any write concern options
const illegalCommandFields = [
'w',
'wtimeout',
'j',
'fsync',
'autoIndexId',
'strict',
'serializeFunctions',
'pkFactory',
'raw',
'readPreference',
'session'
];
/**
* Add a user to the database.
* @method
* @param {Db} db The Db instance on which to add a user.
* @param {string} username The username.
* @param {string} password The password.
* @param {object} [options] Optional settings. See Db.prototype.addUser for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function addUser(db, username, password, options, callback) {
const Db = require('../db');
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed())
return callback(new MongoError('topology was destroyed'));
// Attempt to execute auth command
executeAuthCreateUserCommand(db, username, password, options, (err, r) => {
// We need to perform the backward compatible insert operation
if (err && err.code === -5000) {
const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
// Use node md5 generator
const md5 = crypto.createHash('md5');
// Generate keys used for authentication
md5.update(username + ':mongo:' + password);
const userPassword = md5.digest('hex');
// If we have another db set
const db = options.dbName ? new Db(options.dbName, db.s.topology, db.s.options) : db;
// Fetch a user collection
const collection = db.collection(CONSTANTS.SYSTEM_USER_COLLECTION);
// Check if we are inserting the first user
count(collection, {}, finalOptions, (err, count) => {
// We got an error (f.ex not authorized)
if (err != null) return handleCallback(callback, err, null);
// Check if the user exists and update i
const findOptions = Object.assign({ projection: { dbName: 1 } }, finalOptions);
collection.find({ user: username }, findOptions).toArray(err => {
// We got an error (f.ex not authorized)
if (err != null) return handleCallback(callback, err, null);
// Add command keys
finalOptions.upsert = true;
// We have a user, let's update the password or upsert if not
updateOne(
collection,
{ user: username },
{ $set: { user: username, pwd: userPassword } },
finalOptions,
err => {
if (count === 0 && err)
return handleCallback(callback, null, [{ user: username, pwd: userPassword }]);
if (err) return handleCallback(callback, err, null);
handleCallback(callback, null, [{ user: username, pwd: userPassword }]);
}
);
});
});
return;
}
if (err) return handleCallback(callback, err);
handleCallback(callback, err, r);
});
}
/**
* Fetch all collections for the current db.
*
* @method
* @param {Db} db The Db instance on which to fetch collections.
* @param {object} [options] Optional settings. See Db.prototype.collections for a list of options.
* @param {Db~collectionsResultCallback} [callback] The results callback
*/
function collections(db, options, callback) {
const Collection = require('../collection');
options = Object.assign({}, options, { nameOnly: true });
// Let's get the collection names
db.listCollections({}, options).toArray((err, documents) => {
if (err != null) return handleCallback(callback, err, null);
// Filter collections removing any illegal ones
documents = documents.filter(doc => {
return doc.name.indexOf('$') === -1;
});
// Return the collection objects
handleCallback(
callback,
null,
documents.map(d => {
return new Collection(
db,
db.s.topology,
db.s.databaseName,
d.name,
db.s.pkFactory,
db.s.options
);
})
);
});
}
/**
* Create a new collection on a server with the specified options. Use this to create capped collections.
* More information about command options available at https://docs.mongodb.com/manual/reference/command/create/
*
* @method
* @param {Db} db The Db instance on which to create the collection.
* @param {string} name The collection name to create.
* @param {object} [options] Optional settings. See Db.prototype.createCollection for a list of options.
* @param {Db~collectionResultCallback} [callback] The results callback
*/
function createCollection(db, name, options, callback) {
const Collection = require('../collection');
// Get the write concern options
const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed()) {
return callback(new MongoError('topology was destroyed'));
}
const listCollectionOptions = Object.assign({}, finalOptions, { nameOnly: true });
// Check if we have the name
db
.listCollections({ name }, listCollectionOptions)
.setReadPreference(ReadPreference.PRIMARY)
.toArray((err, collections) => {
if (err != null) return handleCallback(callback, err, null);
if (collections.length > 0 && finalOptions.strict) {
return handleCallback(
callback,
MongoError.create({
message: `Collection ${name} already exists. Currently in strict mode.`,
driver: true
}),
null
);
} else if (collections.length > 0) {
try {
return handleCallback(
callback,
null,
new Collection(db, db.s.topology, db.s.databaseName, name, db.s.pkFactory, options)
);
} catch (err) {
return handleCallback(callback, err);
}
}
// Create collection command
const cmd = { create: name };
// Decorate command with writeConcern if supported
applyWriteConcern(cmd, { db }, options);
// Add all optional parameters
for (let n in options) {
if (
options[n] != null &&
typeof options[n] !== 'function' &&
illegalCommandFields.indexOf(n) === -1
) {
cmd[n] = options[n];
}
}
// Force a primary read Preference
finalOptions.readPreference = ReadPreference.PRIMARY;
// Execute command
executeCommand(db, cmd, finalOptions, err => {
if (err) return handleCallback(callback, err);
handleCallback(
callback,
null,
new Collection(db, db.s.topology, db.s.databaseName, name, db.s.pkFactory, options)
);
});
});
}
/**
* Creates an index on the db and collection.
* @method
* @param {Db} db The Db instance on which to create an index.
* @param {string} name Name of the collection to create the index on.
* @param {(string|object)} fieldOrSpec Defines the index.
* @param {object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function createIndex(db, name, fieldOrSpec, options, callback) {
// Get the write concern options
let finalOptions = Object.assign({}, { readPreference: ReadPreference.PRIMARY }, options);
finalOptions = applyWriteConcern(finalOptions, { db }, options);
// Ensure we have a callback
if (finalOptions.writeConcern && typeof callback !== 'function') {
throw MongoError.create({
message: 'Cannot use a writeConcern without a provided callback',
driver: true
});
}
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed())
return callback(new MongoError('topology was destroyed'));
// Attempt to run using createIndexes command
createIndexUsingCreateIndexes(db, name, fieldOrSpec, finalOptions, (err, result) => {
if (err == null) return handleCallback(callback, err, result);
/**
* The following errors mean that the server recognized `createIndex` as a command so we don't need to fallback to an insert:
* 67 = 'CannotCreateIndex' (malformed index options)
* 85 = 'IndexOptionsConflict' (index already exists with different options)
* 86 = 'IndexKeySpecsConflict' (index already exists with the same name)
* 11000 = 'DuplicateKey' (couldn't build unique index because of dupes)
* 11600 = 'InterruptedAtShutdown' (interrupted at shutdown)
* 197 = 'InvalidIndexSpecificationOption' (`_id` with `background: true`)
*/
if (
err.code === 67 ||
err.code === 11000 ||
err.code === 85 ||
err.code === 86 ||
err.code === 11600 ||
err.code === 197
) {
return handleCallback(callback, err, result);
}
// Create command
const doc = createCreateIndexCommand(db, name, fieldOrSpec, options);
// Set no key checking
finalOptions.checkKeys = false;
// Insert document
db.s.topology.insert(
`${db.s.databaseName}.${CONSTANTS.SYSTEM_INDEX_COLLECTION}`,
doc,
finalOptions,
(err, result) => {
if (callback == null) return;
if (err) return handleCallback(callback, err);
if (result == null) return handleCallback(callback, null, null);
if (result.result.writeErrors)
return handleCallback(callback, MongoError.create(result.result.writeErrors[0]), null);
handleCallback(callback, null, doc.name);
}
);
});
}
// Add listeners to topology
function createListener(db, e, object) {
function listener(err) {
if (object.listeners(e).length > 0) {
object.emit(e, err, db);
// Emit on all associated db's if available
for (let i = 0; i < db.s.children.length; i++) {
db.s.children[i].emit(e, err, db.s.children[i]);
}
}
}
return listener;
}
/**
* Drop a collection from the database, removing it permanently. New accesses will create a new collection.
*
* @method
* @param {Db} db The Db instance on which to drop the collection.
* @param {string} name Name of collection to drop
* @param {Object} [options] Optional settings. See Db.prototype.dropCollection for a list of options.
* @param {Db~resultCallback} [callback] The results callback
*/
function dropCollection(db, name, options, callback) {
executeCommand(db, name, options, (err, result) => {
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed()) {
return callback(new MongoError('topology was destroyed'));
}
if (err) return handleCallback(callback, err);
if (result.ok) return handleCallback(callback, null, true);
handleCallback(callback, null, false);
});
}
/**
* Drop a database, removing it permanently from the server.
*
* @method
* @param {Db} db The Db instance to drop.
* @param {Object} cmd The command document.
* @param {Object} [options] Optional settings. See Db.prototype.dropDatabase for a list of options.
* @param {Db~resultCallback} [callback] The results callback
*/
function dropDatabase(db, cmd, options, callback) {
executeCommand(db, cmd, options, (err, result) => {
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed()) {
return callback(new MongoError('topology was destroyed'));
}
if (callback == null) return;
if (err) return handleCallback(callback, err, null);
handleCallback(callback, null, result.ok ? true : false);
});
}
/**
* Ensures that an index exists. If it does not, creates it.
*
* @method
* @param {Db} db The Db instance on which to ensure the index.
* @param {string} name The index name
* @param {(string|object)} fieldOrSpec Defines the index.
* @param {object} [options] Optional settings. See Db.prototype.ensureIndex for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function ensureIndex(db, name, fieldOrSpec, options, callback) {
// Get the write concern options
const finalOptions = applyWriteConcern({}, { db }, options);
// Create command
const selector = createCreateIndexCommand(db, name, fieldOrSpec, options);
const index_name = selector.name;
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed())
return callback(new MongoError('topology was destroyed'));
// Merge primary readPreference
finalOptions.readPreference = ReadPreference.PRIMARY;
// Check if the index already exists
indexInformation(db, name, finalOptions, (err, indexInformation) => {
if (err != null && err.code !== 26) return handleCallback(callback, err, null);
// If the index does not exist, create it
if (indexInformation == null || !indexInformation[index_name]) {
createIndex(db, name, fieldOrSpec, options, callback);
} else {
if (typeof callback === 'function') return handleCallback(callback, null, index_name);
}
});
}
/**
* Evaluate JavaScript on the server
*
* @method
* @param {Db} db The Db instance.
* @param {Code} code JavaScript to execute on server.
* @param {(object|array)} parameters The parameters for the call.
* @param {object} [options] Optional settings. See Db.prototype.eval for a list of options.
* @param {Db~resultCallback} [callback] The results callback
* @deprecated Eval is deprecated on MongoDB 3.2 and forward
*/
function evaluate(db, code, parameters, options, callback) {
let finalCode = code;
let finalParameters = [];
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed())
return callback(new MongoError('topology was destroyed'));
// If not a code object translate to one
if (!(finalCode && finalCode._bsontype === 'Code')) finalCode = new Code(finalCode);
// Ensure the parameters are correct
if (parameters != null && !Array.isArray(parameters) && typeof parameters !== 'function') {
finalParameters = [parameters];
} else if (parameters != null && Array.isArray(parameters) && typeof parameters !== 'function') {
finalParameters = parameters;
}
// Create execution selector
let cmd = { $eval: finalCode, args: finalParameters };
// Check if the nolock parameter is passed in
if (options['nolock']) {
cmd['nolock'] = options['nolock'];
}
// Set primary read preference
options.readPreference = new ReadPreference(ReadPreference.PRIMARY);
// Execute the command
executeCommand(db, cmd, options, (err, result) => {
if (err) return handleCallback(callback, err, null);
if (result && result.ok === 1) return handleCallback(callback, null, result.retval);
if (result)
return handleCallback(
callback,
MongoError.create({ message: `eval failed: ${result.errmsg}`, driver: true }),
null
);
handleCallback(callback, err, result);
});
}
/**
* Execute a command
*
* @method
* @param {Db} db The Db instance on which to execute the command.
* @param {object} command The command hash
* @param {object} [options] Optional settings. See Db.prototype.command for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function executeCommand(db, command, options, callback) {
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed())
return callback(new MongoError('topology was destroyed'));
// Get the db name we are executing against
const dbName = options.dbName || options.authdb || db.s.databaseName;
// Convert the readPreference if its not a write
options.readPreference = resolveReadPreference(options, { db, default: ReadPreference.primary });
// Debug information
if (db.s.logger.isDebug())
db.s.logger.debug(
`executing command ${JSON.stringify(
command
)} against ${dbName}.$cmd with options [${JSON.stringify(
debugOptions(debugFields, options)
)}]`
);
// Execute command
db.s.topology.command(`${dbName}.$cmd`, command, options, (err, result) => {
if (err) return handleCallback(callback, err);
if (options.full) return handleCallback(callback, null, result);
handleCallback(callback, null, result.result);
});
}
/**
* Runs a command on the database as admin.
*
* @method
* @param {Db} db The Db instance on which to execute the command.
* @param {object} command The command hash
* @param {object} [options] Optional settings. See Db.prototype.executeDbAdminCommand for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function executeDbAdminCommand(db, command, options, callback) {
db.s.topology.command('admin.$cmd', command, options, (err, result) => {
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed()) {
return callback(new MongoError('topology was destroyed'));
}
if (err) return handleCallback(callback, err);
handleCallback(callback, null, result.result);
});
}
/**
* Retrieves this collections index info.
*
* @method
* @param {Db} db The Db instance on which to retrieve the index info.
* @param {string} name The name of the collection.
* @param {object} [options] Optional settings. See Db.prototype.indexInformation for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function indexInformation(db, name, options, callback) {
// If we specified full information
const full = options['full'] == null ? false : options['full'];
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed())
return callback(new MongoError('topology was destroyed'));
// Process all the results from the index command and collection
function processResults(indexes) {
// Contains all the information
let info = {};
// Process all the indexes
for (let i = 0; i < indexes.length; i++) {
const index = indexes[i];
// Let's unpack the object
info[index.name] = [];
for (let name in index.key) {
info[index.name].push([name, index.key[name]]);
}
}
return info;
}
// Get the list of indexes of the specified collection
db
.collection(name)
.listIndexes(options)
.toArray((err, indexes) => {
if (err) return callback(toError(err));
if (!Array.isArray(indexes)) return handleCallback(callback, null, []);
if (full) return handleCallback(callback, null, indexes);
handleCallback(callback, null, processResults(indexes));
});
}
// Transformation methods for cursor results
function listCollectionsTransforms(databaseName) {
const matching = `${databaseName}.`;
return {
doc: doc => {
const index = doc.name.indexOf(matching);
// Remove database name if available
if (doc.name && index === 0) {
doc.name = doc.name.substr(index + matching.length);
}
return doc;
}
};
}
/**
* Retrive the current profiling information for MongoDB
*
* @method
* @param {Db} db The Db instance on which to retrieve the profiling info.
* @param {Object} [options] Optional settings. See Db.protoype.profilingInfo for a list of options.
* @param {Db~resultCallback} [callback] The command result callback.
* @deprecated Query the system.profile collection directly.
*/
function profilingInfo(db, options, callback) {
try {
db
.collection('system.profile')
.find({}, options)
.toArray(callback);
} catch (err) {
return callback(err, null);
}
}
/**
* Retrieve the current profiling level for MongoDB
*
* @method
* @param {Db} db The Db instance on which to retrieve the profiling level.
* @param {Object} [options] Optional settings. See Db.prototype.profilingLevel for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function profilingLevel(db, options, callback) {
executeCommand(db, { profile: -1 }, options, (err, doc) => {
if (err == null && doc.ok === 1) {
const was = doc.was;
if (was === 0) return callback(null, 'off');
if (was === 1) return callback(null, 'slow_only');
if (was === 2) return callback(null, 'all');
return callback(new Error('Error: illegal profiling level value ' + was), null);
} else {
err != null ? callback(err, null) : callback(new Error('Error with profile command'), null);
}
});
}
/**
* Remove a user from a database
*
* @method
* @param {Db} db The Db instance on which to remove the user.
* @param {string} username The username.
* @param {object} [options] Optional settings. See Db.prototype.removeUser for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function removeUser(db, username, options, callback) {
const Db = require('../db');
// Attempt to execute command
executeAuthRemoveUserCommand(db, username, options, (err, result) => {
if (err && err.code === -5000) {
const finalOptions = applyWriteConcern(Object.assign({}, options), { db }, options);
// If we have another db set
const db = options.dbName ? new Db(options.dbName, db.s.topology, db.s.options) : db;
// Fetch a user collection
const collection = db.collection(CONSTANTS.SYSTEM_USER_COLLECTION);
// Locate the user
findOne(collection, { user: username }, finalOptions, (err, user) => {
if (user == null) return handleCallback(callback, err, false);
remove(collection, { user: username }, finalOptions, err => {
handleCallback(callback, err, true);
});
});
return;
}
if (err) return handleCallback(callback, err);
handleCallback(callback, err, result);
});
}
/**
* Set the current profiling level of MongoDB
*
* @method
* @param {Db} db The Db instance on which to execute the command.
* @param {string} level The new profiling level (off, slow_only, all).
* @param {Object} [options] Optional settings. See Db.prototype.setProfilingLevel for a list of options.
* @param {Db~resultCallback} [callback] The command result callback.
*/
function setProfilingLevel(db, level, options, callback) {
const command = {};
let profile = 0;
if (level === 'off') {
profile = 0;
} else if (level === 'slow_only') {
profile = 1;
} else if (level === 'all') {
profile = 2;
} else {
return callback(new Error('Error: illegal profiling level value ' + level));
}
// Set up the profile number
command['profile'] = profile;
executeCommand(db, command, options, (err, doc) => {
if (err == null && doc.ok === 1) return callback(null, level);
return err != null
? callback(err, null)
: callback(new Error('Error with profile command'), null);
});
}
// Validate the database name
function validateDatabaseName(databaseName) {
if (typeof databaseName !== 'string')
throw MongoError.create({ message: 'database name must be a string', driver: true });
if (databaseName.length === 0)
throw MongoError.create({ message: 'database name cannot be the empty string', driver: true });
if (databaseName === '$external') return;
const invalidChars = [' ', '.', '$', '/', '\\'];
for (let i = 0; i < invalidChars.length; i++) {
if (databaseName.indexOf(invalidChars[i]) !== -1)
throw MongoError.create({
message: "database names cannot contain the character '" + invalidChars[i] + "'",
driver: true
});
}
}
/**
* Create the command object for Db.prototype.createIndex.
*
* @param {Db} db The Db instance on which to create the command.
* @param {string} name Name of the collection to create the index on.
* @param {(string|object)} fieldOrSpec Defines the index.
* @param {Object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
* @return {Object} The insert command object.
*/
function createCreateIndexCommand(db, name, fieldOrSpec, options) {
const indexParameters = parseIndexOptions(fieldOrSpec);
const fieldHash = indexParameters.fieldHash;
// Generate the index name
const indexName = typeof options.name === 'string' ? options.name : indexParameters.name;
const selector = {
ns: db.databaseName + '.' + name,
key: fieldHash,
name: indexName
};
// Ensure we have a correct finalUnique
const finalUnique = options == null || 'object' === typeof options ? false : options;
// Set up options
options = options == null || typeof options === 'boolean' ? {} : options;
// Add all the options
const keysToOmit = Object.keys(selector);
for (let optionName in options) {
if (keysToOmit.indexOf(optionName) === -1) {
selector[optionName] = options[optionName];
}
}
if (selector['unique'] == null) selector['unique'] = finalUnique;
// Remove any write concern operations
const removeKeys = ['w', 'wtimeout', 'j', 'fsync', 'readPreference', 'session'];
for (let i = 0; i < removeKeys.length; i++) {
delete selector[removeKeys[i]];
}
// Return the command creation selector
return selector;
}
/**
* Create index using the createIndexes command.
*
* @param {Db} db The Db instance on which to execute the command.
* @param {string} name Name of the collection to create the index on.
* @param {(string|object)} fieldOrSpec Defines the index.
* @param {Object} [options] Optional settings. See Db.prototype.createIndex for a list of options.
* @param {Db~resultCallback} [callback] The command result callback.
*/
function createIndexUsingCreateIndexes(db, name, fieldOrSpec, options, callback) {
// Build the index
const indexParameters = parseIndexOptions(fieldOrSpec);
// Generate the index name
const indexName = typeof options.name === 'string' ? options.name : indexParameters.name;
// Set up the index
const indexes = [{ name: indexName, key: indexParameters.fieldHash }];
// merge all the options
const keysToOmit = Object.keys(indexes[0]).concat([
'writeConcern',
'w',
'wtimeout',
'j',
'fsync',
'readPreference',
'session'
]);
for (let optionName in options) {
if (keysToOmit.indexOf(optionName) === -1) {
indexes[0][optionName] = options[optionName];
}
}
// Get capabilities
const capabilities = db.s.topology.capabilities();
// Did the user pass in a collation, check if our write server supports it
if (indexes[0].collation && capabilities && !capabilities.commandsTakeCollation) {
// Create a new error
const error = new MongoError('server/primary/mongos does not support collation');
error.code = 67;
// Return the error
return callback(error);
}
// Create command, apply write concern to command
const cmd = applyWriteConcern({ createIndexes: name, indexes }, { db }, options);
// ReadPreference primary
options.readPreference = ReadPreference.PRIMARY;
// Build the command
executeCommand(db, cmd, options, (err, result) => {
if (err) return handleCallback(callback, err, null);
if (result.ok === 0) return handleCallback(callback, toError(result), null);
// Return the indexName for backward compatibility
handleCallback(callback, null, indexName);
});
}
/**
* Run the createUser command.
*
* @param {Db} db The Db instance on which to execute the command.
* @param {string} username The username of the user to add.
* @param {string} password The password of the user to add.
* @param {object} [options] Optional settings. See Db.prototype.addUser for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function executeAuthCreateUserCommand(db, username, password, options, callback) {
// Special case where there is no password ($external users)
if (typeof username === 'string' && password != null && typeof password === 'object') {
options = password;
password = null;
}
// Unpack all options
if (typeof options === 'function') {
callback = options;
options = {};
}
// Error out if we digestPassword set
if (options.digestPassword != null) {
return callback(
toError(
"The digestPassword option is not supported via add_user. Please use db.command('createUser', ...) instead for this option."
)
);
}
// Get additional values
const customData = options.customData != null ? options.customData : {};
let roles = Array.isArray(options.roles) ? options.roles : [];
const maxTimeMS = typeof options.maxTimeMS === 'number' ? options.maxTimeMS : null;
// If not roles defined print deprecated message
if (roles.length === 0) {
console.log('Creating a user without roles is deprecated in MongoDB >= 2.6');
}
// Get the error options
const commandOptions = { writeCommand: true };
if (options['dbName']) commandOptions.dbName = options['dbName'];
// Add maxTimeMS to options if set
if (maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS;
// Check the db name and add roles if needed
if (
(db.databaseName.toLowerCase() === 'admin' || options.dbName === 'admin') &&
!Array.isArray(options.roles)
) {
roles = ['root'];
} else if (!Array.isArray(options.roles)) {
roles = ['dbOwner'];
}
const digestPassword = db.s.topology.lastIsMaster().maxWireVersion >= 7;
// Build the command to execute
let command = {
createUser: username,
customData: customData,
roles: roles,
digestPassword
};
// Apply write concern to command
command = applyWriteConcern(command, { db }, options);
let userPassword = password;
if (!digestPassword) {
// Use node md5 generator
const md5 = crypto.createHash('md5');
// Generate keys used for authentication
md5.update(username + ':mongo:' + password);
userPassword = md5.digest('hex');
}
// No password
if (typeof password === 'string') {
command.pwd = userPassword;
}
// Force write using primary
commandOptions.readPreference = ReadPreference.primary;
// Execute the command
executeCommand(db, command, commandOptions, (err, result) => {
if (err && err.ok === 0 && err.code === undefined)
return handleCallback(callback, { code: -5000 }, null);
if (err) return handleCallback(callback, err, null);
handleCallback(
callback,
!result.ok ? toError(result) : null,
result.ok ? [{ user: username, pwd: '' }] : null
);
});
}
/**
* Run the dropUser command.
*
* @param {Db} db The Db instance on which to execute the command.
* @param {string} username The username of the user to remove.
* @param {object} [options] Optional settings. See Db.prototype.removeUser for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function executeAuthRemoveUserCommand(db, username, options, callback) {
if (typeof options === 'function') (callback = options), (options = {});
options = options || {};
// Did the user destroy the topology
if (db.serverConfig && db.serverConfig.isDestroyed())
return callback(new MongoError('topology was destroyed'));
// Get the error options
const commandOptions = { writeCommand: true };
if (options['dbName']) commandOptions.dbName = options['dbName'];
// Get additional values
const maxTimeMS = typeof options.maxTimeMS === 'number' ? options.maxTimeMS : null;
// Add maxTimeMS to options if set
if (maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS;
// Build the command to execute
let command = {
dropUser: username
};
// Apply write concern to command
command = applyWriteConcern(command, { db }, options);
// Force write using primary
commandOptions.readPreference = ReadPreference.primary;
// Execute the command
executeCommand(db, command, commandOptions, (err, result) => {
if (err && !err.ok && err.code === undefined) return handleCallback(callback, { code: -5000 });
if (err) return handleCallback(callback, err, null);
handleCallback(callback, null, result.ok ? true : false);
});
}
module.exports = {
addUser,
collections,
createCollection,
createListener,
createIndex,
dropCollection,
dropDatabase,
ensureIndex,
evaluate,
executeCommand,
executeDbAdminCommand,
listCollectionsTransforms,
indexInformation,
profilingInfo,
profilingLevel,
removeUser,
setProfilingLevel,
validateDatabaseName
};
+646
View File
@@ -0,0 +1,646 @@
'use strict';
const authenticate = require('../authenticate');
const deprecate = require('util').deprecate;
const Logger = require('mongodb-core').Logger;
const MongoError = require('mongodb-core').MongoError;
const Mongos = require('../topologies/mongos');
const parse = require('mongodb-core').parseConnectionString;
const ReadPreference = require('mongodb-core').ReadPreference;
const ReplSet = require('../topologies/replset');
const Server = require('../topologies/server');
const ServerSessionPool = require('mongodb-core').Sessions.ServerSessionPool;
const monitoringEvents = [
'timeout',
'close',
'serverOpening',
'serverDescriptionChanged',
'serverHeartbeatStarted',
'serverHeartbeatSucceeded',
'serverHeartbeatFailed',
'serverClosed',
'topologyOpening',
'topologyClosed',
'topologyDescriptionChanged',
'commandStarted',
'commandSucceeded',
'commandFailed',
'joined',
'left',
'ping',
'ha',
'all',
'fullsetup',
'open'
];
const ignoreOptionNames = ['native_parser'];
const legacyOptionNames = ['server', 'replset', 'replSet', 'mongos', 'db'];
const legacyParse = deprecate(
require('../url_parser'),
'current URL string parser is deprecated, and will be removed in a future version. ' +
'To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.'
);
const validOptionNames = [
'poolSize',
'ssl',
'sslValidate',
'sslCA',
'sslCert',
'sslKey',
'sslPass',
'sslCRL',
'autoReconnect',
'noDelay',
'keepAlive',
'keepAliveInitialDelay',
'connectTimeoutMS',
'family',
'socketTimeoutMS',
'reconnectTries',
'reconnectInterval',
'ha',
'haInterval',
'replicaSet',
'secondaryAcceptableLatencyMS',
'acceptableLatencyMS',
'connectWithNoPrimary',
'authSource',
'w',
'wtimeout',
'j',
'forceServerObjectId',
'serializeFunctions',
'ignoreUndefined',
'raw',
'bufferMaxEntries',
'readPreference',
'pkFactory',
'promiseLibrary',
'readConcern',
'maxStalenessSeconds',
'loggerLevel',
'logger',
'promoteValues',
'promoteBuffers',
'promoteLongs',
'domainsEnabled',
'checkServerIdentity',
'validateOptions',
'appname',
'auth',
'user',
'password',
'authMechanism',
'compression',
'fsync',
'readPreferenceTags',
'numberOfRetries',
'auto_reconnect',
'minSize',
'monitorCommands',
'retryWrites',
'useNewUrlParser'
];
function addListeners(mongoClient, topology) {
topology.on('authenticated', createListener(mongoClient, 'authenticated'));
topology.on('error', createListener(mongoClient, 'error'));
topology.on('timeout', createListener(mongoClient, 'timeout'));
topology.on('close', createListener(mongoClient, 'close'));
topology.on('parseError', createListener(mongoClient, 'parseError'));
topology.once('open', createListener(mongoClient, 'open'));
topology.once('fullsetup', createListener(mongoClient, 'fullsetup'));
topology.once('all', createListener(mongoClient, 'all'));
topology.on('reconnect', createListener(mongoClient, 'reconnect'));
}
function assignTopology(client, topology) {
client.topology = topology;
topology.s.sessionPool = new ServerSessionPool(topology.s.coreTopology);
}
// Clear out all events
function clearAllEvents(topology) {
monitoringEvents.forEach(event => topology.removeAllListeners(event));
}
// Collect all events in order from SDAM
function collectEvents(mongoClient, topology) {
const MongoClient = require('../mongo_client');
const collectedEvents = [];
if (mongoClient instanceof MongoClient) {
monitoringEvents.forEach(event => {
topology.on(event, (object1, object2) => {
if (event === 'open') {
collectedEvents.push({ event: event, object1: mongoClient });
} else {
collectedEvents.push({ event: event, object1: object1, object2: object2 });
}
});
});
}
return collectedEvents;
}
/**
* Connect to MongoDB using a url as documented at
*
* docs.mongodb.org/manual/reference/connection-string/
*
* Note that for replicasets the replicaSet query parameter is required in the 2.0 driver
*
* @method
* @param {MongoClient} mongoClient The MongoClient instance with which to connect.
* @param {string} url The connection URI string
* @param {object} [options] Optional settings. See MongoClient.prototype.connect for a list of options.
* @param {MongoClient~connectCallback} [callback] The command result callback
*/
function connect(mongoClient, url, options, callback) {
options = Object.assign({}, options);
// If callback is null throw an exception
if (callback == null) {
throw new Error('no callback function provided');
}
// Get a logger for MongoClient
const logger = Logger('MongoClient', options);
// Did we pass in a Server/ReplSet/Mongos
if (url instanceof Server || url instanceof ReplSet || url instanceof Mongos) {
return connectWithUrl(mongoClient, url, options, connectCallback);
}
const parseFn = options.useNewUrlParser ? parse : legacyParse;
const transform = options.useNewUrlParser ? transformUrlOptions : legacyTransformUrlOptions;
parseFn(url, options, (err, _object) => {
// Do not attempt to connect if parsing error
if (err) return callback(err);
// Flatten
const object = transform(_object);
// Parse the string
const _finalOptions = createUnifiedOptions(object, options);
// Check if we have connection and socket timeout set
if (_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 360000;
if (_finalOptions.connectTimeoutMS == null) _finalOptions.connectTimeoutMS = 30000;
if (_finalOptions.db_options && _finalOptions.db_options.auth) {
delete _finalOptions.db_options.auth;
}
// Store the merged options object
mongoClient.s.options = _finalOptions;
// Failure modes
if (object.servers.length === 0) {
return callback(new Error('connection string must contain at least one seed host'));
}
// Do we have a replicaset then skip discovery and go straight to connectivity
if (_finalOptions.replicaSet || _finalOptions.rs_name) {
return createTopology(
mongoClient,
'replicaset',
_finalOptions,
connectHandler(mongoClient, _finalOptions, connectCallback)
);
} else if (object.servers.length > 1) {
return createTopology(
mongoClient,
'mongos',
_finalOptions,
connectHandler(mongoClient, _finalOptions, connectCallback)
);
} else {
return createServer(
mongoClient,
_finalOptions,
connectHandler(mongoClient, _finalOptions, connectCallback)
);
}
});
function connectCallback(err, topology) {
const warningMessage = `seed list contains no mongos proxies, replicaset connections requires the parameter replicaSet to be supplied in the URI or options object, mongodb://server:port/db?replicaSet=name`;
if (err && err.message === 'no mongos proxies found in seed list') {
if (logger.isWarn()) {
logger.warn(warningMessage);
}
// Return a more specific error message for MongoClient.connect
return callback(new MongoError(warningMessage));
}
// Return the error and db instance
callback(err, topology);
}
}
function connectHandler(client, options, callback) {
return (err, topology) => {
if (err) {
return handleConnectCallback(err, topology, callback);
}
// No authentication just reconnect
if (!options.auth) {
return handleConnectCallback(err, topology, callback);
}
// Authenticate
authenticate(client, options.user, options.password, options, (err, success) => {
if (success) {
handleConnectCallback(null, topology, callback);
} else {
if (topology) topology.close();
const authError = err ? err : new Error('Could not authenticate user ' + options.auth[0]);
handleConnectCallback(authError, topology, callback);
}
});
};
}
/**
* Connect to MongoDB using a url as documented at
*
* docs.mongodb.org/manual/reference/connection-string/
*
* Note that for replicasets the replicaSet query parameter is required in the 2.0 driver
*
* @method
* @param {MongoClient} mongoClient The MongoClient instance with which to connect.
* @param {MongoClient~connectCallback} [callback] The command result callback
*/
function connectOp(mongoClient, err, callback) {
// Did we have a validation error
if (err) return callback(err);
// Fallback to callback based connect
connect(mongoClient, mongoClient.s.url, mongoClient.s.options, err => {
if (err) return callback(err);
callback(null, mongoClient);
});
}
function connectWithUrl(mongoClient, url, options, connectCallback) {
// Set the topology
assignTopology(mongoClient, url);
// Add listeners
addListeners(mongoClient, url);
// Propagate the events to the client
relayEvents(mongoClient, url);
let finalOptions = Object.assign({}, options);
// If we have a readPreference passed in by the db options, convert it from a string
if (typeof options.readPreference === 'string' || typeof options.read_preference === 'string') {
finalOptions.readPreference = new ReadPreference(
options.readPreference || options.read_preference
);
}
// Connect
return url.connect(
finalOptions,
connectHandler(mongoClient, finalOptions, (err, topology) => {
if (err) return connectCallback(err, topology);
if (finalOptions.user || finalOptions.password || finalOptions.authMechanism) {
return authenticate(
mongoClient,
finalOptions.user,
finalOptions.password,
finalOptions,
err => {
if (err) return connectCallback(err, topology);
connectCallback(err, topology);
}
);
}
connectCallback(err, topology);
})
);
}
function createListener(mongoClient, event) {
const eventSet = new Set(['all', 'fullsetup', 'open', 'reconnect']);
return (v1, v2) => {
if (eventSet.has(event)) {
return mongoClient.emit(event, mongoClient);
}
mongoClient.emit(event, v1, v2);
};
}
function createServer(mongoClient, options, callback) {
// Pass in the promise library
options.promiseLibrary = mongoClient.s.promiseLibrary;
// Set default options
const servers = translateOptions(options);
const server = servers[0];
// Propagate the events to the client
const collectedEvents = collectEvents(mongoClient, server);
// Connect to topology
server.connect(options, (err, topology) => {
if (err) {
server.close(true);
return callback(err);
}
// Clear out all the collected event listeners
clearAllEvents(server);
// Relay all the events
relayEvents(mongoClient, server);
// Add listeners
addListeners(mongoClient, server);
// Check if we are really speaking to a mongos
const ismaster = topology.lastIsMaster();
// Set the topology
assignTopology(mongoClient, topology);
// Do we actually have a mongos
if (ismaster && ismaster.msg === 'isdbgrid') {
// Destroy the current connection
topology.close();
// Create mongos connection instead
return createTopology(mongoClient, 'mongos', options, callback);
}
// Fire all the events
replayEvents(mongoClient, collectedEvents);
// Otherwise callback
callback(err, topology);
});
}
function createTopology(mongoClient, topologyType, options, callback) {
// Pass in the promise library
options.promiseLibrary = mongoClient.s.promiseLibrary;
const translationOptions = {};
if (topologyType === 'unified') translationOptions.createServers = false;
// Set default options
const servers = translateOptions(options, translationOptions);
// Create the topology
let topology;
if (topologyType === 'mongos') {
topology = new Mongos(servers, options);
} else if (topologyType === 'replicaset') {
topology = new ReplSet(servers, options);
}
// Add listeners
addListeners(mongoClient, topology);
// Propagate the events to the client
relayEvents(mongoClient, topology);
// Open the connection
topology.connect(options, (err, newTopology) => {
if (err) {
topology.close(true);
return callback(err);
}
assignTopology(mongoClient, newTopology);
callback(null, newTopology);
});
}
function createUnifiedOptions(finalOptions, options) {
const childOptions = [
'mongos',
'server',
'db',
'replset',
'db_options',
'server_options',
'rs_options',
'mongos_options'
];
const noMerge = ['readconcern', 'compression'];
for (const name in options) {
if (noMerge.indexOf(name.toLowerCase()) !== -1) {
finalOptions[name] = options[name];
} else if (childOptions.indexOf(name.toLowerCase()) !== -1) {
finalOptions = mergeOptions(finalOptions, options[name], false);
} else {
if (
options[name] &&
typeof options[name] === 'object' &&
!Buffer.isBuffer(options[name]) &&
!Array.isArray(options[name])
) {
finalOptions = mergeOptions(finalOptions, options[name], true);
} else {
finalOptions[name] = options[name];
}
}
}
return finalOptions;
}
function handleConnectCallback(err, topology, callback) {
return process.nextTick(() => {
try {
callback(err, topology);
} catch (err) {
if (topology) topology.close();
throw err;
}
});
}
function legacyTransformUrlOptions(object) {
return mergeOptions(createUnifiedOptions({}, object), object, false);
}
/**
* Logout user from server, fire off on all connections and remove all auth info.
*
* @method
* @param {MongoClient} mongoClient The MongoClient instance on which to logout.
* @param {object} [options] Optional settings. See MongoClient.prototype.logout for a list of options.
* @param {Db~resultCallback} [callback] The command result callback
*/
function logout(mongoClient, dbName, callback) {
mongoClient.topology.logout(dbName, err => {
if (err) return callback(err);
callback(null, true);
});
}
function mergeOptions(target, source, flatten) {
for (const name in source) {
if (source[name] && typeof source[name] === 'object' && flatten) {
target = mergeOptions(target, source[name], flatten);
} else {
target[name] = source[name];
}
}
return target;
}
function relayEvents(mongoClient, topology) {
const serverOrCommandEvents = [
'serverOpening',
'serverDescriptionChanged',
'serverHeartbeatStarted',
'serverHeartbeatSucceeded',
'serverHeartbeatFailed',
'serverClosed',
'topologyOpening',
'topologyClosed',
'topologyDescriptionChanged',
'commandStarted',
'commandSucceeded',
'commandFailed',
'joined',
'left',
'ping',
'ha'
];
serverOrCommandEvents.forEach(event => {
topology.on(event, (object1, object2) => {
mongoClient.emit(event, object1, object2);
});
});
}
//
// Replay any events due to single server connection switching to Mongos
//
function replayEvents(mongoClient, events) {
for (let i = 0; i < events.length; i++) {
mongoClient.emit(events[i].event, events[i].object1, events[i].object2);
}
}
const LEGACY_OPTIONS_MAP = validOptionNames.reduce((obj, name) => {
obj[name.toLowerCase()] = name;
return obj;
}, {});
function transformUrlOptions(_object) {
let object = Object.assign({ servers: _object.hosts }, _object.options);
for (let name in object) {
const camelCaseName = LEGACY_OPTIONS_MAP[name];
if (camelCaseName) {
object[camelCaseName] = object[name];
}
}
if (_object.auth) {
const auth = _object.auth;
for (let i in auth) {
if (auth[i]) {
object[i] = auth[i];
}
}
if (auth.username) {
object.auth = auth;
object.user = auth.username;
}
if (auth.db) {
object.authSource = object.authSource || auth.db;
}
}
if (_object.defaultDatabase) {
object.dbName = _object.defaultDatabase;
}
if (object.maxpoolsize) {
object.poolSize = object.maxpoolsize;
}
if (object.readconcernlevel) {
object.readConcern = { level: object.readconcernlevel };
}
if (object.wtimeoutms) {
object.wtimeout = object.wtimeoutms;
}
return object;
}
function translateOptions(options, translationOptions) {
translationOptions = Object.assign({}, { createServers: true }, translationOptions);
// If we have a readPreference passed in by the db options
if (typeof options.readPreference === 'string' || typeof options.read_preference === 'string') {
options.readPreference = new ReadPreference(options.readPreference || options.read_preference);
}
// Do we have readPreference tags, add them
if (options.readPreference && (options.readPreferenceTags || options.read_preference_tags)) {
options.readPreference.tags = options.readPreferenceTags || options.read_preference_tags;
}
// Do we have maxStalenessSeconds
if (options.maxStalenessSeconds) {
options.readPreference.maxStalenessSeconds = options.maxStalenessSeconds;
}
// Set the socket and connection timeouts
if (options.socketTimeoutMS == null) options.socketTimeoutMS = 360000;
if (options.connectTimeoutMS == null) options.connectTimeoutMS = 30000;
if (!translationOptions.createServers) {
return;
}
// Create server instances
return options.servers.map(serverObj => {
return serverObj.domain_socket
? new Server(serverObj.domain_socket, 27017, options)
: new Server(serverObj.host, serverObj.port, options);
});
}
// Validate options object
function validOptions(options) {
const _validOptions = validOptionNames.concat(legacyOptionNames);
for (const name in options) {
if (ignoreOptionNames.indexOf(name) !== -1) {
continue;
}
if (_validOptions.indexOf(name) === -1 && options.validateOptions) {
return new MongoError(`option ${name} is not supported`);
} else if (_validOptions.indexOf(name) === -1) {
console.warn(`the options [${name}] is not supported`);
}
if (legacyOptionNames.indexOf(name) !== -1) {
console.warn(
`the server/replset/mongos/db options are deprecated, ` +
`all their options are supported at the top level of the options object [${validOptionNames}]`
);
}
}
}
module.exports = { connectOp, logout, validOptions };