mirror of https://gitee.com/bigwinds/arangodb
remove sort in more cases
This commit is contained in:
parent
3b12dc0c32
commit
5ba2432d78
|
@ -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() ||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue