LetExpression_test.js 7.0 KB

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