Value.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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. //TODO: klass.coerceToTimestamp = ...?
  79. /**
  80. * Compare two Values.
  81. *
  82. * @static
  83. * @method compare
  84. * @param rL left value
  85. * @param rR right value
  86. * @returns an integer less than zero, zero, or an integer greater than zero, depending on whether rL < rR, rL == rR, or rL > rR
  87. **/
  88. var Document; // loaded lazily below //TODO: a dirty hack; need to investigate and clean up
  89. klass.compare = function compare(l, r) {
  90. //NOTE: deviation from mongo code: we have to do some coercing for null "types" because of javascript
  91. var lt = l === null ? "null" : typeof(l),
  92. rt = r === null ? "null" : typeof(r),
  93. ret;
  94. // 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
  95. ret = (klass.cmp(klass.canonicalize(l), klass.canonicalize(r)));
  96. if(ret !== 0) return ret;
  97. // Numbers
  98. if (lt === "number" && rt === "number"){
  99. //NOTE: deviation from Mongo code: they handle NaN a bit differently
  100. if (isNaN(l)) return isNaN(r) ? 0 : -1;
  101. if (isNaN(r)) return 1;
  102. return klass.cmp(l,r);
  103. }
  104. // Compare MinKey and MaxKey cases
  105. if(l.constructor && l.constructor.name in {'MinKey':1,'MaxKey':1} ){
  106. if(l.constructor.name == r.constructor.name) {
  107. return 0;
  108. } else if (l.constructor.name === 'MinKey'){
  109. return -1;
  110. } else {
  111. return 1; // Must be MaxKey, which is greater than everything but MaxKey (which r cannot be)
  112. }
  113. }
  114. // hack: These should really get converted to their BSON type ids and then compared, we use int vs object in queries
  115. if (lt === "number" && rt === "object"){
  116. return -1;
  117. } else if (lt === "object" && rt === "number") {
  118. return 1;
  119. }
  120. // CW TODO for now, only compare like values
  121. if (lt !== rt) throw new Error("can't compare values of BSON types [" + lt + " " + l.constructor.name + "] and [" + rt + ":" + r.constructor.name + "]; code 16016");
  122. // Compare everything else
  123. switch (lt) {
  124. case "number":
  125. throw new Error("number types should have been handled earlier!");
  126. case "string":
  127. return klass.cmp(l,r);
  128. case "boolean":
  129. return l == r ? 0 : l ? 1 : -1;
  130. 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)
  131. case "null":
  132. return 0;
  133. case "object":
  134. if (l instanceof Array) {
  135. for (var i = 0, ll = l.length, rl = r.length; true ; ++i) {
  136. if (i > ll) {
  137. if (i > rl) return 0; // arrays are same length
  138. return -1; // left array is shorter
  139. }
  140. if (i > rl) return 1; // right array is shorter
  141. var cmp = Value.compare(l[i], r[i]);
  142. if (cmp !== 0) return cmp;
  143. }
  144. throw new Error("logic error in Value.compare for Array types!");
  145. }
  146. if (l instanceof Date) return klass.cmp(l,r);
  147. if (l instanceof RegExp) return klass.cmp(l,r);
  148. if (Document === undefined) Document = require("./Document"); //TODO: a dirty hack; need to investigate and clean up
  149. return Document.compare(l, r);
  150. default:
  151. throw new Error("unhandled left hand type:" + lt);
  152. }
  153. };
  154. //TODO: klass.hashCombine = ...?
  155. //TODO: klass.getWidestNumeric = ...?
  156. //TODO: klass.getApproximateSize = ...?
  157. //TODO: klass.addRef = ...?
  158. //TODO: klass.release = ...?