| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- "use strict";
- var assert = require("assert"),
- VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
- VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
- NaryExpression = require("../../../../lib/pipeline/expressions/NaryExpression"),
- ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
- FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
- Expression = require("../../../../lib/pipeline/expressions/Expression"),
- utils = require("./utils");
- // Mocha one-liner to make these tests self-hosted
- 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));
- // A dummy child of NaryExpression used for testing
- var Testable = (function(){
- // CONSTRUCTOR
- var klass = function Testable(isAssociativeAndCommutative){
- this._isAssociativeAndCommutative = isAssociativeAndCommutative;
- base.call(this);
- }, base = NaryExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
- // MEMBERS
- proto.evaluateInternal = function evaluateInternal(vars) {
- // Just put all the values in a list. This is not associative/commutative so
- // the results will change if a factory is provided and operations are reordered.
- var values = [];
- for (var i = 0, l = this.operands.length; i < l; i++) {
- values.push(this.operands[i].evaluateInternal(vars));
- }
- return values;
- };
- proto.getOpName = function getOpName() {
- return "$testable";
- };
- proto.isAssociativeAndCommutative = function isAssociativeAndCommutative(){
- return this._isAssociativeAndCommutative;
- };
- klass.create = function create(associativeAndCommutative) {
- return new Testable(Boolean(associativeAndCommutative));
- };
- klass.factory = function factory() {
- return new Testable(true);
- };
- klass.createFromOperands = function(operands, haveFactory) {
- if (haveFactory === undefined) haveFactory = false;
- var idGenerator = new VariablesIdGenerator(),
- vps = new VariablesParseState(idGenerator),
- testable = Testable.create(haveFactory);
- operands.forEach(function(element) {
- testable.addOperand(Expression.parseOperand(element, vps));
- });
- return testable;
- };
- proto.assertContents = function assertContents(expectedContents) {
- assert.deepEqual(utils.constify({$testable:expectedContents}), utils.expressionToJson(this));
- };
- return klass;
- })();
- exports.NaryExpression = {
- ".parseArguments()": {
- "should parse a fieldPathExpression": function() {
- var vps = new VariablesParseState(new VariablesIdGenerator()),
- parsedArguments = NaryExpression.parseArguments("$field.path.expression", vps);
- assert.equal(parsedArguments.length, 1);
- assert(parsedArguments[0] instanceof FieldPathExpression);
- },
- "should parse an array of fieldPathExpressions": function() {
- var vps = new VariablesParseState(new VariablesIdGenerator()),
- parsedArguments = NaryExpression.parseArguments(["$field.path.expression", "$another.FPE"], vps);
- assert.equal(parsedArguments.length, 2);
- assert(parsedArguments[0] instanceof FieldPathExpression);
- assert(parsedArguments[1] instanceof FieldPathExpression);
- },
- },
- /** Adding operands to the expression. */
- "AddOperand": function testAddOperand() {
- var testable = Testable.create();
- testable.addOperand(ConstantExpression.create(9));
- testable.assertContents([9]);
- testable.addOperand(FieldPathExpression.create("ab.c"));
- testable.assertContents([9, "$ab.c"]);
- },
- /** Dependencies of the expression. */
- "Dependencies": function testDependencies() {
- var testable = Testable.create();
- var assertDependencies = function assertDependencies(expectedDeps, expr) {
- var deps = {}, //TODO: new DepsTracker
- depsJson = [];
- expr.addDependencies(deps);
- deps.forEach(function(dep) {
- depsJson.push(dep);
- });
- assert.deepEqual(depsJson, expectedDeps);
- assert.equal(deps.needWholeDocument, false);
- assert.equal(deps.needTextScore, false);
- };
- // No arguments.
- assertDependencies([], testable);
- // Add a constant argument.
- testable.addOperand(new ConstantExpression(1));
- assertDependencies([], testable);
- // Add a field path argument.
- testable.addOperand(new FieldPathExpression("ab.c"));
- assertDependencies(["ab.c"], testable);
- // Add an object expression.
- var spec = {a:"$x", q:"$r"},
- specElement = spec,
- ctx = new Expression.ObjectCtx({isDocumentOk:true}),
- vps = new VariablesParseState(new VariablesIdGenerator());
- testable.addOperand(Expression.parseObject(specElement, ctx, vps));
- assertDependencies(["ab.c", "r", "x"]);
- },
- /** Serialize to an object. */
- "AddToJsonObj": function testAddToJsonObj() {
- var testable = Testable.create();
- testable.addOperand(new ConstantExpression(5));
- assert.deepEqual(
- {foo:{$testable:[{$const:5}]}},
- {foo:testable.serialize(false)}
- );
- },
- /** Serialize to an array. */
- "AddToJsonArray": function testAddToJsonArray() {
- var testable = Testable.create();
- testable.addOperand(new ConstantExpression(5));
- assert.deepEqual(
- [{$testable:[{$const:5}]}],
- [testable.serialize(false)]
- );
- },
- /** One operand is optimized to a constant, while another is left as is. */
- "OptimizeOneOperand": function testOptimizeOneOperand() {
- var spec = [{$and:[]}, "$abc"],
- testable = Testable.createFromOperands(spec);
- testable.assertContents(spec);
- assert.deepEqual(testable.serialize(), testable.optimize().serialize());
- testable.assertContents([true, "$abc"]);
- },
- /** All operands are constants, and the operator is evaluated with them. */
- "EvaluateAllConstantOperands": function testEvaluateAllConstantOperands() {
- var spec = [1, 2],
- testable = Testable.createFromOperands(spec);
- testable.assertContents(spec);
- var optimized = testable.optimize();
- assert.notDeepEqual(testable.serialize(), optimized.serialize());
- assert.deepEqual({$const:[1,2]}, utils.expressionToJson(optimized));
- },
- "NoFactoryOptimize": {
- // Without factory optimization, optimization will not produce a new expression.
- /** A string constant prevents factory optimization. */
- "StringConstant": function testStringConstant() {
- var testable = Testable.createFromOperands(["abc", "def", "$path"], true);
- assert.strictEqual(testable, testable.optimize());
- },
- /** A single (instead of multiple) constant prevents optimization. SERVER-6192 */
- "SingleConstant": function testSingleConstant() {
- var testable = Testable.createFromOperands([55, "$path"], true);
- assert.strictEqual(testable, testable.optimize());
- },
- /** Factory optimization is not used without a factory. */
- "NoFactory": function testNoFactory() {
- var testable = Testable.createFromOperands([55, 66, "$path"], false);
- assert.strictEqual(testable, testable.optimize());
- },
- },
- /** Factory optimization separates constant from non constant expressions. */
- "FactoryOptimize": function testFactoryOptimize() {
- // The constant expressions are evaluated separately and placed at the end.
- var testable = Testable.createFromOperands([55, 66, "$path"], true),
- optimized = testable.optimize();
- assert.deepEqual(utils.constify({$testable:["$path", [55, 66]]}), utils.expressionToJson(optimized));
- },
- /** Factory optimization flattens nested operators of the same type. */
- "FlattenOptimize": function testFlattenOptimize() {
- var testable = Testable.createFromOperands(
- [55, "$path", {$add:[5,6,"$q"]}, 66],
- true);
- testable.addOperand(Testable.createFromOperands(
- [99, 100, "$another_path"],
- true));
- var optimized = testable.optimize();
- assert.deepEqual(
- utils.constify({$testable:[
- "$path",
- {$add:["$q", 11]},
- "$another_path",
- [55, 66, [99, 100]]
- ]}),
- utils.expressionToJson(optimized));
- },
- /** Three layers of factory optimization are flattened. */
- "FlattenThreeLayers": function testFlattenThreeLayers() {
- var top = Testable.createFromOperands([1, 2, "$a"], true),
- nested = Testable.createFromOperands([3, 4, "$b"], true);
- nested.addOperand(Testable.createFromOperands([5, 6, "$c"], true));
- top.addOperand(nested);
- var optimized = top.optimize();
- assert.deepEqual(
- utils.constify({
- $testable: ["$a", "$b", "$c", [1, 2, [3, 4, [5, 6]]]]
- }),
- utils.expressionToJson(optimized)
- );
- },
- };
|