mirror of https://gitee.com/bigwinds/arangodb
parent
bdfa6c6951
commit
d67513607d
|
@ -1,6 +1,14 @@
|
|||
devel
|
||||
-----
|
||||
|
||||
* fix issue #9106: Sparse Skiplist Index on multiple fields not used for FILTER + SORT query
|
||||
|
||||
Allow AQL query optimizer to use sparse indexes in more cases, specifically when
|
||||
indexes could not be used for filtering and there finally was an `EnumerateCollectionNode`
|
||||
in the query execution plan followed by a `SortNode`. In this case, sparse indexes were
|
||||
not considered for enumeration in sorted order, because it was unclear to the optimizer
|
||||
if the result set would contain null values or not.
|
||||
|
||||
* upgrade RocksDB to version 6.2
|
||||
|
||||
* updated ArangoDB Starter to 0.14.4
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "Aql/Query.h"
|
||||
#include "Aql/SortCondition.h"
|
||||
#include "Aql/Variable.h"
|
||||
#include "Basics/AttributeNameParser.h"
|
||||
#include "Basics/Exceptions.h"
|
||||
#include "Logger/Logger.h"
|
||||
#include "Transaction/Methods.h"
|
||||
|
@ -45,7 +46,6 @@ using namespace arangodb::aql;
|
|||
using CompareResult = ConditionPartCompareResult;
|
||||
|
||||
namespace {
|
||||
|
||||
// sort comparisons so that > and >= come before < and <=, and that
|
||||
// != and > come before ==
|
||||
// we use this to some advantage when we check the conditions for a sparse
|
||||
|
@ -575,10 +575,13 @@ std::vector<std::vector<arangodb::basics::AttributeName>> Condition::getConstAtt
|
|||
if (_root == nullptr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
TRI_ASSERT(_root->type == NODE_TYPE_OPERATOR_NARY_OR);
|
||||
|
||||
size_t n = _root->numMembers();
|
||||
|
||||
if (n != 1) {
|
||||
// multiple ORs
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -612,6 +615,67 @@ std::vector<std::vector<arangodb::basics::AttributeName>> Condition::getConstAtt
|
|||
return result;
|
||||
}
|
||||
|
||||
/// @brief get the attributes for a sub-condition that are not-null
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>> Condition::getNonNullAttributes(
|
||||
Variable const* reference) const {
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>> result;
|
||||
|
||||
if (_root == nullptr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
TRI_ASSERT(_root->type == NODE_TYPE_OPERATOR_NARY_OR);
|
||||
|
||||
size_t n = _root->numMembers();
|
||||
|
||||
if (n != 1) {
|
||||
// multiple ORs
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<Variable const*, std::vector<arangodb::basics::AttributeName>> parts;
|
||||
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_NE ||
|
||||
member->type == NODE_TYPE_OPERATOR_BINARY_GT ||
|
||||
member->type == NODE_TYPE_OPERATOR_BINARY_LT) {
|
||||
clearAttributeAccess(parts);
|
||||
|
||||
AstNode const* lhs = member->getMember(0);
|
||||
AstNode const* rhs = member->getMember(1);
|
||||
AstNode const* check = nullptr;
|
||||
|
||||
if (lhs->isConstant() &&
|
||||
lhs->isNullValue() &&
|
||||
rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS &&
|
||||
member->type != NODE_TYPE_OPERATOR_BINARY_GT) {
|
||||
// null != doc.value
|
||||
// null < doc.value
|
||||
check = rhs;
|
||||
} else if (rhs->isConstant() &&
|
||||
rhs->isNullValue() &&
|
||||
lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS &&
|
||||
node->type != NODE_TYPE_OPERATOR_BINARY_LT) {
|
||||
// doc.value != null
|
||||
// doc.value > null
|
||||
check = lhs;
|
||||
}
|
||||
|
||||
if (check != nullptr &&
|
||||
check->isAttributeAccessForVariable(parts, false) &&
|
||||
parts.first == reference) {
|
||||
result.emplace(std::move(parts.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief normalize the condition
|
||||
/// this will convert the condition into its disjunctive normal form
|
||||
void Condition::normalize(ExecutionPlan* plan, bool multivalued /*= false*/) {
|
||||
|
|
|
@ -244,6 +244,10 @@ class Condition {
|
|||
/// (i.e. compared with equality)
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> getConstAttributes(
|
||||
Variable const*, bool includeNull) const;
|
||||
|
||||
/// @brief get the attributes for a sub-condition that are not-null
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>> getNonNullAttributes(
|
||||
Variable const*) const;
|
||||
|
||||
private:
|
||||
/// @brief sort ORs for the same attribute so they are in ascending value
|
||||
|
|
|
@ -251,6 +251,7 @@ void ConditionFinder::handleSortCondition(ExecutionNode* en, Variable const* out
|
|||
// we cannot optimize away a sort if we're in an inner loop ourselves
|
||||
sortCondition.reset(new SortCondition(_plan, _sorts,
|
||||
condition->getConstAttributes(outVar, false),
|
||||
condition->getNonNullAttributes(outVar),
|
||||
_variableDefinitions));
|
||||
} else {
|
||||
sortCondition.reset(new SortCondition());
|
||||
|
|
|
@ -196,6 +196,7 @@ bool optimizeSort(IResearchViewNode& viewNode, ExecutionPlan* plan) {
|
|||
SortCondition sortCondition(plan,
|
||||
sorts,
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>>(),
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>>(),
|
||||
variableDefinitions);
|
||||
|
||||
if (sortCondition.isEmpty() || !sortCondition.isOnlyAttributeAccess()) {
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include "Aql/Variable.h"
|
||||
#include "Aql/types.h"
|
||||
#include "Basics/AttributeNameParser.h"
|
||||
#include "Basics/HashSet.h"
|
||||
#include "Basics/NumberUtils.h"
|
||||
#include "Basics/SmallVector.h"
|
||||
#include "Basics/StaticStrings.h"
|
||||
|
@ -2944,11 +2945,105 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
SortNode* _sortNode;
|
||||
std::vector<std::pair<Variable const*, bool>> _sorts;
|
||||
std::unordered_map<VariableId, AstNode const*> _variableDefinitions;
|
||||
std::vector<std::vector<RegisterId>> _filters;
|
||||
bool _modified;
|
||||
|
||||
|
||||
public:
|
||||
explicit SortToIndexNode(ExecutionPlan* plan)
|
||||
: _plan(plan), _sortNode(nullptr), _modified(false) {}
|
||||
: _plan(plan), _sortNode(nullptr), _modified(false) {
|
||||
_filters.emplace_back();
|
||||
}
|
||||
|
||||
/// @brief gets the attributes from the filter conditions that will have a
|
||||
/// constant value (e.g. doc.value == 123) or than can be proven to be != null
|
||||
void getSpecialAttributes(AstNode const* node,
|
||||
Variable const* variable,
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>>& constAttributes,
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>>& nonNullAttributes) const {
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_AND) {
|
||||
// recurse into both sides
|
||||
getSpecialAttributes(node->getMemberUnchecked(0), variable, constAttributes, nonNullAttributes);
|
||||
getSpecialAttributes(node->getMemberUnchecked(1), variable, constAttributes, nonNullAttributes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->isComparisonOperator()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TRI_ASSERT(node->isComparisonOperator());
|
||||
|
||||
AstNode const* lhs = node->getMemberUnchecked(0);
|
||||
AstNode const* rhs = node->getMemberUnchecked(1);
|
||||
AstNode const* check = nullptr;
|
||||
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
|
||||
if (lhs->isConstant() && rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
// const value == doc.value
|
||||
check = rhs;
|
||||
} else if (rhs->isConstant() && lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
// doc.value == const value
|
||||
check = lhs;
|
||||
}
|
||||
} else if (node->type == NODE_TYPE_OPERATOR_BINARY_NE) {
|
||||
if (lhs->isNullValue() && rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
// null != doc.value
|
||||
check = rhs;
|
||||
} else if (rhs->isNullValue() && lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
// doc.value != null
|
||||
check = lhs;
|
||||
}
|
||||
} else if (node->type == NODE_TYPE_OPERATOR_BINARY_LT &&
|
||||
lhs->isConstant() && rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
// const value < doc.value
|
||||
check = rhs;
|
||||
} else if (node->type == NODE_TYPE_OPERATOR_BINARY_LE &&
|
||||
lhs->isConstant() && !lhs->isNullValue() && rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
// const value <= doc.value
|
||||
check = rhs;
|
||||
} else if (node->type == NODE_TYPE_OPERATOR_BINARY_GT &&
|
||||
rhs->isConstant() && lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
// doc.value > const value
|
||||
check = lhs;
|
||||
} else if (node->type == NODE_TYPE_OPERATOR_BINARY_GE &&
|
||||
rhs->isConstant() && !rhs->isNullValue() && lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
// doc.value >= const value
|
||||
check = lhs;
|
||||
}
|
||||
|
||||
if (check == nullptr) {
|
||||
// condition is useless for us
|
||||
return;
|
||||
}
|
||||
|
||||
std::pair<Variable const*, std::vector<arangodb::basics::AttributeName>> result;
|
||||
|
||||
if (check->isAttributeAccessForVariable(result, false) &&
|
||||
result.first == variable) {
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
|
||||
// found a constant value
|
||||
constAttributes.emplace_back(std::move(result.second));
|
||||
} else {
|
||||
// all other cases handle non-null attributes
|
||||
nonNullAttributes.emplace(std::move(result.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processCollectionAttributes(Variable const* variable,
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>>& constAttributes,
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>>& nonNullAttributes) const {
|
||||
// resolve all FILTER variables into their appropriate filter conditions
|
||||
TRI_ASSERT(!_filters.empty());
|
||||
for (auto const& filter : _filters.back()) {
|
||||
auto it = _variableDefinitions.find(filter);
|
||||
if (it != _variableDefinitions.end()) {
|
||||
// AND-combine all filter conditions we found, and fill constAttributes
|
||||
// and nonNullAttributes as we go along
|
||||
getSpecialAttributes((*it).second, variable, constAttributes, nonNullAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool handleEnumerateCollectionNode(EnumerateCollectionNode* enumerateCollectionNode) {
|
||||
if (_sortNode == nullptr) {
|
||||
|
@ -2960,13 +3055,18 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
return true;
|
||||
}
|
||||
|
||||
SortCondition sortCondition(_plan, _sorts,
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>>(),
|
||||
_variableDefinitions);
|
||||
// figure out all attributes from the FILTER conditions that have a constant value
|
||||
// and/or that cannot be null
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> constAttributes;
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>> nonNullAttributes;
|
||||
processCollectionAttributes(enumerateCollectionNode->outVariable(), constAttributes, nonNullAttributes);
|
||||
|
||||
if (!sortCondition.isEmpty() && sortCondition.isOnlyAttributeAccess() &&
|
||||
SortCondition sortCondition(_plan, _sorts, constAttributes, nonNullAttributes, _variableDefinitions);
|
||||
|
||||
if (!sortCondition.isEmpty() &&
|
||||
sortCondition.isOnlyAttributeAccess() &&
|
||||
sortCondition.isUnidirectional()) {
|
||||
// we have found a sort condition, which is unidirectionl
|
||||
// we have found a sort condition, which is unidirectional
|
||||
// now check if any of the collection's indexes covers it
|
||||
|
||||
Variable const* outVariable = enumerateCollectionNode->outVariable();
|
||||
|
@ -3039,7 +3139,7 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
// index conditions do not guarantee sortedness
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (isSparse) {
|
||||
return true;
|
||||
}
|
||||
|
@ -3060,10 +3160,10 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
// if we get here, we either have one index or multiple indexes on the same
|
||||
// attributes
|
||||
bool handled = false;
|
||||
|
||||
|
||||
if (indexes.size() == 1 && isSorted) {
|
||||
// if we have just a single index and we can use it for the filtering
|
||||
// condition, then we can use the index for sorting, too. regardless of it
|
||||
// condition, then we can use the index for sorting, too. regardless of if
|
||||
// the index is sparse or not. because the index would only return
|
||||
// non-null attributes anyway, so we do not need to care about null values
|
||||
// when sorting here
|
||||
|
@ -3072,6 +3172,7 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
|
||||
SortCondition sortCondition(_plan, _sorts,
|
||||
cond->getConstAttributes(outVariable, !isSparse),
|
||||
cond->getNonNullAttributes(outVariable),
|
||||
_variableDefinitions);
|
||||
|
||||
bool const isOnlyAttributeAccess =
|
||||
|
@ -3098,8 +3199,8 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
|
||||
if (!handled && isOnlyAttributeAccess && indexes.size() == 1) {
|
||||
// special case... the index cannot be used for sorting, but we only
|
||||
// compare with equality
|
||||
// lookups. now check if the equality lookup attributes are the same as
|
||||
// compare with equality lookups.
|
||||
// now check if the equality lookup attributes are the same as
|
||||
// the index attributes
|
||||
auto root = cond->root();
|
||||
|
||||
|
@ -3201,9 +3302,16 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
// found some other FOR loop
|
||||
return true;
|
||||
|
||||
case EN::SUBQUERY:
|
||||
case EN::FILTER:
|
||||
case EN::SUBQUERY: {
|
||||
_filters.emplace_back();
|
||||
return false; // skip. we don't care.
|
||||
}
|
||||
|
||||
case EN::FILTER: {
|
||||
auto inVariable = ExecutionNode::castTo<FilterNode const*>(en)->inVariable()->id;
|
||||
_filters.back().emplace_back(inVariable);
|
||||
return false;
|
||||
}
|
||||
|
||||
case EN::CALCULATION: {
|
||||
_variableDefinitions.emplace(
|
||||
|
@ -3252,6 +3360,13 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void after(ExecutionNode* en) override final {
|
||||
if (en->getType() == EN::SUBQUERY) {
|
||||
TRI_ASSERT(!_filters.empty());
|
||||
_filters.pop_back();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void arangodb::aql::useIndexForSortRule(Optimizer* opt, std::unique_ptr<ExecutionPlan> plan,
|
||||
|
|
|
@ -26,17 +26,13 @@
|
|||
#include "Aql/ExecutionPlan.h"
|
||||
#include "Basics/Exceptions.h"
|
||||
|
||||
#include <velocypack/Builder.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
using namespace arangodb::aql;
|
||||
|
||||
namespace {
|
||||
|
||||
/// @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) {
|
||||
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;
|
||||
|
@ -51,8 +47,7 @@ static bool isContained(std::vector<std::vector<arangodb::basics::AttributeName>
|
|||
/// @brief create an empty condition
|
||||
SortCondition::SortCondition()
|
||||
: _plan(nullptr),
|
||||
_fields(),
|
||||
_constAttributes(),
|
||||
_nonNullAttributes(),
|
||||
_unidirectional(false),
|
||||
_onlyAttributeAccess(false),
|
||||
_ascending(true) {}
|
||||
|
@ -61,10 +56,11 @@ SortCondition::SortCondition()
|
|||
SortCondition::SortCondition(
|
||||
ExecutionPlan* plan, std::vector<std::pair<Variable const*, bool>> const& sorts,
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> const& constAttributes,
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>> const& nonNullAttributes,
|
||||
std::unordered_map<VariableId, AstNode const*> const& variableDefinitions)
|
||||
: _plan(plan),
|
||||
_fields(),
|
||||
_constAttributes(constAttributes),
|
||||
_nonNullAttributes(nonNullAttributes),
|
||||
_unidirectional(true),
|
||||
_onlyAttributeAccess(true),
|
||||
_ascending(true) {
|
||||
|
@ -152,6 +148,13 @@ SortCondition::SortCondition(
|
|||
|
||||
/// @brief destroy the sort condition
|
||||
SortCondition::~SortCondition() {}
|
||||
|
||||
bool SortCondition::onlyUsesNonNullSortAttributes(
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> const& attributes) const {
|
||||
return std::all_of(attributes.begin(), attributes.end(), [this](auto const& it) {
|
||||
return _nonNullAttributes.find(it) != _nonNullAttributes.end();
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief returns the number of attributes in the sort condition covered
|
||||
/// by the specified index fields
|
||||
|
|
|
@ -27,12 +27,9 @@
|
|||
#include "Aql/Variable.h"
|
||||
#include "Basics/AttributeNameParser.h"
|
||||
#include "Basics/Common.h"
|
||||
#include "Basics/HashSet.h"
|
||||
|
||||
namespace arangodb {
|
||||
namespace velocypack {
|
||||
class Builder;
|
||||
class Slice;
|
||||
} // namespace velocypack
|
||||
|
||||
namespace aql {
|
||||
struct AstNode;
|
||||
|
@ -47,9 +44,11 @@ class SortCondition {
|
|||
SortCondition();
|
||||
|
||||
/// @brief create the sort condition
|
||||
SortCondition(ExecutionPlan* plan, std::vector<std::pair<Variable const*, bool>> const&,
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> const&,
|
||||
std::unordered_map<VariableId, AstNode const*> const&);
|
||||
SortCondition(ExecutionPlan* plan,
|
||||
std::vector<std::pair<Variable const*, bool>> const& variableDefinitions,
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> const& constAttributes,
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>> const& nonNullAttributes,
|
||||
std::unordered_map<VariableId, AstNode const*> const& sorts);
|
||||
|
||||
/// @brief destroy the sort condition
|
||||
~SortCondition();
|
||||
|
@ -60,7 +59,7 @@ class SortCondition {
|
|||
|
||||
/// @brief whether or not all conditions have the same sort order
|
||||
inline bool isUnidirectional() const { return _unidirectional; }
|
||||
|
||||
|
||||
/// @brief whether or not all sort directions are ascending
|
||||
/// note that the return value of this function is only meaningful if the
|
||||
/// sort is unidirectional
|
||||
|
@ -84,6 +83,11 @@ class SortCondition {
|
|||
size_t coveredAttributes(Variable const*,
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> const&) const;
|
||||
|
||||
/// @brief returns true if all attributes in the sort condition are proven
|
||||
/// to be non-null
|
||||
bool onlyUsesNonNullSortAttributes(
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> const&) const;
|
||||
|
||||
/// @brief return the sort condition (as a tuple containing variable, AstNode
|
||||
/// and sort order) at `position`.
|
||||
/// `position` can be a value between 0 and the result of
|
||||
|
@ -106,6 +110,9 @@ class SortCondition {
|
|||
|
||||
/// @brief const attributes
|
||||
std::vector<std::vector<arangodb::basics::AttributeName>> const _constAttributes;
|
||||
|
||||
/// @brief non-null attributes
|
||||
arangodb::HashSet<std::vector<arangodb::basics::AttributeName>> const _nonNullAttributes;
|
||||
|
||||
/// @brief whether or not the sort is unidirectional
|
||||
bool _unidirectional;
|
||||
|
|
|
@ -368,8 +368,10 @@ bool SortedIndexAttributeMatcher::supportsSortCondition(
|
|||
double& estimatedCost, size_t& coveredAttributes) {
|
||||
TRI_ASSERT(sortCondition != nullptr);
|
||||
|
||||
if (!idx->sparse()) {
|
||||
// only non-sparse indexes can be used for sorting
|
||||
if (!idx->sparse() ||
|
||||
sortCondition->onlyUsesNonNullSortAttributes(idx->fields())) {
|
||||
// non-sparse indexes can be used for sorting, but sparse indexes can only be
|
||||
// used if we can prove that we only need to return non-null index attribute values
|
||||
if (!idx->hasExpansion() && sortCondition->isUnidirectional() &&
|
||||
sortCondition->isOnlyAttributeAccess()) {
|
||||
coveredAttributes = sortCondition->coveredAttributes(reference, idx->fields());
|
||||
|
|
|
@ -187,8 +187,8 @@ static bool indexSupportsSort(Index const* idx, arangodb::aql::Variable const* r
|
|||
arangodb::aql::SortCondition const* sortCondition,
|
||||
size_t itemsInIndex, double& estimatedCost,
|
||||
size_t& coveredAttributes) {
|
||||
if (idx->isSorted() && idx->supportsSortCondition(sortCondition, reference, itemsInIndex,
|
||||
estimatedCost, coveredAttributes)) {
|
||||
if (idx->isSorted() && idx->supportsSortCondition(sortCondition, reference,
|
||||
itemsInIndex, estimatedCost, coveredAttributes)) {
|
||||
// index supports the sort condition
|
||||
return true;
|
||||
}
|
||||
|
@ -616,8 +616,8 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
|
|||
// general be supported by an index. for this, a sort condition must not
|
||||
// be empty, must consist only of attribute access, and all attributes
|
||||
// must be sorted in the direction
|
||||
if (indexSupportsSort(idx.get(), reference, sortCondition, itemsInIndex,
|
||||
sortCost, coveredAttributes)) {
|
||||
if (indexSupportsSort(idx.get(), reference, sortCondition,
|
||||
itemsInIndex, sortCost, coveredAttributes)) {
|
||||
supportsSort = true;
|
||||
}
|
||||
}
|
||||
|
@ -2978,7 +2978,8 @@ std::vector<std::vector<arangodb::basics::AttributeName>> transaction::Methods::
|
|||
bool transaction::Methods::getIndexForSortCondition(
|
||||
std::string const& collectionName, arangodb::aql::SortCondition const* sortCondition,
|
||||
arangodb::aql::Variable const* reference, size_t itemsInIndex,
|
||||
aql::IndexHint const& hint, std::vector<IndexHandle>& usedIndexes,
|
||||
aql::IndexHint const& hint,
|
||||
std::vector<IndexHandle>& usedIndexes,
|
||||
size_t& coveredAttributes) {
|
||||
// We do not have a condition. But we have a sort!
|
||||
if (!sortCondition->isEmpty() && sortCondition->isOnlyAttributeAccess() &&
|
||||
|
@ -2988,11 +2989,6 @@ bool transaction::Methods::getIndexForSortCondition(
|
|||
|
||||
auto considerIndex = [reference, sortCondition, itemsInIndex, &bestCost, &bestIndex,
|
||||
&coveredAttributes](std::shared_ptr<Index> const& idx) -> void {
|
||||
if (idx->sparse()) {
|
||||
// a sparse index may exclude some documents, so it can't be used to
|
||||
// get a sorted view of the ENTIRE collection
|
||||
return;
|
||||
}
|
||||
double sortCost = 0.0;
|
||||
size_t covered = 0;
|
||||
if (indexSupportsSort(idx.get(), reference, sortCondition, itemsInIndex,
|
||||
|
|
|
@ -35,6 +35,10 @@ arangodb::basics::AttributeName::AttributeName(arangodb::velocypack::StringRef c
|
|||
arangodb::basics::AttributeName::AttributeName(arangodb::velocypack::StringRef const& name, bool expand)
|
||||
: name(name.toString()), shouldExpand(expand) {}
|
||||
|
||||
uint64_t arangodb::basics::AttributeName::hash(uint64_t seed) const {
|
||||
return fasthash64(name.data(), name.size(), seed) ^ (shouldExpand ? 0xec59a4d : 0x4040ec59a4d40);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief compare two attribute name vectors
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include <iosfwd>
|
||||
#include "Common.h"
|
||||
#include "Basics/fasthash.h"
|
||||
|
||||
#include <velocypack/StringRef.h>
|
||||
|
||||
|
@ -67,6 +68,8 @@ struct AttributeName {
|
|||
return name != other.name || shouldExpand != other.shouldExpand;
|
||||
}
|
||||
|
||||
uint64_t hash(uint64_t seed) const;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief compare two attribute name vectors
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -126,9 +129,41 @@ void TRI_AttributeNamesJoinNested(std::vector<AttributeName> const& input,
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool TRI_AttributeNamesHaveExpansion(std::vector<AttributeName> const& input);
|
||||
|
||||
} // namespace basics
|
||||
} // namespace arangodb
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<std::vector<arangodb::basics::AttributeName>> {
|
||||
size_t operator()(std::vector<arangodb::basics::AttributeName> const& value) const {
|
||||
size_t hash = 0xdeadbeef;
|
||||
for (auto const& it : value) {
|
||||
hash = it.hash(hash);
|
||||
}
|
||||
return static_cast<size_t>(hash);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct equal_to<std::vector<arangodb::basics::AttributeName>> {
|
||||
bool operator()(std::vector<arangodb::basics::AttributeName> const& lhs, std::vector<arangodb::basics::AttributeName> const& rhs) const {
|
||||
size_t const n = lhs.size();
|
||||
if (n != rhs.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (lhs[i] != rhs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
std::ostream& operator<<(std::ostream&, arangodb::basics::AttributeName const&);
|
||||
std::ostream& operator<<(std::ostream&, std::vector<arangodb::basics::AttributeName> const&);
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ function TtlSuite () {
|
|||
});
|
||||
},
|
||||
|
||||
testIndexNotUsed : function() {
|
||||
testIndexNotUsedForFiltering : function() {
|
||||
let c = db._create(cn, { numberOfShards: 2 });
|
||||
c.ensureIndex({ type: "ttl", fields: ["dateCreated"], expireAfter: 1 });
|
||||
|
||||
|
@ -303,9 +303,6 @@ function TtlSuite () {
|
|||
"FOR doc IN @@collection FILTER doc.@indexAttribute >= '2019-01-01' RETURN doc",
|
||||
"FOR doc IN @@collection FILTER doc.@indexAttribute <= '2019-01-31' RETURN doc",
|
||||
"FOR doc IN @@collection FILTER doc.@indexAttribute >= '2019-01-01' && doc.@indexAttribute <= '2019-01-31' RETURN doc",
|
||||
"FOR doc IN @@collection FILTER doc.@indexAttribute >= '2019-01-01' SORT doc.@indexAttribute RETURN doc",
|
||||
"FOR doc IN @@collection FILTER doc.@indexAttribute <= '2019-01-31' SORT doc.@indexAttribute RETURN doc",
|
||||
"FOR doc IN @@collection FILTER doc.@indexAttribute >= '2019-01-01' && doc.@indexAttribute <= '2019-01-31' SORT doc.@indexAttribute RETURN doc",
|
||||
];
|
||||
|
||||
let bindVars = { "@collection": cn, indexAttribute: "dateCreated" };
|
||||
|
@ -323,6 +320,27 @@ function TtlSuite () {
|
|||
});
|
||||
},
|
||||
|
||||
testIndexUsedForSorting : function() {
|
||||
let c = db._create(cn, { numberOfShards: 2 });
|
||||
c.ensureIndex({ type: "ttl", fields: ["dateCreated"], expireAfter: 1 });
|
||||
|
||||
let queries = [
|
||||
"FOR doc IN @@collection FILTER doc.@indexAttribute >= '2019-01-01' SORT doc.@indexAttribute RETURN doc",
|
||||
"FOR doc IN @@collection FILTER doc.@indexAttribute <= '2019-01-31' && doc.@indexAttribute != null SORT doc.@indexAttribute RETURN doc",
|
||||
"FOR doc IN @@collection FILTER doc.@indexAttribute >= '2019-01-01' && doc.@indexAttribute <= '2019-01-31' SORT doc.@indexAttribute RETURN doc",
|
||||
];
|
||||
|
||||
let bindVars = { "@collection": cn, indexAttribute: "dateCreated" };
|
||||
|
||||
queries.forEach(function(query) {
|
||||
let stmt = db._createStatement({ query, bindVars });
|
||||
let plan = stmt.explain().plan;
|
||||
let rules = plan.rules;
|
||||
assertEqual(-1, rules.indexOf("use-indexes"), query);
|
||||
assertNotEqual(-1, rules.indexOf("use-index-for-sort"), query);
|
||||
});
|
||||
},
|
||||
|
||||
testRemovalsExpireAfterNotPresent : function () {
|
||||
internal.ttlProperties({ active: false });
|
||||
|
||||
|
|
|
@ -859,7 +859,13 @@ function optimizerIndexesSortTestSuite () {
|
|||
|
||||
var queries = [
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 == 10 && i.value3 >= 4 SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 == 10 && i.value3 == 4 SORT i.value2, i.value3 RETURN i.value2"
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 == 10 && i.value3 == 4 SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 > null && i.value3 > null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 != null && i.value3 > null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 != null && i.value3 != null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 > null && i.value3 != null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 > 10 && i.value3 > 4 SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 >= 10 && i.value3 >= 4 SORT i.value2, i.value3 RETURN i.value2",
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
|
@ -891,9 +897,11 @@ function optimizerIndexesSortTestSuite () {
|
|||
var idx = c.ensureSkiplist("value2", "value3", { sparse: true });
|
||||
|
||||
var queries = [
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 > null && i.value3 > null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 > 10 && i.value3 > 4 SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 >= 10 && i.value3 >= 4 SORT i.value2, i.value3 RETURN i.value2"
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 >= null && i.value3 > null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 > null && i.value3 >= null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 >= null && i.value3 >= null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 != null && i.value3 >= null SORT i.value2, i.value3 RETURN i.value2",
|
||||
"FOR i IN " + c.name() + " FILTER i.value2 >= null && i.value3 != null SORT i.value2, i.value3 RETURN i.value2",
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
|
|
Loading…
Reference in New Issue