Value.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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. case "object":
  45. switch (value.constructor.name) {
  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.constructor.name + " to int; codes 16003, 16004, 16005");
  52. }
  53. return value;
  54. default:
  55. throw new Error("can't convert from BSON type " + typeof(value) + " to int; codes 16003, 16004, 16005");
  56. }
  57. };
  58. klass.coerceToDate = function coerceToDate(value) {
  59. //TODO: Support Timestamp BSON type?
  60. if (value instanceof Date) return value;
  61. throw new Error("can't convert from BSON type " + typeof(value) + " to Date; uassert code 16006");
  62. };
  63. //TODO: klass.coerceToTimeT = ...? try to use as Date first rather than having coerceToDate return Date.parse or dateObj.getTime() or similar
  64. //TODO: klass.coerceToTm = ...?
  65. klass.coerceToString = function coerceToString(value) {
  66. if (value === null) return "";
  67. switch (typeof(value)) {
  68. case "undefined":
  69. return "";
  70. case "number":
  71. return value.toString();
  72. case "string":
  73. return value;
  74. default:
  75. throw new Error("can't convert from BSON type " + typeof(value) + " to String; uassert code 16007");
  76. }
  77. };
  78. klass.canonicalize = function canonicalize(x) {
  79. var xType = typeof(x);
  80. if(xType == "object") xType = x === null ? "null" : x.constructor.name;
  81. switch (xType) {
  82. case "MinKey":
  83. return -1;
  84. case "MaxKey":
  85. return 127;
  86. case "EOO":
  87. case "undefined":
  88. case undefined:
  89. return 0;
  90. case "jstNULL":
  91. case "null":
  92. case "Null":
  93. return 5;
  94. case "NumberDouble":
  95. case "NumberInt":
  96. case "NumberLong":
  97. case "number":
  98. return 10;
  99. case "Symbol":
  100. case "string":
  101. return 15;
  102. case "Object":
  103. return 20;
  104. case "Array":
  105. return 25;
  106. case "Binary":
  107. return 30;
  108. case "ObjectId":
  109. return 35;
  110. case "ObjectID":
  111. return 35;
  112. case "boolean":
  113. case "Boolean":
  114. return 40;
  115. case "Date":
  116. case "Timestamp":
  117. return 45;
  118. case "RegEx":
  119. case "RegExp":
  120. return 50;
  121. case "DBRef":
  122. return 55;
  123. case "Code":
  124. return 60;
  125. case "CodeWScope":
  126. return 65;
  127. default:
  128. // Default value for Object
  129. return 20;
  130. }
  131. };
  132. klass.cmp = function cmp(l, r){
  133. return l < r ? -1 : l > r ? 1 : 0;
  134. };
  135. //TODO: klass.coerceToTimestamp = ...?
  136. /**
  137. * Compare two Values.
  138. *
  139. * @static
  140. * @method compare
  141. * @param rL left value
  142. * @param rR right value
  143. * @returns an integer less than zero, zero, or an integer greater than zero, depending on whether rL < rR, rL == rR, or rL > rR
  144. **/
  145. var Document; // loaded lazily below //TODO: a dirty hack; need to investigate and clean up
  146. klass.compare = function compare(l, r) {
  147. //NOTE: deviation from mongo code: we have to do some coercing for null "types" because of javascript
  148. var lt = l === null ? "null" : typeof(l),
  149. rt = r === null ? "null" : typeof(r),
  150. ret;
  151. // 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
  152. ret = (klass.cmp(klass.canonicalize(l), klass.canonicalize(r)));
  153. if(ret !== 0) return ret;
  154. // Numbers
  155. if (lt === "number" && rt === "number"){
  156. //NOTE: deviation from Mongo code: they handle NaN a bit differently
  157. if (isNaN(l)) return isNaN(r) ? 0 : -1;
  158. if (isNaN(r)) return 1;
  159. return klass.cmp(l,r);
  160. }
  161. // CW TODO for now, only compare like values
  162. if (lt !== rt) throw new Error("can't compare values of BSON types [" + lt + " " + l.constructor.name + "] and [" + rt + ":" + r.constructor.name + "]; code 16016");
  163. // Compare everything else
  164. switch (lt) {
  165. case "number":
  166. throw new Error("number types should have been handled earlier!");
  167. case "string":
  168. return klass.cmp(l,r);
  169. case "boolean":
  170. return l == r ? 0 : l ? 1 : -1;
  171. 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)
  172. case "null":
  173. return 0;
  174. case "object":
  175. if (l instanceof Array) {
  176. for (var i = 0, ll = l.length, rl = r.length; true ; ++i) {
  177. if (i > ll) {
  178. if (i > rl) return 0; // arrays are same length
  179. return -1; // left array is shorter
  180. }
  181. if (i > rl) return 1; // right array is shorter
  182. var cmp = Value.compare(l[i], r[i]);
  183. if (cmp !== 0) return cmp;
  184. }
  185. throw new Error("logic error in Value.compare for Array types!");
  186. }
  187. if (l instanceof Date) return klass.cmp(l,r);
  188. if (l instanceof RegExp) return klass.cmp(l,r);
  189. if (Document === undefined) Document = require("./Document"); //TODO: a dirty hack; need to investigate and clean up
  190. return Document.compare(l, r);
  191. default:
  192. throw new Error("unhandled left hand type:" + lt);
  193. }
  194. };
  195. //TODO: klass.hashCombine = ...?
  196. //TODO: klass.getWidestNumeric = ...?
  197. //TODO: klass.getApproximateSize = ...?
  198. //TODO: klass.addRef = ...?
  199. //TODO: klass.release = ...?