CompareExpression.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. "use strict";
  2. var CompareExpression = module.exports = (function(){
  3. // CONSTRUCTOR
  4. /**
  5. * Generic comparison expression that gets used for $eq, $ne, $lt, $lte, $gt, $gte, and $cmp.
  6. *
  7. * @class CompareExpression
  8. * @namespace mungedb.aggregate.pipeline.expressions
  9. * @module mungedb-aggregate
  10. * @constructor
  11. **/
  12. var klass = module.exports = CompareExpression = function CompareExpression(cmpOp) {
  13. if(arguments.length !== 1) throw new Error("args expected: cmpOp");
  14. this.cmpOp = cmpOp;
  15. base.call(this);
  16. }, base = require("./NaryExpression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  17. // DEPENDENCIES
  18. var Value = require("../Value"),
  19. Expression = require("./Expression"),
  20. ConstantExpression = require("./ConstantExpression"),
  21. FieldPathExpression = require("./FieldPathExpression"),
  22. FieldRangeExpression = require("./FieldRangeExpression");
  23. // NESTED CLASSES
  24. /**
  25. * Lookup table for truth value returns
  26. *
  27. * @param truthValues truth value for -1, 0, 1
  28. * @param reverse reverse comparison operator
  29. * @param name string name
  30. **/
  31. var CmpLookup = (function(){ // emulating a struct
  32. // CONSTRUCTOR
  33. var klass = function CmpLookup(truthValues, reverse, name) {
  34. if(arguments.length !== 3) throw new Error("args expected: truthValues, reverse, name");
  35. this.truthValues = truthValues;
  36. this.reverse = reverse;
  37. this.name = name;
  38. }, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  39. return klass;
  40. })();
  41. // PRIVATE STATIC MEMBERS
  42. /**
  43. * a table of cmp type lookups to truth values
  44. * @private
  45. **/
  46. var cmpLookupMap = [ //NOTE: converted from this Array to a Dict/Object below using CmpLookup#name as the key
  47. // -1 0 1 reverse name (taking advantage of the fact that our 'enums' are strings below)
  48. new CmpLookup([false, true, false], Expression.CmpOp.EQ, Expression.CmpOp.EQ),
  49. new CmpLookup([true, false, true], Expression.CmpOp.NE, Expression.CmpOp.NE),
  50. new CmpLookup([false, false, true], Expression.CmpOp.LT, Expression.CmpOp.GT),
  51. new CmpLookup([false, true, true], Expression.CmpOp.LTE, Expression.CmpOp.GTE),
  52. new CmpLookup([true, false, false], Expression.CmpOp.GT, Expression.CmpOp.LT),
  53. new CmpLookup([true, true, false], Expression.CmpOp.GTE, Expression.CmpOp.LTE),
  54. new CmpLookup([false, false, false], Expression.CmpOp.CMP, Expression.CmpOp.CMP)
  55. ].reduce(function(r,o){r[o.name]=o;return r;},{});
  56. // PROTOTYPE MEMBERS
  57. proto.addOperand = function addOperand(expr) {
  58. this.checkArgLimit(2);
  59. base.prototype.addOperand.call(this, expr);
  60. };
  61. proto.evaluate = function evaluate(doc) {
  62. this.checkArgCount(2);
  63. var left = this.operands[0].evaluate(doc),
  64. right = this.operands[1].evaluate(doc),
  65. cmp = Expression.signum(Value.compare(left, right));
  66. if (this.cmpOp == Expression.CmpOp.CMP) return cmp;
  67. return cmpLookupMap[this.cmpOp].truthValues[cmp + 1] || false;
  68. };
  69. proto.optimize = function optimize(){
  70. var expr = base.prototype.optimize.call(this); // first optimize the comparison operands
  71. if (!(expr instanceof CompareExpression)) return expr; // if no longer a comparison, there's nothing more we can do.
  72. // check to see if optimizing comparison operator is supported // CMP and NE cannot use ExpressionFieldRange which is what this optimization uses
  73. var newOp = this.cmpOp;
  74. if (newOp == Expression.CmpOp.CMP || newOp == Expression.CmpOp.NE) return expr;
  75. // There's one localized optimization we recognize: a comparison between a field and a constant. If we recognize that pattern, replace it with an ExpressionFieldRange.
  76. // When looking for this pattern, note that the operands could appear in any order. If we need to reverse the sense of the comparison to put it into the required canonical form, do so.
  77. var leftExpr = this.operands[0],
  78. rightExpr = this.operands[1];
  79. var fieldPathExpr, constantExpr;
  80. if (leftExpr instanceof FieldPathExpression) {
  81. fieldPathExpr = leftExpr;
  82. if (!(rightExpr instanceof ConstantExpression)) return expr; // there's nothing more we can do
  83. constantExpr = rightExpr;
  84. } else {
  85. // if the first operand wasn't a path, see if it's a constant
  86. if (!(leftExpr instanceof ConstantExpression)) return expr; // there's nothing more we can do
  87. constantExpr = leftExpr;
  88. // the left operand was a constant; see if the right is a path
  89. if (!(rightExpr instanceof FieldPathExpression)) return expr; // there's nothing more we can do
  90. fieldPathExpr = rightExpr;
  91. // these were not in canonical order, so reverse the sense
  92. newOp = cmpLookupMap[newOp].reverse;
  93. }
  94. return new FieldRangeExpression(fieldPathExpr, newOp, constantExpr.getValue());
  95. };
  96. proto.getOpName = function getOpName(){
  97. return this.cmpOp;
  98. };
  99. return klass;
  100. })();