1
0
Fork 0

remove sort in more cases

This commit is contained in:
Jan Steemann 2016-02-10 11:23:11 +01:00
parent 3b12dc0c32
commit 5ba2432d78
11 changed files with 319 additions and 128 deletions

View File

@ -415,7 +415,13 @@ std::pair<bool, bool> Condition::findIndexes(
usedIndexes.emplace_back(sortIndex);
}
return std::make_pair(false, true);
TRI_ASSERT(usedIndexes.size() == 1);
if (usedIndexes.back()->sparse) {
// cannot use a sparse index for sorting alone
usedIndexes.clear();
}
return std::make_pair(false, !usedIndexes.empty());
}
canUseForFilter &= canUseIndex.first;
@ -450,6 +456,55 @@ bool Condition::indexSupportsSort(Index const* idx, Variable const* reference,
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the attributes for a sub-condition that are const
/// (i.e. compared with equality)
////////////////////////////////////////////////////////////////////////////////
std::vector<std::vector<arangodb::basics::AttributeName>> Condition::getConstAttributes (Variable const* reference,
bool includeNull) {
std::vector<std::vector<arangodb::basics::AttributeName>> result;
if (_root == nullptr) {
return result;
}
size_t n = _root->numMembers();
if (n != 1) {
return result;
}
AstNode const* node = _root->getMember(0);
n = node->numMembers();
for (size_t i = 0; i < n; ++i) {
auto member = node->getMember(i);
if (member->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
std::pair<Variable const*, std::vector<arangodb::basics::AttributeName>> parts;
auto lhs = member->getMember(0);
auto rhs = member->getMember(1);
if (lhs->isAttributeAccessForVariable(parts) &&
parts.first == reference) {
if (includeNull || (rhs->isConstant() && !rhs->isNullValue())) {
result.emplace_back(std::move(parts.second));
}
}
else if (rhs->isAttributeAccessForVariable(parts) &&
parts.first == reference) {
if (includeNull || (lhs->isConstant() && !lhs->isNullValue())) {
result.emplace_back(std::move(parts.second));
}
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief finds the best index that can match this single node
@ -517,7 +572,7 @@ std::pair<bool, bool> Condition::findIndexForAndNode(
// now check if the index fields are the same as the sort condition fields
// e.g. FILTER c.value1 == 1 && c.value2 == 42 SORT c.value1, c.value2
size_t coveredFields =
sortCondition->coveredAttributes(reference, idx->fields);
sortCondition->coveredAttributes(reference, idx->fields);
if (coveredFields == sortCondition->numAttributes() &&
(idx->isSorted() ||

View File

@ -294,6 +294,13 @@ class Condition {
std::vector<Index const*>&,
SortCondition const*);
//////////////////////////////////////////////////////////////////////////////
/// @brief get the attributes for a sub-condition that are const
/// (i.e. compared with equality)
//////////////////////////////////////////////////////////////////////////////
std::vector<std::vector<arangodb::basics::AttributeName>> getConstAttributes (Variable const*, bool);
private:
//////////////////////////////////////////////////////////////////////////////
/// @brief sort ORs for the same attribute so they are in ascending value

View File

@ -21,7 +21,7 @@
/// @author Michael Hackstein
////////////////////////////////////////////////////////////////////////////////
#include "Aql/ConditionFinder.h"
#include "ConditionFinder.h"
#include "Aql/ExecutionPlan.h"
#include "Aql/IndexNode.h"
#include "Aql/SortCondition.h"
@ -153,7 +153,7 @@ bool ConditionFinder::before(ExecutionNode* en) {
std::unique_ptr<SortCondition> sortCondition;
if (!en->isInInnerLoop()) {
// we cannot optimize away a sort if we're in an inner loop ourselves
sortCondition.reset(new SortCondition(_sorts, _variableDefinitions));
sortCondition.reset(new SortCondition(_sorts, condition->getConstAttributes(node->outVariable(), false), _variableDefinitions));
} else {
sortCondition.reset(new SortCondition);
}

View File

@ -38,6 +38,7 @@
#include "Aql/TraversalConditionFinder.h"
#include "Aql/Variable.h"
#include "Aql/types.h"
#include "Basics/AttributeNameParser.h"
#include "Basics/json-utilities.h"
using namespace arangodb::aql;
@ -1705,7 +1706,7 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
return true;
}
SortCondition sortCondition(_sorts, _variableDefinitions);
SortCondition sortCondition(_sorts, std::vector<std::vector<arangodb::basics::AttributeName>>(), _variableDefinitions);
if (!sortCondition.isEmpty() && sortCondition.isOnlyAttributeAccess() &&
sortCondition.isUnidirectional()) {
@ -1725,8 +1726,8 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
continue;
}
auto numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
size_t const numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
if (numCovered == 0) {
continue;
@ -1734,14 +1735,15 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
double estimatedCost = 0.0;
if (!index->supportsSortCondition(
&sortCondition, outVariable,
&sortCondition,
outVariable,
enumerateCollectionNode->collection()->count(),
estimatedCost)) {
// should never happen
TRI_ASSERT(false);
continue;
}
if (bestIndex == nullptr || estimatedCost < bestCost) {
bestIndex = index;
bestCost = estimatedCost;
@ -1790,6 +1792,10 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
auto const& indexes = indexNode->getIndexes();
auto cond = indexNode->condition();
TRI_ASSERT(cond != nullptr);
Variable const* outVariable = indexNode->outVariable();
TRI_ASSERT(outVariable != nullptr);
if (indexes.size() != 1) {
// can only use this index node if it uses exactly one index or multiple
@ -1824,7 +1830,7 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
auto index = indexes[0];
bool handled = false;
SortCondition sortCondition(_sorts, _variableDefinitions);
SortCondition sortCondition(_sorts, cond->getConstAttributes(outVariable, !index->sparse), _variableDefinitions);
bool const isOnlyAttributeAccess =
(!sortCondition.isEmpty() && sortCondition.isOnlyAttributeAccess());
@ -1835,11 +1841,10 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
// we have found a sort condition, which is unidirectional and in the same
// order as the IndexNode...
// now check if the sort attributes match the ones of the index
Variable const* outVariable = indexNode->outVariable();
auto numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
size_t const numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
if (numCovered == sortCondition.numAttributes()) {
if (numCovered >= sortCondition.numAttributes()) {
// sort condition is fully covered by index... now we can remove the
// sort node from the plan
_plan->unlinkNode(_plan->getNodeById(_sortNode->id()));
@ -1862,11 +1867,11 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
// now check if the index fields are the same as the sort condition
// fields
// e.g. FILTER c.value1 == 1 && c.value2 == 42 SORT c.value1, c.value2
Variable const* outVariable = indexNode->outVariable();
size_t coveredFields =
sortCondition.coveredAttributes(outVariable, index->fields);
size_t const numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
if (coveredFields == sortCondition.numAttributes() &&
if (numCovered == sortCondition.numAttributes() &&
sortCondition.isUnidirectional() &&
(index->isSorted() ||
index->fields.size() == sortCondition.numAttributes())) {
// no need to sort

View File

@ -21,18 +21,34 @@
/// @author Jan Steemann
////////////////////////////////////////////////////////////////////////////////
#include "Aql/SortCondition.h"
#include "SortCondition.h"
#include "Aql/AstNode.h"
#include "Basics/Logger.h"
using namespace arangodb::aql;
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not an attribute is contained in a vector
////////////////////////////////////////////////////////////////////////////////
static bool IsContained (std::vector<std::vector<arangodb::basics::AttributeName>> const& attributes,
std::vector<arangodb::basics::AttributeName> const& attribute) {
for (auto const& it : attributes) {
if (arangodb::basics::AttributeName::isIdentical(it, attribute, false)) {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an empty condition
////////////////////////////////////////////////////////////////////////////////
SortCondition::SortCondition()
: _expressions(),
_fields(),
: _fields(),
_constAttributes(),
_unidirectional(false),
_onlyAttributeAccess(false),
_ascending(true) {}
@ -41,73 +57,22 @@ SortCondition::SortCondition()
/// @brief create the sort condition
////////////////////////////////////////////////////////////////////////////////
SortCondition::SortCondition(
std::vector<std::pair<AstNode const*, bool>> const& expressions)
: _expressions(expressions),
_fields(),
_unidirectional(true),
_onlyAttributeAccess(true),
_ascending(true) {
size_t const n = _expressions.size();
for (size_t i = 0; i < n; ++i) {
if (_unidirectional && i > 0 &&
_expressions[i].second != _expressions[i - 1].second) {
_unidirectional = false;
}
bool handled = false;
auto node = _expressions[i].first;
if (node != nullptr && node->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
std::vector<arangodb::basics::AttributeName> fieldNames;
while (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
fieldNames.emplace_back(
arangodb::basics::AttributeName(node->getStringValue()));
node = node->getMember(0);
}
if (node->type == NODE_TYPE_REFERENCE) {
handled = true;
_fields.emplace_back(std::make_pair(
static_cast<Variable const*>(node->getData()), fieldNames));
}
}
if (!handled) {
_fields.emplace_back(
std::pair<Variable const*,
std::vector<arangodb::basics::AttributeName>>());
_onlyAttributeAccess = false;
}
}
if (n == 0) {
_onlyAttributeAccess = false;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create the sort condition
////////////////////////////////////////////////////////////////////////////////
SortCondition::SortCondition(
std::vector<std::pair<VariableId, bool>> const& sorts,
std::vector<std::vector<arangodb::basics::AttributeName>> const& constAttributes,
std::unordered_map<VariableId, AstNode const*> const& variableDefinitions)
: _expressions(),
: _fields(),
_constAttributes(constAttributes),
_unidirectional(true),
_onlyAttributeAccess(true),
_ascending(true) {
bool foundDirection = false;
size_t const n = sorts.size();
for (size_t i = 0; i < n; ++i) {
if (_unidirectional && i > 0 && sorts[i].second != sorts[i - 1].second) {
_unidirectional = false;
}
if (i == 0) {
_ascending = sorts[i].second;
}
bool isConst = false; // const attribute?
bool handled = false;
auto variableId = sorts[i].first;
@ -129,10 +94,30 @@ SortCondition::SortCondition(
_fields.emplace_back(std::make_pair(
static_cast<Variable const*>(node->getData()), fieldNames));
for (auto const& it2 : constAttributes) {
if (it2 == fieldNames) {
// const attribute
isConst = true;
break;
}
}
}
}
}
if (!isConst) {
// const attributes can be ignored for sorting
if (!foundDirection) {
// first attribute that we found
foundDirection = true;
_ascending = sorts[i].second;
}
else if (_unidirectional && sorts[i].second != _ascending) {
_unidirectional = false;
}
}
if (!handled) {
_fields.emplace_back(
std::pair<Variable const*,
@ -161,40 +146,54 @@ size_t SortCondition::coveredAttributes(
Variable const* reference,
std::vector<std::vector<arangodb::basics::AttributeName>> const&
indexAttributes) const {
size_t numAttributes = 0;
size_t numCovered = 0;
size_t fieldsPosition = 0;
// iterate over all fields of the index definition
size_t const n = indexAttributes.size();
for (size_t i = 0; i < indexAttributes.size(); ++i) {
if (i >= _fields.size()) {
for (size_t i = 0; i < n; /* no hoisting */) {
if (fieldsPosition >= _fields.size()) {
// done
break;
}
auto const& field = _fields[fieldsPosition];
if (reference != _fields[i].first) {
break;
// ...and check if the field is present in the index definition too
if (reference == field.first &&
arangodb::basics::AttributeName::isIdentical(field.second, indexAttributes[i], false)) {
// field match
++fieldsPosition;
++numCovered;
++i; // next index field
continue;
}
auto const& fieldNames = _fields[i].second;
if (fieldNames.size() != indexAttributes[i].size()) {
// different attribute path
break;
// no match
bool isConstant = false;
if (IsContained(_constAttributes, indexAttributes[i])) {
// no field match, but a constant attribute
isConstant = true;
++i; // next index field
}
bool found = true;
for (size_t j = 0; j < indexAttributes[i].size(); ++j) {
if (indexAttributes[i][j].shouldExpand ||
fieldNames[j] != indexAttributes[i][j]) {
// expanded attribute or different attribute
found = false;
break;
if (!isConstant) {
if (IsContained(indexAttributes, field.second) &&
IsContained(_constAttributes, field.second)) {
// no field match, but a constant attribute
isConstant = true;
++fieldsPosition;
++numCovered;
}
}
if (!found) {
if (!isConstant) {
break;
}
// same attribute
++numAttributes;
}
return numAttributes;
TRI_ASSERT(numCovered <= _fields.size());
return numCovered;
}

View File

@ -47,13 +47,8 @@ class SortCondition {
/// @brief create the sort condition
//////////////////////////////////////////////////////////////////////////////
explicit SortCondition(std::vector<std::pair<AstNode const*, bool>> const&);
//////////////////////////////////////////////////////////////////////////////
/// @brief create the sort condition
//////////////////////////////////////////////////////////////////////////////
SortCondition(std::vector<std::pair<VariableId, bool>> const&,
std::vector<std::vector<arangodb::basics::AttributeName>> const&,
std::unordered_map<VariableId, AstNode const*> const&);
//////////////////////////////////////////////////////////////////////////////
@ -115,11 +110,6 @@ class SortCondition {
std::vector<std::vector<arangodb::basics::AttributeName>> const&) const;
private:
//////////////////////////////////////////////////////////////////////////////
/// @brief sort expressions
//////////////////////////////////////////////////////////////////////////////
std::vector<std::pair<AstNode const*, bool>> _expressions;
//////////////////////////////////////////////////////////////////////////////
/// @brief fields used in the sort conditions
@ -127,6 +117,12 @@ class SortCondition {
std::vector<std::pair<Variable const*,
std::vector<arangodb::basics::AttributeName>>> _fields;
//////////////////////////////////////////////////////////////////////////////
/// @brief const attributes
//////////////////////////////////////////////////////////////////////////////
std::vector<std::vector<arangodb::basics::AttributeName>> const _constAttributes;
//////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the sort is unidirectional

View File

@ -698,7 +698,7 @@ function processQuery (query, explain) {
collectionVariables[node.outVariable.id] = node.collection;
var types = [ ];
node.indexes.forEach(function (idx, i) {
var what = (idx.reverse ? "reverse " : "") + idx.type + " index scan";
var what = (node.reverse ? "reverse " : "") + idx.type + " index scan";
if (types.length === 0 || what !== types[types.length - 1]) {
types.push(what);
}

View File

@ -697,7 +697,7 @@ function processQuery (query, explain) {
collectionVariables[node.outVariable.id] = node.collection;
var types = [ ];
node.indexes.forEach(function (idx, i) {
var what = (idx.reverse ? "reverse " : "") + idx.type + " index scan";
var what = (node.reverse ? "reverse " : "") + idx.type + " index scan";
if (types.length === 0 || what !== types[types.length - 1]) {
types.push(what);
}

View File

@ -31,7 +31,6 @@
var jsunity = require("jsunity");
var db = require("@arangodb").db;
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
@ -309,9 +308,7 @@ function optimizerIndexesSortTestSuite () {
////////////////////////////////////////////////////////////////////////////////
testCannotUseHashIndexForSortIfConstRanges : function () {
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
c.ensureHashIndex("value2", "value3");
c.ensureIndex({ type: "hash", fields: [ "value2", "value3" ] });
var queries = [
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value2 ASC, i.value3 ASC RETURN i.value2", false ],
@ -339,6 +336,52 @@ function optimizerIndexesSortTestSuite () {
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testCannotUseHashIndexForSortIfConstRangesMore : function () {
c.ensureIndex({ type: "hash", fields: [ "value2", "value3", "value4" ] });
var queries = [
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC, i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 DESC, i.value4 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value4 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 ASC, i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 DESC, i.value4 DESC RETURN i.value2" ,false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value3 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value3 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value3 ASC, i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value3 DESC, i.value4 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value4 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value2 ASC, i.value3 ASC, i.value4 ASC RETURN i.value2", true ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value2 DESC, i.value3 DESC, i.value4 DESC RETURN i.value2", true ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value2 ASC, i.value3 ASC, i.value4 DESC RETURN i.value2", true ]
];
queries.forEach(function(query) {
var plan = AQL_EXPLAIN(query[0]).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
if (query[1]) {
assertEqual(-1, nodeTypes.indexOf("SortNode"), query[0]);
}
else {
assertNotEqual(-1, nodeTypes.indexOf("SortNode"), query[0]);
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
@ -437,17 +480,22 @@ function optimizerIndexesSortTestSuite () {
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testCannotUseSkiplistIndexForSortIfConstRanges : function () {
testCanUseSkiplistIndexForSortIfConstRanges : function () {
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value, value4: i.value } IN " + c.name());
c.ensureSkiplist("value2", "value3", "value4");
var queries = [
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value3 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value3 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC, i.value4 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 DESC, i.value4 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value4 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value4 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 ASC RETURN i.value2",
@ -470,10 +518,33 @@ function optimizerIndexesSortTestSuite () {
});
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"), query);
assertNotEqual(-1, nodeTypes.indexOf("SortNode"), query);
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testCannotUseSkiplistIndexForSortIfConstRanges : function () {
c.ensureSkiplist("value2", "value3", "value4");
var queries = [
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC, i.value4 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value4 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value3 ASC, i.value4 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value3 ASC, i.value4 DESC RETURN i.value2"
];
queries.forEach(function(query) {
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("SortNode"), query);
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
@ -750,6 +821,7 @@ function optimizerIndexesSortTestSuite () {
"FOR i IN " + c.name() + " FILTER i.value2 == 1 && i.value3 == null SORT i.value2 RETURN i.value2",
];
queries.forEach(function(query) {
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {

View File

@ -203,12 +203,15 @@ function optimizerRuleTestSuite () {
////////////////////////////////////////////////////////////////////////////////
testResults : function () {
var numbers = [ ], strings = [ ], reversed = [ ];
for (var i = 1; i <= 100; ++i) {
var groups = [ ], numbers = [ ], strings = [ ], reversed = [ ], i;
for (i = 1; i <= 100; ++i) {
numbers.push(i);
strings.push("test" + i);
reversed.push("test" + (101 - i));
}
for (i = 0; i < 10; ++i) {
groups.push(i);
}
var queries = [
[ "LET values = NOOPT(RANGE(1, 100)) FOR i IN 1..100 FILTER i IN values RETURN i", numbers ],
@ -223,7 +226,8 @@ function optimizerRuleTestSuite () {
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER CONCAT('test', i) IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER CONCAT('test', i) NOT IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER i IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER i NOT IN values RETURN i", numbers ]
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER i NOT IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(numbers) + ") FOR i IN 1..100 FILTER i IN values COLLECT group = i % 10 RETURN group", groups ]
];
queries.forEach(function(query) {

View File

@ -151,6 +151,57 @@ function optimizerRuleTestSuite() {
skiplist = null;
},
testRuleOptimizeWhenEqComparison : function () {
// skiplist: a, b
// skiplist: d
// hash: c
// hash: y,z
skiplist.ensureIndex({ type: "hash", fields: [ "y", "z" ], unique: false });
var queries = [
[ "FOR v IN " + colName + " FILTER v.u == 1 SORT v.u RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.c == 1 SORT v.c RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.c == 1 SORT v.z RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.c == 1 SORT v.f RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 SORT v.z RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 SORT v.y RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.z == 1 SORT v.y RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.z == 1 SORT v.z RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 && v.z == 1 SORT v.y RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 && v.z == 1 SORT v.z RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 && v.z == 1 SORT v.y, v.z RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.y == 1 && v.z == 1 SORT v.z, v.y RETURN 1", false ], // not supported yet
[ "FOR v IN " + colName + " FILTER v.d == 1 SORT v.d RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.d == 1 && v.e == 1 SORT v.d RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.d == 1 SORT v.e RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 SORT v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.b == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.b == 1 SORT v.b RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.b == 1 SORT v.b, v.a RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.b, v.a RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.c RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.b, v.a RETURN 1", false ]
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query[0]);
if (query[1]) {
assertNotEqual(-1, removeAlwaysOnClusterRules(result.plan.rules).indexOf(ruleName), query[0]);
hasNoSortNode(result);
}
else {
assertEqual(-1, removeAlwaysOnClusterRules(result.plan.rules).indexOf(ruleName), query[0]);
hasSortNode(result);
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test that rule has no effect
////////////////////////////////////////////////////////////////////////////////
@ -162,7 +213,7 @@ function optimizerRuleTestSuite() {
["FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]", true],
["FOR v IN " + colName + " SORT v.b, v.a RETURN [v.a]", true],
["FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]", true],
["FOR v IN " + colName + " SORT v.a + 1 RETURN [v.a]", false],// this will throw...
["FOR v IN " + colName + " SORT v.a + 1 RETURN [v.a]", false],
["FOR v IN " + colName + " SORT CONCAT(TO_STRING(v.a), \"lol\") RETURN [v.a]", true],
// TODO: limit blocks sort atm.
["FOR v IN " + colName + " FILTER v.a > 2 LIMIT 3 SORT v.a RETURN [v.a]", false],
@ -371,7 +422,7 @@ function optimizerRuleTestSuite() {
hasIndexNode(XPresult);
// -> combined use-index-for-sort and use-index-range
// use-index-range superseedes use-index-for-sort
// use-index-range supersedes use-index-for-sort
QResults[2] = AQL_EXECUTE(query, { }, paramIndexFromSort_IndexRange).json;
XPresult = AQL_EXPLAIN(query, { }, paramIndexFromSort_IndexRange);
@ -417,7 +468,8 @@ function optimizerRuleTestSuite() {
/// @brief test in detail that an index range fullfills everything the sort does,
// and thus the sort is removed.
////////////////////////////////////////////////////////////////////////////////
testRangeSuperseedsSort: function () {
testRangeSupersedesSort: function () {
var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a RETURN [v.a, v.b, v.c]";
@ -496,7 +548,8 @@ function optimizerRuleTestSuite() {
/// @brief test in detail that an index range fullfills everything the sort does,
// and thus the sort is removed; multi-dimensional indexes are utilized.
////////////////////////////////////////////////////////////////////////////////
testRangeSuperseedsSort2: function () {
testRangeSupersedesSort2: function () {
var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.b RETURN [v.a, v.b, v.c]";
var XPresult;