LetExpression.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. "use strict";
  2. var LetExpression = module.exports = function LetExpression(vars, subExpression){
  3. if (arguments.length !== 2) throw new Error("Two args expected");
  4. this._variables = vars;
  5. this._subExpression = subExpression;
  6. }, klass = LetExpression, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  7. // DEPENDENCIES
  8. var Variables = require("./Variables"),
  9. VariablesParseState = require("./VariablesParseState");
  10. // PROTOTYPE MEMBERS
  11. klass.parse = function parse(expr, vpsIn){
  12. //NOTE: DEVIATION FROM MONGO: I don't believe this works for us since the operator has been removed from expr by the time
  13. // we get here.
  14. // if(!("$let" in expr)) {
  15. // throw new Error("Tried to create a $let with something other than let. Looks like your parse map went all funny.");
  16. // }
  17. if(typeof(expr) !== 'object' || (expr instanceof Array)) {
  18. throw new Error("$let only supports an object as its argument: 16874");
  19. }
  20. var args = expr,
  21. varsElem = args.vars,
  22. inElem = args['in']; // args.in; ??
  23. //NOTE: DEVIATION FROM MONGO: 1. These if statements are in a loop in the c++ version,
  24. // 2. 'vars' and 'in' are each mandatory here. in the c++ code you only need one of the two.
  25. // 3. Below, we croak if there are more than 2 arguments. The original does not have this limitation, specifically.
  26. // Upon further review, I think our code is more accurate. The c++ code will accept if there are multiple 'in'
  27. // or 'var' values. The previous ones will be overwritten by newer ones.
  28. //
  29. // Final note - I think this code is fine.
  30. //
  31. if(!varsElem) {
  32. throw new Error("Missing 'vars' parameter to $let: 16876");
  33. }
  34. if(!inElem) {
  35. throw new Error("Missing 'in' parameter to $let: 16877");
  36. }
  37. // Should this be !== 2? Why would we have fewer than 2 arguments? Why do we even care what the length of the
  38. // array is? It may be an optimization of sorts. But what we're really wanting here is, 'If any keys are not "in"
  39. // or "vars" then we need to bugcheck.'
  40. if(Object.keys(args).length > 2) {
  41. var bogus = Object.keys(args).filter(function(x) {return !(x === 'in' || x === 'vars');});
  42. throw new Error("Unrecognized parameter to $let: " + bogus.join(",") + "- 16875");
  43. }
  44. var vpsSub = vpsIn, //new VariablesParseState(vpsIn),
  45. vars = {};
  46. // varsElem.forEach(function(varName){
  47. Object.keys(varsElem).forEach(function(varName) {
  48. // var varName = varsElem[key];
  49. Variables.uassertValidNameForUserWrite(varName);
  50. var id = vpsSub.defineVariable(varName);
  51. vars[id] = {};
  52. vars[id][varName] = Expression.parseOperand(varsElem, vpsIn);
  53. });
  54. var subExpression = Expression.parseOperand(inElem, vpsSub);
  55. return new LetExpression(vars, subExpression);
  56. };
  57. proto.optimize = function optimize() {
  58. // This statement doesn't look necessary. We do this work later on if there aren't (or are!) variables.
  59. if(this._variables.length == 0) {
  60. return this._subExpression.optimize();
  61. }
  62. for(var id in this._variables){
  63. for(var name in this._variables[id]) {
  64. //NOTE: DEVIATION FROM MONGO: This is actually ok. The c++ code does this with a single map. The js structure
  65. // is nested objects.
  66. var optimized = this._variables[id][name].optimize();
  67. this._variables[id][name] = optimized;
  68. }
  69. }
  70. this._subExpression = this._subExpression.optimize();
  71. return this;
  72. };
  73. proto.serialize = function serialize(explain) {
  74. var vars = {};
  75. for(var id in this._variables) {
  76. for(var name in this._variables[id]) {
  77. vars[name] = this._variables[id][name];
  78. }
  79. }
  80. return {$let: {vars:vars, 'in':this._subExpression.serialize(explain)}};
  81. };
  82. proto.evaluateInternal = function evaluateInternal(vars) {
  83. for(var id in this._variables) {
  84. for(var name in this._variables[id]) {
  85. // It is guaranteed at parse-time that these expressions don't use the variable ids we
  86. // are setting
  87. var first = id,
  88. second = this._variables[first][name];
  89. //Note - The unary plus on first converts it to an int. Don't remove it.
  90. vars.setValue(+first, second._expressions[name].evaluateInternal(vars));
  91. }
  92. }
  93. return this._subExpression.evaluateInternal(vars);
  94. };
  95. proto.addDependencies = function addDependencies(deps, path){
  96. for(var id in this._variables) {
  97. for(var name in this._variables[id]) {
  98. var first = id,
  99. second = this._variables[first][name];
  100. second._expressions[name].addDependencies(deps);
  101. // this._variables[id][name].addDependencies(deps);
  102. }
  103. }
  104. this._subExpression.addDependencies(deps);
  105. return deps; //NOTE: DEVIATION FROM MONGO: The c++ version does not return a value. We seem to use the returned value
  106. // (or something from a different method named
  107. // addDependencies) in many places.
  108. };
  109. Expression.registerExpression("$let", LetExpression.parse);