Value.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. "use strict";
  2. var Value = module.exports = Value = (function(){
  3. // CONSTRUCTOR
  4. /**
  5. * 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.
  6. *
  7. * @class Value
  8. * @namespace mungedb.aggregate.pipeline
  9. * @module mungedb-aggregate
  10. * @constructor
  11. **/
  12. var klass = function Value(){
  13. if(this.constructor == Value) throw new Error("Never create instances of this! Use the static helpers only.");
  14. }, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  15. // PRIVATE STUFF
  16. function getTypeVerifier(type, IClass, isStrict) {
  17. return function verifyType(value) {
  18. if (typeof(value) != type) throw new Error("typeof value is not: " + type + "; actual: " + typeof(value));
  19. if (typeof(IClass) == "function" && !(isStrict ? value.constructor == IClass : value instanceof IClass)) throw new Error("instanceof value is not: " + IClass.name + "; actual: " + value.constructor.name);
  20. return value;
  21. };
  22. }
  23. // STATIC MEMBERS
  24. klass.verifyNumber = getTypeVerifier("number", Number); //NOTE: replaces #getDouble(), #getInt(), and #getLong()
  25. klass.verifyString = getTypeVerifier("string", String);
  26. klass.verifyDocument = getTypeVerifier("object", Object, true); //TODO: change to verifyObject? since we're not using actual Document instances
  27. klass.verifyArray = getTypeVerifier("object", Array, true);
  28. klass.verifyDate = getTypeVerifier("object", Date, true);
  29. klass.verifyRegExp = getTypeVerifier("object", RegExp, true); //NOTE: renamed from #getRegex()
  30. //TODO: klass.verifyOid = ...?
  31. //TODO: klass.VerifyTimestamp = ...?
  32. klass.verifyBool = getTypeVerifier("boolean", Boolean, true);
  33. klass.coerceToBool = function coerceToBool(value) {
  34. if (typeof(value) == "string") return true;
  35. return !!value; // including null or undefined
  36. };
  37. klass.coerceToInt =
  38. klass.coerceToLong =
  39. klass.coerceToDouble =
  40. klass._coerceToNumber = function _coerceToNumber(value) { //NOTE: replaces .coerceToInt(), .coerceToLong(), and .coerceToDouble()
  41. if (value === null) return 0;
  42. switch (typeof(value)) {
  43. case "undefined":
  44. return 0;
  45. case "number":
  46. return value;
  47. default:
  48. throw new Error("can't convert from BSON type " + typeof(value) + " to int; codes 16003, 16004, 16005");
  49. }
  50. };
  51. klass.coerceToDate = function coerceToDate(value) {
  52. //TODO: Support Timestamp BSON type?
  53. if (value instanceof Date) return value;
  54. throw new Error("can't convert from BSON type " + typeof(value) + " to Date; uassert code 16006");
  55. };
  56. //TODO: klass.coerceToTimeT = ...? try to use as Date first rather than having coerceToDate return Date.parse or dateObj.getTime() or similar
  57. //TODO: klass.coerceToTm = ...?
  58. klass.coerceToString = function coerceToString(value) {
  59. if (value === null) return "";
  60. switch (typeof(value)) {
  61. case "undefined":
  62. return "";
  63. case "number":
  64. return value.toString();
  65. case "string":
  66. return value;
  67. default:
  68. throw new Error("can't convert from BSON type " + typeof(value) + " to String; uassert code 16007");
  69. }
  70. };
  71. //TODO: klass.coerceToTimestamp = ...?
  72. /**
  73. * Compare two Values.
  74. *
  75. * @static
  76. * @method compare
  77. * @param rL left value
  78. * @param rR right value
  79. * @returns an integer less than zero, zero, or an integer greater than zero, depending on whether rL < rR, rL == rR, or rL > rR
  80. **/
  81. var Document; // loaded lazily below //TODO: a dirty hack; need to investigate and clean up
  82. klass.compare = function compare(l, r) {
  83. var lt = typeof(l),
  84. rt = typeof(r);
  85. // Special handling for Undefined and NULL values ...
  86. if (lt === "undefined") {
  87. if (rt === "undefined") return 0;
  88. return -1;
  89. }
  90. if (l === null) {
  91. if (rt === "undefined") return 1;
  92. if (r === null) return 0;
  93. return -1;
  94. }
  95. // We know the left value isn't Undefined, because of the above. Count a NULL value as greater than an undefined one.
  96. if (rt === "undefined" || r === null) return 1;
  97. // Numbers
  98. if (lt === "number" && rt === "number"){
  99. //this is a deviation from the mongo code because they handle NaN differently
  100. if (isNaN(l)){
  101. return isNaN(r)?0:-1;
  102. }
  103. if (isNaN(r)){
  104. return 1;
  105. }
  106. return l < r ? -1 : l > r ? 1 : 0;
  107. }
  108. // CW TODO for now, only compare like values
  109. if (lt !== rt) throw new Error("can't compare values of BSON types [" + lt + " " + l.constructor.name + "] and [" + rt + ":" + r.constructor.name + "]; code 16016");
  110. // Compare everything else
  111. switch (lt) {
  112. case "number":
  113. throw new Error("number types should have been handled earlier!");
  114. case "string":
  115. return l < r ? -1 : l > r ? 1 : 0;
  116. case "boolean":
  117. return l == r ? 0 : l ? 1 : -1;
  118. case "object":
  119. if (l instanceof Array) {
  120. for (var i = 0, ll = l.length, rl = r.length; true ; ++i) {
  121. if (i > ll) {
  122. if (i > rl) return 0; // arrays are same length
  123. return -1; // left array is shorter
  124. }
  125. if (i > rl) return 1; // right array is shorter
  126. var cmp = Value.compare(l[i], r[i]);
  127. if (cmp !== 0) return cmp;
  128. }
  129. throw new Error("logic error in Value.compare for Array types!");
  130. }
  131. if (l instanceof Date) return l < r ? -1 : l > r ? 1 : 0;
  132. if (l instanceof RegExp) return l < r ? -1 : l > r ? 1 : 0;
  133. if(Document === undefined) Document = require("./Document"); //TODO: a dirty hack; need to investigate and clean up
  134. return Document.compare(l, r);
  135. default:
  136. throw new Error("unhandled left hand type:" + lt);
  137. }
  138. };
  139. //TODO: klass.hashCombine = ...?
  140. //TODO: klass.getWidestNumeric = ...?
  141. //TODO: klass.getApproximateSize = ...?
  142. //TODO: klass.addRef = ...?
  143. //TODO: klass.release = ...?
  144. return klass;
  145. })();