NaryExpressionT.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. "use strict";
  2. var assert = require("assert"),
  3. VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
  4. VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
  5. NaryExpressionT = require("../../../../lib/pipeline/expressions/NaryExpressionT"),
  6. ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
  7. FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
  8. Expression = require("../../../../lib/pipeline/expressions/Expression");
  9. function constify(obj, parentIsArray) {
  10. parentIsArray = !parentIsArray ? false : true;
  11. var bob = parentIsArray ? [] : {};
  12. Object.keys(obj).forEach(function(key) {
  13. var elem = obj[key];
  14. if(elem.constructor === Object) {
  15. bob[key] = constify(elem, false);
  16. }
  17. else if(elem.constructor === Array && !parentIsArray) {
  18. bob[key] = constify(elem, true);
  19. }
  20. else if(key === "$const" ||
  21. elem.constructor === String && elem[0] === '$') {
  22. bob[key] = obj[key];
  23. }
  24. else {
  25. bob[key] = {$const:obj[key]}
  26. }
  27. });
  28. return bob;
  29. };
  30. function expressionToJson(expr) {
  31. return expr.serialize(false);
  32. };
  33. function assertDependencies(expectedDeps, expr) {
  34. var deps = new DepsTracker(),
  35. depsJson = [];
  36. expr.addDependencies(deps);
  37. deps.forEach(function(dep) {
  38. depsJson.push(dep);
  39. });
  40. assert.deepEqual(depsJson, expectedDeps);
  41. assert.equal(deps.needWholeDocument, false);
  42. assert.equal(deps.needTextScore, false);
  43. };
  44. // A dummy child of NaryExpression used for testing
  45. var TestableExpression = (function(){
  46. // CONSTRUCTOR
  47. var klass = function TestableExpression(isAssociativeAndCommutative){
  48. this._isAssociativeAndCommutative = isAssociativeAndCommutative;
  49. base.call(this);
  50. }, base = NaryExpressionT(TestableExpression), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  51. // PROTOTYPE MEMBERS
  52. proto.evaluateInternal = function evaluateInternal(vars) {
  53. // Just put all the values in a list. This is not associative/commutative so
  54. // the results will change if a factory is provided and operations are reordered.
  55. return this.operands.map(function(operand) {
  56. return operand.evaluateInternal(vars);
  57. });
  58. };
  59. proto.isAssociativeAndCommutative = function isAssociativeAndCommutative(){
  60. return this._isAssociativeAndCommutative;
  61. };
  62. klass.create = function create(associativeAndCommutative) {
  63. associativeAndCommutative = !associativeAndCommutative ? false : true; //NOTE: coercing to bool -- defaults to false
  64. return new TestableExpression(associativeAndCommutative);
  65. };
  66. klass.factory = function factory() {
  67. return new TestableExpression(true);
  68. };
  69. proto.getOpName = function getOpName() {
  70. return "$testable";
  71. };
  72. proto.assertContents = function assertContents(expectedContents) {
  73. assert.deepEqual(constify({$testable:expectedContents}), expressionToJson(this));
  74. };
  75. klass.createFromOperands = function(operands, haveFactory) {
  76. haveFactory = !haveFactory ? false : true; //NOTE: coercing to bool -- defaults to false
  77. var vps = new VariablesParseState(new VariablesIdGenerator()),
  78. testable = new TestableExpression(haveFactory);
  79. operands.forEach(function(x) {
  80. testable.addOperand(Expression.parseOperand(x, vps));
  81. });
  82. return testable;
  83. };
  84. return klass;
  85. })();
  86. module.exports = {
  87. "NaryExpressionT": {
  88. "generator": {
  89. "can generate a NaryExpression class": function() {
  90. assert.doesNotThrow(function() {
  91. var NaryExpressionClass = NaryExpressionT(String),
  92. naryEpressionIntance = new NaryExpressionClass();
  93. });
  94. }
  95. }
  96. },
  97. "NaryExpression": {
  98. "statics": {
  99. "parseArguments":{
  100. "should parse a fieldPathExpression": function parsesFieldPathExpression() {
  101. var NaryExpressionClass = NaryExpressionT(String),
  102. vps = new VariablesParseState(new VariablesIdGenerator()),
  103. parsedArguments = NaryExpressionClass.parseArguments("$field.path.expression", vps);
  104. assert.equal(parsedArguments.length, 1);
  105. assert(parsedArguments[0] instanceof FieldPathExpression);
  106. },
  107. "should parse an array of fieldPathExpressions": function parsesFieldPathExpression() {
  108. var NaryExpressionClass = NaryExpressionT(String),
  109. vps = new VariablesParseState(new VariablesIdGenerator()),
  110. parsedArguments = NaryExpressionClass.parseArguments(["$field.path.expression", "$another.FPE"], vps);
  111. assert.equal(parsedArguments.length, 2);
  112. assert(parsedArguments[0] instanceof FieldPathExpression);
  113. assert(parsedArguments[1] instanceof FieldPathExpression);
  114. }
  115. }
  116. },
  117. "addOperand": {
  118. "run" : function run() {
  119. var testable = new TestableExpression.create();
  120. testable.addOperand(new ConstantExpression(9));
  121. debugger;
  122. testable.assertContents([9]);
  123. testable.addOperand(new FieldPathExpression("ab.c"));
  124. testable.assertContents([9, "$ab.c"]); //NOTE: Broken, not sure if problem with assertConents or FPE serialize
  125. }
  126. },
  127. "Dependencies": {
  128. "run": function run() {
  129. var testable = new TestableExpression.create();
  130. // No arguments.
  131. assertDependencies([], testable);
  132. // Add a constant argument.
  133. testable.addOperand(new ConstantExpression(1));
  134. assertDependencies([], testable);
  135. // Add a field path argument.
  136. testable.addOperand(new FieldPathExpression("ab.c"));
  137. assertDependencies(["ab.c"], testable);
  138. // Add an object expression.
  139. var spec = {a:"$x", q:"$r"},
  140. specElement = spec,
  141. ctx = new Expression.ObjectCtx({isDocumentOk:true}),
  142. vps = new VariablesParseState(new VariablesIdGenerator());
  143. testable.addOperand(Expression.parseObject(specElement, ctx, vps));
  144. assertDependencies(["ab.c", "r", "x"]);
  145. }
  146. },
  147. "AddToJsonObj": {
  148. "run": function run() {
  149. var testable = new TestableExpression.create();
  150. testable.addOperand(new ConstantExpression(5));
  151. assert.deepEqual(
  152. {foo:{$testable:[{$const:5}]}},
  153. {foo:testable.serialize(false)}
  154. );
  155. }
  156. },
  157. "AddToJsonArray": {
  158. "run": function run() {
  159. var testable = new TestableExpression.create();
  160. testable.addOperand(new ConstantExpression(5));
  161. assert.deepEqual(
  162. [{$testable:[{$const:5}]}],
  163. [testable.serialize(false)]
  164. );
  165. }
  166. },
  167. "OptimizeOneOperand": {
  168. "run": function run() {
  169. var spec = [{$and:[]},"$abc"],
  170. testable = TestableExpression.createFromOperands(spec);
  171. testable.assertContents(spec);
  172. assert.deepEqual(testable.serialize(), testable.optimize().serialize());
  173. assertContents([true, "$abc"])
  174. }
  175. },
  176. "EvaluateAllConstantOperands": {
  177. "run": function run() {
  178. var spec = [1,2],
  179. testable = TestableExpression.createFromOperands(spec);
  180. testable.assertContents(spec);
  181. var optimized = testable.optimize();
  182. assert.notDeepEqual(testable.serialize(), optimized.serialize());
  183. assert.deepEqual({$const:[1,2]}, expressionToJson(optimized));
  184. }
  185. },
  186. "NoFactoryOptimize": {
  187. // Without factory optimization, optimization will not produce a new expression.
  188. /** A string constant prevents factory optimization. */
  189. "StringConstant": function run() {
  190. var testable = TestableExpression.createFromOperands(["abc","def","$path"], true);
  191. assert.deepEqual(testable.serialize(), testable.optimize().serialize());
  192. },
  193. /** A single (instead of multiple) constant prevents optimization. SERVER-6192 */
  194. "SingleConstant": function run() {
  195. var testable = TestableExpression.createFromOperands([55,"$path"], true);
  196. assert.deepEqual(testable.serialize(), testable.optimize().serialize());
  197. },
  198. /** Factory optimization is not used without a factory. */
  199. "NoFactory": function run() {
  200. var testable = TestableExpression.createFromOperands([55,66,"$path"], false);
  201. assert.deepEqual(testable.serialize(), testable.optimize().serialize());
  202. }
  203. },
  204. /** Factory optimization separates constant from non constant expressions. */
  205. "FactoryOptimize": {
  206. // The constant expressions are evaluated separately and placed at the end.
  207. "run": function run() {
  208. var testable = TestableExpression.createFromOperands([55,66,"$path"], false),
  209. optimized = testable.optimize();
  210. assert.deepEqual({$testable:["$path", [55,66]]}, expressionToJson(optimized));
  211. }
  212. },
  213. /** Factory optimization flattens nested operators of the same type. */
  214. "FlattenOptimize": {
  215. "run": function run() {
  216. var testable = TestableExpression.createFromOperands(
  217. [55,"$path",{$add:[5,6,"$q"]},66],
  218. true);
  219. testable.addOperand(Testable.createFromOperands(
  220. [99,100,"$another_path"],
  221. true));
  222. var optimized = testable.optimize();
  223. assert.deepEqual(
  224. constify({$testable:[
  225. "$path",
  226. {$add:["$q", 11]},
  227. "$another_path",
  228. [55, 66, [99, 100]]
  229. ]}),
  230. expressionToJson(optimized));
  231. }
  232. },
  233. /** Three layers of factory optimization are flattened. */
  234. "FlattenThreeLayers": {
  235. "run": function run() {
  236. var top = TestableExpression.createFromOperands([1,2,"$a"], true),
  237. nested = TestableExpression.createFromOperands([3,4,"$b"], true);
  238. nested.addOperand(TestableExpression.createFromOperands([5,6,"$c"],true));
  239. top.addOperand(nested);
  240. var optimized = top.optimize();
  241. assert.deepEqual(
  242. constify({$testable:[
  243. "$a",
  244. "$b",
  245. "$c",
  246. [1,2,[3,4,[5,6]]]]}),
  247. expressionToJson(optimized));
  248. }
  249. },
  250. "constify": {
  251. "simple": function simple() {
  252. var obj = {a:'s'},
  253. constified = constify(obj);
  254. assert.deepEqual(constified, { a: { '$const': 's' } });
  255. },
  256. "array": function array() {
  257. var obj = {a:['s']},
  258. constified = constify(obj);
  259. assert.deepEqual(constified, { a: [ { '$const': 's' } ] });
  260. },
  261. "array2": function array2() {
  262. var obj = {a:['s', [5], {a:5}]},
  263. constified = constify(obj);
  264. assert.deepEqual(constified,
  265. { a:
  266. [{ '$const': 's' },
  267. { '$const': [ 5 ] },
  268. { a: { '$const': 5 } }]
  269. });
  270. },
  271. "object": function object() {
  272. var obj = {a:{b:{c:5}, d:'hi'}},
  273. constified = constify(obj);
  274. assert.deepEqual(constified,
  275. { a:
  276. { b: { c: { '$const': 5 } },
  277. d: { '$const': 'hi' } } });
  278. },
  279. "fieldPathExpression": function fieldPathExpression() {
  280. var obj = {a:"$field.path"},
  281. constified = constify(obj);
  282. assert.deepEqual(constified, obj);
  283. }
  284. }
  285. }
  286. };
  287. if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);