1
0
Fork 0

Index hints (#8431)

This commit is contained in:
Dan Larkin-York 2019-03-19 04:14:18 -04:00 committed by Jan
parent 29b941a677
commit 2eadab33e7
26 changed files with 918 additions and 54 deletions

View File

@ -26,6 +26,8 @@ devel
be used that has access to the `_system` database, in order to create
the databases on restore.
* added index hints feature to AQL
* added "name" property for indices
If a name is not specified on index creation, one will be auto-generated.

View File

@ -33,7 +33,7 @@ required that *expression* returns an array in all cases. The empty array is
allowed, too. The current array element is made available for further processing
in the variable specified by *variableName*.
```
```js
FOR u IN users
RETURN u
```
@ -52,7 +52,7 @@ placed in is closed.
Another example that uses a statically declared array of values to iterate over:
```
```js
FOR year IN [ 2011, 2012, 2013 ]
RETURN { "year" : year, "isLeapYear" : year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }
```
@ -61,7 +61,7 @@ Nesting of multiple *FOR* statements is allowed, too. When *FOR* statements are
nested, a cross product of the array elements returned by the individual *FOR*
statements will be created.
```
```js
FOR u IN users
FOR l IN locations
RETURN { "user" : u, "location" : l }
@ -72,3 +72,39 @@ In this example, there are two array iterations: an outer iteration over the arr
traversed as many times as there are elements in the outer array. For each
iteration, the current values of *users* and *locations* are made available for
further processing in the variable *u* and *l*.
## Options
For collections and views, the `FOR` construct supports an optional `OPTIONS`
suffix to modify behavior. The general syntax is:
```js
FOR variableName IN expression OPTIONS {option: value, ...}
```
### Index hints
For collections, index hints are provided though this inline options mechanism.
Hints can be specified in two different formats.
The first format option is the simplest, just a single index name. This should
be sufficient for many cases. Whenever there is a choice to potentially use an
index for this `FOR` loop, the optimizer will first check if the specified index
can be used. If so, it will use it, regardless of whether it would normally use
a different index. If it cannot use that index, then it will fall back to its
normal logic to select another index. If the optional `forceIndexHint: true` is
specified, then it will not fall back, and instead generate an error.
```js
OPTIONS {indexHint: 'byName'[, forceIndexHint: <boolean>]}
```
The second is an array of index names, in order of preference. When specified
this way, the optimizer will behave much in the same way as above, but will
check the feasibility of each of the specified indices, in the order they are
given, falling back to its normal logic or failing only if none of the specified
indices are feasible.
```js
OPTIONS {indexHint: ['byName', 'byColor'][, forceIndexHint: <boolean>]}
```

View File

@ -262,6 +262,14 @@ name _cannot_ be changed after index creation. No two indices on the same
collection may share the same name, but two indices on different collections
may.
### Index Hints in AQL
Users may now take advantage of the `indexHint` inline query option to override
the internal optimizer decision regarding which index to use to serve content
from a given collection. The index hint works with the named indices feature
above, making it easy to specify which index to use.
Client tools
------------

View File

@ -462,12 +462,14 @@ std::pair<bool, bool> Condition::findIndexes(EnumerateCollectionNode const* node
}
if (_root == nullptr) {
size_t dummy;
return std::make_pair<bool, bool>(false, trx->getIndexForSortCondition(collectionName, sortCondition, reference,
itemsInIndex, usedIndexes, dummy));
return std::make_pair<bool, bool>(
false, trx->getIndexForSortCondition(collectionName, sortCondition, reference, itemsInIndex,
node->hint(), usedIndexes, dummy));
}
return trx->getBestIndexHandlesForFilterCondition(collectionName, _ast, _root,
reference, sortCondition, itemsInIndex,
reference, sortCondition,
itemsInIndex, node->hint(),
usedIndexes, _isSorted);
}

View File

@ -1275,7 +1275,8 @@ EnumerateCollectionNode::EnumerateCollectionNode(ExecutionPlan* plan,
: ExecutionNode(plan, base),
DocumentProducingNode(plan, base),
CollectionAccessingNode(plan, base),
_random(base.get("random").getBoolean()) {}
_random(base.get("random").getBoolean()),
_hint(base) {}
/// @brief toVelocyPack, for EnumerateCollectionNode
void EnumerateCollectionNode::toVelocyPackHelper(VPackBuilder& builder, unsigned flags) const {
@ -1284,6 +1285,8 @@ void EnumerateCollectionNode::toVelocyPackHelper(VPackBuilder& builder, unsigned
builder.add("random", VPackValue(_random));
_hint.toVelocyPack(builder);
// add outvariable and projection
DocumentProducingNode::toVelocyPack(builder);
@ -1325,7 +1328,7 @@ ExecutionNode* EnumerateCollectionNode::clone(ExecutionPlan* plan, bool withDepe
}
auto c = std::make_unique<EnumerateCollectionNode>(plan, _id, _collection,
outVariable, _random);
outVariable, _random, _hint);
c->projections(_projections);

View File

@ -56,6 +56,7 @@
#include "Aql/CostEstimate.h"
#include "Aql/DocumentProducingNode.h"
#include "Aql/Expression.h"
#include "Aql/IndexHint.h"
#include "Aql/Variable.h"
#include "Aql/WalkerWorker.h"
#include "Aql/types.h"
@ -637,11 +638,12 @@ class EnumerateCollectionNode : public ExecutionNode,
/// @brief constructor with a vocbase and a collection name
public:
EnumerateCollectionNode(ExecutionPlan* plan, size_t id, aql::Collection const* collection,
Variable const* outVariable, bool random)
Variable const* outVariable, bool random, IndexHint const& hint)
: ExecutionNode(plan, id),
DocumentProducingNode(outVariable),
CollectionAccessingNode(collection),
_random(random) {}
_random(random),
_hint(hint) {}
EnumerateCollectionNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base);
@ -675,9 +677,15 @@ class EnumerateCollectionNode : public ExecutionNode,
/// @brief enable random iteration of documents in collection
void setRandom() { _random = true; }
/// @brief user hint regarding which index ot use
IndexHint const& hint() const { return _hint; }
private:
/// @brief whether or not we want random iteration
bool _random;
/// @brief a possible hint from the user regarding which index to use
IndexHint _hint;
};
/// @brief class EnumerateListNode

