LetExpression_test.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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"),
  7. FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
  8. VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
  9. Variables = require("../../../../lib/pipeline/expressions/Variables"),
  10. VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
  11. Expression = require("../../../../lib/pipeline/expressions/Expression");
  12. module.exports = {
  13. "LetExpression": {
  14. beforeEach: function() {
  15. this.vps = new VariablesParseState(new VariablesIdGenerator());
  16. },
  17. "constructor()": {
  18. "should throw an Error when constructing without args": function () {
  19. assert.throws(function () {
  20. new LetExpression();
  21. });
  22. },
  23. "should throw Error when constructing with one arg": function () {
  24. assert.throws(function () {
  25. new LetExpression(1);
  26. });
  27. },
  28. "should not throw when constructing with two args": function () {
  29. assert.doesNotThrow(function () {
  30. new LetExpression(1, 2);
  31. });
  32. },
  33. "#parse()": {
  34. beforeEach: function(){
  35. var self = this;
  36. this.dieForTheRightReason = function(expr, regex) {
  37. var self = this;
  38. assert.throws(function () {
  39. Expression.parseOperand(expr, self.vps);
  40. }, regex);
  41. }
  42. },
  43. "should throw if $let isn't in expr": function () {
  44. this.dieForTheRightReason({$xlet: ['$$a', 1]}, /15999/);
  45. },
  46. "should throw if the $let expression isn't an object": function () {
  47. this.dieForTheRightReason({$let: "this is not an object"}, /16874/);
  48. },
  49. "should throw if the $let expression is an array": function () {
  50. this.dieForTheRightReason({$let: [1, 2, 3]}, /16874/);
  51. },
  52. "should throw if there is no vars parameter to $let": function () {
  53. this.dieForTheRightReason({$let: {noVars: 1}}, /16876/);
  54. },
  55. "should throw if there is no input parameter to $let": function () {
  56. this.dieForTheRightReason({$let: {vars: 1, noIn: 2}}, /16877/);
  57. },
  58. "should throw if any of the arguments to $let are not 'in' or 'var'": function () {
  59. this.dieForTheRightReason({$let: {vars: 1, in: 2, zoot:3}}, /16875/);
  60. },
  61. "should throw if the var name is not writable (1)": function () {
  62. this.dieForTheRightReason({$let: {vars: {a:"@"}, in: 2}}, /FieldPath: '@' doesn't start with a \$; uassert code 16873/);
  63. },
  64. "should throw if the var name is not writable (2)": function () {
  65. this.dieForTheRightReason({$let: {vars: {a:"$$"}, in: 2}}, /empty variable names are not allowed; uassert code 16869/);
  66. },
  67. "should return a Let expression": function () {
  68. var x = Expression.parseOperand({$let: {vars: {a:{$const:123}}, in: 2}}, this.vps);
  69. assert(x instanceof LetExpression);
  70. assert(x._subExpression instanceof ConstantExpression);
  71. assert(x._subExpression.getValue() == 2);
  72. assert(x._variables[0].a._expressions.a instanceof ConstantExpression);
  73. assert.equal(x._variables[0].a._expressions.a.getValue(), 123, "Expected to see 123, but instead saw "+x._variables[0].a._expressions.a.getValue());
  74. },
  75. "should show we collect multiple vars": function() {
  76. var x = Expression.parseOperand({$let: {vars: {a:{$const:1}, b:{$const:2}, c:{$const:3}}, in: 2}}, this.vps);
  77. assert.deepEqual(x._variables[0].a._expressions.a.getValue(), 1);
  78. assert.deepEqual(x._variables[1].b._expressions.b.getValue(), 2);
  79. assert.deepEqual(x._variables[2].c._expressions.c.getValue(), 3);
  80. },
  81. },
  82. "#optimize()": {
  83. beforeEach: function () {
  84. this.testInOpt = function (expr, expected) {
  85. assert(expr._subExpression instanceof ConstantExpression, "Expected the $multiply to be optimized to a constant. Saw '" + expr._subExpression.constructor.name + "'");
  86. assert.equal(expr._subExpression.operands.length, 0, "Expected no operands, saw " + expr._subExpression.operands.length);
  87. assert(expr._subExpression.getValue(), expected, "Expected the multiply to optimize to "+expected+" but saw "+expr._subExpression.getValue());
  88. };
  89. this.testVarOpt = function (expr, expected) {
  90. var here = expr._variables[0].a._expressions.a;
  91. assert(here instanceof ConstantExpression, "Expected the $multiply to be optimized to a constant. Saw '" + here.constructor.name + "'");
  92. assert(here.getValue(), expected, "Expected the multiply to optimize to "+expected+" but saw "+here.getValue());
  93. };
  94. },
  95. "should optimize subexpressions if there are no variables": function () {
  96. var x = Expression.parseOperand({$let: {vars: {}, in: {$multiply: [2,3]}}}, this.vps).optimize();
  97. this.testInOpt(x, 6);
  98. },
  99. "should optimize variables": function () {
  100. var x = Expression.parseOperand({$let: {vars: {a: {$multiply:[5,4]}}, in: {$const: 6}}}, this.vps).optimize();
  101. this.testVarOpt(x, 20);
  102. },
  103. "should optimize subexpressions if there are variables": function () {
  104. var x = Expression.parseOperand({$let: {vars: {a: {$multiply:[5,4]}}, in: {$multiply: [2,3]}}}, this.vps).optimize();
  105. this.testInOpt(x, 6);
  106. this.testVarOpt(x, 20);
  107. }
  108. },
  109. "#serialize()": {
  110. "should serialize variables and the subexpression": function () {
  111. var s = Expression.parseOperand({$let: {vars: {a:{$const:1}, b:{$const:2}}, in: {$multiply: [2,3]}}}, this.vps).optimize().serialize("zoot");
  112. var expected = '{"$let":{"vars":{"a":{"excludeId":false,"_atRoot":false,"_expressions":{"a":{"value":1,"operands":[]},"b":{"value":2,"operands":[]}},"_order":["a","b"]},"b":{"excludeId":false,"_atRoot":false,"_expressions":{"a":{"value":1,"operands":[]},"b":{"value":2,"operands":[]}},"_order":["a","b"]}},"in":{"$const":6}}}';
  113. assert.deepEqual(JSON.stringify(s), expected);
  114. }
  115. },
  116. "#evaluateInternal()": {
  117. "should perform the evaluation for variables and the subexpression": function () {
  118. var x = Expression.parseOperand({$let: {vars: {a: '$in1', b: '$in2'}, in: { $multiply: ["$$a", "$$b"] }}}, this.vps).optimize();
  119. var y = x.evaluate(new Variables(10, {in1: 6, in2: 7}));
  120. assert.equal(y, 42);
  121. }
  122. },
  123. "#addDependencies()": {
  124. "should add dependencies": function(){
  125. var expr = Expression.parseOperand({$let: {vars: {a: {$multiply:['$a','$b']}}, in: {$multiply: ['$c','$d']}}}, this.vps);
  126. var deps = new DepsTracker();
  127. expr.addDependencies(deps);
  128. assert.equal(Object.keys(deps.fields).length, 4);
  129. assert('a' in deps.fields);
  130. assert('b' in deps.fields);
  131. assert('c' in deps.fields);
  132. assert('d' in deps.fields);
  133. assert.strictEqual(deps.needWholeDocument, false);
  134. assert.strictEqual(deps.needTextScore, false);
  135. }
  136. },
  137. "The Guantlet": {
  138. "example from http://docs.mongodb.org/manual/reference/operator/aggregation/let/": function () {
  139. var x = Expression.parseOperand(
  140. {$let: { vars: { total: { $add: [ '$price', '$tax' ] }, discounted: { $cond: { if: '$applyDiscount', then: 0.9, else: 1 } }}, in: { $multiply: [ '$$total', '$$discounted' ] }}},
  141. this.vps).optimize();
  142. var y = x.evaluate(new Variables(10, {price: 90, tax: .05}));
  143. assert.equal(y, 100);
  144. }
  145. },
  146. }
  147. }
  148. };
  149. if (!module.parent)(new (require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);