123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- "use strict";
- var FieldRangeExpression = module.exports = (function(){
- // CONSTRUCTOR
- /**
- * Create a field range expression.
- *
- * Field ranges are meant to match up with classic Matcher semantics, and therefore are conjunctions.
- *
- * For example, these appear in mongo shell predicates in one of these forms:
- * { a : C } -> (a == C) // degenerate "point" range
- * { a : { $lt : C } } -> (a < C) // open range
- * { a : { $gt : C1, $lte : C2 } } -> ((a > C1) && (a <= C2)) // closed
- *
- * When initially created, a field range only includes one end of the range. Additional points may be added via intersect().
- *
- * Note that NE and CMP are not supported.
- *
- * @class FieldRangeExpression
- * @namespace mungedb.aggregate.pipeline.expressions
- * @module mungedb-aggregate
- * @extends munge.pipeline.expressions.Expression
- * @constructor
- * @param pathExpr the field path for extracting the field value
- * @param cmpOp the comparison operator
- * @param value the value to compare against
- * @returns the newly created field range expression
- **/
- var klass = function FieldRangeExpression(pathExpr, cmpOp, value){
- if(arguments.length !== 3) throw new Error("args expected: pathExpr, cmpOp, and value");
- this.pathExpr = pathExpr;
- this.range = new Range({cmpOp:cmpOp, value:value});
- }, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
- // DEPENDENCIES
- var Value = require("../Value"),
- ConstantExpression = require("./ConstantExpression");
- // NESTED CLASSES
- var Range = (function(){
- /**
- * create a new Range; opts is either {cmpOp:..., value:...} or {bottom:..., isBottomOpen:..., top:..., isTopOpen:...}
- * @private
- **/
- var klass = function Range(opts){
- this.isBottomOpen = this.isTopOpen = false;
- this.bottom = this.top = undefined;
- if(opts.hasOwnProperty("cmpOp") && opts.hasOwnProperty("value")){
- switch (opts.cmpOp) {
- case Expression.CmpOp.EQ:
- this.bottom = this.top = opts.value;
- break;
- case Expression.CmpOp.GT:
- this.isBottomOpen = true;
- /* falls through */
- case Expression.CmpOp.GTE:
- this.isTopOpen = true;
- this.bottom = opts.value;
- break;
- case Expression.CmpOp.LT:
- this.isTopOpen = true;
- /* falls through */
- case Expression.CmpOp.LTE:
- this.isBottomOpen = true;
- this.top = opts.value;
- break;
- case Expression.CmpOp.NE:
- case Expression.CmpOp.CMP:
- throw new Error("CmpOp not allowed: " + opts.cmpOp);
- default:
- throw new Error("Unexpected CmpOp: " + opts.cmpOp);
- }
- }else{
- this.bottom = opts.bottom;
- this.isBottomOpen = opts.isBottomOpen;
- this.top = opts.top;
- this.isTopOpen = opts.isTopOpen;
- }
- }, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
- // PROTOTYPE MEMBERS
- proto.intersect = function intersect(range){
- // Find the max of the bottom end of ranges
- var maxBottom = range.bottom,
- maxBottomOpen = range.isBottomOpen;
- if(this.bottom !== undefined){
- if(range.bottom === undefined){
- maxBottom = this.bottom;
- maxBottomOpen = this.isBottomOpen;
- }else{
- if(Value.compare(this.bottom, range.bottom) === 0){
- maxBottomOpen = this.isBottomOpen || range.isBottomOpen;
- }else{
- maxBottom = this.bottom;
- maxBottomOpen = this.isBottomOpen;
- }
- }
- }
- // Find the min of the tops of the ranges
- var minTop = range.top,
- minTopOpen = range.isTopOpen;
- if(this.top !== undefined){
- if(range.top === undefined){
- minTop = this.top;
- minTopOpen = this.isTopOpen;
- }else{
- if(Value.compare(this.top, range.top) === 0){
- minTopOpen = this.isTopOpen || range.isTopOpen;
- }else{
- minTop = this.top;
- minTopOpen = this.isTopOpen;
- }
- }
- }
- if(Value.compare(maxBottom, minTop) <= 0)
- return new Range({bottom:maxBottom, isBottomOpen:maxBottomOpen, top:minTop, isTopOpen:minTopOpen});
- return null; // empty intersection
- };
- proto.contains = function contains(value){
- var cmp;
- if(this.bottom !== undefined){
- cmp = Value.compare(value, this.bottom);
- if(cmp < 0) return false;
- if(this.isBottomOpen && cmp === 0) return false;
- }
- if(this.top !== undefined){
- cmp = Value.compare(value, this.top);
- if(cmp > 0) return false;
- if(this.isTopOpen && cmp === 0) return false;
- }
- return true;
- };
- return klass;
- })();
- // PROTOTYPE MEMBERS
- proto.evaluate = function evaluate(obj){
- if(this.range === undefined) return false;
- var value = this.pathExpr.evaluate(obj);
- return this.range.contains(value);
- };
- proto.optimize = function optimize(){
- if(this.range === undefined) return new ConstantExpression(false);
- if(this.range.bottom === undefined && this.range.top === undefined) return new ConstantExpression(true);
- return this;
- };
- proto.addDependencies = function(deps){
- return this.pathExpr.addDependencies(deps);
- };
- /**
- * Add an intersecting range.
- *
- * This can be done any number of times after creation. The range is
- * internally optimized for each new addition. If the new intersection
- * extends or reduces the values within the range, the internal
- * representation is adjusted to reflect that.
- *
- * Note that NE and CMP are not supported.
- *
- * @method intersect
- * @param cmpOp the comparison operator
- * @param pValue the value to compare against
- **/
- proto.intersect = function intersect(cmpOp, value){
- this.range = this.range.intersect(new Range({cmpOp:cmpOp, value:value}));
- };
- proto.toJson = function toJson(){
- if (this.range === undefined) return false; //nothing will satisfy this predicate
- if (this.range.top === undefined && this.range.bottom === undefined) return true; // any value will satisfy this predicate
- // FIXME Append constant values using the $const operator. SERVER-6769
- var json = {};
- if (this.range.top === this.range.bottom) {
- json[Expression.CmpOp.EQ] = [this.pathExpr.toJson(), this.range.top];
- }else{
- var leftOp = {};
- if (this.range.bottom !== undefined) {
- leftOp[this.range.isBottomOpen ? Expression.CmpOp.GT : Expression.CmpOp.GTE] = [this.pathExpr.toJson(), this.range.bottom];
- if (this.range.top === undefined) return leftOp;
- }
- var rightOp = {};
- if(this.range.top !== undefined){
- rightOp[this.range.isTopOpen ? Expression.CmpOp.LT : Expression.CmpOp.LTE] = [this.pathExpr.toJson(), this.range.top];
- if (this.range.bottom === undefined) return rightOp;
- }
- json.$and = [leftOp, rightOp];
- }
- return json;
- };
- //TODO: proto.addToBson = ...?
- //TODO: proto.addToBsonObj = ...?
- //TODO: proto.addToBsonArray = ...?
- //TODO: proto.toMatcherBson = ...? WILL PROBABLY NEED THESE...
- return klass;
- })();
|