LetExpression_test.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. "use strict";
  2. if (!module.parent) return require.cache[__filename] = 0, (new(require("mocha"))()).addFile(__filename).ui("exports").run(process.exit);
  3. var assert = require("assert"),
  4. DepsTracker = require("../../../../lib/pipeline/DepsTracker"),
  5. LetExpression = require("../../../../lib/pipeline/expressions/LetExpression"),
  6. ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
  7. MultiplyExpression = require("../../../../lib/pipeline/expressions/MultiplyExpression"), //jshint ignore:line
  8. AddExpression = require("../../../../lib/pipeline/expressions/AddExpression"), //jshint ignore:line
  9. CondExpression = require("../../../../lib/pipeline/expressions/CondExpression"), //jshint ignore:line
  10. FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"), //jshint ignore:line
  11. VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
  12. Variables = require("../../../../lib/pipeline/expressions/Variables"),
  13. VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
  14. Expression = require("../../../../lib/pipeline/expressions/Expression");
  15. exports.LetExpression = {
  16. beforeEach: function() {
  17. this.vps = new VariablesParseState(new VariablesIdGenerator());
  18. },
  19. "constructor()": {
  20. "should throw an Error when constructing without args": function() {
  21. assert.throws(function() {
  22. new LetExpression();
  23. });
  24. },
  25. "should throw Error when constructing with one arg": function() {
  26. assert.throws(function() {
  27. new LetExpression(1);
  28. });
  29. },
  30. "should not throw when constructing with two args": function() {
  31. assert.doesNotThrow(function() {
  32. new LetExpression(1, 2);
  33. });
  34. },
  35. },
  36. "#parse()": {
  37. "should throw if $let isn't in expr": function() {
  38. var self = this;
  39. assert.throws(function() {
  40. Expression.parseOperand({$xlet: ["$$a", 1]}, self.vps);
  41. }, /15999/);
  42. },
  43. "should throw if the $let expression isn't an object": function() {
  44. var self = this;
  45. assert.throws(function() {
  46. Expression.parseOperand({$let: "this is not an object"}, self.vps);
  47. }, /16874/);
  48. },
  49. "should throw if the $let expression is an array": function() {
  50. var self = this;
  51. assert.throws(function() {
  52. Expression.parseOperand({$let: [1, 2, 3]}, self.vps);
  53. }, /16874/);
  54. },
  55. "should throw if there is no vars parameter to $let": function() {
  56. var self = this;
  57. assert.throws(function() {
  58. Expression.parseOperand({$let: {vars: undefined}}, self.vps);
  59. }, /16876/);
  60. },
  61. "should throw if there is no input parameter to $let": function() {
  62. var self = this;
  63. assert.throws(function() {
  64. Expression.parseOperand({$let: {vars: 1, in: undefined}}, self.vps);
  65. }, /16877/);
  66. },
  67. "should throw if any of the arguments to $let are not 'in' or 'vars'": function() {
  68. var self = this;
  69. assert.throws(function() {
  70. Expression.parseOperand({$let: {vars: 1, in: 2, zoot:3}}, self.vps);
  71. }, /16875/);
  72. },
  73. "should return a Let expression": function() {
  74. var x = Expression.parseOperand({$let: {vars: {a:{$const:123}}, in: 2}}, this.vps);
  75. assert(x instanceof LetExpression);
  76. assert(x._subExpression instanceof ConstantExpression);
  77. assert.strictEqual(x._subExpression.getValue(), 2);
  78. assert(x._variables[0].expression instanceof ConstantExpression);
  79. assert.strictEqual(x._variables[0].expression.getValue(), 123);
  80. },
  81. "should show we collect multiple vars": function() {
  82. var x = Expression.parseOperand({$let: {vars: {a:{$const:1}, b:{$const:2}, c:{$const:3}}, in: 2}}, this.vps);
  83. assert.strictEqual(x._variables[0].expression.getValue(), 1);
  84. assert.strictEqual(x._variables[1].expression.getValue(), 2);
  85. assert.strictEqual(x._variables[2].expression.getValue(), 3);
  86. },
  87. },
  88. "#optimize()": {
  89. beforeEach: function() {
  90. this.testInOpt = function(expr, expected) {
  91. assert(expr._subExpression instanceof ConstantExpression, "should have $const subexpr");
  92. assert.strictEqual(expr._subExpression.operands.length, 0);
  93. assert.strictEqual(expr._subExpression.getValue(), expected);
  94. };
  95. this.testVarOpt = function(expr, expected) {
  96. var varExpr = expr._variables[0].expression;
  97. assert(varExpr instanceof ConstantExpression, "should have $const first var");
  98. assert.strictEqual(varExpr.getValue(), expected);
  99. };
  100. },
  101. "should optimize to subexpression if no variables": function() {
  102. var x = Expression.parseOperand({$let:{vars:{}, in:{$multiply:[2,3]}}}, this.vps).optimize();
  103. assert(x instanceof ConstantExpression, "should become $const");
  104. assert.strictEqual(x.getValue(), 6);
  105. },
  106. "should optimize variables": function() {
  107. var x = Expression.parseOperand({$let:{vars:{a:{$multiply:[5,4]}}, in:{$const:6}}}, this.vps).optimize();
  108. this.testVarOpt(x, 20);
  109. },
  110. "should optimize subexpressions if there are variables": function() {
  111. var x = Expression.parseOperand({$let:{vars:{a:{$multiply:[5,4]}}, in: {$multiply:[2,3]}}}, this.vps).optimize();
  112. this.testInOpt(x, 6);
  113. this.testVarOpt(x, 20);
  114. },
  115. },
  116. "#serialize()": {
  117. "should serialize variables and the subexpression": function() {
  118. var spec = {$let: {vars: {a:{$const:1}, b:{$const:2}}, in: {$multiply: [2,3]}}},
  119. expr = Expression.parseOperand(spec, this.vps).optimize();
  120. assert.deepEqual(expr.serialize(false), {$let:{vars:{a:{$const:1},b:{$const:2}},in:{$const:6}}});
  121. },
  122. },
  123. "#evaluate()": {
  124. "should perform the evaluation for variables and the subexpression": function() {
  125. var x = Expression.parseOperand({$let: {vars: {a: "$in1", b: "$in2"}, in: { $multiply: ["$$a", "$$b"] }}}, this.vps).optimize();
  126. var y = x.evaluate(new Variables(10, {in1: 6, in2: 7}));
  127. assert.equal(y, 42);
  128. },
  129. },
  130. "#addDependencies()": {
  131. "should add dependencies": function() {
  132. var expr = Expression.parseOperand({$let: {vars: {a: {$multiply:["$a","$b"]}}, in: {$multiply: ["$c","$d"]}}}, this.vps);
  133. var deps = new DepsTracker();
  134. expr.addDependencies(deps);
  135. assert.equal(Object.keys(deps.fields).length, 4);
  136. assert("a" in deps.fields);
  137. assert("b" in deps.fields);
  138. assert("c" in deps.fields);
  139. assert("d" in deps.fields);
  140. assert.strictEqual(deps.needWholeDocument, false);
  141. assert.strictEqual(deps.needTextScore, false);
  142. },
  143. },
  144. "The Gauntlet": {
  145. "example from http://docs.mongodb.org/manual/reference/operator/aggregation/let/": function() {
  146. var spec = {$let: {
  147. vars: {
  148. total: { $add: [ "$price", "$tax" ] },
  149. discounted: { $cond: { if: "$applyDiscount", then: 0.9, else: 1 } }},
  150. in: { $multiply: [ "$$total", "$$discounted" ] }
  151. }},
  152. x = Expression.parseOperand(spec, this.vps).optimize(),
  153. y;
  154. y = x.evaluate(new Variables(10, {price: 90, tax: 0.05}));
  155. assert.equal(y, 90.05);
  156. y = x.evaluate(new Variables(10, {price: 90, tax: 0.05, applyDiscount: 1}));
  157. assert.equal(y, 90.05 * 0.9);
  158. },
  159. },
  160. };