CompareExpression.js 4.6 KB

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