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