Value.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 "Binary":
  97. return 30;
  98. case "ObjectId":
  99. return 35;
  100. case "ObjectID":
  101. return 35;
  102. case "boolean":
  103. case "Boolean":
  104. return 40;
  105. case "Date":
  106. case "Timestamp":
  107. return 45;
  108. case "RegEx":
  109. case "RegExp":
  110. return 50;
  111. case "DBRef":
  112. return 55;
  113. case "Code":
  114. return 60;
  115. case "CodeWScope":
  116. return 65;
  117. default:
  118. // Default value for Object
  119. return 20;
  120. }
  121. };
  122. klass.cmp = function cmp(l, r){
  123. return l < r ? -1 : l > r ? 1 : 0;
  124. };
  125. //TODO: klass.coerceToTimestamp = ...?
  126. /**
  127. * Compare two Values.
  128. *
  129. * @static
  130. * @method compare
  131. * @param rL left value
  132. * @param rR right value
  133. * @returns an integer less than zero, zero, or an integer greater than zero, depending on whether rL < rR, rL == rR, or rL > rR
  134. **/
  135. var Document; // loaded lazily below //TODO: a dirty hack; need to investigate and clean up
  136. klass.compare = function compare(l, r) {
  137. //NOTE: deviation from mongo code: we have to do some coercing for null "types" because of javascript
  138. var lt = l === null ? "null" : typeof(l),
  139. rt = r === null ? "null" : typeof(r),
  140. ret;
  141. // 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
  142. ret = (klass.cmp(klass.canonicalize(l), klass.canonicalize(r)));
  143. if(ret !== 0) return ret;
  144. // Numbers
  145. if (lt === "number" && rt === "number"){
  146. //NOTE: deviation from Mongo code: they handle NaN a bit differently
  147. if (isNaN(l)) return isNaN(r) ? 0 : -1;
  148. if (isNaN(r)) return 1;
  149. return klass.cmp(l,r);
  150. }
  151. // CW TODO for now, only compare like values
  152. if (lt !== rt) throw new Error("can't compare values of BSON types [" + lt + " " + l.constructor.name + "] and [" + rt + ":" + r.constructor.name + "]; code 16016");
  153. // Compare everything else
  154. switch (lt) {
  155. case "number":
  156. throw new Error("number types should have been handled earlier!");
  157. case "string":
  158. return klass.cmp(l,r);
  159. case "boolean":
  160. return l == r ? 0 : l ? 1 : -1;
  161. 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)
  162. case "null":
  163. return 0;
  164. case "object":
  165. if (l instanceof Array) {
  166. for (var i = 0, ll = l.length, rl = r.length; true ; ++i) {
  167. if (i > ll) {
  168. if (i > rl) return 0; // arrays are same length
  169. return -1; // left array is shorter
  170. }
  171. if (i > rl) return 1; // right array is shorter
  172. var cmp = Value.compare(l[i], r[i]);
  173. if (cmp !== 0) return cmp;
  174. }
  175. throw new Error("logic error in Value.compare for Array types!");
  176. }
  177. if (l instanceof Date) return klass.cmp(l,r);
  178. if (l instanceof RegExp) return klass.cmp(l,r);
  179. if (Document === undefined) Document = require("./Document"); //TODO: a dirty hack; need to investigate and clean up
  180. return Document.compare(l, r);
  181. default:
  182. throw new Error("unhandled left hand type:" + lt);
  183. }
  184. };
  185. //TODO: klass.hashCombine = ...?
  186. //TODO: klass.getWidestNumeric = ...?
  187. //TODO: klass.getApproximateSize = ...?
  188. //TODO: klass.addRef = ...?
  189. //TODO: klass.release = ...?