mirror of https://gitee.com/bigwinds/arangodb
271 lines
14 KiB
JavaScript
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();
|
|
|