From fc5e9a5640ccee2cc2a5925423ff75f189cac2a4 Mon Sep 17 00:00:00 2001 From: Max Neunhoeffer Date: Thu, 15 Oct 2015 15:33:26 +0200 Subject: [PATCH 1/3] Add tests for usage of multiple indexes. --- .../tests/aql-optimizer-indexes-multi.js | 1953 +++++++++++++++++ 1 file changed, 1953 insertions(+) create mode 100644 js/server/tests/aql-optimizer-indexes-multi.js diff --git a/js/server/tests/aql-optimizer-indexes-multi.js b/js/server/tests/aql-optimizer-indexes-multi.js new file mode 100644 index 0000000000..5f85840cf4 --- /dev/null +++ b/js/server/tests/aql-optimizer-indexes-multi.js @@ -0,0 +1,1953 @@ +/*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("org/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 () { + var c; + + 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 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 +//////////////////////////////////////////////////////////////////////////////// + + 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 +//////////////////////////////////////////////////////////////////////////////// + + testUseSkiplistIndexForMultipleOr : 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 == "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"; + }); + filterchecks.push( { type : "compare in", nrSubs : 2 } ); + + 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"; + }); + filterchecks.push( { type : "compare in", 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 +//////////////////////////////////////////////////////////////////////////////// + + 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 = []; + var filterchecks = []; + + 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; + }); + filterchecks.push( { type : "compare in", nrSubs : 2 } ); + + 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; + }); + filterchecks.push( { type : "compare in", 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 hash index on a +//////////////////////////////////////////////////////////////////////////////// + + testUseHashIndexForMultipleOr : function () { + c.ensureIndex( { type: "hash", 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 == "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"; + }); + filterchecks.push( { type : "compare in", nrSubs : 2 } ); + + 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"; + }); + filterchecks.push( { type : "compare in", 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 hash index on a +//////////////////////////////////////////////////////////////////////////////// + + testUseHashIndexForIn : function () { + c.ensureIndex( { type: "hash", sparse: false, unique: false, + fields: ["a"] } ); + + var queries = []; + var makers = []; + var filterchecks = []; + + 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; + }); + filterchecks.push( { type : "compare in", nrSubs : 2 } ); + + 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; + }); + filterchecks.push( { type : "compare in", 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 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 executes the test suites +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(optimizerIndexesMultiTestSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: From 0087c24db2c9c4ea52709f4b7c8abdd9d446449d Mon Sep 17 00:00:00 2001 From: Max Neunhoeffer Date: Thu, 15 Oct 2015 15:36:51 +0200 Subject: [PATCH 2/3] Remove some trailing whitespace on some lines. --- .../tests/aql-optimizer-indexes-multi.js | 560 +++++++++--------- 1 file changed, 280 insertions(+), 280 deletions(-) diff --git a/js/server/tests/aql-optimizer-indexes-multi.js b/js/server/tests/aql-optimizer-indexes-multi.js index 5f85840cf4..0113c1b5ad 100644 --- a/js/server/tests/aql-optimizer-indexes-multi.js +++ b/js/server/tests/aql-optimizer-indexes-multi.js @@ -47,13 +47,13 @@ function makeNumber (nr, len) { } function makeObj (i) { - return { _key: "test" + i, - a: "a" + makeNumber(i,4), + return { _key: "test" + i, + a: "a" + makeNumber(i,4), b: "b" + makeNumber(i % 100,3), - c: "c" + makeNumber((((i * 17) % 2001) * 12) % 79, 3) + c: "c" + makeNumber((((i * 17) % 2001) * 12) % 79, 3) }; } - + function makeResult (f) { var res = []; for (var i = 0; i < 8000; i++) { @@ -87,66 +87,66 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseTwoHashIndexesOr : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["b"] } ); - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return x.b === "b007" && x.c === "c023"; }); filterchecks.push( { type : "compare ==", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.b === "b044" && x.c >= "c034"; }); filterchecks.push( { type : "compare >=", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c006" && x.b === "b012"; }); filterchecks.push( { type : "compare ==", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c007" && x.b >= "b042"; }); filterchecks.push( { type : "compare >=", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c077" && x.b <= "b043"; }); filterchecks.push( { type : "compare <=", nrSubs : 2 } ); @@ -175,11 +175,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -196,66 +196,66 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseTwoSkiplistIndexesOr : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["b"] } ); - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return x.b === "b007" && x.c === "c023"; }); filterchecks.push( { type : "compare ==", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.b === "b044" && x.c >= "c034"; }); filterchecks.push( { type : "compare >=", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c006" && x.b === "b012"; }); filterchecks.push( { type : "compare ==", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c007" && x.b >= "b042"; }); filterchecks.push( { type : "compare >=", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c077" && x.b <= "b043"; }); filterchecks.push( { type : "compare <=", nrSubs : 2 } ); @@ -284,11 +284,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -305,75 +305,75 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkipAndHashIndexForOr : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["b"] } ); - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return x.b === "b007" && x.c === "c023"; }); filterchecks.push( { type : "compare ==", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.b === "b044" && x.c >= "c034"; }); filterchecks.push( { type : "compare >=", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c006" && x.b === "b012"; }); filterchecks.push( { type : "compare ==", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c007" && x.b === "b042"; }); filterchecks.push( { type : "compare ==", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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"; + makers.push(function (x) { + return x.c === "c077" && x.b <= "b043"; }); filterchecks.push( { type : "compare <=", nrSubs : 2 } ); @@ -401,11 +401,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -422,28 +422,28 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseHashIndexForDNF : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["b", "c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -471,11 +471,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -492,28 +492,28 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseHashIndexForDNF2 : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["b"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -541,11 +541,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -562,28 +562,28 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseHashIndexForDNF3 : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -611,11 +611,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -632,30 +632,30 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseHashIndexForDNF4: function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["b"] } ); - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -683,11 +683,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -704,28 +704,28 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseHashIndexForDNF5 : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["c", "b"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -753,11 +753,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -774,28 +774,28 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForDNF : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["b", "c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -823,11 +823,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -844,28 +844,28 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForDNF2 : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["b"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -893,11 +893,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -914,28 +914,28 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForDNF3: function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -963,11 +963,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -984,30 +984,30 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForDNF4 : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["b"] } ); - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -1035,11 +1035,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1056,28 +1056,28 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForDNF5 : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["c", "b"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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"; + 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()} + 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"; + makers.push(function (x) { + return (x.c === "c012" || x.c === "c073") && x.b === "b022"; }); filterchecks.push( { type : "logical and", nrSubs : 2 } ); @@ -1105,11 +1105,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1126,32 +1126,32 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForMultipleOr : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + 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 == "a5564" || + 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"; + makers.push(function (x) { + return x.a === "a0123" || x.a === "a5564" || + x.a === "a7768" || x.a === "a0678"; }); filterchecks.push( { type : "compare in", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} - FILTER x.a == "a0123" || x.a == "a1234" || + 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"; + makers.push(function (x) { + return x.a === "a0123" || x.a === "a1234" || + x.a === "a4567" || x.a === "a5567"; }); filterchecks.push( { type : "compare in", nrSubs : 2 } ); @@ -1179,11 +1179,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1200,32 +1200,32 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForMultipleOr2 : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + 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" || + 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"; + 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" || + 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"; + makers.push(function (x) { + return x.a === "a1234" || x.a >= "a7800" || + x.a >= "a7810" || x.a === "a6543"; }); filterchecks.push( { type : "logical or", nrSubs : 2 } ); @@ -1253,11 +1253,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1274,43 +1274,43 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForMultipleOr3: function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + 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" || + 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"; + 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" || + 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"; + 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" || + 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"; + makers.push(function (x) { + return x.a === "a5555" || x.a < "a5123" || + x.a > "a4964" || x.a === "a6666"; }); filterchecks.push( { type : "logical or", nrSubs : 2 } ); @@ -1338,11 +1338,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1359,27 +1359,27 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexForIn : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["a"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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) { + makers.push(function (x) { return ["a0123", "a5564", "a7768", "a0678"].indexOf(x.a) !== -1; }); filterchecks.push( { type : "compare in", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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) { + makers.push(function (x) { return ["a0123", "a1234", "a4567", "a5567"].indexOf(x.a) !== -1; }); filterchecks.push( { type : "compare in", nrSubs : 2 } ); @@ -1408,11 +1408,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1429,32 +1429,32 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseHashIndexForMultipleOr : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", 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 == "a5564" || + 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"; + makers.push(function (x) { + return x.a === "a0123" || x.a === "a5564" || + x.a === "a7768" || x.a === "a0678"; }); filterchecks.push( { type : "compare in", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} - FILTER x.a == "a0123" || x.a == "a1234" || + 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"; + makers.push(function (x) { + return x.a === "a0123" || x.a === "a1234" || + x.a === "a4567" || x.a === "a5567"; }); filterchecks.push( { type : "compare in", nrSubs : 2 } ); @@ -1482,11 +1482,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1503,27 +1503,27 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseHashIndexForIn : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["a"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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) { + makers.push(function (x) { return ["a0123", "a5564", "a7768", "a0678"].indexOf(x.a) !== -1; }); filterchecks.push( { type : "compare in", nrSubs : 2 } ); - queries.push(`FOR x in ${c.name()} + 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) { + makers.push(function (x) { return ["a0123", "a1234", "a4567", "a5567"].indexOf(x.a) !== -1; }); filterchecks.push( { type : "compare in", nrSubs : 2 } ); @@ -1552,11 +1552,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1573,54 +1573,54 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseHashIndexesForInOrIn : function () { - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["b"] } ); - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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) { + 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()} + 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) { + 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()} + 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) { + 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()} + 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) { + makers.push(function (x) { return ["b017", "b057"].indexOf(x.b) !== -1 || ["c023", "c056"].indexOf(x.c) !== -1; }); @@ -1650,11 +1650,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1671,54 +1671,54 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistIndexesForInOrIn : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["b"] } ); - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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) { + 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()} + 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) { + 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()} + 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) { + 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()} + 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) { + makers.push(function (x) { return ["b017", "b057"].indexOf(x.b) !== -1 || ["c023", "c056"].indexOf(x.c) !== -1; }); @@ -1748,11 +1748,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1769,50 +1769,50 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistRespHashIndexesForInOrEq : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["b"] } ); - c.ensureIndex( { type: "hash", sparse: false, unique: false, + c.ensureIndex( { type: "hash", sparse: false, unique: false, fields: ["c"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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) { + 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()} + 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) { + 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()} + 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) { + 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()} + 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) { + makers.push(function (x) { return ["c017", "c057"].indexOf(x.c) !== -1 || x.b === "b056"; }); @@ -1842,11 +1842,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } @@ -1863,30 +1863,30 @@ function optimizerIndexesMultiTestSuite () { //////////////////////////////////////////////////////////////////////////////// testUseSkiplistForOverlappingRanges : function () { - c.ensureIndex( { type: "skiplist", sparse: false, unique: false, + c.ensureIndex( { type: "skiplist", sparse: false, unique: false, fields: ["a"] } ); var queries = []; var makers = []; var filterchecks = []; - queries.push(`FOR x in ${c.name()} + 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) { + 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()} + queries.push(`FOR x in ${c.name()} FILTER (x.a >= "a0200" && x.a < "a0300") || - (x.a >= "a0123" && x.a < "a0207") + (x.a >= "a0123" && x.a < "a0207") SORT x.a RETURN x.a`); - makers.push(function (x) { + makers.push(function (x) { return x.a >= "a0200" && x.a < "a0300" || x.a >= "a0123" && x.a < "a0207"; }); @@ -1920,11 +1920,11 @@ function optimizerIndexesMultiTestSuite () { 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, + 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, + plan.nodes[2].expression.subNodes.length, "Number of subnodes in filter expression, " + query); } From 7f778b148a71952d58e9ea7d93ba9ed0c4a57144 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Thu, 15 Oct 2015 16:19:33 +0200 Subject: [PATCH 3/3] blind commit --- arangod/Aql/AstNode.cpp | 2 +- arangod/Aql/AstNode.h | 2 +- arangod/Aql/Condition.cpp | 447 +++++++++++++++-------------- arangod/Aql/Condition.h | 86 +++++- arangod/Aql/OptimizerRules.cpp | 29 +- lib/Basics/AttributeNameParser.cpp | 19 ++ lib/Basics/AttributeNameParser.h | 7 + 7 files changed, 359 insertions(+), 233 deletions(-) diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index 063525997c..3fc31d03d6 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -668,7 +668,7 @@ TRI_json_t* AstNode::computeJson () const { } //////////////////////////////////////////////////////////////////////////////// -/// @brief sort the members of a (list) node +/// @brief sort the members of an (array) node /// this will also set the VALUE_SORTED flag for the node //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/AstNode.h b/arangod/Aql/AstNode.h index 01e0e7e4c4..eb6ff66618 100644 --- a/arangod/Aql/AstNode.h +++ b/arangod/Aql/AstNode.h @@ -270,7 +270,7 @@ namespace triagens { TRI_json_t* computeJson () const; //////////////////////////////////////////////////////////////////////////////// -/// @brief sort the members of a (list) node +/// @brief sort the members of an (array) node /// this will also set the FLAG_SORTED flag for the node //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Condition.cpp b/arangod/Aql/Condition.cpp index cadf751859..69f0b38fdf 100644 --- a/arangod/Aql/Condition.cpp +++ b/arangod/Aql/Condition.cpp @@ -27,6 +27,8 @@ /// @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// +// TODO: sort IN values + #include "Condition.h" #include "Aql/Ast.h" #include "Aql/AstNode.h" @@ -38,8 +40,6 @@ #include "Basics/json.h" #include "Basics/JsonHelper.h" -#include - using namespace triagens::aql; using CompareResult = ConditionPartCompareResult; @@ -527,6 +527,9 @@ std::pair Condition::findIndexForAndNode (size_t position, } _root->changeMember(position, bestIndex->specializeCondition(node, reference)); +#if 0 + _isSorted = sortOrs(); +#endif usedIndexes.emplace_back(bestIndex); @@ -549,8 +552,6 @@ void Condition::normalize (ExecutionPlan* plan) { optimize(plan); - fixOrs(plan); - #ifdef TRI_ENABLE_MAINTAINER_MODE if (_root != nullptr) { // _root->dump(0); @@ -559,16 +560,176 @@ void Condition::normalize (ExecutionPlan* plan) { #endif } -void Condition::fixOrs (ExecutionPlan* plan) { - if (_root == nullptr) { - return; +//////////////////////////////////////////////////////////////////////////////// +/// @brief removes condition parts from another +//////////////////////////////////////////////////////////////////////////////// + +AstNode const* Condition::removeIndexCondition (Variable const* variable, + AstNode const* other) { + if (_root == nullptr || other == nullptr) { + return _root; + } + + TRI_ASSERT(_root != nullptr); + TRI_ASSERT(_root->type == NODE_TYPE_OPERATOR_NARY_OR); + + TRI_ASSERT(other != nullptr); + TRI_ASSERT(other->type == NODE_TYPE_OPERATOR_NARY_OR); + + if (other->numMembers() != 1 && _root->numMembers() != 1) { + return _root; + } + + auto andNode = _root->getMemberUnchecked(0); + TRI_ASSERT(andNode->type == NODE_TYPE_OPERATOR_NARY_AND); + size_t const n = andNode->numMembers(); + + std::unordered_set toRemove; + + for (size_t i = 0; i < n; ++i) { + auto operand = andNode->getMemberUnchecked(i); + + if (operand->isComparisonOperator()) { + auto lhs = operand->getMember(0); + auto rhs = operand->getMember(1); + + if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { + std::pair> result; + + if (lhs->isAttributeAccessForVariable(result) && + rhs->isConstant()) { + if (result.first != variable) { + // attribute access for different variable + continue; + } + + ConditionPart current(variable, result.second, operand, ATTRIBUTE_LEFT, nullptr); + + if (canRemove(current, other)) { + toRemove.emplace(i); + } + } + } + + if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS || + rhs->type == NODE_TYPE_EXPANSION) { + std::pair> result; + + if (rhs->isAttributeAccessForVariable(result) && + lhs->isConstant()) { + if (result.first != variable) { + // attribute access for different variable + continue; + } + + ConditionPart current(variable, result.second, operand, ATTRIBUTE_RIGHT, nullptr); + + if (canRemove(current, other)) { + toRemove.emplace(i); + } + } + } + } + } + + if (toRemove.empty()) { + return _root; + } + + // build a new AST condition + AstNode* newNode = nullptr; + + for (size_t i = 0; i < n; ++i) { + if (toRemove.find(i) == toRemove.end()) { + auto what = andNode->getMemberUnchecked(i); + + if (newNode == nullptr) { + // the only node so far + newNode = what; + } + else { + // AND-combine with existing node + newNode = _ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_AND, newNode, what); + } + } + } + + return newNode; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief remove (now) invalid variables from the condition +//////////////////////////////////////////////////////////////////////////////// + +bool Condition::removeInvalidVariables (std::unordered_set const& validVars) { + if (_root == nullptr) { + return false; + } + + TRI_ASSERT(_root != nullptr); + TRI_ASSERT(_root->type == NODE_TYPE_OPERATOR_NARY_OR); + + bool isEmpty = false; + + // handle sub nodes of top-level OR node + size_t const n = _root->numMembers(); + std::unordered_set varsUsed; + + for (size_t i = 0; i < n; ++i) { + auto andNode = _root->getMemberUnchecked(i); + TRI_ASSERT(andNode->type == NODE_TYPE_OPERATOR_NARY_AND); + + size_t nAnd = andNode->numMembers(); + for (size_t j = 0; j < nAnd; /* no hoisting */) { + // check which variables are used in each AND + varsUsed.clear(); + Ast::getReferencedVariables(andNode, varsUsed); + + bool invalid = false; + for (auto& it : varsUsed) { + if (validVars.find(it) == validVars.end()) { + // found an invalid variable here... + invalid = true; + break; + } + } + + if (invalid) { + andNode->removeMemberUnchecked(j); + // repeat with some member index + TRI_ASSERT(nAnd > 0); + --nAnd; + if (nAnd == 0) { + isEmpty = true; + } + } + else { + ++j; + } + } + } + + return isEmpty; +} + +// ----------------------------------------------------------------------------- +// --SECTION-- private methods +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief sort ORs for the same attribute so they are in ascending value +/// order. this will only work if the condition is for a single attribute +//////////////////////////////////////////////////////////////////////////////// + +bool Condition::sortOrs () { + if (_root == nullptr) { + return true; } -return; size_t const n = _root->numMembers(); if (n < 2) { - return; + return true; } std::vector parts; @@ -583,13 +744,13 @@ return; if (nAnd != 1) { // we can't handle this one - return; + return false; } auto operand = sub->getMemberUnchecked(0); if (! operand->isComparisonOperator()) { - return; + return false; } auto lhs = operand->getMember(0); @@ -617,7 +778,6 @@ return; } } - return; TRI_ASSERT(parts.size() == _root->numMembers()); // now sort all conditions by variable name, attribute name, attribute value @@ -637,63 +797,67 @@ return; } // compare attribute values next - res = CompareAstNodes(lhs.valueNode, rhs.valueNode, false); + auto ll = lhs.lowerBound(); + auto lr = rhs.lowerBound(); - if (res != 0) { - return res < 0; + if (ll == nullptr && lr != nullptr) { + // left lower bound is not set but right + return true; + } + else if (ll != nullptr && lr == nullptr) { + // left lower bound is set but not right + return false; + } + + if (ll != nullptr && lr != nullptr) { + // both lower bounds are set + res = CompareAstNodes(ll, lr, false); + + if (res != 0) { + return res < 0; + } + } + + if (lhs.isLowerInclusive() && ! rhs.isLowerInclusive()) { + return true; + } + if (rhs.isLowerInclusive() && ! lhs.isLowerInclusive()) { + return false; } // all things equal return false; }); + /* - struct CompValue { - AstNode const* lowerValue = nullptr; - AstNode const* upperValue = nullptr; - bool lowerIncluded = false; - bool upperIncluded = false; - bool empty = true; - - CompValue (AstNodeType opType, AstNode const* valueNode) { - if (opType == NODE_TYPE_OPERATOR_BINARY_LE || opType == NODE_TYPE_OPERATOR_BINARY_LT || opType == NODE_TYPE_OPERATOR_BINARY_EQ) { - upperValue = valueNode; - } - if (opType == NODE_TYPE_OPERATOR_BINARY_GE || opType == NODE_TYPE_OPERATOR_BINARY_GT || opType == NODE_TYPE_OPERATOR_BINARY_EQ) { - lowerValue = valueNode; - } - lowerIncluded = (opType == NODE_TYPE_OPERATOR_BINARY_GE || opType == NODE_TYPE_OPERATOR_BINARY_EQ); - upperIncluded = (opType == NODE_TYPE_OPERATOR_BINARY_LE || opType == NODE_TYPE_OPERATOR_BINARY_EQ); - empty = false; - } - }; + auto l = 0; + for (size_t r = 1; r < n; ++r) { + auto& l = parts[l].data; + auto& r = parts[r].data; - CompValue lastValue; -*/ - // now finally sort the members of the AND-node - for (size_t i = 1; i < parts.size(); ++i) { - // Results are -1, 0, 1, move to 0, 1, 2 for the lookup: - auto& other = parts[i - 1]; - auto& current = parts[i]; - - ConditionPartCompareResult res = ConditionPart::ResultsTable - [CompareAstNodes(current.valueNode, other.valueNode, false) + 1] - [current.whichCompareOperation()] - [other.whichCompareOperation()]; - - std::cout << "CURRENT: " << current.valueNode << ", OTHER: " << other.valueNode << ", RES: " << (int) res << "\n"; - if (res == CompareResult::IMPOSSIBLE) { - // means disjoint ranges here - std::cout << "DISJOINT\n"; - } - else if (res == CompareResult::OTHER_CONTAINED_IN_SELF) { - std::cout << "KEEPING OTHER\n"; - } - else if (res == CompareResult::DISJOINT) { - std::cout << "KEEPING OTHER\n"; + if (l.higher > r.higher || + (l.higher == r.higher && (l.inclusive || ! r.inclusive)) { + // r is contained in l => remove r (i.e. do nothing) + r.data = nullptr; } + else if (r.lower < l.higher || (r.lower == l.higher && (r.inclusive || l.inclusive))) { + // r extends l => fuse l.lower & r.higher -// _root->changeMember(i, static_cast(parts[i].data)); + r.data = nullptr; + newOrNode->getMember(newor + } + else { + // disjoint ranges. simply add the node + newOrNode->addMember(r); + } } + */ + + for (size_t i = 0; i < n; ++i) { + _root->changeMember(i, static_cast(parts[i].data)); + } + + return true; } //////////////////////////////////////////////////////////////////////////////// @@ -766,7 +930,6 @@ restartThisOrItem: andNode->changeMember(p++, it); stack.pop_back(); } - } // optimization is only necessary if an AND node has multiple members @@ -928,174 +1091,16 @@ restartThisOrItem: } // foreach sub-and-node fastForwardToNextOrItem: - if (retry) { - // number of root sub-nodes has probably changed. - // now recalculate the number and don't modify r! - n = _root->numMembers(); - } - else { + if (! retry) { // root nodes hasn't changed. go to next sub-node! ++r; } + // number of root sub-nodes has probably changed. + // now recalculate the number and don't modify r! + n = _root->numMembers(); } } -//////////////////////////////////////////////////////////////////////////////// -/// @brief removes condition parts from another -//////////////////////////////////////////////////////////////////////////////// - -AstNode const* Condition::removeIndexCondition (Variable const* variable, - AstNode const* other) { - if (_root == nullptr || other == nullptr) { - return _root; - } - - TRI_ASSERT(_root != nullptr); - TRI_ASSERT(_root->type == NODE_TYPE_OPERATOR_NARY_OR); - - TRI_ASSERT(other != nullptr); - TRI_ASSERT(other->type == NODE_TYPE_OPERATOR_NARY_OR); - - if (other->numMembers() != 1 && _root->numMembers() != 1) { - return _root; - } - - auto andNode = _root->getMemberUnchecked(0); - TRI_ASSERT(andNode->type == NODE_TYPE_OPERATOR_NARY_AND); - size_t const n = andNode->numMembers(); - - std::unordered_set toRemove; - - for (size_t i = 0; i < n; ++i) { - auto operand = andNode->getMemberUnchecked(i); - - if (operand->isComparisonOperator()) { - auto lhs = operand->getMember(0); - auto rhs = operand->getMember(1); - - if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { - std::pair> result; - - if (lhs->isAttributeAccessForVariable(result) && - rhs->isConstant()) { - if (result.first != variable) { - // attribute access for different variable - continue; - } - - ConditionPart current(variable, result.second, operand, ATTRIBUTE_LEFT, nullptr); - - if (canRemove(current, other)) { - toRemove.emplace(i); - } - } - } - - if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS || - rhs->type == NODE_TYPE_EXPANSION) { - std::pair> result; - - if (rhs->isAttributeAccessForVariable(result) && - lhs->isConstant()) { - if (result.first != variable) { - // attribute access for different variable - continue; - } - - ConditionPart current(variable, result.second, operand, ATTRIBUTE_RIGHT, nullptr); - - if (canRemove(current, other)) { - toRemove.emplace(i); - } - } - } - } - } - - if (toRemove.empty()) { - return _root; - } - - // build a new AST condition - AstNode* newNode = nullptr; - - for (size_t i = 0; i < n; ++i) { - if (toRemove.find(i) == toRemove.end()) { - auto what = andNode->getMemberUnchecked(i); - - if (newNode == nullptr) { - // the only node so far - newNode = what; - } - else { - // AND-combine with existing node - newNode = _ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_AND, newNode, what); - } - } - } - - return newNode; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief remove (now) invalid variables from the condition -//////////////////////////////////////////////////////////////////////////////// - -bool Condition::removeInvalidVariables (std::unordered_set const& validVars) { - if (_root == nullptr) { - return false; - } - - TRI_ASSERT(_root != nullptr); - TRI_ASSERT(_root->type == NODE_TYPE_OPERATOR_NARY_OR); - - bool isEmpty = false; - - // handle sub nodes of top-level OR node - size_t const n = _root->numMembers(); - std::unordered_set varsUsed; - - for (size_t i = 0; i < n; ++i) { - auto andNode = _root->getMemberUnchecked(i); - TRI_ASSERT(andNode->type == NODE_TYPE_OPERATOR_NARY_AND); - - size_t nAnd = andNode->numMembers(); - for (size_t j = 0; j < nAnd; /* no hoisting */) { - // check which variables are used in each AND - varsUsed.clear(); - Ast::getReferencedVariables(andNode, varsUsed); - - bool invalid = false; - for (auto& it : varsUsed) { - if (validVars.find(it) == validVars.end()) { - // found an invalid variable here... - invalid = true; - break; - } - } - - if (invalid) { - andNode->removeMemberUnchecked(j); - // repeat with some member index - TRI_ASSERT(nAnd > 0); - --nAnd; - if (nAnd == 0) { - isEmpty = true; - } - } - else { - ++j; - } - } - } - - return isEmpty; -} - -// ----------------------------------------------------------------------------- -// --SECTION-- private methods -// ----------------------------------------------------------------------------- - //////////////////////////////////////////////////////////////////////////////// /// @brief registers an attribute access for a particular (collection) variable //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Condition.h b/arangod/Aql/Condition.h index fc847632cb..db395961de 100644 --- a/arangod/Aql/Condition.h +++ b/arangod/Aql/Condition.h @@ -109,6 +109,56 @@ namespace triagens { } } +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns the lower bound +//////////////////////////////////////////////////////////////////////////////// + + inline AstNode const* lowerBound () const { + if (operatorType == NODE_TYPE_OPERATOR_BINARY_GT || + operatorType == NODE_TYPE_OPERATOR_BINARY_GE || + operatorType == NODE_TYPE_OPERATOR_BINARY_EQ) { + return valueNode; + } + return nullptr; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns if the lower bound is inclusive +//////////////////////////////////////////////////////////////////////////////// + + inline bool isLowerInclusive () const { + if (operatorType == NODE_TYPE_OPERATOR_BINARY_GE || + operatorType == NODE_TYPE_OPERATOR_BINARY_EQ) { + return true; + } + return false; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns the upper bound +//////////////////////////////////////////////////////////////////////////////// + + inline AstNode const* upperBound () const { + if (operatorType == NODE_TYPE_OPERATOR_BINARY_LT || + operatorType == NODE_TYPE_OPERATOR_BINARY_LE || + operatorType == NODE_TYPE_OPERATOR_BINARY_EQ) { + return valueNode; + } + return nullptr; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns if the upper bound is inclusive +//////////////////////////////////////////////////////////////////////////////// + + inline bool isUpperInclusive () const { + if (operatorType == NODE_TYPE_OPERATOR_BINARY_LE || + operatorType == NODE_TYPE_OPERATOR_BINARY_EQ) { + return true; + } + return false; + } + //////////////////////////////////////////////////////////////////////////////// /// @brief true if the condition is completely covered by the other condition //////////////////////////////////////////////////////////////////////////////// @@ -188,6 +238,15 @@ namespace triagens { return (_root->numMembers() == 0); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the condition results will be sorted (this is only +/// relevant if the condition consists of multiple ORs) +//////////////////////////////////////////////////////////////////////////////// + + inline bool isSorted () const { + return _isSorted; + } + //////////////////////////////////////////////////////////////////////////////// /// @brief return the condition as a Json object //////////////////////////////////////////////////////////////////////////////// @@ -226,14 +285,6 @@ namespace triagens { void normalize (ExecutionPlan*); - void fixOrs (ExecutionPlan*); - -//////////////////////////////////////////////////////////////////////////////// -/// @brief optimize the condition expression tree -//////////////////////////////////////////////////////////////////////////////// - - void optimize (ExecutionPlan*); - //////////////////////////////////////////////////////////////////////////////// /// @brief removes condition parts from another //////////////////////////////////////////////////////////////////////////////// @@ -263,6 +314,19 @@ namespace triagens { private: +//////////////////////////////////////////////////////////////////////////////// +/// @brief sort ORs for the same attribute so they are in ascending value +/// order. this will only work if the condition is for a single attribute +//////////////////////////////////////////////////////////////////////////////// + + bool sortOrs (); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimize the condition expression tree +//////////////////////////////////////////////////////////////////////////////// + + void optimize (ExecutionPlan*); + //////////////////////////////////////////////////////////////////////////////// /// @brief registers an attribute access for a particular (collection) variable //////////////////////////////////////////////////////////////////////////////// @@ -365,6 +429,12 @@ namespace triagens { bool _isNormalized; +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the condition will return a sorted result +//////////////////////////////////////////////////////////////////////////////// + + bool _isSorted; + }; } } diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index d7f8ad63ba..d018b96527 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -1871,16 +1871,41 @@ struct SortToIndexNode final : public WalkerWorker { } auto const& indexes = indexNode->getIndexes(); + if (indexes.size() != 1) { - // can only use this index node if it uses exactly one index - return true; + // can only use this index node if it uses exactly one index or multiple indexes on exactly the same attributes + auto cond = indexNode->condition(); + + if (! cond->isSorted()) { + // index conditions do not guarantee sortedness + return true; + } + + std::vector> seen; + + for (auto& index : indexes) { + if (index->sparse) { + // cannot use a sparse index for sorting + return true; + } + + if (! seen.empty() && triagens::basics::AttributeName::isIdentical(index->fields, seen)) { + // different attributes + return true; + } + } + + // all indexes use the same attributes and index conditions guarantee sorted output } + // if we get here, we either have one index or multiple indexes on the same attributes auto index = indexes[0]; + if (! index->isSorted()) { // can only use a sorted index return true; } + if (index->sparse) { // cannot use a sparse index for sorting return true; diff --git a/lib/Basics/AttributeNameParser.cpp b/lib/Basics/AttributeNameParser.cpp index 6751d42e01..d0e555eadb 100644 --- a/lib/Basics/AttributeNameParser.cpp +++ b/lib/Basics/AttributeNameParser.cpp @@ -52,6 +52,25 @@ bool triagens::basics::AttributeName::isIdentical (std::vector co return true; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief compare two attribute name vectors +//////////////////////////////////////////////////////////////////////////////// + +bool triagens::basics::AttributeName::isIdentical (std::vector> const& lhs, + std::vector> const& rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (size_t i = 0; i < lhs.size(); ++i) { + if (! isIdentical(lhs[i], rhs[i])) { + return false; + } + } + + return true; +} + void triagens::basics::TRI_ParseAttributeString (std::string const& input, std::vector& result) { size_t parsedUntil = 0; diff --git a/lib/Basics/AttributeNameParser.h b/lib/Basics/AttributeNameParser.h index 4d5468f7d1..f9551eb74e 100644 --- a/lib/Basics/AttributeNameParser.h +++ b/lib/Basics/AttributeNameParser.h @@ -78,6 +78,13 @@ namespace triagens { static bool isIdentical (std::vector const&, std::vector const&); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief compare two attribute name vectors +//////////////////////////////////////////////////////////////////////////////// + + static bool isIdentical (std::vector> const&, + std::vector> const&); }; // -----------------------------------------------------------------------------