1
0
Fork 0
This commit is contained in:
Jan 2019-05-29 18:27:38 +02:00 committed by GitHub
parent bdfa6c6951
commit d67513607d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 318 additions and 52 deletions

View File

@ -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

View File

@ -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*/) {

View File

@ -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

View File

@ -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());

View File

@ -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()) {

View File

@ -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,

View File

@ -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

View File

@ -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;

View File

@ -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());

View File

@ -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,

View File

@ -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
////////////////////////////////////////////////////////////////////////////////

View File

@ -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&);

View File

@ -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 });

View File

@ -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) {