1 // Date and time types
  2 if (typeof(Timestamp) != "undefined") {
  3     Timestamp.prototype.tojson = function() {
  4         return this.toString();
  5     };
  6 
  7     Timestamp.prototype.getTime = function() {
  8         return this.t;
  9     };
 10 
 11     Timestamp.prototype.getInc = function() {
 12         return this.i;
 13     };
 14 
 15     Timestamp.prototype.toString = function() {
 16         return "Timestamp(" + this.t + ", " + this.i + ")";
 17     };
 18 } else {
 19     print("warning: no Timestamp class");
 20 }
 21 
 22 Date.timeFunc = function(theFunc, numTimes) {
 23     var start = new Date();
 24     numTimes = numTimes || 1;
 25     for (var i = 0; i < numTimes; i++) {
 26         theFunc.apply(null, Array.from(arguments).slice(2));
 27     }
 28 
 29     return (new Date()).getTime() - start.getTime();
 30 };
 31 
 32 Date.prototype.tojson = function() {
 33     var UTC = 'UTC';
 34     var year = this['get' + UTC + 'FullYear']().zeroPad(4);
 35     var month = (this['get' + UTC + 'Month']() + 1).zeroPad(2);
 36     var date = this['get' + UTC + 'Date']().zeroPad(2);
 37     var hour = this['get' + UTC + 'Hours']().zeroPad(2);
 38     var minute = this['get' + UTC + 'Minutes']().zeroPad(2);
 39     var sec = this['get' + UTC + 'Seconds']().zeroPad(2);
 40 
 41     if (this['get' + UTC + 'Milliseconds']())
 42         sec += '.' + this['get' + UTC + 'Milliseconds']().zeroPad(3);
 43 
 44     var ofs = 'Z';
 45     // // print a non-UTC time
 46     // var ofsmin = this.getTimezoneOffset();
 47     // if (ofsmin != 0){
 48     //     ofs = ofsmin > 0 ? '-' : '+'; // This is correct
 49     //     ofs += (ofsmin/60).zeroPad(2)
 50     //     ofs += (ofsmin%60).zeroPad(2)
 51     // }
 52     return 'ISODate("' + year + '-' + month + '-' + date + 'T' + hour + ':' + minute + ':' + sec +
 53         ofs + '")';
 54 };
 55 
 56 ISODate = function(isoDateStr) {
 57     if (!isoDateStr)
 58         return new Date();
 59 
 60     var isoDateRegex =
 61         /(\d{4})-?(\d{2})-?(\d{2})([T ](\d{2})(:?(\d{2})(:?(\d{2}(\.\d+)?))?)?(Z|([+-])(\d{2}):?(\d{2})?)?)?/;
 62     var res = isoDateRegex.exec(isoDateStr);
 63 
 64     if (!res)
 65         throw Error("invalid ISO date");
 66 
 67     var year = parseInt(res[1], 10) || 1970;  // this should always be present
 68     var month = (parseInt(res[2], 10) || 1) - 1;
 69     var date = parseInt(res[3], 10) || 0;
 70     var hour = parseInt(res[5], 10) || 0;
 71     var min = parseInt(res[7], 10) || 0;
 72     var sec = parseInt((res[9] && res[9].substr(0, 2)), 10) || 0;
 73     var ms = Math.round((parseFloat(res[10]) || 0) * 1000);
 74     if (ms == 1000) {
 75         ms = 0;
 76         ++sec;
 77     }
 78     if (sec == 60) {
 79         sec = 0;
 80         ++min;
 81     }
 82     if (min == 60) {
 83         min = 0;
 84         ++hour;
 85     }
 86     if (hour == 24) {
 87         hour = 0;  // the day wrapped, let JavaScript figure out the rest
 88         var tempTime = Date.UTC(year, month, date, hour, min, sec, ms);
 89         tempTime += 24 * 60 * 60 * 1000;  // milliseconds in a day
 90         var tempDate = new Date(tempTime);
 91         year = tempDate.getUTCFullYear();
 92         month = tempDate.getUTCMonth();
 93         date = tempDate.getUTCDate();
 94     }
 95 
 96     var time = Date.UTC(year, month, date, hour, min, sec, ms);
 97 
 98     if (res[11] && res[11] != 'Z') {
 99         var ofs = 0;
100         ofs += (parseInt(res[13], 10) || 0) * 60 * 60 * 1000;  // hours
101         ofs += (parseInt(res[14], 10) || 0) * 60 * 1000;       // mins
102         if (res[12] == '+')                                    // if ahead subtract
103             ofs *= -1;
104 
105         time += ofs;
106     }
107 
108     return new Date(time);
109 };
110 
111 // Regular Expression
112 RegExp.escape = function(text) {
113     return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
114 };
115 
116 RegExp.prototype.tojson = RegExp.prototype.toString;
117 
118 // Array
119 Array.contains = function(a, x) {
120     if (!Array.isArray(a)) {
121         throw new Error("The first argument to Array.contains must be an array");
122     }
123 
124     for (var i = 0; i < a.length; i++) {
125         if (a[i] == x)
126             return true;
127     }
128     return false;
129 };
130 
131 Array.unique = function(a) {
132     if (!Array.isArray(a)) {
133         throw new Error("The first argument to Array.unique must be an array");
134     }
135 
136     var u = [];
137     for (var i = 0; i < a.length; i++) {
138         var o = a[i];
139         if (!Array.contains(u, o)) {
140             u.push(o);
141         }
142     }
143     return u;
144 };
145 
146 Array.shuffle = function(arr) {
147     if (!Array.isArray(arr)) {
148         throw new Error("The first argument to Array.shuffle must be an array");
149     }
150 
151     for (var i = 0; i < arr.length - 1; i++) {
152         var pos = i + Random.randInt(arr.length - i);
153         var save = arr[i];
154         arr[i] = arr[pos];
155         arr[pos] = save;
156     }
157     return arr;
158 };
159 
160 Array.tojson = function(a, indent, nolint) {
161     if (!Array.isArray(a)) {
162         throw new Error("The first argument to Array.tojson must be an array");
163     }
164 
165     var elementSeparator = nolint ? " " : "\n";
166 
167     if (!indent)
168         indent = "";
169     if (nolint)
170         indent = "";
171 
172     if (a.length == 0) {
173         return "[ ]";
174     }
175 
176     var s = "[" + elementSeparator;
177 
178     // add to indent if we are pretty
179     if (!nolint)
180         indent += "\t";
181 
182     for (var i = 0; i < a.length; i++) {
183         s += indent + tojson(a[i], indent, nolint);
184         if (i < a.length - 1) {
185             s += "," + elementSeparator;
186         }
187     }
188 
189     // remove from indent if we are pretty
190     if (!nolint)
191         indent = indent.substring(1);
192 
193     s += elementSeparator + indent + "]";
194     return s;
195 };
196 
197 Array.fetchRefs = function(arr, coll) {
198     if (!Array.isArray(arr)) {
199         throw new Error("The first argument to Array.fetchRefs must be an array");
200     }
201 
202     var n = [];
203     for (var i = 0; i < arr.length; i++) {
204         var z = arr[i];
205         if (coll && coll != z.getCollection())
206             continue;
207         n.push(z.fetch());
208     }
209     return n;
210 };
211 
212 Array.sum = function(arr) {
213     if (!Array.isArray(arr)) {
214         throw new Error("The first argument to Array.sum must be an array");
215     }
216 
217     if (arr.length == 0)
218         return null;
219     var s = arr[0];
220     for (var i = 1; i < arr.length; i++)
221         s += arr[i];
222     return s;
223 };
224 
225 Array.avg = function(arr) {
226     if (!Array.isArray(arr)) {
227         throw new Error("The first argument to Array.avg must be an array");
228     }
229 
230     if (arr.length == 0)
231         return null;
232     return Array.sum(arr) / arr.length;
233 };
234 
235 Array.stdDev = function(arr) {
236     if (!Array.isArray(arr)) {
237         throw new Error("The first argument to Array.stdDev must be an array");
238     }
239 
240     var avg = Array.avg(arr);
241     var sum = 0;
242 
243     for (var i = 0; i < arr.length; i++) {
244         sum += Math.pow(arr[i] - avg, 2);
245     }
246 
247     return Math.sqrt(sum / arr.length);
248 };
249 
250 // Object
251 Object.extend = function(dst, src, deep) {
252     for (var k in src) {
253         var v = src[k];
254         if (deep && typeof(v) == "object" && v !== null) {
255             if (v.constructor === ObjectId) {  // convert ObjectId properly
256                 eval("v = " + tojson(v));
257             } else if ("floatApprox" in v) {  // convert NumberLong properly
258                 eval("v = " + tojson(v));
259             } else {
260                 v = Object.extend(typeof(v.length) == "number" ? [] : {}, v, true);
261             }
262         }
263         dst[k] = v;
264     }
265     return dst;
266 };
267 
268 Object.merge = function(dst, src, deep) {
269     var clone = Object.extend({}, dst, deep);
270     return Object.extend(clone, src, deep);
271 };
272 
273 Object.keySet = function(o) {
274     var ret = new Array();
275     for (var i in o) {
276         if (!(i in o.__proto__ && o[i] === o.__proto__[i])) {
277             ret.push(i);
278         }
279     }
280     return ret;
281 };
282 
283 // String
284 if (String.prototype.trim === undefined) {
285     String.prototype.trim = function() {
286         return this.replace(/^\s+|\s+$/g, "");
287     };
288 }
289 if (String.prototype.trimLeft === undefined) {
290     String.prototype.trimLeft = function() {
291         return this.replace(/^\s+/, "");
292     };
293 }
294 if (String.prototype.trimRight === undefined) {
295     String.prototype.trimRight = function() {
296         return this.replace(/\s+$/, "");
297     };
298 }
299 
300 // always provide ltrim and rtrim for backwards compatibility
301 String.prototype.ltrim = String.prototype.trimLeft;
302 String.prototype.rtrim = String.prototype.trimRight;
303 
304 String.prototype.startsWith = function(str) {
305     return this.indexOf(str) == 0;
306 };
307 
308 String.prototype.endsWith = function(str) {
309     return this.indexOf(str, this.length - str.length) !== -1;
310 };
311 
312 // Polyfill taken from
313 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
314 if (!String.prototype.includes) {
315     String.prototype.includes = function() {
316         'use strict';
317         return String.prototype.indexOf.apply(this, arguments) !== -1;
318     };
319 }
320 
321 // Returns a copy padded with the provided character _chr_ so it becomes (at least) _length_
322 // characters long.
323 // No truncation is performed if the string is already longer than _length_.
324 // @param length minimum length of the returned string
325 // @param right if falsy add leading whitespace, otherwise add trailing whitespace
326 // @param chr character to be used for padding, defaults to whitespace
327 // @return the padded string
328 String.prototype.pad = function(length, right, chr) {
329     if (typeof chr == 'undefined')
330         chr = ' ';
331     var str = this;
332     for (var i = length - str.length; i > 0; i--) {
333         if (right) {
334             str = str + chr;
335         } else {
336             str = chr + str;
337         }
338     }
339     return str;
340 };
341 
342 // Number
343 Number.prototype.toPercentStr = function() {
344     return (this * 100).toFixed(2) + "%";
345 };
346 
347 Number.prototype.zeroPad = function(width) {
348     return ('' + this).pad(width, false, '0');
349 };
350 
351 // NumberLong
352 if (!NumberLong.prototype) {
353     NumberLong.prototype = {};
354 }
355 
356 NumberLong.prototype.tojson = function() {
357     return this.toString();
358 };
359 
360 // NumberInt
361 if (!NumberInt.prototype) {
362     NumberInt.prototype = {};
363 }
364 
365 NumberInt.prototype.tojson = function() {
366     return this.toString();
367 };
368 
369 // NumberDecimal
370 if (typeof NumberDecimal !== 'undefined') {
371     if (!NumberDecimal.prototype) {
372         NumberDecimal.prototype = {};
373     }
374 
375     NumberDecimal.prototype.tojson = function() {
376         return this.toString();
377     };
378 }
379 
380 // ObjectId
381 if (!ObjectId.prototype)
382     ObjectId.prototype = {};
383 
384 ObjectId.prototype.toString = function() {
385     return "ObjectId(" + tojson(this.str) + ")";
386 };
387 
388 ObjectId.prototype.tojson = function() {
389     return this.toString();
390 };
391 
392 ObjectId.prototype.valueOf = function() {
393     return this.str;
394 };
395 
396 ObjectId.prototype.isObjectId = true;
397 
398 ObjectId.prototype.getTimestamp = function() {
399     return new Date(parseInt(this.valueOf().slice(0, 8), 16) * 1000);
400 };
401 
402 ObjectId.prototype.equals = function(other) {
403     return this.str == other.str;
404 };
405 
406 // Creates an ObjectId from a Date.
407 // Based on solution discussed here:
408 //     http://stackoverflow.com/questions/8749971/can-i-query-mongodb-objectid-by-date
409 ObjectId.fromDate = function(source) {
410     if (!source) {
411         throw Error("date missing or undefined");
412     }
413 
414     var sourceDate;
415 
416     // Extract Date from input.
417     // If input is a string, assume ISO date string and
418     // create a Date from the string.
419     if (source instanceof Date) {
420         sourceDate = source;
421     } else {
422         throw Error("Cannot create ObjectId from " + typeof(source) + ": " + tojson(source));
423     }
424 
425     // Convert date object to seconds since Unix epoch.
426     var seconds = Math.floor(sourceDate.getTime() / 1000);
427 
428     // Generate hex timestamp with padding.
429     var hexTimestamp = seconds.toString(16).pad(8, false, '0') + "0000000000000000";
430 
431     // Create an ObjectId with hex timestamp.
432     var objectId = ObjectId(hexTimestamp);
433 
434     return objectId;
435 };
436 
437 // DBPointer
438 if (typeof(DBPointer) != "undefined") {
439     DBPointer.prototype.fetch = function() {
440         assert(this.ns, "need a ns");
441         assert(this.id, "need an id");
442         return db[this.ns].findOne({_id: this.id});
443     };
444 
445     DBPointer.prototype.tojson = function(indent) {
446         return this.toString();
447     };
448 
449     DBPointer.prototype.getCollection = function() {
450         return this.ns;
451     };
452 
453     DBPointer.prototype.getId = function() {
454         return this.id;
455     };
456 
457     DBPointer.prototype.toString = function() {
458         return "DBPointer(" + tojson(this.ns) + ", " + tojson(this.id) + ")";
459     };
460 } else {
461     print("warning: no DBPointer");
462 }
463 
464 // DBRef
465 if (typeof(DBRef) != "undefined") {
466     DBRef.prototype.fetch = function() {
467         assert(this.$ref, "need a ns");
468         assert(this.$id, "need an id");
469         var coll = this.$db ? db.getSiblingDB(this.$db).getCollection(this.$ref) : db[this.$ref];
470         return coll.findOne({_id: this.$id});
471     };
472 
473     DBRef.prototype.tojson = function(indent) {
474         return this.toString();
475     };
476 
477     DBRef.prototype.getDb = function() {
478         return this.$db || undefined;
479     };
480 
481     DBRef.prototype.getCollection = function() {
482         return this.$ref;
483     };
484 
485     DBRef.prototype.getRef = function() {
486         return this.$ref;
487     };
488 
489     DBRef.prototype.getId = function() {
490         return this.$id;
491     };
492 
493     DBRef.prototype.toString = function() {
494         return "DBRef(" + tojson(this.$ref) + ", " + tojson(this.$id) +
495             (this.$db ? ", " + tojson(this.$db) : "") + ")";
496     };
497 } else {
498     print("warning: no DBRef");
499 }
500 
501 // BinData
502 if (typeof(BinData) != "undefined") {
503     BinData.prototype.tojson = function() {
504         return this.toString();
505     };
506 
507     BinData.prototype.subtype = function() {
508         return this.type;
509     };
510     BinData.prototype.length = function() {
511         return this.len;
512     };
513 } else {
514     print("warning: no BinData class");
515 }
516 
517 // Map
518 if (typeof(Map) == "undefined") {
519     Map = function() {
520         this._data = {};
521     };
522 }
523 
524 Map.hash = function(val) {
525     if (!val)
526         return val;
527 
528     switch (typeof(val)) {
529         case 'string':
530         case 'number':
531         case 'date':
532             return val.toString();
533         case 'object':
534         case 'array':
535             var s = "";
536             for (var k in val) {
537                 s += k + val[k];
538             }
539             return s;
540     }
541 
542     throw Error("can't hash : " + typeof(val));
543 };
544 
545 Map.prototype.put = function(key, value) {
546     var o = this._get(key);
547     var old = o.value;
548     o.value = value;
549     return old;
550 };
551 
552 Map.prototype.get = function(key) {
553     return this._get(key).value;
554 };
555 
556 Map.prototype._get = function(key) {
557     var h = Map.hash(key);
558     var a = this._data[h];
559     if (!a) {
560         a = [];
561         this._data[h] = a;
562     }
563     for (var i = 0; i < a.length; i++) {
564         if (friendlyEqual(key, a[i].key)) {
565             return a[i];
566         }
567     }
568     var o = {key: key, value: null};
569     a.push(o);
570     return o;
571 };
572 
573 Map.prototype.values = function() {
574     var all = [];
575     for (var k in this._data) {
576         this._data[k].forEach(function(z) {
577             all.push(z.value);
578         });
579     }
580     return all;
581 };
582 
583 if (typeof(gc) == "undefined") {
584     gc = function() {
585         print("warning: using noop gc()");
586     };
587 }
588 
589 // Free Functions
590 tojsononeline = function(x) {
591     return tojson(x, " ", true);
592 };
593 
594 tojson = function(x, indent, nolint) {
595     if (x === null)
596         return "null";
597 
598     if (x === undefined)
599         return "undefined";
600 
601     if (!indent)
602         indent = "";
603 
604     switch (typeof x) {
605         case "string": {
606             var out = new Array(x.length + 1);
607             out[0] = '"';
608             for (var i = 0; i < x.length; i++) {
609                 switch (x[i]) {
610                     case '"':
611                         out[out.length] = '\\"';
612                         break;
613                     case '\\':
614                         out[out.length] = '\\\\';
615                         break;
616                     case '\b':
617                         out[out.length] = '\\b';
618                         break;
619                     case '\f':
620                         out[out.length] = '\\f';
621                         break;
622                     case '\n':
623                         out[out.length] = '\\n';
624                         break;
625                     case '\r':
626                         out[out.length] = '\\r';
627                         break;
628                     case '\t':
629                         out[out.length] = '\\t';
630                         break;
631 
632                     default: {
633                         var code = x.charCodeAt(i);
634                         if (code < 0x20) {
635                             out[out.length] =
636                                 (code < 0x10 ? '\\u000' : '\\u00') + code.toString(16);
637                         } else {
638                             out[out.length] = x[i];
639                         }
640                     }
641                 }
642             }
643 
644             return out.join('') + "\"";
645         }
646         case "number":
647         case "boolean":
648             return "" + x;
649         case "object": {
650             var s = tojsonObject(x, indent, nolint);
651             if ((nolint == null || nolint == true) && s.length < 80 &&
652                 (indent == null || indent.length == 0)) {
653                 s = s.replace(/[\t\r\n]+/gm, " ");
654             }
655             return s;
656         }
657         case "function":
658             if (x === MinKey || x === MaxKey)
659                 return x.tojson();
660             return x.toString();
661         default:
662             throw Error("tojson can't handle type " + (typeof x));
663     }
664 
665 };
666 
667 tojsonObject = function(x, indent, nolint) {
668     var lineEnding = nolint ? " " : "\n";
669     var tabSpace = nolint ? "" : "\t";
670     assert.eq((typeof x), "object", "tojsonObject needs object, not [" + (typeof x) + "]");
671 
672     if (!indent)
673         indent = "";
674 
675     if (typeof(x.tojson) == "function" && x.tojson != tojson) {
676         return x.tojson(indent, nolint);
677     }
678 
679     if (x.constructor && typeof(x.constructor.tojson) == "function" &&
680         x.constructor.tojson != tojson) {
681         return x.constructor.tojson(x, indent, nolint);
682     }
683 
684     if (x instanceof Error) {
685         return x.toString();
686     }
687 
688     try {
689         x.toString();
690     } catch (e) {
691         // toString not callable
692         return "[object]";
693     }
694 
695     var s = "{" + lineEnding;
696 
697     // push one level of indent
698     indent += tabSpace;
699 
700     var keys = x;
701     if (typeof(x._simpleKeys) == "function")
702         keys = x._simpleKeys();
703     var fieldStrings = [];
704     for (var k in keys) {
705         var val = x[k];
706 
707         // skip internal DB types to avoid issues with interceptors
708         if (typeof DB != 'undefined' && val == DB.prototype)
709             continue;
710         if (typeof DBCollection != 'undefined' && val == DBCollection.prototype)
711             continue;
712 
713         fieldStrings.push(indent + "\"" + k + "\" : " + tojson(val, indent, nolint));
714     }
715 
716     if (fieldStrings.length > 0) {
717         s += fieldStrings.join("," + lineEnding);
718     } else {
719         s += indent;
720     }
721     s += lineEnding;
722 
723     // pop one level of indent
724     indent = indent.substring(1);
725     return s + indent + "}";
726 };
727 
728 printjson = function(x) {
729     print(tojson(x));
730 };
731 
732 printjsononeline = function(x) {
733     print(tojsononeline(x));
734 };
735 
736 isString = function(x) {
737     return typeof(x) == "string";
738 };
739 
740 isNumber = function(x) {
741     return typeof(x) == "number";
742 };
743 
744 // This function returns true even if the argument is an array.  See SERVER-14220.
745 isObject = function(x) {
746     return typeof(x) == "object";
747 };
748