1
0
Fork 0
arangodb/tests/js/server/aql/aql-optimizer-indexes-multi.js

1909 lines
78 KiB
JavaScript

/*jshint globalstrict:true, strict:true, maxlen: 500 */
/*global assertTrue, assertEqual, assertNotEqual, AQL_EXPLAIN, AQL_EXECUTE */
"use strict";
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for index usage, case of multiple indexes
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2015 triagens GmbH, Cologne, Germany
/// Copyright 2010-2015 ArangoDB 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 Max Neunhoeffer
/// @author Copyright 2015, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
var db = require("@arangodb").db;
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function makeNumber (nr, len) {
var s = nr.toString();
while (s.length < len) {
s = "0" + s;
}
return s;
}
function makeObj (i) {
return { _key: "test" + i,
a: "a" + makeNumber(i,4),
b: "b" + makeNumber(i % 100,3),
c: "c" + makeNumber((((i * 17) % 2001) * 12) % 79, 3)
};
}
function makeResult (f) {
var res = [];
for (var i = 0; i < 8000; i++) {
var o = makeObj(i);
if (f(o)) {
res.push(o);
}
}
return res;
}
function optimizerIndexesMultiTestSuite () {
let c;
let noProjections = { optimizer: { rules: ["-reduce-extraction-to-projection"] } };
return {
setUp : function () {
db._drop("UnitTestsCollection");
c = db._create("UnitTestsCollection");
for (var i = 0; i < 8000; ++i) {
c.save(makeObj(i));
}
},
tearDown : function () {
db._drop("UnitTestsCollection");
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test use of two hash indexes for "||" and one for "&&"
////////////////////////////////////////////////////////////////////////////////
testUseTwoHashIndexesOr : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["b"] } );
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b012" || x.c == "c017"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b012" || x.c === "c017";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b007" && x.c == "c023"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b007" && x.c === "c023";
});
filterchecks.push( { type : "compare ==", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b044" && x.c >= "c034"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b044" && x.c >= "c034";
});
filterchecks.push( { type : "compare >=", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c006" && x.b == "b012"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c006" && x.b === "b012";
});
filterchecks.push( { type : "compare ==", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c007" && x.b >= "b042"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c007" && x.b >= "b042";
});
filterchecks.push( { type : "compare >=", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c077" && x.b <= "b043"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c077" && x.b <= "b043";
});
filterchecks.push( { type : "compare <=", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test use of two skiplist indexes for "||" and one for "&&"
////////////////////////////////////////////////////////////////////////////////
testUseTwoSkiplistIndexesOr : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["b"] } );
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b012" || x.c == "c017"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b012" || x.c === "c017";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b007" && x.c == "c023"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b007" && x.c === "c023";
});
filterchecks.push( { type : "compare ==", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b044" && x.c >= "c034"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b044" && x.c >= "c034";
});
filterchecks.push( { type : "compare >=", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c006" && x.b == "b012"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c006" && x.b === "b012";
});
filterchecks.push( { type : "compare ==", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c007" && x.b >= "b042"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c007" && x.b >= "b042";
});
filterchecks.push( { type : "compare >=", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c077" && x.b <= "b043"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c077" && x.b <= "b043";
});
filterchecks.push( { type : "compare <=", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test use of skiplist index and a hash index for "||"
////////////////////////////////////////////////////////////////////////////////
testUseSkipAndHashIndexForOr : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["b"] } );
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b012" || x.c == "c017"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b012" || x.c === "c017";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b019" || x.c >= "c077"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b019" || x.c >= "c077";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b007" && x.c == "c023"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b007" && x.c === "c023";
});
filterchecks.push( { type : "compare ==", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.b == "b044" && x.c >= "c034"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.b === "b044" && x.c >= "c034";
});
filterchecks.push( { type : "compare >=", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c006" && x.b == "b012"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c006" && x.b === "b012";
});
filterchecks.push( { type : "compare ==", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c007" && x.b == "b042"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c007" && x.b === "b042";
});
filterchecks.push( { type : "compare ==", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.c == "c077" && x.b <= "b043"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.c === "c077" && x.b <= "b043";
});
filterchecks.push( { type : "compare <=", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a hash index on b and c
////////////////////////////////////////////////////////////////////////////////
testUseHashIndexForDNF : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["b", "c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a hash index on b
////////////////////////////////////////////////////////////////////////////////
testUseHashIndexForDNF2 : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["b"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a hash index on c
////////////////////////////////////////////////////////////////////////////////
testUseHashIndexForDNF3 : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a hash index on b and one on c
////////////////////////////////////////////////////////////////////////////////
testUseHashIndexForDNF4: function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["b"] } );
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a hash index on c and b
////////////////////////////////////////////////////////////////////////////////
testUseHashIndexForDNF5 : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["c", "b"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a skiplist index on b and c
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForDNF : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["b", "c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a skiplist index on b
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForDNF2 : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["b"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a skiplist index on c
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForDNF3: function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a skiplist index on b and one on c
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForDNF4 : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["b"] } );
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (b== || b==) && c== with a skiplist index on c and b
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForDNF5 : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["c", "b"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b == "b012" || x.b == "b073") && x.c == "c022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.b === "b012" || x.b === "b073") && x.c === "c022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c == "c012" || x.c == "c073") && x.b == "b022"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return (x.c === "c012" || x.c === "c073") && x.b === "b022";
});
filterchecks.push( { type : "logical and", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (a== || a== || a== || a==) with a skiplist index on a
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForMultipleOr : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["a"] } );
var queries = [];
var makers = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.a == "a0123" || x.a == "a5564" ||
x.a == "a7768" || x.a == "a0678"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a === "a0123" || x.a === "a5564" ||
x.a === "a7768" || x.a === "a0678";
});
queries.push(`FOR x IN ${c.name()}
FILTER x.a == "a0123" || x.a == "a1234" ||
x.a == "a4567" || x.a == "a5567"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a === "a0123" || x.a === "a1234" ||
x.a === "a4567" || x.a === "a5567";
});
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query, null, noProjections).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is the Return Node
// No filtering needed any more
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual(-1, nodeTypes.indexOf("FilterNode"), "filter used for: " + query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (a>= || a>= || a== || a==) with a skiplist index on a
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForMultipleOr2 : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["a"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.a >= "a7800" || x.a >= "a7810" ||
x.a == "a1234" || x.a == "a6543"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a >= "a7800" || x.a >= "a7810" ||
x.a === "a1234" || x.a === "a6543";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.a == "a1234" || x.a >= "a7800" ||
x.a >= "a7810" || x.a == "a6543"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a === "a1234" || x.a >= "a7800" ||
x.a >= "a7810" || x.a === "a6543";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (a> || a< || a== || a==) with a skiplist index on a
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForMultipleOr3: function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["a"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.a < "a0123" || x.a > "a6964" ||
x.a == "a5555" || x.a == "a6666"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a < "a0123" || x.a > "a6964" ||
x.a === "a5555" || x.a === "a6666";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.a == "a5555" || x.a < "a0123" ||
x.a > "a6964" || x.a == "a6666"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a === "a5555" || x.a < "a0123" ||
x.a > "a6964" || x.a === "a6666";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER x.a == "a5555" || x.a < "a5123" ||
x.a > "a4964" || x.a == "a6666"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a === "a5555" || x.a < "a5123" ||
x.a > "a4964" || x.a === "a6666";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test a in [...] with a skiplist index on a
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexForIn : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["a"] } );
var queries = [];
var makers = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.a IN ["a0123", "a5564", "a7768", "a0678"]
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["a0123", "a5564", "a7768", "a0678"].indexOf(x.a) !== -1;
});
queries.push(`FOR x IN ${c.name()}
FILTER x.a IN ["a0123", "a1234", "a4567", "a5567"]
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["a0123", "a1234", "a4567", "a5567"].indexOf(x.a) !== -1;
});
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query, null, noProjections).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
assertEqual("CalculationNode", plan.nodes[2].type, query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (a== || a== || a== || a==) with a hash index on a
////////////////////////////////////////////////////////////////////////////////
testUseHashIndexForMultipleOr : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["a"] } );
var queries = [];
var makers = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.a == "a0123" || x.a == "a5564" ||
x.a == "a7768" || x.a == "a0678"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a === "a0123" || x.a === "a5564" ||
x.a === "a7768" || x.a === "a0678";
});
queries.push(`FOR x IN ${c.name()}
FILTER x.a == "a0123" || x.a == "a1234" ||
x.a == "a4567" || x.a == "a5567"
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a === "a0123" || x.a === "a1234" ||
x.a === "a4567" || x.a === "a5567";
});
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query, null, noProjections).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a ReturnNode refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
assertEqual("CalculationNode", plan.nodes[2].type, query);
if (db._engine().name !== "rocksdb") {
assertEqual("SortNode", plan.nodes[3].type, query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test a in [...] with a hash index on a
////////////////////////////////////////////////////////////////////////////////
testUseHashIndexForIn : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["a"] } );
var queries = [];
var makers = [];
queries.push(`FOR x IN ${c.name()}
FILTER x.a IN ["a0123", "a5564", "a7768", "a0678"]
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["a0123", "a5564", "a7768", "a0678"].indexOf(x.a) !== -1;
});
queries.push(`FOR x IN ${c.name()}
FILTER x.a IN ["a0123", "a1234", "a4567", "a5567"]
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["a0123", "a1234", "a4567", "a5567"].indexOf(x.a) !== -1;
});
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query, null, noProjections).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a SortNode.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
assertEqual("CalculationNode", plan.nodes[2].type, query);
if (db._engine().name !== "rocksdb") {
assertEqual("SortNode", plan.nodes[3].type, query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test b in [...] || c in [...] with hash indexes on b and c
////////////////////////////////////////////////////////////////////////////////
testUseHashIndexesForInOrIn : function () {
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["b"] } );
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b057", "b017"]) ||
(x.c IN ["c056", "c023"])
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b057", "b017"].indexOf(x.b) !== -1 ||
["c056", "c023"].indexOf(x.c) !== -1;
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b017", "b057"]) ||
(x.c IN ["c056", "c023"])
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b017", "b057"].indexOf(x.b) !== -1 ||
["c056", "c023"].indexOf(x.c) !== -1;
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b057", "b017"]) ||
(x.c IN ["c023", "c056"])
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b057", "b017"].indexOf(x.b) !== -1 ||
["c023", "c056"].indexOf(x.c) !== -1;
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b017", "b057"]) ||
(x.c IN ["c023", "c056"])
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b017", "b057"].indexOf(x.b) !== -1 ||
["c023", "c056"].indexOf(x.c) !== -1;
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test b in [...] || c in [...] with skiplist indexes on b and c
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistIndexesForInOrIn : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["b"] } );
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b057", "b017"]) ||
(x.c IN ["c056", "c023"])
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b057", "b017"].indexOf(x.b) !== -1 ||
["c056", "c023"].indexOf(x.c) !== -1;
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b017", "b057"]) ||
(x.c IN ["c056", "c023"])
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b017", "b057"].indexOf(x.b) !== -1 ||
["c056", "c023"].indexOf(x.c) !== -1;
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b057", "b017"]) ||
(x.c IN ["c023", "c056"])
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b057", "b017"].indexOf(x.b) !== -1 ||
["c023", "c056"].indexOf(x.c) !== -1;
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b017", "b057"]) ||
(x.c IN ["c023", "c056"])
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b017", "b057"].indexOf(x.b) !== -1 ||
["c023", "c056"].indexOf(x.c) !== -1;
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test b in [...] || c== with skiplist index on b and hash on c
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistRespHashIndexesForInOrEq : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["b"] } );
c.ensureIndex( { type: "hash", sparse: false, unique: false,
fields: ["c"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.b IN ["b057", "b017"]) || (x.c == "c056")
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["b057", "b017"].indexOf(x.b) !== -1 ||
x.c === "c056";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c IN ["c017", "c057"]) || (x.b == "b056")
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["c017", "c057"].indexOf(x.c) !== -1 ||
x.b === "b056";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c IN ["c057", "c017"]) || (x.b == "b056")
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["c057", "c017"].indexOf(x.c) !== -1 ||
x.b === "b056";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.c IN ["c017", "c057"]) || (x.b == "b056")
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return ["c017", "c057"].indexOf(x.c) !== -1 ||
x.b === "b056";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test (a>= && a<) || (a>= && a<) overlapping with skiplist index
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistForOverlappingRanges : function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["a"] } );
var queries = [];
var makers = [];
var filterchecks = [];
queries.push(`FOR x IN ${c.name()}
FILTER (x.a >= "a0123" && x.a < "a0207") ||
(x.a >= "a0200" && x.a < "a0300")
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a >= "a0123" && x.a < "a0207" ||
x.a >= "a0200" && x.a < "a0300";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
queries.push(`FOR x IN ${c.name()}
FILTER (x.a >= "a0200" && x.a < "a0300") ||
(x.a >= "a0123" && x.a < "a0207")
SORT x.a
RETURN x.a`);
makers.push(function (x) {
return x.a >= "a0200" && x.a < "a0300" ||
x.a >= "a0123" && x.a < "a0207";
});
filterchecks.push( { type : "logical or", nrSubs : 2 } );
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var filtercheck = filterchecks[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
// Check for a sort node, later, with better optimizations this
// might be gone, we will adjust tests then.
assertNotEqual(-1, nodeTypes.indexOf("SortNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
// This is somewhat fragile, we test whether the 3rd node is
// a calculation node and the 4th is a filter refering to it.
// Furthermore, we check the type of expression in the CalcNode
// and the number of subnodes:
if (filtercheck !== null) {
assertEqual("CalculationNode", plan.nodes[2].type, query);
assertEqual("FilterNode", plan.nodes[3].type, query);
assertEqual(plan.nodes[2].outVariable, plan.nodes[3].inVariable,
query);
assertEqual(filtercheck.type, plan.nodes[2].expression.type, query);
assertEqual(filtercheck.nrSubs,
plan.nodes[2].expression.subNodes.length,
"Number of subnodes in filter expression, " + query);
}
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test multiple ranges with ||
////////////////////////////////////////////////////////////////////////////////
testUseSkiplistForMultipleRangesWithOr: function () {
c.ensureIndex( { type: "skiplist", sparse: false, unique: false,
fields: ["a"] } );
var queries = [];
var makers = [];
var intervals = [
// First all combinations of two closed intervals:
[{ low:"a0100", lowincl:true, high:"a0200", highincl:true },
{ low:"a0150", lowincl:true, high:"a0250", highincl:true }],
[{ low:"a0100", lowincl:true, high:"a0200", highincl:true },
{ low:"a0200", lowincl:true, high:"a0300", highincl:true }],
[{ low:"a0100", lowincl:true, high:"a0200", highincl:true },
{ low:"a0250", lowincl:true, high:"a0350", highincl:true }],
[{ low:"a0150", lowincl:true, high:"a0250", highincl:true },
{ low:"a0100", lowincl:true, high:"a0200", highincl:true }],
[{ low:"a0200", lowincl:true, high:"a0300", highincl:true },
{ low:"a0100", lowincl:true, high:"a0200", highincl:true }],
[{ low:"a0250", lowincl:true, high:"a0350", highincl:true },
{ low:"a0100", lowincl:true, high:"a0200", highincl:true }],
// All combinations of two open intervals:
[{ low:"a0100", lowincl:false, high:"a0200", highincl:false },
{ low:"a0150", lowincl:false, high:"a0250", highincl:false }],
[{ low:"a0100", lowincl:false, high:"a0200", highincl:false },
{ low:"a0200", lowincl:false, high:"a0300", highincl:false }],
[{ low:"a0100", lowincl:false, high:"a0200", highincl:false },
{ low:"a0250", lowincl:false, high:"a0350", highincl:false }],
[{ low:"a0150", lowincl:false, high:"a0250", highincl:false },
{ low:"a0100", lowincl:false, high:"a0200", highincl:false }],
[{ low:"a0200", lowincl:false, high:"a0300", highincl:false },
{ low:"a0100", lowincl:false, high:"a0200", highincl:false }],
[{ low:"a0250", lowincl:false, high:"a0350", highincl:false },
{ low:"a0100", lowincl:false, high:"a0200", highincl:false }],
// All combinations of two half-open intervals:
[{ low:"a0100", lowincl:true, high:"a0200", highincl:false },
{ low:"a0150", lowincl:true, high:"a0250", highincl:false }],
[{ low:"a0100", lowincl:true, high:"a0200", highincl:false },
{ low:"a0200", lowincl:true, high:"a0300", highincl:false }],
[{ low:"a0100", lowincl:true, high:"a0200", highincl:false },
{ low:"a0250", lowincl:true, high:"a0350", highincl:false }],
[{ low:"a0150", lowincl:true, high:"a0250", highincl:false },
{ low:"a0100", lowincl:true, high:"a0200", highincl:false }],
[{ low:"a0200", lowincl:true, high:"a0300", highincl:false },
{ low:"a0100", lowincl:true, high:"a0200", highincl:false }],
[{ low:"a0250", lowincl:true, high:"a0350", highincl:false },
{ low:"a0100", lowincl:true, high:"a0200", highincl:false }],
// Other orientation:
[{ low:"a0100", lowincl:false, high:"a0200", highincl:true },
{ low:"a0150", lowincl:false, high:"a0250", highincl:true }],
[{ low:"a0100", lowincl:false, high:"a0200", highincl:true },
{ low:"a0200", lowincl:false, high:"a0300", highincl:true }],
[{ low:"a0100", lowincl:false, high:"a0200", highincl:true },
{ low:"a0250", lowincl:false, high:"a0350", highincl:true }],
[{ low:"a0150", lowincl:false, high:"a0250", highincl:true },
{ low:"a0100", lowincl:false, high:"a0200", highincl:true }],
[{ low:"a0200", lowincl:false, high:"a0300", highincl:true },
{ low:"a0100", lowincl:false, high:"a0200", highincl:true }],
[{ low:"a0250", lowincl:false, high:"a0350", highincl:true },
{ low:"a0100", lowincl:false, high:"a0200", highincl:true }],
// One open on the right, the other on the left:
[{ low:"a0100", lowincl:true, high:"a0200", highincl:false },
{ low:"a0150", lowincl:false, high:"a0250", highincl:true }],
[{ low:"a0100", lowincl:true, high:"a0200", highincl:false },
{ low:"a0200", lowincl:false, high:"a0300", highincl:true }],
[{ low:"a0100", lowincl:true, high:"a0200", highincl:false },
{ low:"a0250", lowincl:false, high:"a0350", highincl:true }],
[{ low:"a0150", lowincl:true, high:"a0250", highincl:false },
{ low:"a0100", lowincl:false, high:"a0200", highincl:true }],
[{ low:"a0200", lowincl:true, high:"a0300", highincl:false },
{ low:"a0100", lowincl:false, high:"a0200", highincl:true }],
[{ low:"a0250", lowincl:true, high:"a0350", highincl:false },
{ low:"a0100", lowincl:false, high:"a0200", highincl:true }],
// Three intervals in some permutations:
[{ low:"a0100", lowincl:true, high:"a0200", highincl:true },
{ low:"a0150", lowincl:true, high:"a0250", highincl:true },
{ low:"a0200", lowincl:true, high:"a0300", highincl:true }],
[{ low:"a0100", lowincl:true, high:"a0200", highincl:true },
{ low:"a0200", lowincl:true, high:"a0300", highincl:true },
{ low:"a0150", lowincl:true, high:"a0250", highincl:true }],
[{ low:"a0150", lowincl:true, high:"a0250", highincl:true },
{ low:"a0100", lowincl:true, high:"a0200", highincl:true },
{ low:"a0200", lowincl:true, high:"a0300", highincl:true }],
[{ low:"a0150", lowincl:true, high:"a0250", highincl:true },
{ low:"a0200", lowincl:true, high:"a0300", highincl:true },
{ low:"a0100", lowincl:true, high:"a0200", highincl:true }],
[{ low:"a0200", lowincl:true, high:"a0300", highincl:true },
{ low:"a0100", lowincl:true, high:"a0200", highincl:true },
{ low:"a0150", lowincl:true, high:"a0250", highincl:true }],
[{ low:"a0200", lowincl:true, high:"a0300", highincl:true },
{ low:"a0150", lowincl:true, high:"a0250", highincl:true },
{ low:"a0100", lowincl:true, high:"a0200", highincl:true }],
];
var j, k;
for (j = 0; j < intervals.length; j++) {
var inters = intervals[j];
var q = `FOR x IN ${c.name()} FILTER `;
for (k = 0; k < inters.length; k++) {
if (k > 0) {
q += " || ";
}
q += "(x.a >";
if (inters[k].lowincl) {
q += "=";
}
q += ' "' + inters[k].low + '" && x.a <';
if (inters[k].highincl) {
q += "=";
}
q += ' "' + inters[k].high + '")';
}
q += " SORT x.a RETURN x.a";
queries.push(q);
var m = function (intervalList, x) {
var i;
var y = x.a;
for (i = 0; i < intervalList.length; i++) {
if ((y > intervalList[i].low ||
(y === intervalList[i].low && intervalList[i].lowincl)) &&
(y < intervalList[i].high ||
(y === intervalList[i].high && intervalList[i].highincl))) {
return true;
}
}
return false;
}.bind(this, inters);
makers.push(m);
}
for (var i = 0; i < queries.length; i++) {
var query = queries[i];
var maker = makers[i];
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("EnumerateCollection"),
"found EnumerateCollection node for:" + query);
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"),
"no index used for: " + query);
// Check for a sort node, later, with better optimizations this
// might be gone, we will adjust tests then.
assertNotEqual(-1, nodeTypes.indexOf("SortNode"),
"no index used for: " + query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query);
var correct = makeResult(maker).map(function(x) { return x.a; });
assertEqual(correct, results.json, query);
assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0);
}
},
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suites
////////////////////////////////////////////////////////////////////////////////
jsunity.run(optimizerIndexesMultiTestSuite);
return jsunity.done();