LetExpression_test.js 6.7 KB

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