Value.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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. // PRIVATE STUFF
  13. function getTypeVerifier(type, IClass, isStrict) {
  14. return function verifyType(value) {
  15. if (typeof(value) != type) throw new Error("typeof value is not: " + type + "; actual: " + typeof(value));
  16. if (typeof(IClass) == "function" && !(isStrict ? value.constructor == IClass : value instanceof IClass)) throw new Error("instanceof value is not: " + IClass.name + "; actual: " + value.constructor.name);
  17. return value;
  18. };
  19. }
  20. // STATIC MEMBERS
  21. klass.verifyNumber = getTypeVerifier("number", Number); //NOTE: replaces #getDouble(), #getInt(), and #getLong()
  22. klass.verifyString = getTypeVerifier("string", String);
  23. klass.verifyDocument = getTypeVerifier("object", Object, true); //TODO: change to verifyObject? since we're not using actual Document instances
  24. klass.verifyArray = getTypeVerifier("object", Array, true);
  25. klass.verifyDate = getTypeVerifier("object", Date, true);
  26. klass.verifyRegExp = getTypeVerifier("object", RegExp, true); //NOTE: renamed from #getRegex()
  27. //TODO: klass.verifyOid = ...?
  28. //TODO: klass.VerifyTimestamp = ...?
  29. klass.verifyBool = getTypeVerifier("boolean", Boolean, true);
  30. klass.coerceToBool = function coerceToBool(value) {
  31. if (typeof(value) == "string") return true;
  32. return !!value; // including null or undefined
  33. };
  34. klass.coerceToInt =
  35. klass.coerceToLong =
  36. klass.coerceToDouble =
  37. klass._coerceToNumber = function _coerceToNumber(value) { //NOTE: replaces .coerceToInt(), .coerceToLong(), and .coerceToDouble()
  38. if (value === null) return 0;
  39. switch (typeof(value)) {
  40. case "undefined":
  41. return 0;
  42. case "number":
  43. return value;
  44. default:
  45. throw new Error("can't convert from BSON type " + typeof(value) + " to int; codes 16003, 16004, 16005");
  46. }
  47. };
  48. klass.coerceToDate = function coerceToDate(value) {
  49. //TODO: Support Timestamp BSON type?
  50. if (value instanceof Date) return value;
  51. throw new Error("can't convert from BSON type " + typeof(value) + " to Date; uassert code 16006");
  52. };
  53. //TODO: klass.coerceToTimeT = ...? try to use as Date first rather than having coerceToDate return Date.parse or dateObj.getTime() or similar
  54. //TODO: klass.coerceToTm = ...?
  55. klass.coerceToString = function coerceToString(value) {
  56. if (value === null) return "";
  57. switch (typeof(value)) {
  58. case "undefined":
  59. return "";
  60. case "number":
  61. return value.toString();
  62. case "string":
  63. return value;
  64. default:
  65. throw new Error("can't convert from BSON type " + typeof(value) + " to String; uassert code 16007");
  66. }
  67. };
  68. klass.canonicalize = function canonicalize(x) {
  69. var xType = typeof(x);
  70. if(xType == "object") xType = x === null ? "null" : x.constructor.name;
  71. switch (xType) {
  72. case "MinKey":
  73. return -1;
  74. case "MaxKey":
  75. return 127;
  76. case "EOO":
  77. case "undefined":
  78. case undefined:
  79. return 0;
  80. case "jstNULL":
  81. case "null":
  82. case "Null":
  83. return 5;
  84. case "NumberDouble":
  85. case "NumberInt":
  86. case "NumberLong":
  87. case "number":
  88. return 10;
  89. case "Symbol":
  90. case "string":
  91. return 15;
  92. case "Object":
  93. return 20;
  94. case "Array":
  95. return 25;
  96. case "BinData":
  97. return 30;
  98. case "jstOID":
  99. return 35;
  100. case "boolean":
  101. case "Boolean":
  102. return 40;
  103. case "Date":
  104. case "Timestamp":
  105. return 45;
  106. case "RegEx":
  107. case "RegExp":
  108. return 50;
  109. case "DBRef":
  110. return 55;
  111. case "Code":
  112. return 60;
  113. case "CodeWScope":
  114. return 65;
  115. default:
  116. throw new Error("Unexpected type in mongodb-aggregate canonicalize");
  117. }
  118. };
  119. klass.cmp = function cmp(l, r){
  120. return l < r ? -1 : l > r ? 1 : 0;
  121. };
  122. //TODO: klass.coerceToTimestamp = ...?
  123. /**
  124. * Compare two Values.
  125. *
  126. * @static
  127. * @method compare
  128. * @param rL left value
  129. * @param rR right value
  130. * @returns an integer less than zero, zero, or an integer greater than zero, depending on whether rL < rR, rL == rR, or rL > rR
  131. **/
  132. var Document; // loaded lazily below //TODO: a dirty hack; need to investigate and clean up
  133. klass.compare = function compare(l, r) {
  134. var lt = typeof(l),
  135. rt = typeof(r),
  136. ret;
  137. // 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
  138. ret = (klass.cmp(klass.canonicalize(l), klass.canonicalize(r)));
  139. if(ret !== 0) return ret;
  140. // Numbers
  141. if (lt === "number" && rt === "number"){
  142. //NOTE: deviation from Mongo code: they handle NaN a bit differently
  143. if (isNaN(l)) return isNaN(r) ? 0 : -1;
  144. if (isNaN(r)) return 1;
  145. return klass.cmp(l,r);
  146. }
  147. // CW TODO for now, only compare like values
  148. if (lt !== rt) throw new Error("can't compare values of BSON types [" + lt + " " + l.constructor.name + "] and [" + rt + ":" + r.constructor.name + "]; code 16016");
  149. // Compare everything else
  150. switch (lt) {
  151. case "number":
  152. throw new Error("number types should have been handled earlier!");
  153. case "string":
  154. return klass.cmp(l,r);
  155. case "boolean":
  156. return l == r ? 0 : l ? 1 : -1;
  157. 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)
  158. case "null":
  159. return 0;
  160. case "object":
  161. if (l instanceof Array) {
  162. for (var i = 0, ll = l.length, rl = r.length; true ; ++i) {
  163. if (i > ll) {
  164. if (i > rl) return 0; // arrays are same length
  165. return -1; // left array is shorter
  166. }
  167. if (i > rl) return 1; // right array is shorter
  168. var cmp = Value.compare(l[i], r[i]);
  169. if (cmp !== 0) return cmp;
  170. }
  171. throw new Error("logic error in Value.compare for Array types!");
  172. }
  173. if (l instanceof Date) return klass.cmp(l,r);
  174. if (l instanceof RegExp) return klass.cmp(l,r);
  175. if (Document === undefined) Document = require("./Document"); //TODO: a dirty hack; need to investigate and clean up
  176. return Document.compare(l, r);
  177. default:
  178. throw new Error("unhandled left hand type:" + lt);
  179. }
  180. };
  181. //TODO: klass.hashCombine = ...?
  182. //TODO: klass.getWidestNumeric = ...?
  183. //TODO: klass.getApproximateSize = ...?
  184. //TODO: klass.addRef = ...?
  185. //TODO: klass.release = ...?