Value.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  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. /** Coercion operators to extract values with fuzzy type logic.
  30. * These currently assert if called on an unconvertible type.
  31. * TODO: decided how to handle unsupported types.
  32. */
  33. klass.coerceToWholeNumber = function coerceToInt(value) {
  34. return klass.coerceToNumber(value) | 0;
  35. };
  36. klass.coerceToInt = klass.coerceToWholeNumber;
  37. klass.coerceToLong = klass.coerceToWholeNumber;
  38. klass.coerceToNumber = function coerceToNumber(value) {
  39. if (value === null) return 0;
  40. switch (typeof(value)) {
  41. case "undefined":
  42. return 0;
  43. case "number":
  44. return value;
  45. case "object":
  46. switch (value.constructor.name) {
  47. case "Long":
  48. return parseInt(value.toString(), 10);
  49. case "Double":
  50. return parseFloat(value.value, 10);
  51. default:
  52. throw new Error("can't convert from BSON type " + value.constructor.name + " to int; codes 16003, 16004, 16005");
  53. }
  54. return value;
  55. default:
  56. throw new Error("can't convert from BSON type " + typeof(value) + " to int; codes 16003, 16004, 16005");
  57. }
  58. };
  59. klass.coerceToDouble = klass.coerceToNumber;
  60. klass.coerceToDate = function coerceToDate(value) {
  61. if (value instanceof Date) return value;
  62. throw new Error("can't convert from BSON type " + typeof(value) + " to Date; uassert code 16006");
  63. };
  64. //SKIPPED: coerceToTimeT -- not required; just use Date
  65. //SKIPPED: coerceToTm -- not required; just use Date
  66. //SKIPPED: tmToISODateString -- not required; just use Date
  67. klass.coerceToString = function coerceToString(value) {
  68. var type = typeof(value);
  69. if (type == "object") type = value === null ? "null" : value.constructor.name;
  70. switch (type) {
  71. //TODO: BSON numbers?
  72. case "number":
  73. return value.toString();
  74. //TODO: BSON Code?
  75. //TODO: BSON Symbol?
  76. case "string":
  77. return value;
  78. //TODO: BSON Timestamp?
  79. case "Date":
  80. return value.toISOString().split(".")[0];
  81. case "null":
  82. case "undefined":
  83. return "";
  84. default:
  85. throw new Error("can't convert from BSON type " + typeof(value) + " to String; uassert code 16007");
  86. }
  87. };
  88. //SKIPPED: coerceToTimestamp
  89. /**
  90. * Helper function for Value.compare
  91. * @method cmp
  92. * @static
  93. */
  94. klass.cmp = function cmp(l, r){
  95. return l < r ? -1 : l > r ? 1 : 0;
  96. };
  97. /** Compare two Values.
  98. * @static
  99. * @method compare
  100. * @returns an integer less than zero, zero, or an integer greater than zero, depending on whether lhs < rhs, lhs == rhs, or lhs > rhs
  101. * Warning: may return values other than -1, 0, or 1
  102. */
  103. klass.compare = function compare(l, r) {
  104. //NOTE: deviation from mongo code: we have to do some coercing for null "types" because of javascript
  105. var lt = l === null ? "null" : typeof(l),
  106. rt = r === null ? "null" : typeof(r),
  107. ret;
  108. // NOTE: deviation from mongo code: javascript types do not work quite the same, so for proper results we always canonicalize, and we don't need the "speed" hack
  109. ret = (klass.cmp(klass.canonicalize(l), klass.canonicalize(r)));
  110. if(ret !== 0) return ret;
  111. // Numbers
  112. if (lt === "number" && rt === "number"){
  113. //NOTE: deviation from Mongo code: they handle NaN a bit differently
  114. if (isNaN(l)) return isNaN(r) ? 0 : -1;
  115. if (isNaN(r)) return 1;
  116. return klass.cmp(l,r);
  117. }
  118. // Compare MinKey and MaxKey cases
  119. if (l instanceof Object && ["MinKey", "MaxKey"].indexOf(l.constructor.name) !== -1) {
  120. if (l.constructor.name == r.constructor.name) {
  121. return 0;
  122. } else if (l.constructor.name === "MinKey") {
  123. return -1;
  124. } else {
  125. return 1; // Must be MaxKey, which is greater than everything but MaxKey (which r cannot be)
  126. }
  127. }
  128. // hack: These should really get converted to their BSON type ids and then compared, we use int vs object in queries
  129. if (lt === "number" && rt === "object"){
  130. return -1;
  131. } else if (lt === "object" && rt === "number") {
  132. return 1;
  133. }
  134. // CW TODO for now, only compare like values
  135. if (lt !== rt) throw new Error("can't compare values of BSON types [" + lt + " " + l.constructor.name + "] and [" + rt + ":" + r.constructor.name + "]; code 16016");
  136. // Compare everything else
  137. switch (lt) {
  138. case "number":
  139. throw new Error("number types should have been handled earlier!");
  140. case "string":
  141. return klass.cmp(l, r);
  142. case "boolean":
  143. return l == r ? 0 : l ? 1 : -1;
  144. case "undefined": //NOTE: deviation from mongo code: we are comparing null to null or undefined to undefined (otherwise the ret stuff above would have caught it)
  145. case "null":
  146. return 0;
  147. case "object":
  148. if (l instanceof Array) {
  149. for (var i = 0, ll = l.length, rl = r.length; true ; ++i) {
  150. if (i > ll) {
  151. if (i > rl) return 0; // arrays are same length
  152. return -1; // left array is shorter
  153. }
  154. if (i > rl) return 1; // right array is shorter
  155. var cmp = Value.compare(l[i], r[i]);
  156. if (cmp !== 0) return cmp;
  157. }
  158. throw new Error("logic error in Value.compare for Array types!");
  159. }
  160. if (l instanceof Date) return klass.cmp(l,r);
  161. if (l instanceof RegExp) return klass.cmp(l,r);
  162. if (Document === undefined) Document = require("./Document"); //TODO: a dirty hack; need to investigate and clean up
  163. return Document.compare(l, r);
  164. default:
  165. throw new Error("unhandled left hand type:" + lt);
  166. }
  167. };
  168. //SKIPPED: hash_combine
  169. //SKIPPED: getWidestNumeric
  170. //SKIPPED: getApproximateSize
  171. //SKIPPED: toString
  172. //SKIPPED: operator <<
  173. //SKIPPED: serializeForSorter
  174. //SKIPPED: deserializeForSorter
  175. /**
  176. * Takes an array and removes items and adds them to returned array.
  177. * @method consume
  178. * @static
  179. * @param consumed {Array} The array to be copied, emptied.
  180. **/
  181. klass.consume = function consume(consumed) {
  182. return consumed.splice(0);
  183. };
  184. //NOTE: DEVIATION FROM MONGO: many of these do not apply or are inlined (code where relevant)
  185. // missing(val): val == undefined
  186. // nullish(val): val == null || val == undefined
  187. // numeric(val): typeof val == "number"
  188. klass.getType = function getType(v) {
  189. var t = typeof v;
  190. if (t == "object") t = (v === null ? "null" : v.constructor.name || t);
  191. return t;
  192. };
  193. // getArrayLength(arr): arr.length
  194. // getString(val): val.toString() //NOTE: same for getStringData(val) I think
  195. // getOid
  196. // getBool
  197. // getDate
  198. // getTimestamp
  199. // getRegex(re): re.source
  200. // getRegexFlags(re): re.toString().slice(-re.toString().lastIndexOf('/') + 2)
  201. // getSymbol
  202. // getCode
  203. // getInt
  204. // getLong
  205. //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
  206. // from bsontypes
  207. klass.canonicalize = function canonicalize(x) {
  208. var xType = typeof(x);
  209. if (xType == "object") xType = x === null ? "null" : x.constructor.name;
  210. switch (xType) {
  211. case "MinKey":
  212. return -1;
  213. case "MaxKey":
  214. return 127;
  215. case "EOO":
  216. case "undefined":
  217. case undefined:
  218. return 0;
  219. case "jstNULL":
  220. case "null":
  221. case "Null":
  222. return 5;
  223. case "NumberDouble":
  224. case "NumberInt":
  225. case "NumberLong":
  226. case "number":
  227. return 10;
  228. case "Symbol":
  229. case "string":
  230. return 15;
  231. case "Object":
  232. return 20;
  233. case "Array":
  234. return 25;
  235. case "Binary":
  236. return 30;
  237. case "ObjectId":
  238. return 35;
  239. case "ObjectID":
  240. return 35;
  241. case "boolean":
  242. case "Boolean":
  243. return 40;
  244. case "Date":
  245. case "Timestamp":
  246. return 45;
  247. case "RegEx":
  248. case "RegExp":
  249. return 50;
  250. case "DBRef":
  251. return 55;
  252. case "Code":
  253. return 60;
  254. case "CodeWScope":
  255. return 65;
  256. default:
  257. // Default value for Object
  258. return 20;
  259. }
  260. };