View File

@ -30,6 +30,7 @@
#include "Aql/ExecutionNode.h"
#include "Aql/Expression.h"
#include "Aql/Function.h"
#include "Aql/IndexHint.h"
#include "Aql/ModificationNodes.h"
#include "Aql/NodeFinder.h"
#include "Aql/OptimizerRulesFeature.h"
@ -842,7 +843,8 @@ ExecutionNode* ExecutionPlan::fromNodeFor(ExecutionNode* previous, AstNode const
auto variable = node->getMember(0);
auto expression = node->getMember(1);
// TODO: process FOR options here if we want to use them later
auto options = node->getMember(2);
IndexHint hint(options);
// fetch 1st operand (out variable name)
TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE);
@ -862,7 +864,7 @@ ExecutionNode* ExecutionPlan::fromNodeFor(ExecutionNode* previous, AstNode const
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"no collection for EnumerateCollection");
}
en = registerNode(new EnumerateCollectionNode(this, nextId(), collection, v, false));
en = registerNode(new EnumerateCollectionNode(this, nextId(), collection, v, false, hint));
#ifdef USE_IRESEARCH
} else if (expression->type == NODE_TYPE_VIEW) {
// second operand is a view

196
arangod/Aql/IndexHint.cpp Normal file
View File

@ -0,0 +1,196 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2019 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Dan Larkin-York
////////////////////////////////////////////////////////////////////////////////
#include "IndexHint.h"
#include <velocypack/Iterator.h>
#include <velocypack/StringRef.h>
#include "Aql/AstNode.h"
#include "Basics/StaticStrings.h"
#include "Basics/VelocyPackHelper.h"
namespace {
std::string const TypeIllegal("illegal");
std::string const TypeNone("none");
std::string const TypeSimple("simple");
std::string const FieldContainer("indexHint");
std::string const FieldForced("forced");
std::string const FieldHint("hint");
std::string const FieldType("type");
bool extractForced(arangodb::aql::AstNode const* node) {
using arangodb::aql::AstNode;
using arangodb::aql::AstNodeType;
using arangodb::aql::AstNodeValueType;
bool forced = false;
if (node->type == AstNodeType::NODE_TYPE_OBJECT) {
for (size_t i = 0; i < node->numMembers(); i++) {
AstNode const* child = node->getMember(i);
if (child->type == AstNodeType::NODE_TYPE_OBJECT_ELEMENT) {
VPackStringRef name(child->getStringValue(), child->getStringLength());
if (name == arangodb::StaticStrings::IndexHintOptionForce) {
TRI_ASSERT(child->numMembers() > 0);
AstNode const* value = child->getMember(0);
if (value->type == AstNodeType::NODE_TYPE_VALUE &&
value->value.type == AstNodeValueType::VALUE_TYPE_BOOL) {
forced = value->value.value._bool;
}
}
}
}
}
return forced;
}
arangodb::aql::IndexHint::HintType fromTypeName(std::string const& typeName) {
if (::TypeSimple == typeName) {
return arangodb::aql::IndexHint::HintType::Simple;
} else if (::TypeNone == typeName) {
return arangodb::aql::IndexHint::HintType::None;
}
return arangodb::aql::IndexHint::HintType::Illegal;
}
} // namespace
namespace arangodb {
namespace aql {
IndexHint::IndexHint() : _type{HintType::None}, _forced{false} {}
IndexHint::IndexHint(AstNode const* node)
: _type{HintType::None}, _forced{::extractForced(node)} {
if (node->type == AstNodeType::NODE_TYPE_OBJECT) {
for (size_t i = 0; i < node->numMembers(); i++) {
AstNode const* child = node->getMember(i);
if (child->type == AstNodeType::NODE_TYPE_OBJECT_ELEMENT) {
VPackStringRef name(child->getStringValue(), child->getStringLength());
if (name == StaticStrings::IndexHintOption) {
TRI_ASSERT(child->numMembers() > 0);
AstNode const* value = child->getMember(0);
if (value->type == AstNodeType::NODE_TYPE_VALUE &&
value->value.type == AstNodeValueType::VALUE_TYPE_STRING) {
_type = HintType::Simple;
_hint.simple.emplace_back(value->getStringValue(), value->getStringLength());
}
if (value->type == AstNodeType::NODE_TYPE_ARRAY) {
_type = HintType::Simple;
for (size_t j = 0; j < value->numMembers(); j++) {
AstNode const* member = value->getMember(j);
if (member->type == AstNodeType::NODE_TYPE_VALUE &&
member->value.type == AstNodeValueType::VALUE_TYPE_STRING) {
_hint.simple.emplace_back(member->getStringValue(),
member->getStringLength());
}
}
}
}
}
}
}
}
IndexHint::IndexHint(VPackSlice const& slice)
: _type{::fromTypeName(
basics::VelocyPackHelper::getStringValue(slice.get(::FieldContainer),
::FieldType, ""))},
_forced{basics::VelocyPackHelper::getBooleanValue(slice.get(::FieldContainer),
::FieldForced, false)} {
if (_type != HintType::Illegal && _type != HintType::None) {
VPackSlice container = slice.get(::FieldContainer);
TRI_ASSERT(container.isObject());
if (_type == HintType::Simple) {
VPackSlice hintSlice = container.get(::FieldHint);
TRI_ASSERT(hintSlice.isArray());
for (VPackSlice index : VPackArrayIterator(hintSlice)) {
TRI_ASSERT(index.isString());
_hint.simple.emplace_back(index.copyString());
}
}
}
}
IndexHint::HintType IndexHint::type() const { return _type; }
bool IndexHint::isForced() const { return _forced; }
std::vector<std::string> const& IndexHint::hint() const {
TRI_ASSERT(_type == HintType::Simple);
return _hint.simple;
}
std::string IndexHint::typeName() const {
switch (_type) {
case HintType::Illegal:
return ::TypeIllegal;
case HintType::None:
return ::TypeNone;
case HintType::Simple:
return ::TypeSimple;
}
return ::TypeIllegal;
}
void IndexHint::toVelocyPack(VPackBuilder& builder) const {
TRI_ASSERT(builder.isOpenObject());
VPackObjectBuilder guard(&builder, ::FieldContainer);
builder.add(::FieldForced, VPackValue(_forced));
builder.add(::FieldType, VPackValue(typeName()));
if (_type == HintType::Simple) {
VPackArrayBuilder hintGuard(&builder, ::FieldHint);
for (std::string const& index : _hint.simple) {
builder.add(VPackValue(index));
}
}
}
std::string IndexHint::toString() const {
VPackBuilder builder;
{
VPackObjectBuilder guard(&builder);
toVelocyPack(builder);
}
return builder.slice().toJson();
}
std::ostream& operator<<(std::ostream& stream, arangodb::aql::IndexHint const& hint) {
stream << hint.toString();
return stream;
}
} // namespace aql
} // namespace arangodb

74
arangod/Aql/IndexHint.h Normal file
View File

@ -0,0 +1,74 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2019 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Dan Larkin-York
////////////////////////////////////////////////////////////////////////////////
#ifndef ARANGOD_AQL_INDEX_HINT_H
#define ARANGOD_AQL_INDEX_HINT_H 1
#include <iostream>
#include <velocypack/Builder.h>
#include <velocypack/Slice.h>
#include <velocypack/velocypack-aliases.h>
#include "Aql/AstNode.h"
namespace arangodb {
namespace aql {
/// @brief container for index hint information
class IndexHint {
public:
enum HintType : uint8_t { Illegal, None, Simple };
public:
explicit IndexHint();
explicit IndexHint(AstNode const* node);
explicit IndexHint(VPackSlice const& slice);
public:
HintType type() const;
bool isForced() const;
std::vector<std::string> const& hint() const;
void toVelocyPack(VPackBuilder& builder) const;
std::string typeName() const;
std::string toString() const;
private:
HintType _type;
bool const _forced;
// actual hint is a recursive structure, with the data type determined by the
// _type above; in the case of a nested IndexHint, the value of isForced() is
// inherited
struct HintData {
std::vector<std::string> simple;
} _hint;
};
std::ostream& operator<<(std::ostream& stream, arangodb::aql::IndexHint const& hint);
} // namespace aql
} // namespace arangodb
#endif

View File

@ -2880,11 +2880,10 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
std::vector<transaction::Methods::IndexHandle> usedIndexes;
auto trx = _plan->getAst()->query()->trx();
size_t coveredAttributes = 0;
bool canBeUsed =
trx->getIndexForSortCondition(enumerateCollectionNode->collection()->name(),
&sortCondition, outVariable,
enumerateCollectionNode->collection()->count(trx),
usedIndexes, coveredAttributes);
bool canBeUsed = trx->getIndexForSortCondition(
enumerateCollectionNode->collection()->name(), &sortCondition,
outVariable, enumerateCollectionNode->collection()->count(trx),
enumerateCollectionNode->hint(), usedIndexes, coveredAttributes);
if (canBeUsed) {
// If this bit is set, then usedIndexes has length exactly one
// and contains the best index found.

View File

@ -24,6 +24,7 @@
#include "Aql/ExecutionNode.h"
#include "Aql/ExecutionPlan.h"
#include "Aql/Function.h"
#include "Aql/IndexHint.h"
#include "Aql/IndexNode.h"
#include "Aql/Optimizer.h"
#include "Aql/Query.h"
@ -266,7 +267,7 @@ AstNode* replaceNearOrWithin(AstNode* funAstNode, ExecutionNode* calcNode,
ExecutionNode* eEnumerate = plan->registerNode(
// link output of index with the return node
new EnumerateCollectionNode(plan, plan->nextId(), aqlCollection,
enumerateOutVariable, false));
enumerateOutVariable, false, IndexHint()));
//// build sort condition - DISTANCE(d.lat, d.long, param.lat, param.lon)
auto* docRef = ast->createNodeReference(enumerateOutVariable);

View File

@ -227,6 +227,7 @@ SET(ARANGOD_SOURCES
Aql/InAndOutRowExpressionContext.cpp
Aql/IdExecutor.cpp
Aql/IndexExecutor.cpp
Aql/IndexHint.cpp
Aql/IndexNode.cpp
Aql/InputAqlItemRow.cpp
Aql/LimitExecutor.cpp

View File

@ -31,6 +31,7 @@
#include "Basics/tri-strings.h"
#include "Cluster/ClusterComm.h"
#include "Cluster/ClusterInfo.h"
#include "ClusterMethods.h"
#include "Graph/Traverser.h"
#include "Indexes/Index.h"
#include "RestServer/TtlFeature.h"

View File

@ -248,9 +248,10 @@ void BaseOptions::injectLookupInfoInList(std::vector<LookupInfo>& list,
aql::AstNode* condition) {
LookupInfo info;
info.indexCondition = condition->clone(plan->getAst());
bool res = _trx->getBestIndexHandleForFilterCondition(collectionName,
info.indexCondition, _tmpVar,
1000, info.idxHandles[0]);
bool res =
_trx->getBestIndexHandleForFilterCondition(collectionName, info.indexCondition,
_tmpVar, 1000, aql::IndexHint(),
info.idxHandles[0]);
// Right now we have an enforced edge index which should always fit.
if (!res) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,

View File

@ -46,21 +46,25 @@ EdgeCollectionInfo::EdgeCollectionInfo(transaction::Methods* trx,
auto var = _searchBuilder.getVariable();
if (_dir == TRI_EDGE_OUT) {
auto cond = _searchBuilder.getOutboundCondition();
bool worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond,
var, 1000, _forwardIndexId);
bool worked =
_trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var, 1000,
aql::IndexHint(), _forwardIndexId);
TRI_ASSERT(worked); // We always have an edge Index
cond = _searchBuilder.getInboundCondition();
worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var,
1000, _backwardIndexId);
1000, aql::IndexHint(),
_backwardIndexId);
TRI_ASSERT(worked); // We always have an edge Index
} else {
auto cond = _searchBuilder.getInboundCondition();
bool worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond,
var, 1000, _forwardIndexId);
bool worked =
_trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var, 1000,
aql::IndexHint(), _forwardIndexId);
TRI_ASSERT(worked); // We always have an edge Index
cond = _searchBuilder.getOutboundCondition();
worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var,
1000, _backwardIndexId);
1000, aql::IndexHint(),
_backwardIndexId);
TRI_ASSERT(worked); // We always have an edge Index
}
}

