Value.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. "use strict";
  2. /**
  3. * 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.
  4. * @class Value
  5. * @namespace mungedb-aggregate.pipeline
  6. * @module mungedb-aggregate
  7. * @constructor
  8. **/
  9. var Value = module.exports = function Value(){
  10. if(this.constructor === Value) throw new Error("Never create instances of this! Use the static helpers only.");
  11. }, klass = Value;
  12. var Document; // loaded lazily below //TODO: a dirty hack; need to investigate and clean up
  13. //SKIPPED: ValueStorage -- probably not required; use JSON?
  14. //SKIPPED: createIntOrLong -- not required; use Number
  15. //SKIPPED: operator <Array>[] -- not required; use arr[i]
  16. //SKIPPED: operator <Object>[] -- not required; use obj[key]
  17. //SKIPPED: operator << -- not required
  18. //SKIPPED: addToBsonObj -- not required; use obj[key] = <val>
  19. //SKIPPED: addToBsonArray -- not required; use arr.push(<val>)
  20. /** Coerce a value to a bool using BSONElement::trueValue() rules.
  21. * Some types unsupported. SERVER-6120
  22. * @method coerceToBool
  23. * @static
  24. */
  25. klass.coerceToBool = function coerceToBool(value) {
  26. if (typeof value === "string") return true;
  27. return !!value; // including null or undefined
  28. };
  29. /**
  30. * Coercion operators to extract values with fuzzy type logic.
  31. * These currently assert if called on an unconvertible type.
  32. * TODO: decided how to handle unsupported types.
  33. */
  34. klass.coerceToWholeNumber = function coerceToInt(value) {
  35. return klass.coerceToNumber(value) | 0;
  36. };
  37. klass.coerceToInt = klass.coerceToWholeNumber;
  38. klass.coerceToLong = klass.coerceToWholeNumber;
  39. klass.coerceToNumber = function coerceToNumber(value) {
  40. if (value === null) return 0;
  41. switch (Value.getType(value)) {
  42. case "undefined":
  43. return 0;
  44. case "number":
  45. return value;
  46. case "Long":
  47. return parseInt(value.toString(), 10);
  48. case "Double":
  49. return parseFloat(value.value, 10);
  50. default:
  51. throw new Error("can't convert from BSON type " + Value.getType(value) + " to int; codes 16003, 16004, 16005");
  52. }
  53. };
  54. klass.coerceToDouble = klass.coerceToNumber;
  55. klass.coerceToDate = function coerceToDate(value) {
  56. if (value instanceof Date) return value;
  57. throw new Error("can't convert from BSON type " + Value.getType(value) + " to Date; uassert code 16006");
  58. };
  59. //SKIPPED: coerceToTimeT -- not required; just use Date
  60. //SKIPPED: coerceToTm -- not required; just use Date
  61. //SKIPPED: tmToISODateString -- not required; just use Date
  62. klass.coerceToString = function coerceToString(value) {
  63. var type = Value.getType(value);
  64. switch (type) {
  65. //TODO: BSON numbers?
  66. case "number":
  67. return value.toString();
  68. //TODO: BSON Code?
  69. //TODO: BSON Symbol?
  70. case "string":
  71. return value;
  72. //TODO: BSON Timestamp?
  73. case "Date":
  74. return value.toISOString().split(".")[0];
  75. case "null":
  76. case "undefined":
  77. return "";
  78. default:
  79. throw new Error("can't convert from BSON type " + Value.getType(value) + " to String; uassert code 16007");
  80. }
  81. };
  82. //SKIPPED: coerceToTimestamp
  83. /**
  84. * Helper function for Value.compare
  85. * @method cmp
  86. * @static
  87. */
  88. var cmp = klass.cmp = function cmp(left, right){
  89. // The following is lifted directly from compareElementValues
  90. // to ensure identical handling of NaN
  91. if (left < right)
  92. return -1;
  93. if (left === right)
  94. return 0;
  95. if (isNaN(left))
  96. return isNaN(right) ? 0 : -1;
  97. return 1;
  98. };
  99. /** Compare two Values.
  100. * @static
  101. * @method compare
  102. * @returns an integer less than zero, zero, or an integer greater than zero, depending on whether lhs < rhs, lhs == rhs, or lhs > rhs
  103. * Warning: may return values other than -1, 0, or 1
  104. */
  105. klass.compare = function compare(l, r) {
  106. var lType = Value.getType(l),
  107. rType = Value.getType(r),
  108. ret;
  109. ret = lType === rType ?
  110. 0 // fast-path common case
  111. : cmp(klass.canonicalize(l), klass.canonicalize(r));
  112. if(ret !== 0)
  113. return ret;
  114. // CW TODO for now, only compare like values
  115. if (lType !== rType)
  116. throw new Error("can't compare values of BSON types [" + lType + "] and [" + rType + "]; code 16016");
  117. switch (lType) {
  118. // Order of types is the same as in compareElementValues() to make it easier to verify
  119. // These are valueless types
  120. //SKIPPED: case "EOO":
  121. case "undefined":
  122. case "null":
  123. //SKIPPED: case "jstNULL":
  124. case "MaxKey":
  125. case "MinKey":
  126. return ret;
  127. case "boolean":
  128. return l - r;
  129. // WARNING: Timestamp and Date have same canonical type, but compare differently.
  130. // Maintaining behavior from normal BSON.
  131. //SKIPPED: case "Timestamp": //unsigned-----//TODO: handle case for bson.Timestamp()
  132. case "Date": // signed
  133. return cmp(l.getTime(), r.getTime());
  134. // Numbers should compare by equivalence even if different types
  135. case "number":
  136. return cmp(l, r);
  137. //SKIPPED: case "jstOID":----//TODO: handle case for bson.ObjectID()
  138. case "Code":
  139. case "Symbol":
  140. case "string":
  141. l = String(l);
  142. r = String(r);
  143. return l < r ? -1 : l > r ? 1 : 0;
  144. case "Object":
  145. if (Document === undefined) Document = require("./Document"); //TODO: a dirty hack; need to investigate and clean up
  146. return Document.compare(l, r);
  147. case "Array":
  148. var lArr = l,
  149. rArr = r;
  150. var elems = Math.min(lArr.length, rArr.length);
  151. for (var i = 0; i < elems; i++) {
  152. // compare the two corresponding elements
  153. ret = Value.compare(lArr[i], rArr[i]);
  154. if (ret !== 0)
  155. return ret;
  156. }
  157. // if we get here we are either equal or one is prefix of the other
  158. return cmp(lArr.length, rArr.length);
  159. //SKIPPED: case "DBRef":-----//TODO: handle case for bson.DBRef()
  160. //SKIPPED: case "BinData":-----//TODO: handle case for bson.BinData()
  161. case "RegExp": // same as String in this impl but keeping order same as compareElementValues
  162. l = String(l);
  163. r = String(r);
  164. return l < r ? -1 : l > r ? 1 : 0;
  165. //SKIPPED: case "CodeWScope":-----//TODO: handle case for bson.CodeWScope()
  166. }
  167. throw new Error("Assertion failure");
  168. };
  169. //SKIPPED: hash_combine
  170. //SKIPPED: getWidestNumeric
  171. //SKIPPED: getApproximateSize
  172. //SKIPPED: toString
  173. //SKIPPED: operator <<
  174. //SKIPPED: serializeForSorter
  175. //SKIPPED: deserializeForSorter
  176. /**
  177. * Takes an array and removes items and adds them to returned array.
  178. * @method consume
  179. * @static
  180. * @param consumed {Array} The array to be copied, emptied.
  181. **/
  182. klass.consume = function consume(consumed) {
  183. return consumed.splice(0);
  184. };
  185. //NOTE: DEVIATION FROM MONGO: many of these do not apply or are inlined (code where relevant)
  186. // missing(val): val === undefined
  187. // nullish(val): val === null || val === undefined
  188. // numeric(val): typeof val === "number"
  189. klass.getType = function getType(v) {
  190. var t = typeof v;
  191. if (t !== "object")
  192. return t;
  193. if (v === null)
  194. return "null";
  195. return v.constructor.name || t;
  196. };
  197. // getArrayLength(arr): arr.length
  198. // getString(val): val.toString() //NOTE: same for getStringData(val) I think
  199. // getOid
  200. // getBool
  201. // getDate
  202. // getTimestamp
  203. // getRegex(re): re.source
  204. // getRegexFlags(re): re.toString().slice(-re.toString().lastIndexOf('/') + 2)
  205. // getSymbol
  206. // getCode
  207. // getInt
  208. // getLong
  209. //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
  210. // from bsontypes
  211. klass.canonicalize = function canonicalize(x) {
  212. var xType = Value.getType(x);
  213. switch (xType) {
  214. case "MinKey":
  215. return -1;
  216. case "MaxKey":
  217. return 127;
  218. case "EOO":
  219. case "undefined":
  220. case undefined:
  221. return 0;
  222. case "jstNULL":
  223. case "null":
  224. case "Null":
  225. return 5;
  226. case "NumberDouble":
  227. case "NumberInt":
  228. case "NumberLong":
  229. case "number":
  230. return 10;
  231. case "Symbol":
  232. case "string":
  233. return 15;
  234. case "Object":
  235. return 20;
  236. case "Array":
  237. return 25;
  238. case "Binary":
  239. return 30;
  240. case "ObjectId":
  241. return 35;
  242. case "ObjectID":
  243. return 35;
  244. case "boolean":
  245. case "Boolean":
  246. return 40;
  247. case "Date":
  248. case "Timestamp":
  249. return 45;
  250. case "RegEx":
  251. case "RegExp":
  252. return 50;
  253. case "DBRef":
  254. return 55;
  255. case "Code":
  256. return 60;
  257. case "CodeWScope":
  258. return 65;
  259. default:
  260. // Default value for Object
  261. return 20;
  262. }
  263. };