NaryExpression_test.js 8.6 KB

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