1 | 'use strict';
|
2 | const MongoError = require('./core/error').MongoError;
|
3 | const ReadPreference = require('./core/topologies/read_preference');
|
4 | const WriteConcern = require('./write_concern');
|
5 |
|
6 | var shallowClone = function(obj) {
|
7 | var copy = {};
|
8 | for (var name in obj) copy[name] = obj[name];
|
9 | return copy;
|
10 | };
|
11 |
|
12 |
|
13 | var translateReadPreference = function(options) {
|
14 | var r = null;
|
15 | if (options.readPreference) {
|
16 | r = options.readPreference;
|
17 | } else {
|
18 | return options;
|
19 | }
|
20 |
|
21 | if (typeof r === 'string') {
|
22 | options.readPreference = new ReadPreference(r);
|
23 | } else if (r && !(r instanceof ReadPreference) && typeof r === 'object') {
|
24 | const mode = r.mode || r.preference;
|
25 | if (mode && typeof mode === 'string') {
|
26 | options.readPreference = new ReadPreference(mode, r.tags, {
|
27 | maxStalenessSeconds: r.maxStalenessSeconds
|
28 | });
|
29 | }
|
30 | } else if (!(r instanceof ReadPreference)) {
|
31 | throw new TypeError('Invalid read preference: ' + r);
|
32 | }
|
33 |
|
34 | return options;
|
35 | };
|
36 |
|
37 |
|
38 | var getSingleProperty = function(obj, name, value) {
|
39 | Object.defineProperty(obj, name, {
|
40 | enumerable: true,
|
41 | get: function() {
|
42 | return value;
|
43 | }
|
44 | });
|
45 | };
|
46 |
|
47 | var formatSortValue = (exports.formatSortValue = function(sortDirection) {
|
48 | var value = ('' + sortDirection).toLowerCase();
|
49 |
|
50 | switch (value) {
|
51 | case 'ascending':
|
52 | case 'asc':
|
53 | case '1':
|
54 | return 1;
|
55 | case 'descending':
|
56 | case 'desc':
|
57 | case '-1':
|
58 | return -1;
|
59 | default:
|
60 | throw new Error(
|
61 | 'Illegal sort clause, must be of the form ' +
|
62 | "[['field1', '(ascending|descending)'], " +
|
63 | "['field2', '(ascending|descending)']]"
|
64 | );
|
65 | }
|
66 | });
|
67 |
|
68 | var formattedOrderClause = (exports.formattedOrderClause = function(sortValue) {
|
69 | var orderBy = {};
|
70 | if (sortValue == null) return null;
|
71 | if (Array.isArray(sortValue)) {
|
72 | if (sortValue.length === 0) {
|
73 | return null;
|
74 | }
|
75 |
|
76 | for (var i = 0; i < sortValue.length; i++) {
|
77 | if (sortValue[i].constructor === String) {
|
78 | orderBy[sortValue[i]] = 1;
|
79 | } else {
|
80 | orderBy[sortValue[i][0]] = formatSortValue(sortValue[i][1]);
|
81 | }
|
82 | }
|
83 | } else if (sortValue != null && typeof sortValue === 'object') {
|
84 | orderBy = sortValue;
|
85 | } else if (typeof sortValue === 'string') {
|
86 | orderBy[sortValue] = 1;
|
87 | } else {
|
88 | throw new Error(
|
89 | 'Illegal sort clause, must be of the form ' +
|
90 | "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
|
91 | );
|
92 | }
|
93 |
|
94 | return orderBy;
|
95 | });
|
96 |
|
97 | var checkCollectionName = function checkCollectionName(collectionName) {
|
98 | if ('string' !== typeof collectionName) {
|
99 | throw new MongoError('collection name must be a String');
|
100 | }
|
101 |
|
102 | if (!collectionName || collectionName.indexOf('..') !== -1) {
|
103 | throw new MongoError('collection names cannot be empty');
|
104 | }
|
105 |
|
106 | if (
|
107 | collectionName.indexOf('$') !== -1 &&
|
108 | collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null
|
109 | ) {
|
110 | throw new MongoError("collection names must not contain '$'");
|
111 | }
|
112 |
|
113 | if (collectionName.match(/^\.|\.$/) != null) {
|
114 | throw new MongoError("collection names must not start or end with '.'");
|
115 | }
|
116 |
|
117 |
|
118 | if (collectionName.indexOf('\x00') !== -1) {
|
119 | throw new MongoError('collection names cannot contain a null character');
|
120 | }
|
121 | };
|
122 |
|
123 | var handleCallback = function(callback, err, value1, value2) {
|
124 | try {
|
125 | if (callback == null) return;
|
126 |
|
127 | if (callback) {
|
128 | return value2 ? callback(err, value1, value2) : callback(err, value1);
|
129 | }
|
130 | } catch (err) {
|
131 | process.nextTick(function() {
|
132 | throw err;
|
133 | });
|
134 | return false;
|
135 | }
|
136 |
|
137 | return true;
|
138 | };
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | var toError = function(error) {
|
146 | if (error instanceof Error) return error;
|
147 |
|
148 | var msg = error.err || error.errmsg || error.errMessage || error;
|
149 | var e = MongoError.create({ message: msg, driver: true });
|
150 |
|
151 |
|
152 | var keys = typeof error === 'object' ? Object.keys(error) : [];
|
153 |
|
154 | for (var i = 0; i < keys.length; i++) {
|
155 | try {
|
156 | e[keys[i]] = error[keys[i]];
|
157 | } catch (err) {
|
158 |
|
159 | }
|
160 | }
|
161 |
|
162 | return e;
|
163 | };
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | var normalizeHintField = function normalizeHintField(hint) {
|
169 | var finalHint = null;
|
170 |
|
171 | if (typeof hint === 'string') {
|
172 | finalHint = hint;
|
173 | } else if (Array.isArray(hint)) {
|
174 | finalHint = {};
|
175 |
|
176 | hint.forEach(function(param) {
|
177 | finalHint[param] = 1;
|
178 | });
|
179 | } else if (hint != null && typeof hint === 'object') {
|
180 | finalHint = {};
|
181 | for (var name in hint) {
|
182 | finalHint[name] = hint[name];
|
183 | }
|
184 | }
|
185 |
|
186 | return finalHint;
|
187 | };
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 | var parseIndexOptions = function(fieldOrSpec) {
|
196 | var fieldHash = {};
|
197 | var indexes = [];
|
198 | var keys;
|
199 |
|
200 |
|
201 | if ('string' === typeof fieldOrSpec) {
|
202 |
|
203 | indexes.push(fieldOrSpec + '_' + 1);
|
204 | fieldHash[fieldOrSpec] = 1;
|
205 | } else if (Array.isArray(fieldOrSpec)) {
|
206 | fieldOrSpec.forEach(function(f) {
|
207 | if ('string' === typeof f) {
|
208 |
|
209 | indexes.push(f + '_' + 1);
|
210 | fieldHash[f] = 1;
|
211 | } else if (Array.isArray(f)) {
|
212 |
|
213 | indexes.push(f[0] + '_' + (f[1] || 1));
|
214 | fieldHash[f[0]] = f[1] || 1;
|
215 | } else if (isObject(f)) {
|
216 |
|
217 | keys = Object.keys(f);
|
218 | keys.forEach(function(k) {
|
219 | indexes.push(k + '_' + f[k]);
|
220 | fieldHash[k] = f[k];
|
221 | });
|
222 | } else {
|
223 |
|
224 | }
|
225 | });
|
226 | } else if (isObject(fieldOrSpec)) {
|
227 |
|
228 | keys = Object.keys(fieldOrSpec);
|
229 | keys.forEach(function(key) {
|
230 | indexes.push(key + '_' + fieldOrSpec[key]);
|
231 | fieldHash[key] = fieldOrSpec[key];
|
232 | });
|
233 | }
|
234 |
|
235 | return {
|
236 | name: indexes.join('_'),
|
237 | keys: keys,
|
238 | fieldHash: fieldHash
|
239 | };
|
240 | };
|
241 |
|
242 | var isObject = (exports.isObject = function(arg) {
|
243 | return '[object Object]' === Object.prototype.toString.call(arg);
|
244 | });
|
245 |
|
246 | var debugOptions = function(debugFields, options) {
|
247 | var finaloptions = {};
|
248 | debugFields.forEach(function(n) {
|
249 | finaloptions[n] = options[n];
|
250 | });
|
251 |
|
252 | return finaloptions;
|
253 | };
|
254 |
|
255 | var decorateCommand = function(command, options, exclude) {
|
256 | for (var name in options) {
|
257 | if (exclude.indexOf(name) === -1) command[name] = options[name];
|
258 | }
|
259 |
|
260 | return command;
|
261 | };
|
262 |
|
263 | var mergeOptions = function(target, source) {
|
264 | for (var name in source) {
|
265 | target[name] = source[name];
|
266 | }
|
267 |
|
268 | return target;
|
269 | };
|
270 |
|
271 |
|
272 | var translateOptions = function(target, source) {
|
273 | var translations = {
|
274 |
|
275 | sslCA: 'ca',
|
276 | sslCRL: 'crl',
|
277 | sslValidate: 'rejectUnauthorized',
|
278 | sslKey: 'key',
|
279 | sslCert: 'cert',
|
280 | sslPass: 'passphrase',
|
281 |
|
282 | socketTimeoutMS: 'socketTimeout',
|
283 | connectTimeoutMS: 'connectionTimeout',
|
284 |
|
285 | replicaSet: 'setName',
|
286 | rs_name: 'setName',
|
287 | secondaryAcceptableLatencyMS: 'acceptableLatency',
|
288 | connectWithNoPrimary: 'secondaryOnlyConnectionAllowed',
|
289 |
|
290 | acceptableLatencyMS: 'localThresholdMS'
|
291 | };
|
292 |
|
293 | for (var name in source) {
|
294 | if (translations[name]) {
|
295 | target[translations[name]] = source[name];
|
296 | } else {
|
297 | target[name] = source[name];
|
298 | }
|
299 | }
|
300 |
|
301 | return target;
|
302 | };
|
303 |
|
304 | var filterOptions = function(options, names) {
|
305 | var filterOptions = {};
|
306 |
|
307 | for (var name in options) {
|
308 | if (names.indexOf(name) !== -1) filterOptions[name] = options[name];
|
309 | }
|
310 |
|
311 |
|
312 | return filterOptions;
|
313 | };
|
314 |
|
315 |
|
316 | var writeConcernKeys = ['w', 'j', 'wtimeout', 'fsync'];
|
317 |
|
318 |
|
319 | var mergeOptionsAndWriteConcern = function(targetOptions, sourceOptions, keys, mergeWriteConcern) {
|
320 |
|
321 | for (var i = 0; i < keys.length; i++) {
|
322 | if (!targetOptions[keys[i]] && sourceOptions[keys[i]] !== undefined) {
|
323 | targetOptions[keys[i]] = sourceOptions[keys[i]];
|
324 | }
|
325 | }
|
326 |
|
327 |
|
328 | if (!mergeWriteConcern) return targetOptions;
|
329 |
|
330 |
|
331 | var found = false;
|
332 | for (i = 0; i < writeConcernKeys.length; i++) {
|
333 | if (targetOptions[writeConcernKeys[i]]) {
|
334 | found = true;
|
335 | break;
|
336 | }
|
337 | }
|
338 |
|
339 | if (!found) {
|
340 | for (i = 0; i < writeConcernKeys.length; i++) {
|
341 | if (sourceOptions[writeConcernKeys[i]]) {
|
342 | targetOptions[writeConcernKeys[i]] = sourceOptions[writeConcernKeys[i]];
|
343 | }
|
344 | }
|
345 | }
|
346 |
|
347 | return targetOptions;
|
348 | };
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 | const executeLegacyOperation = (topology, operation, args, options) => {
|
365 | if (topology == null) {
|
366 | throw new TypeError('This method requires a valid topology instance');
|
367 | }
|
368 |
|
369 | if (!Array.isArray(args)) {
|
370 | throw new TypeError('This method requires an array of arguments to apply');
|
371 | }
|
372 |
|
373 | options = options || {};
|
374 | const Promise = topology.s.promiseLibrary;
|
375 | let callback = args[args.length - 1];
|
376 |
|
377 |
|
378 |
|
379 | let session, opOptions, owner;
|
380 | if (!options.skipSessions && topology.hasSessionSupport()) {
|
381 | opOptions = args[args.length - 2];
|
382 | if (opOptions == null || opOptions.session == null) {
|
383 | owner = Symbol();
|
384 | session = topology.startSession({ owner });
|
385 | const optionsIndex = args.length - 2;
|
386 | args[optionsIndex] = Object.assign({}, args[optionsIndex], { session: session });
|
387 | } else if (opOptions.session && opOptions.session.hasEnded) {
|
388 | throw new MongoError('Use of expired sessions is not permitted');
|
389 | }
|
390 | }
|
391 |
|
392 | const makeExecuteCallback = (resolve, reject) =>
|
393 | function executeCallback(err, result) {
|
394 | if (session && session.owner === owner && !options.returnsCursor) {
|
395 | session.endSession(() => {
|
396 | delete opOptions.session;
|
397 | if (err) return reject(err);
|
398 | resolve(result);
|
399 | });
|
400 | } else {
|
401 | if (err) return reject(err);
|
402 | resolve(result);
|
403 | }
|
404 | };
|
405 |
|
406 |
|
407 | if (typeof callback === 'function') {
|
408 | callback = args.pop();
|
409 | const handler = makeExecuteCallback(
|
410 | result => callback(null, result),
|
411 | err => callback(err, null)
|
412 | );
|
413 | args.push(handler);
|
414 |
|
415 | try {
|
416 | return operation.apply(null, args);
|
417 | } catch (e) {
|
418 | handler(e);
|
419 | throw e;
|
420 | }
|
421 | }
|
422 |
|
423 |
|
424 | if (args[args.length - 1] != null) {
|
425 | throw new TypeError('final argument to `executeLegacyOperation` must be a callback');
|
426 | }
|
427 |
|
428 | return new Promise(function(resolve, reject) {
|
429 | const handler = makeExecuteCallback(resolve, reject);
|
430 | args[args.length - 1] = handler;
|
431 |
|
432 | try {
|
433 | return operation.apply(null, args);
|
434 | } catch (e) {
|
435 | handler(e);
|
436 | }
|
437 | });
|
438 | };
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 | function applyRetryableWrites(target, db) {
|
447 | if (db && db.s.options.retryWrites) {
|
448 | target.retryWrites = true;
|
449 | }
|
450 |
|
451 | return target;
|
452 | }
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 | function applyWriteConcern(target, sources, options) {
|
464 | options = options || {};
|
465 | const db = sources.db;
|
466 | const coll = sources.collection;
|
467 |
|
468 | if (options.session && options.session.inTransaction()) {
|
469 |
|
470 | if (target.writeConcern) {
|
471 | delete target.writeConcern;
|
472 | }
|
473 |
|
474 | return target;
|
475 | }
|
476 |
|
477 | const writeConcern = WriteConcern.fromOptions(options);
|
478 | if (writeConcern) {
|
479 | return Object.assign(target, { writeConcern });
|
480 | }
|
481 |
|
482 | if (coll && coll.writeConcern) {
|
483 | return Object.assign(target, { writeConcern: Object.assign({}, coll.writeConcern) });
|
484 | }
|
485 |
|
486 | if (db && db.writeConcern) {
|
487 | return Object.assign(target, { writeConcern: Object.assign({}, db.writeConcern) });
|
488 | }
|
489 |
|
490 | return target;
|
491 | }
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 |
|
501 |
|
502 |
|
503 | function resolveReadPreference(parent, options) {
|
504 | options = options || {};
|
505 | const session = options.session;
|
506 |
|
507 | const inheritedReadPreference = parent.readPreference;
|
508 |
|
509 | let readPreference;
|
510 | if (options.readPreference) {
|
511 | readPreference = ReadPreference.fromOptions(options);
|
512 | } else if (session && session.inTransaction() && session.transaction.options.readPreference) {
|
513 |
|
514 | readPreference = session.transaction.options.readPreference;
|
515 | } else if (inheritedReadPreference != null) {
|
516 | readPreference = inheritedReadPreference;
|
517 | } else {
|
518 | throw new Error('No readPreference was provided or inherited.');
|
519 | }
|
520 |
|
521 | return typeof readPreference === 'string' ? new ReadPreference(readPreference) : readPreference;
|
522 | }
|
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 |
|
530 | function isPromiseLike(maybePromise) {
|
531 | return maybePromise && typeof maybePromise.then === 'function';
|
532 | }
|
533 |
|
534 |
|
535 |
|
536 |
|
537 |
|
538 |
|
539 |
|
540 |
|
541 | function decorateWithCollation(command, target, options) {
|
542 | const topology = (target.s && target.s.topology) || target.topology;
|
543 |
|
544 | if (!topology) {
|
545 | throw new TypeError('parameter "target" is missing a topology');
|
546 | }
|
547 |
|
548 | const capabilities = topology.capabilities();
|
549 | if (options.collation && typeof options.collation === 'object') {
|
550 | if (capabilities && capabilities.commandsTakeCollation) {
|
551 | command.collation = options.collation;
|
552 | } else {
|
553 | throw new MongoError(`Current topology does not support collation`);
|
554 | }
|
555 | }
|
556 | }
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 |
|
564 | function decorateWithReadConcern(command, coll, options) {
|
565 | if (options && options.session && options.session.inTransaction()) {
|
566 | return;
|
567 | }
|
568 | let readConcern = Object.assign({}, command.readConcern || {});
|
569 | if (coll.s.readConcern) {
|
570 | Object.assign(readConcern, coll.s.readConcern);
|
571 | }
|
572 |
|
573 | if (Object.keys(readConcern).length > 0) {
|
574 | Object.assign(command, { readConcern: readConcern });
|
575 | }
|
576 | }
|
577 |
|
578 | const emitProcessWarning = msg => process.emitWarning(msg, 'DeprecationWarning');
|
579 | const emitConsoleWarning = msg => console.error(msg);
|
580 | const emitDeprecationWarning = process.emitWarning ? emitProcessWarning : emitConsoleWarning;
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 |
|
590 |
|
591 | function defaultMsgHandler(name, option) {
|
592 | return `${name} option [${option}] is deprecated and will be removed in a later version.`;
|
593 | }
|
594 |
|
595 |
|
596 |
|
597 |
|
598 |
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 |
|
605 |
|
606 |
|
607 |
|
608 | function deprecateOptions(config, fn) {
|
609 | if (process.noDeprecation === true) {
|
610 | return fn;
|
611 | }
|
612 |
|
613 | const msgHandler = config.msgHandler ? config.msgHandler : defaultMsgHandler;
|
614 |
|
615 | const optionsWarned = new Set();
|
616 | function deprecated() {
|
617 | const options = arguments[config.optionsIndex];
|
618 |
|
619 |
|
620 | if (!isObject(options) || Object.keys(options).length === 0) {
|
621 | return fn.apply(this, arguments);
|
622 | }
|
623 |
|
624 | config.deprecatedOptions.forEach(deprecatedOption => {
|
625 | if (options.hasOwnProperty(deprecatedOption) && !optionsWarned.has(deprecatedOption)) {
|
626 | optionsWarned.add(deprecatedOption);
|
627 | const msg = msgHandler(config.name, deprecatedOption);
|
628 | emitDeprecationWarning(msg);
|
629 | if (this && this.getLogger) {
|
630 | const logger = this.getLogger();
|
631 | if (logger) {
|
632 | logger.warn(msg);
|
633 | }
|
634 | }
|
635 | }
|
636 | });
|
637 |
|
638 | return fn.apply(this, arguments);
|
639 | }
|
640 |
|
641 |
|
642 |
|
643 | Object.setPrototypeOf(deprecated, fn);
|
644 | if (fn.prototype) {
|
645 |
|
646 |
|
647 |
|
648 | deprecated.prototype = fn.prototype;
|
649 | }
|
650 |
|
651 | return deprecated;
|
652 | }
|
653 |
|
654 | const SUPPORTS = {};
|
655 |
|
656 | try {
|
657 | require('./async/async_iterator');
|
658 | SUPPORTS.ASYNC_ITERATOR = true;
|
659 | } catch (e) {
|
660 | SUPPORTS.ASYNC_ITERATOR = false;
|
661 | }
|
662 |
|
663 | class MongoDBNamespace {
|
664 | constructor(db, collection) {
|
665 | this.db = db;
|
666 | this.collection = collection;
|
667 | }
|
668 |
|
669 | toString() {
|
670 | return this.collection ? `${this.db}.${this.collection}` : this.db;
|
671 | }
|
672 |
|
673 | withCollection(collection) {
|
674 | return new MongoDBNamespace(this.db, collection);
|
675 | }
|
676 |
|
677 | static fromString(namespace) {
|
678 | if (!namespace) {
|
679 | throw new Error(`Cannot parse namespace from "${namespace}"`);
|
680 | }
|
681 |
|
682 | const index = namespace.indexOf('.');
|
683 | return new MongoDBNamespace(namespace.substring(0, index), namespace.substring(index + 1));
|
684 | }
|
685 | }
|
686 |
|
687 | function* makeCounter(seed) {
|
688 | let count = seed || 0;
|
689 | while (true) {
|
690 | const newCount = count;
|
691 | count += 1;
|
692 | yield newCount;
|
693 | }
|
694 | }
|
695 |
|
696 |
|
697 |
|
698 |
|
699 |
|
700 |
|
701 |
|
702 |
|
703 |
|
704 |
|
705 |
|
706 | function maybePromise(parent, callback, fn) {
|
707 | const PromiseLibrary = (parent && parent.s && parent.s.promiseLibrary) || Promise;
|
708 |
|
709 | let result;
|
710 | if (typeof callback !== 'function') {
|
711 | result = new PromiseLibrary((resolve, reject) => {
|
712 | callback = (err, res) => {
|
713 | if (err) return reject(err);
|
714 | resolve(res);
|
715 | };
|
716 | });
|
717 | }
|
718 |
|
719 | fn(function(err, res) {
|
720 | if (err != null) {
|
721 | try {
|
722 | callback(err);
|
723 | } catch (error) {
|
724 | return process.nextTick(() => {
|
725 | throw error;
|
726 | });
|
727 | }
|
728 | return;
|
729 | }
|
730 |
|
731 | callback(err, res);
|
732 | });
|
733 |
|
734 | return result;
|
735 | }
|
736 |
|
737 | module.exports = {
|
738 | filterOptions,
|
739 | mergeOptions,
|
740 | translateOptions,
|
741 | shallowClone,
|
742 | getSingleProperty,
|
743 | checkCollectionName,
|
744 | toError,
|
745 | formattedOrderClause,
|
746 | parseIndexOptions,
|
747 | normalizeHintField,
|
748 | handleCallback,
|
749 | decorateCommand,
|
750 | isObject,
|
751 | debugOptions,
|
752 | MAX_JS_INT: Number.MAX_SAFE_INTEGER + 1,
|
753 | mergeOptionsAndWriteConcern,
|
754 | translateReadPreference,
|
755 | executeLegacyOperation,
|
756 | applyRetryableWrites,
|
757 | applyWriteConcern,
|
758 | isPromiseLike,
|
759 | decorateWithCollation,
|
760 | decorateWithReadConcern,
|
761 | deprecateOptions,
|
762 | SUPPORTS,
|
763 | MongoDBNamespace,
|
764 | resolveReadPreference,
|
765 | emitDeprecationWarning,
|
766 | makeCounter,
|
767 | maybePromise
|
768 | };
|