1
0
Fork 0
arangodb/js/server/tests/aql/aql-optimizer-rule-optimize...

271 lines
14 KiB
JavaScript

/*jshint globalstrict:false, strict:false, maxlen: 500 */
/*global assertEqual, assertTrue, assertNotEqual, AQL_EXPLAIN, AQL_EXECUTE, AQL_EXECUTEJSON */
////////////////////////////////////////////////////////////////////////////////
/// @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 Wilfried Goesgens
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
var helper = require("@arangodb/aql-helper");
var isEqual = helper.isEqual;
var _ = require("lodash");
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function optimizerRuleTestSuite () {
var ruleName = "optimize-traversals";
// various choices to control the optimizer:
var paramEnabled = { optimizer: { rules: [ "-all", "+" + ruleName ] } };
var paramDisabled = { optimizer: { rules: [ "+all", "-" + ruleName ] } };
var graphName = "myUnittestGraph";
var opts = _.clone(paramEnabled);
var edgeKey;
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief set up
////////////////////////////////////////////////////////////////////////////////
setUp : function () {
var graph_module = require("@arangodb/general-graph");
opts.allPlans = true;
opts.verbosePlans = true;
try { graph_module._drop(graphName, true); } catch (x) {}
var graph = graph_module._create(graphName, [
graph_module._relation("edges", "circles", ["circles"])]);
// Add circle circles
graph.circles.save({"_key": "A", "label": '1'});
graph.circles.save({"_key": "B", "label": '2'});
graph.circles.save({"_key": "C", "label": '3'});
graph.circles.save({"_key": "D", "label": '4'});
graph.circles.save({"_key": "E", "label": '5'});
graph.circles.save({"_key": "F", "label": '6'});
graph.circles.save({"_key": "G", "label": '7'});
// Add relevant edges
edgeKey = graph.edges.save("circles/A", "circles/C", {theFalse: false, theTruth: true, "label": 'foo'})._key;
graph.edges.save("circles/A", "circles/B", {theFalse: false, theTruth: true, "label": 'bar'});
graph.edges.save("circles/B", "circles/D", {theFalse: false, theTruth: true, "label": 'blarg'});
graph.edges.save("circles/B", "circles/E", {theFalse: false, theTruth: true, "label": 'blub'});
graph.edges.save("circles/C", "circles/F", {theFalse: false, theTruth: true, "label": 'schubi'});
graph.edges.save("circles/C", "circles/G", {theFalse: false, theTruth: true, "label": 'doo'});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief tear down
////////////////////////////////////////////////////////////////////////////////
tearDown : function () {
var graph_module = require("@arangodb/general-graph");
graph_module._drop(graphName, true);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test that rule removes variables
////////////////////////////////////////////////////////////////////////////////
testRuleRemoveVariables : function () {
var queries = [
[ "FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN 1", true, [ true, false, false ] ],
[ "FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [v, e]", true, [ true, true, false ] ],
[ "FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [v, p]", true, [ true, false, true ] ],
[ "FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [e, p]", false, [ true, true, true ] ],
[ "FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [v]", true, [ true, false, false ] ],
[ "FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [e]", true, [ true, true, false ] ],
[ "FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [p]", true, [ true, false, true ] ],
[ "FOR v, e IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [v, e]", false, [ true, true, false ] ],
[ "FOR v, e IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [v]", true, [ true, false, false ] ],
[ "FOR v IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' RETURN [v]", false, [ true, false, false ] ]
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query[0], { }, paramEnabled);
assertEqual(query[1], result.plan.rules.indexOf(ruleName) !== -1, query);
// check that variables were correctly optimized away
var found = false;
result.plan.nodes.forEach(function (thisNode) {
if (thisNode.type === "TraversalNode") {
assertEqual(query[2][0], thisNode.hasOwnProperty("vertexOutVariable"));
assertEqual(query[2][1], thisNode.hasOwnProperty("edgeOutVariable"));
assertEqual(query[2][2], thisNode.hasOwnProperty("pathOutVariable"));
found = true;
}
});
assertTrue(found);
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test that rule has no effect
////////////////////////////////////////////////////////////////////////////////
testRuleNoEffect : function () {
var queries = [
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' LET localScopeVar = NOOPT(true) FILTER p.edges[0].theTruth == localScopeVar return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[-1].theTruth == true return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[*].theTruth == true or p.edges[1].label == 'bar' return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[RAND()].theFalse == false return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[p.edges.length - 1].theFalse == false return {v:v,e:e,p:p}",
"FOR v, e, p IN 2 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER CONCAT(p.edges[0]._key, '') == " + edgeKey + " SORT v._key RETURN {v:v,e:e,p:p}",
"FOR v, e, p IN 2 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER NOOPT(CONCAT(p.edges[0]._key, '')) == " + edgeKey + " SORT v._key RETURN {v:v,e:e,p:p}",
"FOR v, e, p IN 2 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER NOOPT(V8(CONCAT(p.edges[0]._key, ''))) == " + edgeKey + " SORT v._key RETURN {v:v,e:e,p:p}"
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query, { }, paramEnabled);
assertTrue(result.plan.rules.indexOf(ruleName) === -1, query);
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test that rule has an effect
////////////////////////////////////////////////////////////////////////////////
testRuleHasEffect : function () {
var queries = [
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[0].theTruth == true RETURN {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[1].theTruth == true RETURN {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[2].theTruth == true RETURN {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[2].theTruth == true AND p.edges[1].label == 'bar' RETURN {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[0].theTruth == true FILTER p.edges[1].label == 'bar' RETURN {v:v,e:e,p:p}",
"FOR snippet IN ['a', 'b'] FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[1].label == CONCAT(snippet, 'ar') RETURN {v:v,e:e,p:p}"
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query, { }, paramEnabled);
assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query);
result.plan.nodes.forEach(function (thisNode) {
if (thisNode.type === "TraversalNode") {
assertTrue(thisNode.hasOwnProperty("condition"), query);
}
});
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test results
////////////////////////////////////////////////////////////////////////////////
testResults : function () {
var queries = [
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[0].label == 'foo' return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[1].label == 'foo' return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[0].theTruth == true FILTER p.edges[0].label == 'foo' return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[0].theTruth == true FILTER p.edges[1].label == 'foo' return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[0].theTruth == true FILTER p.edges[0].label == 'foo' return {v:v,e:e,p:p}",
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[1].theTruth == true FILTER p.edges[1].label == 'foo' return {v:v,e:e,p:p}",
];
queries.forEach(function(query) {
var planDisabled = AQL_EXPLAIN(query, { }, paramDisabled);
var planEnabled = AQL_EXPLAIN(query, { }, paramEnabled);
var resultDisabled = AQL_EXECUTE(query, { }, paramDisabled).json;
var resultEnabled = AQL_EXECUTE(query, { }, paramEnabled).json;
assertTrue(isEqual(resultDisabled, resultEnabled), query);
assertEqual(-1, planDisabled.plan.rules.indexOf(ruleName), query);
assertNotEqual(-1, planEnabled.plan.rules.indexOf(ruleName), query);
var plans = AQL_EXPLAIN(query, {}, opts).plans;
plans.forEach(function(plan) {
var jsonResult = AQL_EXECUTEJSON(plan, { optimizer: { rules: [ "-all" ] } }).json;
assertEqual(jsonResult, resultDisabled, query);
});
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test early abort function
////////////////////////////////////////////////////////////////////////////////
testFunction : function () {
var queries = [
"FOR v, e, p IN 2 OUTBOUND @start @@ecol FILTER p.edges[0]._key == CONCAT(@edgeKey, '') SORT v._key RETURN v._key",
"FOR v, e, p IN 2 OUTBOUND @start @@ecol FILTER p.edges[0]._key == @edgeKey SORT v._key RETURN v._key",
"FOR v, e, p IN 2 OUTBOUND @start @@ecol FILTER CONCAT(p.edges[0]._key, '') == @edgeKey SORT v._key RETURN v._key",
"FOR v, e, p IN 2 OUTBOUND @start @@ecol FILTER NOOPT(CONCAT(p.edges[0]._key, '')) == @edgeKey SORT v._key RETURN v._key",
"FOR v, e, p IN 2 OUTBOUND @start @@ecol FILTER NOOPT(V8(CONCAT(p.edges[0]._key, ''))) == @edgeKey SORT v._key RETURN v._key"
];
var bindVars = {
start: "circles/A",
"@ecol": "edges",
edgeKey: edgeKey
};
queries.forEach(function (q) {
var res = AQL_EXECUTE(q, bindVars, paramDisabled).json;
assertEqual(res.length, 2, "query: " + q);
assertEqual(res[0], "F", "query: " + q);
assertEqual(res[1], "G", "query: " + q);
res = AQL_EXECUTE(q, bindVars, paramEnabled).json;
assertEqual(res.length, 2, "query (enabled): " + q);
assertEqual(res[0], "F", "query (enabled): " + q);
assertEqual(res[1], "G", "query (enabled): " + q);
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test NOOP results
////////////////////////////////////////////////////////////////////////////////
testNoResults : function () {
var queries = [
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[7].label == 'foo' return {v:v,e:e,p:p}",
// indexed access starts with 0 - this is also forbidden since it will look for the 6th!
"FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH '" + graphName + "' FILTER p.edges[5].label == 'foo' return {v:v,e:e,p:p}",
// "FOR v, e, p IN 1..5 OUTBOUND 'circles/A' GRAPH 'myGraph' FILTER p.edges[1].label == 'foo' AND p.edges[1].label == 'bar' return {v:v,e:e,p:p}"
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query, { }, paramEnabled);
var simplePlan = helper.getCompactPlan(result);
assertNotEqual(-1, result.plan.rules.indexOf(ruleName), query);
assertEqual(simplePlan[2].type, "NoResultsNode");
});
}
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suite
////////////////////////////////////////////////////////////////////////////////
jsunity.run(optimizerRuleTestSuite);
return jsunity.done();