NaryExpression_test.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. "use strict";
  2. var assert = require("assert"),
  3. VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
  4. VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
  5. NaryExpression = require("../../../../lib/pipeline/expressions/NaryExpression"),
  6. ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
  7. FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
  8. Expression = require("../../../../lib/pipeline/expressions/Expression"),
  9. utils = require("./utils");
  10. // Mocha one-liner to make these tests self-hosted
  11. 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));
  12. // A dummy child of NaryExpression used for testing
  13. var Testable = (function(){
  14. // CONSTRUCTOR
  15. var klass = function Testable(isAssociativeAndCommutative){
  16. this._isAssociativeAndCommutative = isAssociativeAndCommutative;
  17. base.call(this);
  18. }, base = NaryExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  19. // MEMBERS
  20. proto.evaluateInternal = function evaluateInternal(vars) {
  21. // Just put all the values in a list. This is not associative/commutative so
  22. // the results will change if a factory is provided and operations are reordered.
  23. return this.operands.map(function(operand) {
  24. return operand.evaluateInternal(vars);
  25. });
  26. };
  27. proto.getOpName = function getOpName() {
  28. return "$testable";
  29. };
  30. proto.isAssociativeAndCommutative = function isAssociativeAndCommutative(){
  31. return this._isAssociativeAndCommutative;
  32. };
  33. klass.create = function create(associativeAndCommutative) {
  34. return new Testable(!!associativeAndCommutative);
  35. };
  36. klass.factory = function factory() {
  37. return new Testable(true);
  38. };
  39. klass.createFromOperands = function(operands, haveFactory) {
  40. if (haveFactory === undefined) haveFactory = false;
  41. var idGenerator = new VariablesIdGenerator(),
  42. vps = new VariablesParseState(idGenerator),
  43. testable = Testable.create(haveFactory);
  44. operands.forEach(function(element) {
  45. testable.addOperand(Expression.parseOperand(element, vps));
  46. });
  47. return testable;
  48. };
  49. proto.assertContents = function assertContents(expectedContents) {
  50. assert.deepEqual(utils.constify({$testable:expectedContents}), utils.expressionToJson(this));
  51. };
  52. return klass;
  53. })();
  54. exports.NaryExpression = {
  55. ".parseArguments()": {
  56. "should parse a fieldPathExpression": function() {
  57. var vps = new VariablesParseState(new VariablesIdGenerator()),
  58. parsedArguments = NaryExpression.parseArguments("$field.path.expression", vps);
  59. assert.equal(parsedArguments.length, 1);
  60. assert(parsedArguments[0] instanceof FieldPathExpression);
  61. },
  62. "should parse an array of fieldPathExpressions": function() {
  63. var vps = new VariablesParseState(new VariablesIdGenerator()),
  64. parsedArguments = NaryExpression.parseArguments(["$field.path.expression", "$another.FPE"], vps);
  65. assert.equal(parsedArguments.length, 2);
  66. assert(parsedArguments[0] instanceof FieldPathExpression);
  67. assert(parsedArguments[1] instanceof FieldPathExpression);
  68. },
  69. },
  70. /** Adding operands to the expression. */
  71. "AddOperand": function testAddOperand() {
  72. var testable = Testable.create();
  73. testable.addOperand(ConstantExpression.create(9));
  74. testable.assertContents([9]);
  75. testable.addOperand(FieldPathExpression.create("ab.c"));
  76. testable.assertContents([9, "$ab.c"]);
  77. },
  78. /** Dependencies of the expression. */
  79. "Dependencies": function testDependencies() {
  80. var testable = Testable.create();
  81. var assertDependencies = function assertDependencies(expectedDeps, expr) {
  82. var deps = {}, //TODO: new DepsTracker
  83. depsJson = [];
  84. expr.addDependencies(deps);
  85. deps.forEach(function(dep) {
  86. depsJson.push(dep);
  87. });
  88. assert.deepEqual(depsJson, expectedDeps);
  89. assert.equal(deps.needWholeDocument, false);
  90. assert.equal(deps.needTextScore, false);
  91. };
  92. // No arguments.
  93. assertDependencies([], testable);
  94. // Add a constant argument.
  95. testable.addOperand(new ConstantExpression(1));
  96. assertDependencies([], testable);
  97. // Add a field path argument.
  98. testable.addOperand(new FieldPathExpression("ab.c"));
  99. assertDependencies(["ab.c"], testable);
  100. // Add an object expression.
  101. var spec = {a:"$x", q:"$r"},
  102. specElement = spec,
  103. ctx = new Expression.ObjectCtx({isDocumentOk:true}),
  104. vps = new VariablesParseState(new VariablesIdGenerator());
  105. testable.addOperand(Expression.parseObject(specElement, ctx, vps));
  106. assertDependencies(["ab.c", "r", "x"]);
  107. },
  108. /** Serialize to an object. */
  109. "AddToJsonObj": function testAddToJsonObj() {
  110. var testable = Testable.create();
  111. testable.addOperand(new ConstantExpression(5));
  112. assert.deepEqual(
  113. {foo:{$testable:[{$const:5}]}},
  114. {foo:testable.serialize(false)}
  115. );
  116. },
  117. /** Serialize to an array. */
  118. "AddToJsonArray": function testAddToJsonArray() {
  119. var testable = Testable.create();
  120. testable.addOperand(new ConstantExpression(5));
  121. assert.deepEqual(
  122. [{$testable:[{$const:5}]}],
  123. [testable.serialize(false)]
  124. );
  125. },
  126. /** One operand is optimized to a constant, while another is left as is. */
  127. "OptimizeOneOperand": function testOptimizeOneOperand() {
  128. var spec = [{$and:[]}, "$abc"],
  129. testable = Testable.createFromOperands(spec);
  130. testable.assertContents(spec);
  131. assert.deepEqual(testable.serialize(), testable.optimize().serialize());
  132. testable.assertContents([true, "$abc"]);
  133. },
  134. /** All operands are constants, and the operator is evaluated with them. */
  135. "EvaluateAllConstantOperands": function testEvaluateAllConstantOperands() {
  136. var spec = [1, 2],
  137. testable = Testable.createFromOperands(spec);
  138. testable.assertContents(spec);
  139. var optimized = testable.optimize();
  140. assert.notDeepEqual(testable.serialize(), optimized.serialize());
  141. assert.deepEqual({$const:[1,2]}, utils.expressionToJson(optimized));
  142. },
  143. "NoFactoryOptimize": {
  144. // Without factory optimization, optimization will not produce a new expression.
  145. /** A string constant prevents factory optimization. */
  146. "StringConstant": function testStringConstant() {
  147. var testable = Testable.createFromOperands(["abc", "def", "$path"], true);
  148. assert.deepEqual(testable.serialize(), testable.optimize().serialize());
  149. },
  150. /** A single (instead of multiple) constant prevents optimization. SERVER-6192 */
  151. "SingleConstant": function testSingleConstant() {
  152. var testable = Testable.createFromOperands([55, "$path"], true);
  153. assert.deepEqual(testable.serialize(), testable.optimize().serialize());
  154. },
  155. /** Factory optimization is not used without a factory. */
  156. "NoFactory": function testNoFactory() {
  157. var testable = Testable.createFromOperands([55, 66, "$path"], false);
  158. assert.deepEqual(testable.serialize(), testable.optimize().serialize());
  159. },
  160. },
  161. /** Factory optimization separates constant from non constant expressions. */
  162. "FactoryOptimize": function testFactoryOptimize() {
  163. // The constant expressions are evaluated separately and placed at the end.
  164. var testable = Testable.createFromOperands([55, 66, "$path"], false),
  165. optimized = testable.optimize();
  166. assert.deepEqual({$testable:["$path", [55, 66]]}, utils.expressionToJson(optimized));
  167. },
  168. /** Factory optimization flattens nested operators of the same type. */
  169. "FlattenOptimize": function testFlattenOptimize() {
  170. var testable = Testable.createFromOperands(
  171. [55, "$path", {$add:[5,6,"$q"]}, 66],
  172. true);
  173. testable.addOperand(Testable.createFromOperands(
  174. [99, 100, "$another_path"],
  175. true));
  176. var optimized = testable.optimize();
  177. assert.deepEqual(
  178. utils.constify({$testable:[
  179. "$path",
  180. {$add:["$q", 11]},
  181. "$another_path",
  182. [55, 66, [99, 100]]
  183. ]}),
  184. utils.expressionToJson(optimized));
  185. },
  186. /** Three layers of factory optimization are flattened. */
  187. "FlattenThreeLayers": function testFlattenThreeLayers() {
  188. var top = Testable.createFromOperands([1, 2, "$a"], true),
  189. nested = Testable.createFromOperands([3, 4, "$b"], true);
  190. nested.addOperand(Testable.createFromOperands([5, 6, "$c"], true));
  191. top.addOperand(nested);
  192. var optimized = top.optimize();
  193. assert.deepEqual(
  194. utils.constify({$testable:[
  195. "$a",
  196. "$b",
  197. "$c",
  198. [1,2,[3,4,[5,6]]]]}),
  199. utils.expressionToJson(optimized));
  200. },
  201. };