LetExpression.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. "use strict";
  2. var LetExpression = module.exports = function LetExpression(vars, subExpression){
  3. if (arguments.length !== 2) throw new Error(klass.name + ": expected args: vars, subExpression");
  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. var Value = require("../Value"),
  8. Variables = require("./Variables");
  9. function NameAndExpression(name, expr){
  10. this.name = name;
  11. this.expression = expr;
  12. }
  13. klass.parse = function parse(expr, vpsIn){
  14. // if (!(exprFieldName === "$let")) throw new Error("Assertion failure"); //NOTE: DEVIATION FROM MONGO: we do not have exprFieldName here
  15. if (Value.getType(expr) !== "Object")
  16. throw new Error("$let only supports an object as it's argument; uassert code 16874");
  17. var args = expr;
  18. // varsElem must be parsed before inElem regardless of BSON order.
  19. var varsElem,
  20. inElem;
  21. for (var argFieldName in args) {
  22. var arg = args[argFieldName];
  23. if (argFieldName === "vars") {
  24. varsElem = arg;
  25. } else if (argFieldName === "in") {
  26. inElem = arg;
  27. } else {
  28. throw new Error("Unrecognized parameter to $let: " + argFieldName + "; uasserted code 16875");
  29. }
  30. }
  31. if (!varsElem)
  32. throw new Error("Missing 'vars' parameter to $let; uassert code 16876");
  33. if (!inElem)
  34. throw new Error("Missing 'in' parameter to $let; uassert code 16877");
  35. // parse "vars"
  36. var vpsSub = vpsIn, // vpsSub gets our vars, vpsIn doesn't.
  37. vars = {}; // using like a VariableMap
  38. if (Value.getType(varsElem) !== "Object") //NOTE: emulate varsElem.embeddedObjectUserCheck()
  39. throw new Error("invalid parameter: expected an object (vars); uasserted code 10065");
  40. for (var varName in varsElem) {
  41. var varElem = varsElem[varName];
  42. Variables.uassertValidNameForUserWrite(varName);
  43. var id = vpsSub.defineVariable(varName);
  44. vars[id] = new NameAndExpression(varName,
  45. Expression.parseOperand(varElem, vpsIn)); // only has outer vars
  46. }
  47. // parse "in"
  48. var subExpression = Expression.parseOperand(inElem, vpsSub); // has our vars
  49. return new LetExpression(vars, subExpression);
  50. };
  51. proto.optimize = function optimize() {
  52. if (Object.keys(this._variables).length === 0) {
  53. // we aren't binding any variables so just return the subexpression
  54. return this._subExpression.optimize();
  55. }
  56. for (var id in this._variables) {
  57. this._variables[id].expression = this._variables[id].expression.optimize();
  58. }
  59. // TODO be smarter with constant "variables"
  60. this._subExpression = this._subExpression.optimize();
  61. return this;
  62. };
  63. proto.serialize = function serialize(explain) {
  64. var vars = {};
  65. for (var id in this._variables) {
  66. vars[this._variables[id].name] = this._variables[id].expression.serialize(explain);
  67. }
  68. return {
  69. $let: {
  70. vars: vars,
  71. in : this._subExpression.serialize(explain)
  72. }
  73. };
  74. };
  75. proto.evaluateInternal = function evaluateInternal(vars) {
  76. for (var id in this._variables) {
  77. var itFirst = +id, //NOTE: using the unary + to coerce it to a Number
  78. itSecond = this._variables[itFirst];
  79. // It is guaranteed at parse-time that these expressions don't use the variable ids we
  80. // are setting
  81. vars.setValue(itFirst,
  82. itSecond.expression.evaluateInternal(vars));
  83. }
  84. return this._subExpression.evaluateInternal(vars);
  85. };
  86. proto.addDependencies = function addDependencies(deps, path){
  87. for (var id in this._variables) {
  88. var itFirst = +id, //NOTE: using the unary + to coerce it to a Number
  89. itSecond = this._variables[itFirst];
  90. itSecond.expression.addDependencies(deps);
  91. }
  92. // TODO be smarter when CURRENT is a bound variable
  93. this._subExpression.addDependencies(deps);
  94. };
  95. Expression.registerExpression("$let", LetExpression.parse);