CompareExpression.js 4.6 KB

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