Value.js 5.6 KB

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