| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- "use strict";
- /**
- * Represents a `Value` (i.e., an `Object`) in `mongo` but in `munge` this is only a set of static helpers since we treat all `Object`s like `Value`s.
- * @class Value
- * @namespace mungedb-aggregate.pipeline
- * @module mungedb-aggregate
- * @constructor
- **/
- var Value = module.exports = function Value(){
- if(this.constructor === Value) throw new Error("Never create instances of this! Use the static helpers only.");
- }, klass = Value;
- var Document; // loaded lazily below //TODO: a dirty hack; need to investigate and clean up
- //SKIPPED: ValueStorage -- probably not required; use JSON?
- //SKIPPED: createIntOrLong -- not required; use Number
- //SKIPPED: operator <Array>[] -- not required; use arr[i]
- //SKIPPED: operator <Object>[] -- not required; use obj[key]
- //SKIPPED: operator << -- not required
- //SKIPPED: addToBsonObj -- not required; use obj[key] = <val>
- //SKIPPED: addToBsonArray -- not required; use arr.push(<val>)
- /** Coerce a value to a bool using BSONElement::trueValue() rules.
- * Some types unsupported. SERVER-6120
- * @method coerceToBool
- * @static
- */
- klass.coerceToBool = function coerceToBool(value) {
- if (typeof value === "string") return true;
- return !!value; // including null or undefined
- };
- /**
- * Coercion operators to extract values with fuzzy type logic.
- * These currently assert if called on an unconvertible type.
- * TODO: decided how to handle unsupported types.
- */
- klass.coerceToWholeNumber = function coerceToInt(value) {
- return klass.coerceToNumber(value) | 0;
- };
- klass.coerceToInt = klass.coerceToWholeNumber;
- klass.coerceToLong = klass.coerceToWholeNumber;
- klass.coerceToNumber = function coerceToNumber(value) {
- if (value === null) return 0;
- switch (Value.getType(value)) {
- case "undefined":
- return 0;
- case "number":
- return value;
- case "Long":
- return parseInt(value.toString(), 10);
- case "Double":
- return parseFloat(value.value, 10);
- default:
- throw new Error("can't convert from BSON type " + Value.getType(value) + " to int; codes 16003, 16004, 16005");
- }
- };
- klass.coerceToDouble = klass.coerceToNumber;
- klass.coerceToDate = function coerceToDate(value) {
- if (value instanceof Date) return value;
- throw new Error("can't convert from BSON type " + Value.getType(value) + " to Date; uassert code 16006");
- };
- //SKIPPED: coerceToTimeT -- not required; just use Date
- //SKIPPED: coerceToTm -- not required; just use Date
- //SKIPPED: tmToISODateString -- not required; just use Date
- klass.coerceToString = function coerceToString(value) {
- var type = Value.getType(value);
- switch (type) {
- //TODO: BSON numbers?
- case "number":
- return value.toString();
- //TODO: BSON Code?
- //TODO: BSON Symbol?
- case "string":
- return value;
- //TODO: BSON Timestamp?
- case "Date":
- return value.toISOString().split(".")[0];
- case "null":
- case "undefined":
- return "";
- default:
- throw new Error("can't convert from BSON type " + Value.getType(value) + " to String; uassert code 16007");
- }
- };
- //SKIPPED: coerceToTimestamp
- /**
- * Helper function for Value.compare
- * @method cmp
- * @static
- */
- var cmp = klass.cmp = function cmp(left, right){
- // The following is lifted directly from compareElementValues
- // to ensure identical handling of NaN
- if (left < right)
- return -1;
- if (left === right)
- return 0;
- if (isNaN(left))
- return isNaN(right) ? 0 : -1;
- return 1;
- };
- /** Compare two Values.
- * @static
- * @method compare
- * @returns an integer less than zero, zero, or an integer greater than zero, depending on whether lhs < rhs, lhs == rhs, or lhs > rhs
- * Warning: may return values other than -1, 0, or 1
- */
- klass.compare = function compare(l, r) {
- var lType = Value.getType(l),
- rType = Value.getType(r),
- ret;
- ret = lType === rType ?
- 0 // fast-path common case
- : cmp(klass.canonicalize(l), klass.canonicalize(r));
- if(ret !== 0)
- return ret;
- // CW TODO for now, only compare like values
- if (lType !== rType)
- throw new Error("can't compare values of BSON types [" + lType + "] and [" + rType + "]; code 16016");
- switch (lType) {
- // Order of types is the same as in compareElementValues() to make it easier to verify
- // These are valueless types
- //SKIPPED: case "EOO":
- case "undefined":
- case "null":
- //SKIPPED: case "jstNULL":
- case "MaxKey":
- case "MinKey":
- return ret;
- case "boolean":
- return l - r;
- // WARNING: Timestamp and Date have same canonical type, but compare differently.
- // Maintaining behavior from normal BSON.
- //SKIPPED: case "Timestamp": //unsigned-----//TODO: handle case for bson.Timestamp()
- case "Date": // signed
- return cmp(l.getTime(), r.getTime());
- // Numbers should compare by equivalence even if different types
- case "number":
- return cmp(l, r);
- //SKIPPED: case "jstOID":----//TODO: handle case for bson.ObjectID()
- case "Code":
- case "Symbol":
- case "string":
- l = String(l);
- r = String(r);
- return l < r ? -1 : l > r ? 1 : 0;
- case "Object":
- if (Document === undefined) Document = require("./Document"); //TODO: a dirty hack; need to investigate and clean up
- return Document.compare(l, r);
- case "Array":
- var lArr = l,
- rArr = r;
- var elems = Math.min(lArr.length, rArr.length);
- for (var i = 0; i < elems; i++) {
- // compare the two corresponding elements
- ret = Value.compare(lArr[i], rArr[i]);
- if (ret !== 0)
- return ret;
- }
- // if we get here we are either equal or one is prefix of the other
- return cmp(lArr.length, rArr.length);
- //SKIPPED: case "DBRef":-----//TODO: handle case for bson.DBRef()
- //SKIPPED: case "BinData":-----//TODO: handle case for bson.BinData()
- case "RegExp": // same as String in this impl but keeping order same as compareElementValues
- l = String(l);
- r = String(r);
- return l < r ? -1 : l > r ? 1 : 0;
- //SKIPPED: case "CodeWScope":-----//TODO: handle case for bson.CodeWScope()
- }
- throw new Error("Assertion failure");
- };
- //SKIPPED: hash_combine
- //SKIPPED: getWidestNumeric
- //SKIPPED: getApproximateSize
- //SKIPPED: toString
- //SKIPPED: operator <<
- //SKIPPED: serializeForSorter
- //SKIPPED: deserializeForSorter
- /**
- * Takes an array and removes items and adds them to returned array.
- * @method consume
- * @static
- * @param consumed {Array} The array to be copied, emptied.
- **/
- klass.consume = function consume(consumed) {
- return consumed.splice(0);
- };
- //NOTE: DEVIATION FROM MONGO: many of these do not apply or are inlined (code where relevant)
- // missing(val): val === undefined
- // nullish(val): val === null || val === undefined
- // numeric(val): typeof val === "number"
- klass.getType = function getType(v) {
- var t = typeof v;
- if (t !== "object")
- return t;
- if (v === null)
- return "null";
- return v.constructor.name || t;
- };
- // getArrayLength(arr): arr.length
- // getString(val): val.toString() //NOTE: same for getStringData(val) I think
- // getOid
- // getBool
- // getDate
- // getTimestamp
- // getRegex(re): re.source
- // getRegexFlags(re): re.toString().slice(-re.toString().lastIndexOf('/') + 2)
- // getSymbol
- // getCode
- // getInt
- // getLong
- //NOTE: also, because of this we are not throwing if the type does not match like the mongo code would but maybe that's okay
- // from bsontypes
- klass.canonicalize = function canonicalize(x) {
- var xType = Value.getType(x);
- switch (xType) {
- case "MinKey":
- return -1;
- case "MaxKey":
- return 127;
- case "EOO":
- case "undefined":
- case undefined:
- return 0;
- case "jstNULL":
- case "null":
- case "Null":
- return 5;
- case "NumberDouble":
- case "NumberInt":
- case "NumberLong":
- case "number":
- return 10;
- case "Symbol":
- case "string":
- return 15;
- case "Object":
- return 20;
- case "Array":
- return 25;
- case "Binary":
- return 30;
- case "ObjectId":
- return 35;
- case "ObjectID":
- return 35;
- case "boolean":
- case "Boolean":
- return 40;
- case "Date":
- case "Timestamp":
- return 45;
- case "RegEx":
- case "RegExp":
- return 50;
- case "DBRef":
- return 55;
- case "Code":
- return 60;
- case "CodeWScope":
- return 65;
- default:
- // Default value for Object
- return 20;
- }
- };
|