View File

@ -67,8 +67,9 @@ void RestEdgesHandler::readCursor(aql::AstNode* condition, aql::Variable const*
SingleCollectionTransaction& trx,
std::function<void(LocalDocumentId const&)> const& cb) {
transaction::Methods::IndexHandle indexId;
bool foundIdx = trx.getBestIndexHandleForFilterCondition(collectionName, condition,
var, 1000, indexId);
bool foundIdx =
trx.getBestIndexHandleForFilterCondition(collectionName, condition, var,
1000, aql::IndexHint(), indexId);
if (!foundIdx) {
// Right now we enforce an edge index that can exactly! work on this
// condition. So it is impossible to not find an index.

View File

@ -576,14 +576,16 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
std::vector<std::shared_ptr<Index>> const& indexes,
arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference,
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection,
std::vector<transaction::Methods::IndexHandle>& usedIndexes,
aql::IndexHint const& hint, std::vector<transaction::Methods::IndexHandle>& usedIndexes,
arangodb::aql::AstNode*& specializedCondition, bool& isSparse) const {
std::shared_ptr<Index> bestIndex;
double bestCost = 0.0;
bool bestSupportsFilter = false;
bool bestSupportsSort = false;
for (auto const& idx : indexes) {
auto considerIndex = [&bestIndex, &bestCost, &bestSupportsFilter, &bestSupportsSort,
&indexes, node, reference, itemsInCollection,
sortCondition](std::shared_ptr<Index> const& idx) -> void {
double filterCost = 0.0;
double sortCost = 0.0;
size_t itemsInIndex = itemsInCollection;
@ -636,7 +638,7 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
}
if (!supportsFilter && !supportsSort) {
continue;
return;
}
double totalCost = filterCost;
@ -666,6 +668,38 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
bestSupportsFilter = supportsFilter;
bestSupportsSort = supportsSort;
}
};
if (hint.type() == aql::IndexHint::HintType::Simple) {
std::vector<std::string> const& hintedIndices = hint.hint();
for (std::string const& hinted : hintedIndices) {
std::shared_ptr<Index> matched;
for (std::shared_ptr<Index> const& idx : indexes) {
if (idx->name() == hinted) {
matched = idx;
break;
}
}
if (matched != nullptr) {
considerIndex(matched);
if (bestIndex != nullptr) {
break;
}
}
}
if (hint.isForced() && bestIndex == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(
TRI_ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE,
"could not use index hint to serve query; " + hint.toString());
}
}
if (bestIndex == nullptr) {
for (auto const& idx : indexes) {
considerIndex(idx);
}
}
if (bestIndex == nullptr) {
@ -681,13 +715,14 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
}
bool transaction::Methods::findIndexHandleForAndNode(
std::vector<std::shared_ptr<Index>> const& indexes,
arangodb::aql::AstNode*& node, arangodb::aql::Variable const* reference,
size_t itemsInCollection, transaction::Methods::IndexHandle& usedIndex) const {
std::vector<std::shared_ptr<Index>> const& indexes, arangodb::aql::AstNode*& node,
arangodb::aql::Variable const* reference, size_t itemsInCollection,
aql::IndexHint const& hint, transaction::Methods::IndexHandle& usedIndex) const {
std::shared_ptr<Index> bestIndex;
double bestCost = 0.0;
for (auto const& idx : indexes) {
auto considerIndex = [&bestIndex, &bestCost, itemsInCollection, &indexes, &node,
reference](std::shared_ptr<Index> const& idx) -> void {
size_t itemsInIndex = itemsInCollection;
// check if the index supports the filter expression
@ -709,7 +744,7 @@ bool transaction::Methods::findIndexHandleForAndNode(
<< ", node: " << node;
if (!supportsFilter) {
continue;
return;
}
// index supports the filter condition
@ -721,6 +756,38 @@ bool transaction::Methods::findIndexHandleForAndNode(
bestIndex = idx;
bestCost = estimatedCost;
}
};
if (hint.type() == aql::IndexHint::HintType::Simple) {
std::vector<std::string> const& hintedIndices = hint.hint();
for (std::string const& hinted : hintedIndices) {
std::shared_ptr<Index> matched;
for (std::shared_ptr<Index> const& idx : indexes) {
if (idx->name() == hinted) {
matched = idx;
break;
}
}
if (matched != nullptr) {
considerIndex(matched);
if (bestIndex != nullptr) {
break;
}
}
}
if (hint.isForced() && bestIndex == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(
TRI_ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE,
"could not use index hint to serve query; " + hint.toString());
}
}
if (bestIndex == nullptr) {
for (auto const& idx : indexes) {
considerIndex(idx);
}
}
if (bestIndex == nullptr) {
@ -2755,7 +2822,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
std::string const& collectionName, arangodb::aql::Ast* ast,
arangodb::aql::AstNode* root, arangodb::aql::Variable const* reference,
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection,
std::vector<IndexHandle>& usedIndexes, bool& isSorted) {
aql::IndexHint const& hint, std::vector<IndexHandle>& usedIndexes, bool& isSorted) {
// We can only start after DNF transformation
TRI_ASSERT(root->type == arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_NARY_OR);
auto indexes = indexesForCollection(collectionName);
@ -2771,7 +2838,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
auto node = root->getMemberUnchecked(i);
arangodb::aql::AstNode* specializedCondition = nullptr;
auto canUseIndex = findIndexHandleForAndNode(indexes, node, reference, sortCondition,
itemsInCollection, usedIndexes,
itemsInCollection, hint, usedIndexes,
specializedCondition, isSparse);
if (canUseIndex.second && !canUseIndex.first) {
@ -2817,7 +2884,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
bool transaction::Methods::getBestIndexHandleForFilterCondition(
std::string const& collectionName, arangodb::aql::AstNode*& node,
arangodb::aql::Variable const* reference, size_t itemsInCollection,
IndexHandle& usedIndex) {
aql::IndexHint const& hint, IndexHandle& usedIndex) {
// We can only start after DNF transformation and only a single AND
TRI_ASSERT(node->type == arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_NARY_AND);
if (node->numMembers() == 0) {
@ -2829,7 +2896,8 @@ bool transaction::Methods::getBestIndexHandleForFilterCondition(
// Const cast is save here. Giving computeSpecialization == false
// Makes sure node is NOT modified.
return findIndexHandleForAndNode(indexes, node, reference, itemsInCollection, usedIndex);
return findIndexHandleForAndNode(indexes, node, reference, itemsInCollection,
hint, usedIndex);
}
/// @brief Checks if the index supports the filter condition.
@ -2872,20 +2940,20 @@ 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,
std::vector<IndexHandle>& usedIndexes, size_t& coveredAttributes) {
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() &&
sortCondition->isUnidirectional()) {
double bestCost = 0.0;
std::shared_ptr<Index> bestIndex;
auto indexes = indexesForCollection(collectionName);
for (auto const& idx : indexes) {
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
continue;
return;
}
double sortCost = 0.0;
size_t covered = 0;
@ -2897,6 +2965,40 @@ bool transaction::Methods::getIndexForSortCondition(
coveredAttributes = covered;
}
}
};
auto indexes = indexesForCollection(collectionName);
if (hint.type() == aql::IndexHint::HintType::Simple) {
std::vector<std::string> const& hintedIndices = hint.hint();
for (std::string const& hinted : hintedIndices) {
std::shared_ptr<Index> matched;
for (std::shared_ptr<Index> const& idx : indexes) {
if (idx->name() == hinted) {
matched = idx;
break;
}
}
if (matched != nullptr) {
considerIndex(matched);
if (bestIndex != nullptr) {
break;
}
}
}
if (hint.isForced() && bestIndex == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(
TRI_ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE,
"could not use index hint to serve query; " + hint.toString());
}
}
if (bestIndex == nullptr) {
for (auto const& idx : indexes) {
considerIndex(idx);
}
}
if (bestIndex != nullptr) {

View File

@ -24,6 +24,7 @@
#ifndef ARANGOD_TRANSACTION_METHODS_H
#define ARANGOD_TRANSACTION_METHODS_H 1
#include "Aql/IndexHint.h"
#include "Basics/Common.h"
#include "Basics/Exceptions.h"
#include "Basics/Result.h"
@ -331,7 +332,7 @@ class Methods {
ENTERPRISE_VIRT std::pair<bool, bool> getBestIndexHandlesForFilterCondition(
std::string const&, arangodb::aql::Ast*, arangodb::aql::AstNode*,
arangodb::aql::Variable const*, arangodb::aql::SortCondition const*,
size_t, std::vector<IndexHandle>&, bool&);
size_t, aql::IndexHint const&, std::vector<IndexHandle>&, bool&);
/// @brief Gets the best fitting index for one specific condition.
/// Difference to IndexHandles: Condition is only one NARY_AND
@ -339,10 +340,9 @@ class Methods {
/// Returns false if no index could be found.
/// If it returned true, the AstNode contains the specialized condition
ENTERPRISE_VIRT bool getBestIndexHandleForFilterCondition(std::string const&,
arangodb::aql::AstNode*&,
arangodb::aql::Variable const*,
size_t, IndexHandle&);
ENTERPRISE_VIRT bool getBestIndexHandleForFilterCondition(
std::string const&, arangodb::aql::AstNode*&,
arangodb::aql::Variable const*, size_t, aql::IndexHint const&, IndexHandle&);
/// @brief Checks if the index supports the filter condition.
/// note: the caller must have read-locked the underlying collection when
@ -362,7 +362,8 @@ class Methods {
ENTERPRISE_VIRT bool getIndexForSortCondition(std::string const&,
arangodb::aql::SortCondition const*,
arangodb::aql::Variable const*,
size_t, std::vector<IndexHandle>&,
size_t, aql::IndexHint const&,
std::vector<IndexHandle>&,
size_t& coveredAttributes);
/// @brief factory for IndexIterator objects from AQL
@ -562,13 +563,14 @@ class Methods {
std::vector<std::shared_ptr<Index>> const& indexes,
arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference,
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection,
std::vector<transaction::Methods::IndexHandle>& usedIndexes,
aql::IndexHint const& hint, std::vector<transaction::Methods::IndexHandle>& usedIndexes,
arangodb::aql::AstNode*& specializedCondition, bool& isSparse) const;
/// @brief findIndexHandleForAndNode, Shorthand which does not support Sort
bool findIndexHandleForAndNode(std::vector<std::shared_ptr<Index>> const& indexes,
arangodb::aql::AstNode*& node,
arangodb::aql::Variable const* reference, size_t itemsInCollection,
arangodb::aql::Variable const* reference,
size_t itemsInCollection, aql::IndexHint const& hint,
transaction::Methods::IndexHandle& usedIndex) const;
/// @brief Get one index by id for a collection name, coordinator case

View File

@ -215,6 +215,7 @@
"ERROR_QUERY_INVALID_AGGREGATE_EXPRESSION" : { "code" : 1574, "message" : "invalid aggregate expression" },
"ERROR_QUERY_COMPILE_TIME_OPTIONS" : { "code" : 1575, "message" : "query options must be readable at query compile time" },
"ERROR_QUERY_EXCEPTION_OPTIONS" : { "code" : 1576, "message" : "query options expected" },
"ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE" : { "code" : 1577, "message" : "could not use forced index hint" },
"ERROR_QUERY_DISALLOWED_DYNAMIC_CALL" : { "code" : 1578, "message" : "disallowed dynamic call to '%s'" },
"ERROR_QUERY_ACCESS_AFTER_MODIFICATION" : { "code" : 1579, "message" : "access after data-modification by %s" },
"ERROR_QUERY_FUNCTION_INVALID_NAME" : { "code" : 1580, "message" : "invalid user function name" },

View File

@ -294,6 +294,7 @@ function printIndexes(indexes) {
var maxCollectionLen = String('Collection').length;
var maxUniqueLen = String('Unique').length;
var maxSparseLen = String('Sparse').length;
var maxNameLen = String('Name').length;
var maxTypeLen = String('Type').length;
var maxSelectivityLen = String('Selectivity').length;
var maxFieldsLen = String('Fields').length;
@ -302,6 +303,10 @@ function printIndexes(indexes) {
if (l > maxIdLen) {
maxIdLen = l;
}
l = index.name.length;
if (l > maxNameLen) {
maxNameLen = l;
}
l = index.type.length;
if (l > maxTypeLen) {
maxTypeLen = l;
@ -316,6 +321,7 @@ function printIndexes(indexes) {
}
});
var line = ' ' + pad(1 + maxIdLen - String('By').length) + header('By') + ' ' +
header('Name') + pad(1 + maxNameLen - 'Name'.length) + ' ' +
header('Type') + pad(1 + maxTypeLen - 'Type'.length) + ' ' +
header('Collection') + pad(1 + maxCollectionLen - 'Collection'.length) + ' ' +
header('Unique') + pad(1 + maxUniqueLen - 'Unique'.length) + ' ' +
@ -344,6 +350,7 @@ function printIndexes(indexes) {
);
line = ' ' +
pad(1 + maxIdLen - String(indexes[i].node).length) + variable(String(indexes[i].node)) + ' ' +
collection(indexes[i].name) + pad(1 + maxNameLen - indexes[i].name.length) + ' ' +
keyword(indexes[i].type) + pad(1 + maxTypeLen - indexes[i].type.length) + ' ' +
collection(indexes[i].collection) + pad(1 + maxCollectionLen - indexes[i].collection.length) + ' ' +
value(uniqueness) + pad(1 + maxUniqueLen - uniqueness.length) + ' ' +

View File

@ -107,6 +107,17 @@ std::string const StaticStrings::IndexNameEdgeTo("edge_to");
std::string const StaticStrings::IndexNameInaccessible("inaccessible");
std::string const StaticStrings::IndexNamePrimary("primary");
// index hint strings
std::string const StaticStrings::IndexHintAny("any");
std::string const StaticStrings::IndexHintCollection("collection");
std::string const StaticStrings::IndexHintHint("hint");
std::string const StaticStrings::IndexHintDepth("depth");
std::string const StaticStrings::IndexHintInbound("inbound");
std::string const StaticStrings::IndexHintOption("indexHint");
std::string const StaticStrings::IndexHintOptionForce("forceIndexHint");
std::string const StaticStrings::IndexHintOutbound("outbound");
std::string const StaticStrings::IndexHintWildcard("*");
// HTTP headers
std::string const StaticStrings::Accept("accept");
std::string const StaticStrings::AcceptEncoding("accept-encoding");

View File

@ -106,6 +106,17 @@ class StaticStrings {
static std::string const IndexNameInaccessible;
static std::string const IndexNamePrimary;
// index hint strings
static std::string const IndexHintAny;
static std::string const IndexHintCollection;
static std::string const IndexHintHint;
static std::string const IndexHintDepth;
static std::string const IndexHintInbound;
static std::string const IndexHintOption;
static std::string const IndexHintOptionForce;
static std::string const IndexHintOutbound;
static std::string const IndexHintWildcard;
// HTTP headers
static std::string const Accept;
static std::string const AcceptEncoding;

View File

@ -258,6 +258,7 @@ ERROR_QUERY_MULTI_MODIFY,1573,"multi-modify query","Will be raised when an AQL q
ERROR_QUERY_INVALID_AGGREGATE_EXPRESSION,1574,"invalid aggregate expression","Will be raised when an AQL query contains an invalid aggregate expression."
ERROR_QUERY_COMPILE_TIME_OPTIONS,1575,"query options must be readable at query compile time","Will be raised when an AQL data-modification query contains options that cannot be figured out at query compile time."
ERROR_QUERY_EXCEPTION_OPTIONS,1576,"query options expected","Will be raised when an AQL data-modification query contains an invalid options specification."
ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE,1577,"could not use forced index hint","Will be raised when forceIndexHint is specified, and the hint cannot be used to serve the query."
ERROR_QUERY_DISALLOWED_DYNAMIC_CALL,1578,"disallowed dynamic call to '%s'","Will be raised when a dynamic function call is made to a function that cannot be called dynamically."
ERROR_QUERY_ACCESS_AFTER_MODIFICATION,1579,"access after data-modification by %s","Will be raised when collection data are accessed after a data-modification operation."

View File

@ -214,6 +214,7 @@ void TRI_InitializeErrorMessages() {
REG_ERROR(ERROR_QUERY_INVALID_AGGREGATE_EXPRESSION, "invalid aggregate expression");
REG_ERROR(ERROR_QUERY_COMPILE_TIME_OPTIONS, "query options must be readable at query compile time");
REG_ERROR(ERROR_QUERY_EXCEPTION_OPTIONS, "query options expected");
REG_ERROR(ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE, "could not use forced index hint");
REG_ERROR(ERROR_QUERY_DISALLOWED_DYNAMIC_CALL, "disallowed dynamic call to '%s'");
REG_ERROR(ERROR_QUERY_ACCESS_AFTER_MODIFICATION, "access after data-modification by %s");
REG_ERROR(ERROR_QUERY_FUNCTION_INVALID_NAME, "invalid user function name");

View File

@ -1148,6 +1148,12 @@ constexpr int TRI_ERROR_QUERY_COMPILE_TIME_OPTIONS
/// options specification.
constexpr int TRI_ERROR_QUERY_EXCEPTION_OPTIONS = 1576;
/// 1577: ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE
/// "could not use forced index hint"
/// Will be raised when forceIndexHint is specified, and the hint cannot be
/// used to serve the query.
constexpr int TRI_ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE = 1577;
/// 1578: ERROR_QUERY_DISALLOWED_DYNAMIC_CALL
/// "disallowed dynamic call to '%s'"
/// Will be raised when a dynamic function call is made to a function that

View File

@ -0,0 +1,383 @@
/*jshint globalstrict:false, strict:false, maxlen: 500 */
/*global fail, assertEqual, AQL_EXPLAIN */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for Ahuacatl, skiplist index queries
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2016 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2016, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var internal = require("internal");
var db = internal.db;
var jsunity = require("jsunity");
var errors = internal.errors;
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function ahuacatlSkiplistOverlappingTestSuite () {
const getIndexNames = function (query) {
return AQL_EXPLAIN(query, {}, { optimizer: { rules: [ "-all", "+use-indexes" ] } })
.plan.nodes.filter(node => (node.type === 'IndexNode'))
.map(node => node.indexes.map(index => index.name));
};
const cn = 'UnitTestsIndexHints';
let collection;
let defaultEqualityIndex;
let alternateEqualityIndex;
let defaultSortingIndex;
let alternateSortingIndex;
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief set up
////////////////////////////////////////////////////////////////////////////////
setUp : function () {
internal.db._drop(cn);
collection = internal.db._create(cn);
collection.ensureIndex({type: 'hash', name: 'hash_a', fields: ['a']});
collection.ensureIndex({type: 'hash', name: 'hash_a_b', fields: ['a', 'b']});
collection.ensureIndex({type: 'hash', name: 'hash_b_a', fields: ['b', 'a']});
collection.ensureIndex({type: 'skiplist', name: 'skip_a', fields: ['a']});
collection.ensureIndex({type: 'skiplist', name: 'skip_a_b', fields: ['a', 'b']});
collection.ensureIndex({type: 'skiplist', name: 'skip_b_a', fields: ['b', 'a']});
const isMMFiles = db._engine().name === "mmfiles";
defaultEqualityIndex = isMMFiles ? 'skip_a' : 'hash_a';
alternateEqualityIndex = isMMFiles ? 'hash_a' : 'skip_a';
defaultSortingIndex = isMMFiles ? 'skip_a' : 'hash_a';
alternateSortingIndex = 'skip_a_b';
},
tearDown : function () {
internal.db._drop(cn);
},
testFilterNoHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultEqualityIndex);
},
testFilterDefaultHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: '${defaultEqualityIndex}'}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultEqualityIndex);
},
testFilterDefaultHintForced : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: '${defaultEqualityIndex}', forceIndexHint: true}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultEqualityIndex);
},
testFilterNonexistentIndexHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'foo'}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultEqualityIndex);
},
testFilterNonexistentIndexHintForced : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'foo', forceIndexHint: true}
FILTER doc.a == 1
RETURN doc
`;
try {
const usedIndexes = getIndexNames(query);
fail();
} catch (err) {
assertEqual(errors.ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE.code, err.errorNum);
}
},
testFilterUnusableHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'hash_b_a'}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultEqualityIndex);
},
testFilterUnusableHintForced : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'hash_b_a', forceIndexHint: true}
FILTER doc.a == 1
RETURN doc
`;
try {
const usedIndexes = getIndexNames(query);
fail();
} catch (err) {
assertEqual(errors.ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE.code, err.errorNum);
}
},
testFilterTypeHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: '${alternateEqualityIndex}'}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], alternateEqualityIndex);
},
testFilterPartialCoverageHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'skip_a_b'}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], 'skip_a_b');
},
testFilterListFirstHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: ['skip_a_b', '${alternateEqualityIndex}']}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], `skip_a_b`);
},
testFilterListLastHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: ['skip_b_a', '${alternateEqualityIndex}']}
FILTER doc.a == 1
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], alternateEqualityIndex);
},
testFilterNestedMatchedHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: '${alternateEqualityIndex}'}
FILTER doc.a == 1
FOR sub IN ${cn} OPTIONS {indexHint: '${alternateEqualityIndex}'}
FILTER sub.a == 2
RETURN [doc, sub]
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 2);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], alternateEqualityIndex);
assertEqual(usedIndexes[1].length, 1);
assertEqual(usedIndexes[1][0], alternateEqualityIndex);
},
testFilterNestedUnmatchedHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: '${alternateEqualityIndex}'}
FILTER doc.a == 1
FOR sub IN ${cn} OPTIONS {indexHint: 'skip_a_b'}
FILTER sub.a == 2
RETURN [doc, sub]
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 2);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], alternateEqualityIndex);
assertEqual(usedIndexes[1].length, 1);
assertEqual(usedIndexes[1][0], 'skip_a_b');
},
testSortNoHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {}
SORT doc.a
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultSortingIndex);
},
testSortDefaultHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: '${defaultSortingIndex}'}
SORT doc.a
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultSortingIndex);
},
testSortDefaultHintForced : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: '${defaultSortingIndex}', forceIndexHint: true}
SORT doc.a
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultSortingIndex);
},
testSortNonexistentIndexHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'foo'}
SORT doc.a
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultSortingIndex);
},
testSortNonexistentIndexHintForced : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'foo', forceIndexHint: true}
SORT doc.a
RETURN doc
`;
try {
const usedIndexes = getIndexNames(query);
fail();
} catch (err) {
assertEqual(errors.ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE.code, err.errorNum);
}
},
testSortUnusableHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'skip_b_a'}
SORT doc.a
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], defaultSortingIndex);
},
testSortUnusableHintForced : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: 'skip_b_a', forceIndexHint: true}
SORT doc.a
RETURN doc
`;
try {
const usedIndexes = getIndexNames(query);
fail();
} catch (err) {
assertEqual(errors.ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE.code, err.errorNum);
}
},
testSortPartialCoverageHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: '${alternateSortingIndex}'}
SORT doc.a
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], alternateSortingIndex);
},
testSortListFirstHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: ['${alternateSortingIndex}', '${defaultSortingIndex}']}
SORT doc.a
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], alternateSortingIndex);
},
testSortListLastHint : function () {
const query = `
FOR doc IN ${cn} OPTIONS {indexHint: ['skip_b_a', '${alternateSortingIndex}']}
SORT doc.a
RETURN doc
`;
const usedIndexes = getIndexNames(query);
assertEqual(usedIndexes.length, 1);
assertEqual(usedIndexes[0].length, 1);
assertEqual(usedIndexes[0][0], alternateSortingIndex);
},
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suite
////////////////////////////////////////////////////////////////////////////////
jsunity.run(ahuacatlSkiplistOverlappingTestSuite);
return jsunity.done();