From 1c935b6ab20a38b8ca95400f6d20964ece3cf94b Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Thu, 22 Jan 2015 14:16:58 +0100 Subject: [PATCH] added tests for filter order, not optimization etc. --- arangod/Aql/OptimizerRules.cpp | 5 +- arangod/Aql/RangeInfo.cpp | 31 ++ arangod/Aql/RangeInfo.h | 7 + js/server/tests/aql-optimizer-indexes.js | 348 ++++++++++++++++++++++- 4 files changed, 388 insertions(+), 3 deletions(-) diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index 3a0c5b9d60..9426694eeb 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -1369,7 +1369,7 @@ static RangeInfoMapVec* BuildRangeInfo (ExecutionPlan* plan, } // distribute AND into OR - return andCombineRangeInfoMapVecs(lhs, rhs); + return andCombineRangeInfoMapVecsIgnoreEmpty(lhs, rhs); } if (node->type == NODE_TYPE_OPERATOR_BINARY_IN) { @@ -1512,6 +1512,7 @@ class FilterToEnumCollFinder : public WalkerWorker { std::string attr; Variable const* enumCollVar = nullptr; auto expression = node->expression()->node(); + // there is an implicit AND between FILTER statements if (_rangeInfoMapVec == nullptr) { // don't yet have anything to AND-combine @@ -1519,7 +1520,7 @@ class FilterToEnumCollFinder : public WalkerWorker { } else { // AND-combine with previous ranges - _rangeInfoMapVec = andCombineRangeInfoMapVecs( + _rangeInfoMapVec = andCombineRangeInfoMapVecsIgnoreEmpty( _rangeInfoMapVec, BuildRangeInfo(_plan, expression, enumCollVar, attr, _mustNotUseRanges) ); diff --git a/arangod/Aql/RangeInfo.cpp b/arangod/Aql/RangeInfo.cpp index 267183d230..13fef328b6 100644 --- a/arangod/Aql/RangeInfo.cpp +++ b/arangod/Aql/RangeInfo.cpp @@ -669,6 +669,37 @@ RangeInfoMapVec* triagens::aql::andCombineRangeInfoMapVecs (RangeInfoMapVec* lhs return rimv; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief andCombineRangeInfoMapVecs: same as before, but will return the +/// mapvec even if one side is a nullptr +//////////////////////////////////////////////////////////////////////////////// + +RangeInfoMapVec* triagens::aql::andCombineRangeInfoMapVecsIgnoreEmpty (RangeInfoMapVec* lhs, + RangeInfoMapVec* rhs) { + if (lhs == nullptr && rhs == nullptr) { + return nullptr; + } + + if ((lhs == nullptr || lhs->empty()) && rhs != nullptr) { + if (lhs != nullptr) { + delete lhs; + } + return rhs; + } + + if (lhs != nullptr && (rhs == nullptr || rhs->empty())) { + if (rhs != nullptr) { + delete rhs; + } + return lhs; + } + + TRI_ASSERT(lhs != nullptr); + TRI_ASSERT(rhs != nullptr); + + return andCombineRangeInfoMapVecs(lhs, rhs); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief comparison of range infos //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/RangeInfo.h b/arangod/Aql/RangeInfo.h index ef752b3972..b92dc74113 100644 --- a/arangod/Aql/RangeInfo.h +++ b/arangod/Aql/RangeInfo.h @@ -921,6 +921,13 @@ namespace triagens { RangeInfoMapVec* andCombineRangeInfoMapVecs (RangeInfoMapVec*, RangeInfoMapVec*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief andCombineRangeInfoMapVecs: same as before, but will return the +/// mapvec even if one side is a nullptr +//////////////////////////////////////////////////////////////////////////////// + + RangeInfoMapVec* andCombineRangeInfoMapVecsIgnoreEmpty (RangeInfoMapVec*, RangeInfoMapVec*); + //////////////////////////////////////////////////////////////////////////////// /// @brief IndexOrCondition, type for vector of vector of RangeInfo. The meaning /// is: the outer vector means an implicit "OR" between the entries. Each diff --git a/js/server/tests/aql-optimizer-indexes.js b/js/server/tests/aql-optimizer-indexes.js index 3fe5880d14..7b17450d31 100644 --- a/js/server/tests/aql-optimizer-indexes.js +++ b/js/server/tests/aql-optimizer-indexes.js @@ -1009,6 +1009,78 @@ function optimizerIndexesTestSuite () { assertEqual(0, results.stats.scannedFull); assertTrue(results.stats.scannedIndex > 0); }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexOrNoIndexBecauseOfDifferentAttributes : function () { + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: 1 } IN " + c.name()); + + var queries = [ + "FOR i IN " + c.name() + " FILTER i.value == 1 || i.value2 == 2 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 || i.value == 1 RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value == 1 || i.value == 2) || i.value2 == 2 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 || (i.value == 1 || i.value == 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value == 1 || (i.value == 2 || i.value2 == 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value2 == 2 || i.value == 1) || i.value == 2 RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value == 1 && i.value2 == 1) || (i.value == 2 || i.value3 != 1) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value == 1) || (i.value3 != 1 || i.value == 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value == 1 && i.value2 == 1) || (i.value == 2 || i.value3 == 0) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value == 1) || (i.value3 == 0 || i.value == 2) RETURN i.value" + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query); + assertEqual(2, results.json.length); + assertTrue(results.stats.scannedFull > 0); + assertEqual(0, results.stats.scannedIndex); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexOrNoIndexBecauseOfOperator : function () { + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: 1 } IN " + c.name()); + + var queries = [ + "FOR i IN " + c.name() + " FILTER i.value == 1 || i.value2 != i.value RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value2 != i.value || i.value == 1 RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value != 1 || i.value != 2) && i.value == 3 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value == 3 && (i.value != 1 || i.value != 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value2 != 1 || i.value2 != 2) && i.value == 3 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value == 3 && (i.value2 != 1 || i.value2 != 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value != 1 && PASSTHRU(i.value2) == 2 RETURN i.value", + "FOR i IN " + c.name() + " FILTER PASSTHRU(i.value2) == 2 && i.value != 1 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value2 != 2 && PASSTHRU(i.value) == 1 RETURN i.value", + "FOR i IN " + c.name() + " FILTER PASSTHRU(i.value) == 1 && i.value2 != 2 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value == 1 || i.value3 != 1 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value3 != 1 || i.value == 1 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value3 != 1 || i.value2 == 2 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 || i.value3 != 1 RETURN i.value" + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query); + assertEqual(1, results.json.length, query); + assertTrue(results.stats.scannedFull > 0); + assertEqual(0, results.stats.scannedIndex); + }); + }, //////////////////////////////////////////////////////////////////////////////// /// @brief test index usage @@ -1124,7 +1196,9 @@ function optimizerIndexesTestSuite () { "LET a = PASSTHRU(0) FOR i IN " + c.name() + " FILTER i.value == 1 && a + 1 == 1 RETURN i.value", "LET a = PASSTHRU(0) FOR i IN " + c.name() + " FILTER a + 1 == 1 && i.value == 1 RETURN i.value", "LET a = PASSTHRU(0) FOR i IN " + c.name() + " FILTER i.value == 1 && a IN [ 0, 1 ] RETURN i.value", - "LET a = PASSTHRU(0) FOR i IN " + c.name() + " FILTER a IN [ 0, 1 ] && i.value == 1 RETURN i.value" + "LET a = PASSTHRU(0) FOR i IN " + c.name() + " FILTER a IN [ 0, 1 ] && i.value == 1 RETURN i.value", + "FOR i IN " + c.name() + " FILTER i.value == 1 && NOT (i.value != 1) RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 1 || NOT NOT (i.value == 1) RETURN i" ]; queries.forEach(function(query) { @@ -1140,6 +1214,35 @@ function optimizerIndexesTestSuite () { assertEqual(0, results.stats.scannedFull); }); }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexAndDespiteOr : function () { + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: 1 } IN " + c.name()); + + var queries = [ + "FOR i IN " + c.name() + " FILTER (i.value == 1 && i.value2 == 1) || (i.value == 2 && i.value2 == 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value == 1) || (i.value2 == 2 && i.value == 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value == 1) || (i.value3 != 2 && i.value == 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value == 1 && i.value2 == 1) || (i.value == 2 && i.value3 != 2) RETURN i.value", + "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value == 1) || (i.value3 == 1 && i.value == 2) RETURN i.value" + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query); + assertEqual(2, results.json.length, query); + assertTrue(results.stats.scannedIndex > 0); + assertEqual(0, results.stats.scannedFull); + }); + }, //////////////////////////////////////////////////////////////////////////////// /// @brief test index usage @@ -1304,6 +1407,249 @@ function optimizerIndexesTestSuite () { }); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexAndSkiplistIndexedNonIndexed : function () { + // value2 is not indexed + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); + + var queries = [ + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 && i.value == 2 RETURN i", + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query); + assertEqual(1, results.json.length); + assertEqual(2, results.json[0].value); + assertTrue(results.stats.scannedIndex > 0); + assertEqual(0, results.stats.scannedFull); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexAndHashIndexedNonIndexed : function () { + // drop the skiplist index + c.dropIndex(c.getIndexes()[1]); + c.ensureHashIndex("value"); + // value2 is not indexed + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); + + var queries = [ + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 && i.value == 2 RETURN i" + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query); + assertEqual(1, results.json.length); + assertEqual(2, results.json[0].value); + assertTrue(results.stats.scannedIndex > 0); + assertEqual(0, results.stats.scannedFull); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexAndAlternativeHashAndSkiplistIndexedNonIndexed : function () { + // create a competitor index + c.ensureHashIndex("value"); + + // value2 is not indexed + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); + + var queries = [ + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 && i.value == 2 RETURN i" + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query); + assertEqual(1, results.json.length); + assertEqual(2, results.json[0].value); + assertTrue(results.stats.scannedIndex > 0); + assertEqual(0, results.stats.scannedFull); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexAndSkiplistPartialIndexedNonIndexed : function () { + // drop the skiplist index + c.dropIndex(c.getIndexes()[1]); + + // create an alternative index + c.ensureSkiplist("value", "value3"); + + // value2 is not indexed + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); + + var queries = [ + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 && i.value == 2 RETURN i" + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query); + assertEqual(1, results.json.length); + assertEqual(2, results.json[0].value); + assertTrue(results.stats.scannedIndex > 0); + assertEqual(0, results.stats.scannedFull); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexAndAlternativeSkiplistPartialIndexedNonIndexed : function () { + // create a competitor index + c.ensureSkiplist("value", "value3"); + + // value2 is not indexed + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); + + var queries = [ + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 FILTER i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value == 2 && i.value2 != 1 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 FILTER i.value == 2 RETURN i", + "FOR i IN " + c.name() + " FILTER i.value2 != 1 && i.value == 2 RETURN i" + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query); + assertEqual(1, results.json.length); + assertEqual(2, results.json[0].value); + assertTrue(results.stats.scannedIndex > 0); + assertEqual(0, results.stats.scannedFull); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexNotNoIndex : function () { + c.ensureSkiplist("value2"); + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); + + var queries = [ + [ "FOR i IN " + c.name() + " FILTER NOT (i.value == 1 || i.value == 2) RETURN i", 1998 ], + [ "FOR i IN " + c.name() + " FILTER NOT (i.value == 1 && i.value == 2) RETURN i", 2000 ], + [ "FOR i IN " + c.name() + " FILTER i.value == 1 || NOT (i.value == -1) RETURN i", 2000 ] + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query[0]).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query[0]); + assertEqual(query[1], results.json.length); + assertTrue(results.stats.scannedFull > 0); + assertEqual(0, results.stats.scannedIndex); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test index usage +//////////////////////////////////////////////////////////////////////////////// + + testIndexNotUseIndex : function () { + c.ensureSkiplist("value2"); + AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); + + var queries = [ + [ "FOR i IN " + c.name() + " FILTER NOT (i.value == 2) && i.value == 1 RETURN i", 1 ], + [ "FOR i IN " + c.name() + " FILTER i.value == 1 && NOT (i.value == 2) RETURN i", 1 ], + [ "FOR i IN " + c.name() + " FILTER i.value == 1 && NOT (i.value != 1) RETURN i", 1 ], + [ "FOR i IN " + c.name() + " FILTER NOT (i.value != 1) && i.value == 1 RETURN i", 1 ], + [ "FOR i IN " + c.name() + " FILTER i.value == 1 || NOT (i.value != -1) RETURN i", 1 ] + ]; + + queries.forEach(function(query) { + var plan = AQL_EXPLAIN(query[0]).plan; + var nodeTypes = plan.nodes.map(function(node) { + return node.type; + }); + + assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); + var results = AQL_EXECUTE(query[0]); + assertEqual(query[1], results.json.length); + assertTrue(results.stats.scannedIndex > 0); + assertEqual(0, results.stats.scannedFull); + }); + } + }; }