/*jshint globalstrict:false, strict:false, maxlen: 500 */ /*global assertEqual, assertNotEqual, assertTrue, AQL_EXPLAIN, AQL_EXECUTE */ //////////////////////////////////////////////////////////////////////////////// /// @brief tests for optimizer rules /// /// @file /// /// DISCLAIMER /// /// Copyright 2010-2012 triagens GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// /// @author Jan Steemann /// @author Copyright 2012, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// var jsunity = require("jsunity"); var helper = require("@arangodb/aql-helper"); var isEqual = helper.isEqual; var db = require("@arangodb").db; var ruleName = "inline-subqueries"; function optimizerRuleTestSuite () { // various choices to control the optimizer: var paramNone = { optimizer: { rules: [ "-all" ] } }; var paramEnabled = { optimizer: { rules: [ "-all", "+" + ruleName ] }, inspectSimplePlans: true }; var paramDisabled = { optimizer: { rules: [ "+all", "-" + ruleName ] } }; return { setUp : function () { }, tearDown : function () { }, //////////////////////////////////////////////////////////////////////////////// /// @brief test that rule has no effect when explicitly disabled //////////////////////////////////////////////////////////////////////////////// testRuleDisabled : function () { var queries = [ "FOR i IN (FOR x IN [1,2,3] RETURN x) RETURN i", "FOR i IN (FOR x IN (FOR y IN [1,2,3] RETURN y) RETURN x) RETURN i", "FOR i IN [1,2,3] LET x = (FOR j IN (FOR k IN [1,2,3] RETURN k) RETURN j) RETURN x" ]; queries.forEach(function(query) { var result = AQL_EXPLAIN(query, { }, paramNone); assertEqual([ ], result.plan.rules, query); }); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test that rule has no effect //////////////////////////////////////////////////////////////////////////////// testRuleNoEffect : function () { var queries = [ "FOR i IN [1,2,3] RETURN i", "LET a = [1,2,3] FOR i IN a RETURN i", "FOR i IN [1,2,3] LET x = (FOR j IN [1,2,3] RETURN j) RETURN x", "FOR i IN (FOR j IN [1,2,3] COLLECT v = j INTO g RETURN [v, g]) RETURN i", "FOR i IN [1,2,3] LET x = (FOR j IN [1,2,3] LIMIT 1 RETURN j) FOR k IN x RETURN k", "FOR i IN [1,2,3] LET x = (FOR j IN [1,2,3] SORT j RETURN j) FOR k IN x RETURN k" ]; queries.forEach(function(query) { var result = AQL_EXPLAIN(query, { }, paramEnabled); assertEqual([ ], result.plan.rules, query); result = AQL_EXPLAIN(query, { }, paramNone); assertEqual([ ], result.plan.rules, query); }); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test that rule has an effect //////////////////////////////////////////////////////////////////////////////// testRuleHasEffect : function () { var queries = [ "FOR i IN (FOR j IN [1,2,3] RETURN j) RETURN i", "FOR i IN (FOR j IN [1,2,3] FILTER j > 1 RETURN j) RETURN i", "FOR i IN (FOR j IN [1,2,3] FILTER j > 1 RETURN j) FILTER i > 1 RETURN i", "FOR i IN (FOR j IN [1,2,3] FILTER j > 1 SORT j RETURN j) FILTER i > 1 RETURN i", "FOR i IN (FOR j IN [1,2,3] FILTER j > 1 SORT j RETURN j) FILTER i > 1 SORT i RETURN i", "FOR i IN (FOR j IN [1,2,3] FILTER j > 1 RETURN j * 2) FILTER i > 1 RETURN i * 3", "LET x = (FOR j IN [1,2,3] LIMIT 2 RETURN j) FOR k IN x RETURN k", "LET x = (FOR j IN [1,2,3] SORT j RETURN j) FOR k IN x RETURN k" ]; queries.forEach(function(query) { var result = AQL_EXPLAIN(query, { }, paramEnabled); assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query); result = AQL_EXPLAIN(query, { }, paramNone); var resultDisabled = AQL_EXECUTE(query, { }, paramDisabled).json; var resultEnabled = AQL_EXECUTE(query, { }, paramEnabled).json; assertTrue(isEqual(resultDisabled, resultEnabled), query[0]); }); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test generated plans //////////////////////////////////////////////////////////////////////////////// testPlans : function () { var plans = [ // Const propagation: ["FOR i IN (FOR j IN [1,2,3] RETURN j) RETURN i", ["SingletonNode", "CalculationNode", "EnumerateListNode", "ReturnNode"]], ["FOR i IN (FOR j IN [1,2,3] FILTER j > 1 RETURN j) RETURN i", ["SingletonNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "FilterNode", "ReturnNode"]], ["FOR i IN (FOR j IN [1,2,3] RETURN j * 2) RETURN i", ["SingletonNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "ReturnNode"]], ["FOR i IN (FOR j IN (FOR k IN [1,2,3] RETURN k) RETURN j) RETURN i", ["SingletonNode", "CalculationNode", "EnumerateListNode", "ReturnNode"]] ]; plans.forEach(function(plan) { var result = AQL_EXPLAIN(plan[0], { }, paramEnabled); assertNotEqual(-1, result.plan.rules.indexOf(ruleName), plan[0]); assertEqual(plan[1], helper.getCompactPlan(result).map(function(node) { return node.type; }), plan[0]); }); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test generated results //////////////////////////////////////////////////////////////////////////////// testResults : function () { var queries = [ [ "FOR i IN (FOR j IN [1,2,3] RETURN j) RETURN i", [ 1, 2, 3 ] ], [ "FOR i IN (FOR j IN [1,2,3] FILTER j > 1 RETURN j) RETURN i", [ 2, 3 ] ], [ "FOR i IN (FOR j IN [1,2,3] FILTER j > 1 RETURN j * 2) RETURN i", [ 4, 6 ] ], [ "FOR i IN (FOR j IN [1,2,3] FILTER j > 1 RETURN j * 2) FILTER i >= 6 RETURN i", [ 6 ] ], [ "FOR i IN (FOR j IN (FOR k IN [1,2,3] RETURN k) RETURN j * 2) RETURN i * 2", [ 4, 8, 12 ] ], [ "FOR i IN (FOR j IN (FOR k IN [1,2,3,4] SORT k DESC LIMIT 3 RETURN k) LIMIT 2 RETURN j) RETURN i", [ 4, 3 ] ], [ "LET x = (FOR j IN [1,2,3] LIMIT 2 RETURN j) FOR k IN x RETURN k", [ 1, 2 ] ], [ "LET x = (FOR j IN [1,2,3] LIMIT 1, 2 RETURN j) FOR k IN x RETURN k", [ 2, 3 ] ], [ "LET x = (FOR j IN [1,2,3,4] LIMIT 2 RETURN j) FOR k IN x LIMIT 1, 1 RETURN k", [ 2 ] ], [ "LET x = (FOR j IN [1,2,3,4] LIMIT 1, 2 RETURN j) FOR k IN x LIMIT 1, 1 RETURN k", [ 3 ] ], [ "LET x = (FOR j IN [1,2,3,4] RETURN j) FOR k IN x LIMIT 1, 1 RETURN k", [ 2 ] ], [ "LET x = (FOR j IN [1,2,3,4] SORT j DESC RETURN j) FOR k IN x RETURN k", [ 4, 3, 2, 1 ] ], [ "LET x = (FOR j IN [1,2,3,4] SORT j DESC LIMIT 2 RETURN j) FOR k IN x RETURN k", [ 4, 3 ] ], [ "LET x = (FOR j IN [1,2,3,4] SORT j DESC LIMIT 2 RETURN j) FOR k IN x LIMIT 1 RETURN k", [ 4 ] ] ]; queries.forEach(function(query) { var result = AQL_EXPLAIN(query[0]); assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query); result = AQL_EXECUTE(query[0]).json; assertEqual(query[1], result, query); }); } }; } function optimizerRuleCollectionTestSuite () { var c = null; var cn = "UnitTestsOptimizer"; return { //////////////////////////////////////////////////////////////////////////////// /// @brief set up //////////////////////////////////////////////////////////////////////////////// setUp : function () { db._drop(cn); c = db._create(cn); }, //////////////////////////////////////////////////////////////////////////////// /// @brief tear down //////////////////////////////////////////////////////////////////////////////// tearDown : function () { db._drop(cn); c = null; }, testSpecificPlan1 : function () { var query = "LET x = (FOR doc IN @@cn RETURN doc) FOR doc2 IN x RETURN doc2"; var result = AQL_EXPLAIN(query, { "@cn" : cn }); assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query); var nodes = helper.removeClusterNodesFromPlan(result.plan.nodes); assertEqual(3, nodes.length); assertEqual("ReturnNode", nodes[nodes.length - 1].type); assertEqual("doc2", nodes[nodes.length - 1].inVariable.name); assertEqual("EnumerateCollectionNode", nodes[nodes.length - 2].type); assertEqual(cn, nodes[nodes.length - 2].collection); }, testSpecificPlan2 : function () { var query = "LET x = (FOR doc IN @@cn FILTER doc.foo == 'bar' RETURN doc) FOR doc2 IN x RETURN doc2"; var result = AQL_EXPLAIN(query, { "@cn" : cn }); assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query); var nodes = helper.removeClusterNodesFromPlan(result.plan.nodes); assertEqual(5, nodes.length); assertEqual("ReturnNode", nodes[nodes.length - 1].type); assertEqual("doc2", nodes[nodes.length - 1].inVariable.name); assertEqual("FilterNode", nodes[nodes.length - 2].type); assertEqual("CalculationNode", nodes[nodes.length - 3].type); assertEqual("EnumerateCollectionNode", nodes[nodes.length - 4].type); assertEqual(cn, nodes[nodes.length - 4].collection); }, testSpecificPlan3 : function () { var query = "LET x = (FOR doc IN @@cn RETURN doc) FOR doc2 IN x RETURN x"; var result = AQL_EXPLAIN(query, { "@cn" : cn }); assertEqual(-1, result.plan.rules.indexOf(ruleName), query); // no optimization }, testSpecificPlan4 : function () { var query = "LET x = (FOR doc IN @@cn RETURN doc) FOR i IN 1..10 FILTER LENGTH(x) FOR y IN x RETURN y"; var result = AQL_EXPLAIN(query, { "@cn" : cn }); assertEqual(-1, result.plan.rules.indexOf(ruleName), query); // no optimization }, testSpecificPlan5 : function () { var query = "FOR j IN 1..10 LET x = (FOR doc IN @@cn RETURN doc) FOR i IN 1..10 FILTER LENGTH(x) FOR y IN x RETURN y"; var result = AQL_EXPLAIN(query, { "@cn" : cn }); assertEqual(-1, result.plan.rules.indexOf(ruleName), query); // no optimization } }; } function optimizerRuleViewTestSuite () { let cn = "UnitTestsOptimizer"; return { setUp : function () { db._dropView(cn + "View"); db._drop(cn); db._create(cn); db._createView(cn + "View", "arangosearch", { links: { "UnitTestsOptimizer" : { includeAllFields: true } } }); }, tearDown : function () { db._dropView(cn + "View"); db._drop(cn); }, testVariableReplacementInSearchCondition : function () { let query = "LET sub = (RETURN 1) FOR outer IN sub FOR v IN " + cn + "View SEARCH v.something == outer RETURN v"; let result = AQL_EXPLAIN(query); assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query); let nodes = helper.removeClusterNodesFromPlan(result.plan.nodes); assertEqual("ReturnNode", nodes[nodes.length - 1].type); assertEqual("EnumerateViewNode", nodes[nodes.length - 2].type); let viewNode = nodes[nodes.length - 2]; assertEqual(cn + "View", viewNode.view); assertEqual("v", viewNode.outVariable.name); assertEqual("n-ary or", viewNode.condition.type); assertEqual("n-ary and", viewNode.condition.subNodes[0].type); assertEqual("compare ==", viewNode.condition.subNodes[0].subNodes[0].type); assertEqual("attribute access", viewNode.condition.subNodes[0].subNodes[0].subNodes[0].type); assertEqual("something", viewNode.condition.subNodes[0].subNodes[0].subNodes[0].name); assertEqual("reference", viewNode.condition.subNodes[0].subNodes[0].subNodes[0].subNodes[0].type); assertEqual("v", viewNode.condition.subNodes[0].subNodes[0].subNodes[0].subNodes[0].name); assertEqual("reference", viewNode.condition.subNodes[0].subNodes[0].subNodes[1].type); assertEqual("outer", viewNode.condition.subNodes[0].subNodes[0].subNodes[1].name); assertEqual([], viewNode.scorers); }, testNoVariableReplacementInSearchCondition : function () { let query = "LET sub = (RETURN 1) FOR outer IN sub FOR v IN " + cn + "View SEARCH v.something == 1 RETURN v"; let result = AQL_EXPLAIN(query); assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query); let nodes = helper.removeClusterNodesFromPlan(result.plan.nodes); assertEqual("ReturnNode", nodes[nodes.length - 1].type); assertEqual("EnumerateViewNode", nodes[nodes.length - 2].type); let viewNode = nodes[nodes.length - 2]; assertEqual(cn + "View", viewNode.view); assertEqual("v", viewNode.outVariable.name); assertEqual("n-ary or", viewNode.condition.type); assertEqual("n-ary and", viewNode.condition.subNodes[0].type); assertEqual("compare ==", viewNode.condition.subNodes[0].subNodes[0].type); assertEqual("attribute access", viewNode.condition.subNodes[0].subNodes[0].subNodes[0].type); assertEqual("something", viewNode.condition.subNodes[0].subNodes[0].subNodes[0].name); assertEqual("reference", viewNode.condition.subNodes[0].subNodes[0].subNodes[0].subNodes[0].type); assertEqual("v", viewNode.condition.subNodes[0].subNodes[0].subNodes[0].subNodes[0].name); assertEqual("value", viewNode.condition.subNodes[0].subNodes[0].subNodes[1].type); assertEqual([], viewNode.scorers); }, }; } jsunity.run(optimizerRuleTestSuite); jsunity.run(optimizerRuleCollectionTestSuite); jsunity.run(optimizerRuleViewTestSuite); return jsunity.done();