FieldRangeExpression.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. var FieldRangeExpression = module.exports = (function(){
  2. // CONSTRUCTOR
  3. /**
  4. * Create a field range expression.
  5. *
  6. * Field ranges are meant to match up with classic Matcher semantics, and therefore are conjunctions.
  7. *
  8. * For example, these appear in mongo shell predicates in one of these forms:
  9. * { a : C } -> (a == C) // degenerate "point" range
  10. * { a : { $lt : C } } -> (a < C) // open range
  11. * { a : { $gt : C1, $lte : C2 } } -> ((a > C1) && (a <= C2)) // closed
  12. *
  13. * When initially created, a field range only includes one end of the range. Additional points may be added via intersect().
  14. *
  15. * Note that NE and CMP are not supported.
  16. *
  17. * @class FieldRangeExpression
  18. * @namespace munge.pipeline.expressions
  19. * @module munge
  20. * @extends munge.pipeline.expressions.Expression
  21. * @constructor
  22. * @param pathExpr the field path for extracting the field value
  23. * @param cmpOp the comparison operator
  24. * @param value the value to compare against
  25. * @returns the newly created field range expression
  26. **/
  27. var klass = function FieldRangeExpression(pathExpr, cmpOp, value){
  28. if(arguments.length !== 3) throw new Error("args expected: pathExpr, cmpOp, and value");
  29. this.pathExpr = pathExpr;
  30. this.range = new Range({cmpOp:cmpOp, value:value});
  31. }, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  32. // DEPENDENCIES
  33. var Value = require("../Value"),
  34. ConstantExpression = require("./ConstantExpression");
  35. // NESTED CLASSES
  36. var Range = (function(){
  37. /** create a new Range; opts is either {cmpOp:..., value:...} or {bottom:..., isBottomOpen:..., top:..., isTopOpen:...} **/
  38. var klass = function Range(opts){
  39. this.isBottomOpen = this.isTopOpen = false;
  40. this.bottom = this.top = undefined;
  41. if(opts.hasOwnProperty("cmpOp") && opts.hasOwnProperty("value")){
  42. switch (opts.cmpOp) {
  43. case Expression.CmpOp.EQ:
  44. this.bottom = this.top = opts.value;
  45. break;
  46. case Expression.CmpOp.GT:
  47. this.isBottomOpen = true;
  48. /* falls through */
  49. case Expression.CmpOp.GTE:
  50. this.isTopOpen = true;
  51. this.bottom = opts.value;
  52. break;
  53. case Expression.CmpOp.LT:
  54. this.isTopOpen = true;
  55. /* falls through */
  56. case Expression.CmpOp.LTE:
  57. this.isBottomOpen = true;
  58. this.top = opts.value;
  59. break;
  60. case Expression.CmpOp.NE:
  61. case Expression.CmpOp.CMP:
  62. throw new Error("CmpOp not allowed: " + opts.cmpOp);
  63. default:
  64. throw new Error("Unexpected CmpOp: " + opts.cmpOp);
  65. }
  66. }else{
  67. this.bottom = opts.bottom;
  68. this.isBottomOpen = opts.isBottomOpen;
  69. this.top = opts.top;
  70. this.isTopOpen = opts.isTopOpen;
  71. }
  72. }, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  73. // PROTOTYPE MEMBERS
  74. proto.intersect = function intersect(range){
  75. // Find the max of the bottom end of ranges
  76. var maxBottom = range.bottom,
  77. maxBottomOpen = range.isBottomOpen;
  78. if(this.bottom !== undefined){
  79. if(range.bottom === undefined){
  80. maxBottom = this.bottom;
  81. maxBottomOpen = this.isBottomOpen;
  82. }else{
  83. if(Value.compare(this.bottom, range.bottom) === 0){
  84. maxBottomOpen = this.isBottomOpen || range.isBottomOpen;
  85. }else{
  86. maxBottom = this.bottom;
  87. maxBottomOpen = this.isBottomOpen;
  88. }
  89. }
  90. }
  91. // Find the min of the tops of the ranges
  92. var minTop = range.top,
  93. minTopOpen = range.isTopOpen;
  94. if(this.top !== undefined){
  95. if(range.top === undefined){
  96. minTop = this.top;
  97. minTopOpen = this.isTopOpen;
  98. }else{
  99. if(Value.compare(this.top, range.top) === 0){
  100. minTopOpen = this.isTopOpen || range.isTopOpen;
  101. }else{
  102. minTop = this.top;
  103. minTopOpen = this.isTopOpen;
  104. }
  105. }
  106. }
  107. if(Value.compare(maxBottom, minTop) <= 0)
  108. return new Range({bottom:maxBottom, isBottomOpen:maxBottomOpen, top:minTop, isTopOpen:minTopOpen});
  109. return null; // empty intersection
  110. };
  111. proto.contains = function contains(value){
  112. var cmp;
  113. if(this.bottom !== undefined){
  114. cmp = Value.compare(value, this.bottom);
  115. if(cmp < 0) return false;
  116. if(this.isBottomOpen && cmp === 0) return false;
  117. }
  118. if(this.top !== undefined){
  119. cmp = Value.compare(value, this.top);
  120. if(cmp > 0) return false;
  121. if(this.isTopOpen && cmp === 0) return false;
  122. }
  123. return true;
  124. };
  125. return klass;
  126. })();
  127. // PROTOTYPE MEMBERS
  128. proto.evaluate = function evaluate(obj){
  129. if(this.range === undefined) return false;
  130. var value = this.pathExpr.evaluate(obj);
  131. return this.range.contains(value);
  132. };
  133. proto.optimize = function optimize(){
  134. if(this.range === undefined) return new ConstantExpression(false);
  135. if(this.range.bottom === undefined && this.range.top === undefined) return new ConstantExpression(true);
  136. return this;
  137. };
  138. proto.addDependencies = function(deps){
  139. return this.pathExpr.addDependencies(deps);
  140. };
  141. /**
  142. * Add an intersecting range.
  143. *
  144. * This can be done any number of times after creation. The range is
  145. * internally optimized for each new addition. If the new intersection
  146. * extends or reduces the values within the range, the internal
  147. * representation is adjusted to reflect that.
  148. *
  149. * Note that NE and CMP are not supported.
  150. *
  151. * @param cmpOp the comparison operator
  152. * @param pValue the value to compare against
  153. **/
  154. proto.intersect = function intersect(cmpOp, value){
  155. this.range = this.range.intersect(new Range({cmpOp:cmpOp, value:value}));
  156. };
  157. proto.toJson = function toJson(){
  158. if (this.range === undefined) return false; //nothing will satisfy this predicate
  159. if (this.range.top === undefined && this.range.bottom === undefined) return true; // any value will satisfy this predicate
  160. // FIXME Append constant values using the $const operator. SERVER-6769
  161. var json = {};
  162. if (this.range.top === this.range.bottom) {
  163. json[Expression.CmpOp.EQ] = [this.pathExpr.toJson(), this.range.top];
  164. }else{
  165. var leftOp = {};
  166. if (this.range.bottom !== undefined) {
  167. leftOp[this.range.isBottomOpen ? Expression.CmpOp.GT : Expression.CmpOp.GTE] = [this.pathExpr.toJson(), this.range.bottom];
  168. if (this.range.top === undefined) return leftOp;
  169. }
  170. var rightOp = {};
  171. if(this.range.top !== undefined){
  172. rightOp[this.range.isTopOpen ? Expression.CmpOp.LT : Expression.CmpOp.LTE] = [this.pathExpr.toJson(), this.range.top];
  173. if (this.range.bottom === undefined) return rightOp;
  174. }
  175. json.$and = [leftOp, rightOp];
  176. }
  177. return json;
  178. };
  179. //TODO: proto.addToBson = ...?
  180. //TODO: proto.addToBsonObj = ...?
  181. //TODO: proto.addToBsonArray = ...?
  182. //TODO: proto.toMatcherBson = ...? WILL PROBABLY NEED THESE...
  183. return klass;
  184. })();