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 be used that has access to the `_system` database, in order to create
the databases on restore. the databases on restore.
* added index hints feature to AQL
* added "name" property for indices * added "name" property for indices
If a name is not specified on index creation, one will be auto-generated. 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 allowed, too. The current array element is made available for further processing
in the variable specified by *variableName*. in the variable specified by *variableName*.
``` ```js
FOR u IN users FOR u IN users
RETURN u RETURN u
``` ```
@ -52,7 +52,7 @@ placed in is closed.
Another example that uses a statically declared array of values to iterate over: Another example that uses a statically declared array of values to iterate over:
``` ```js
FOR year IN [ 2011, 2012, 2013 ] FOR year IN [ 2011, 2012, 2013 ]
RETURN { "year" : year, "isLeapYear" : year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } 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* nested, a cross product of the array elements returned by the individual *FOR*
statements will be created. statements will be created.
``` ```js
FOR u IN users FOR u IN users
FOR l IN locations FOR l IN locations
RETURN { "user" : u, "location" : l } 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 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 iteration, the current values of *users* and *locations* are made available for
further processing in the variable *u* and *l*. 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 collection may share the same name, but two indices on different collections
may. 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 Client tools
------------ ------------

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@
#include "Aql/ExecutionNode.h" #include "Aql/ExecutionNode.h"
#include "Aql/Expression.h" #include "Aql/Expression.h"
#include "Aql/Function.h" #include "Aql/Function.h"
#include "Aql/IndexHint.h"
#include "Aql/ModificationNodes.h" #include "Aql/ModificationNodes.h"
#include "Aql/NodeFinder.h" #include "Aql/NodeFinder.h"
#include "Aql/OptimizerRulesFeature.h" #include "Aql/OptimizerRulesFeature.h"
@ -842,7 +843,8 @@ ExecutionNode* ExecutionPlan::fromNodeFor(ExecutionNode* previous, AstNode const
auto variable = node->getMember(0); auto variable = node->getMember(0);
auto expression = node->getMember(1); 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) // fetch 1st operand (out variable name)
TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE); 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, THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"no collection for EnumerateCollection"); "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 #ifdef USE_IRESEARCH
} else if (expression->type == NODE_TYPE_VIEW) { } else if (expression->type == NODE_TYPE_VIEW) {
// second operand is a 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; std::vector<transaction::Methods::IndexHandle> usedIndexes;
auto trx = _plan->getAst()->query()->trx(); auto trx = _plan->getAst()->query()->trx();
size_t coveredAttributes = 0; size_t coveredAttributes = 0;
bool canBeUsed = bool canBeUsed = trx->getIndexForSortCondition(
trx->getIndexForSortCondition(enumerateCollectionNode->collection()->name(), enumerateCollectionNode->collection()->name(), &sortCondition,
&sortCondition, outVariable, outVariable, enumerateCollectionNode->collection()->count(trx),
enumerateCollectionNode->collection()->count(trx), enumerateCollectionNode->hint(), usedIndexes, coveredAttributes);
usedIndexes, coveredAttributes);
if (canBeUsed) { if (canBeUsed) {
// If this bit is set, then usedIndexes has length exactly one // If this bit is set, then usedIndexes has length exactly one
// and contains the best index found. // and contains the best index found.

View File

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

View File

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

View File

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

View File

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

View File

@ -46,21 +46,25 @@ EdgeCollectionInfo::EdgeCollectionInfo(transaction::Methods* trx,
auto var = _searchBuilder.getVariable(); auto var = _searchBuilder.getVariable();
if (_dir == TRI_EDGE_OUT) { if (_dir == TRI_EDGE_OUT) {
auto cond = _searchBuilder.getOutboundCondition(); auto cond = _searchBuilder.getOutboundCondition();
bool worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, bool worked =
var, 1000, _forwardIndexId); _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var, 1000,
aql::IndexHint(), _forwardIndexId);
TRI_ASSERT(worked); // We always have an edge Index TRI_ASSERT(worked); // We always have an edge Index
cond = _searchBuilder.getInboundCondition(); cond = _searchBuilder.getInboundCondition();
worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var, worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var,
1000, _backwardIndexId); 1000, aql::IndexHint(),
_backwardIndexId);
TRI_ASSERT(worked); // We always have an edge Index TRI_ASSERT(worked); // We always have an edge Index
} else { } else {
auto cond = _searchBuilder.getInboundCondition(); auto cond = _searchBuilder.getInboundCondition();
bool worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, bool worked =
var, 1000, _forwardIndexId); _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var, 1000,
aql::IndexHint(), _forwardIndexId);
TRI_ASSERT(worked); // We always have an edge Index TRI_ASSERT(worked); // We always have an edge Index
cond = _searchBuilder.getOutboundCondition(); cond = _searchBuilder.getOutboundCondition();
worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var, worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var,
1000, _backwardIndexId); 1000, aql::IndexHint(),
_backwardIndexId);
TRI_ASSERT(worked); // We always have an edge Index 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, SingleCollectionTransaction& trx,
std::function<void(LocalDocumentId const&)> const& cb) { std::function<void(LocalDocumentId const&)> const& cb) {
transaction::Methods::IndexHandle indexId; transaction::Methods::IndexHandle indexId;
bool foundIdx = trx.getBestIndexHandleForFilterCondition(collectionName, condition, bool foundIdx =
var, 1000, indexId); trx.getBestIndexHandleForFilterCondition(collectionName, condition, var,
1000, aql::IndexHint(), indexId);
if (!foundIdx) { if (!foundIdx) {
// Right now we enforce an edge index that can exactly! work on this // Right now we enforce an edge index that can exactly! work on this
// condition. So it is impossible to not find an index. // 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, std::vector<std::shared_ptr<Index>> const& indexes,
arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference, arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference,
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection, 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 { arangodb::aql::AstNode*& specializedCondition, bool& isSparse) const {
std::shared_ptr<Index> bestIndex; std::shared_ptr<Index> bestIndex;
double bestCost = 0.0; double bestCost = 0.0;
bool bestSupportsFilter = false; bool bestSupportsFilter = false;
bool bestSupportsSort = 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 filterCost = 0.0;
double sortCost = 0.0; double sortCost = 0.0;
size_t itemsInIndex = itemsInCollection; size_t itemsInIndex = itemsInCollection;
@ -636,7 +638,7 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
} }
if (!supportsFilter && !supportsSort) { if (!supportsFilter && !supportsSort) {
continue; return;
} }
double totalCost = filterCost; double totalCost = filterCost;
@ -666,6 +668,38 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
bestSupportsFilter = supportsFilter; bestSupportsFilter = supportsFilter;
bestSupportsSort = supportsSort; 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) { if (bestIndex == nullptr) {
@ -681,13 +715,14 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
} }
bool transaction::Methods::findIndexHandleForAndNode( bool transaction::Methods::findIndexHandleForAndNode(
std::vector<std::shared_ptr<Index>> const& indexes, std::vector<std::shared_ptr<Index>> const& indexes, arangodb::aql::AstNode*& node,
arangodb::aql::AstNode*& node, arangodb::aql::Variable const* reference, arangodb::aql::Variable const* reference, size_t itemsInCollection,
size_t itemsInCollection, transaction::Methods::IndexHandle& usedIndex) const { aql::IndexHint const& hint, transaction::Methods::IndexHandle& usedIndex) const {
std::shared_ptr<Index> bestIndex; std::shared_ptr<Index> bestIndex;
double bestCost = 0.0; 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; size_t itemsInIndex = itemsInCollection;
// check if the index supports the filter expression // check if the index supports the filter expression
@ -709,7 +744,7 @@ bool transaction::Methods::findIndexHandleForAndNode(
<< ", node: " << node; << ", node: " << node;
if (!supportsFilter) { if (!supportsFilter) {
continue; return;
} }
// index supports the filter condition // index supports the filter condition
@ -721,6 +756,38 @@ bool transaction::Methods::findIndexHandleForAndNode(
bestIndex = idx; bestIndex = idx;
bestCost = estimatedCost; 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) { if (bestIndex == nullptr) {
@ -2755,7 +2822,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
std::string const& collectionName, arangodb::aql::Ast* ast, std::string const& collectionName, arangodb::aql::Ast* ast,
arangodb::aql::AstNode* root, arangodb::aql::Variable const* reference, arangodb::aql::AstNode* root, arangodb::aql::Variable const* reference,
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection, 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 // We can only start after DNF transformation
TRI_ASSERT(root->type == arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_NARY_OR); TRI_ASSERT(root->type == arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_NARY_OR);
auto indexes = indexesForCollection(collectionName); auto indexes = indexesForCollection(collectionName);
@ -2771,7 +2838,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
auto node = root->getMemberUnchecked(i); auto node = root->getMemberUnchecked(i);
arangodb::aql::AstNode* specializedCondition = nullptr; arangodb::aql::AstNode* specializedCondition = nullptr;
auto canUseIndex = findIndexHandleForAndNode(indexes, node, reference, sortCondition, auto canUseIndex = findIndexHandleForAndNode(indexes, node, reference, sortCondition,
itemsInCollection, usedIndexes, itemsInCollection, hint, usedIndexes,
specializedCondition, isSparse); specializedCondition, isSparse);
if (canUseIndex.second && !canUseIndex.first) { if (canUseIndex.second && !canUseIndex.first) {
@ -2817,7 +2884,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
bool transaction::Methods::getBestIndexHandleForFilterCondition( bool transaction::Methods::getBestIndexHandleForFilterCondition(
std::string const& collectionName, arangodb::aql::AstNode*& node, std::string const& collectionName, arangodb::aql::AstNode*& node,
arangodb::aql::Variable const* reference, size_t itemsInCollection, 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 // We can only start after DNF transformation and only a single AND
TRI_ASSERT(node->type == arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_NARY_AND); TRI_ASSERT(node->type == arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_NARY_AND);
if (node->numMembers() == 0) { if (node->numMembers() == 0) {
@ -2829,7 +2896,8 @@ bool transaction::Methods::getBestIndexHandleForFilterCondition(
// Const cast is save here. Giving computeSpecialization == false // Const cast is save here. Giving computeSpecialization == false
// Makes sure node is NOT modified. // 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. /// @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( bool transaction::Methods::getIndexForSortCondition(
std::string const& collectionName, arangodb::aql::SortCondition const* sortCondition, std::string const& collectionName, arangodb::aql::SortCondition const* sortCondition,
arangodb::aql::Variable const* reference, size_t itemsInIndex, 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! // We do not have a condition. But we have a sort!
if (!sortCondition->isEmpty() && sortCondition->isOnlyAttributeAccess() && if (!sortCondition->isEmpty() && sortCondition->isOnlyAttributeAccess() &&
sortCondition->isUnidirectional()) { sortCondition->isUnidirectional()) {
double bestCost = 0.0; double bestCost = 0.0;
std::shared_ptr<Index> bestIndex; std::shared_ptr<Index> bestIndex;
auto indexes = indexesForCollection(collectionName); auto considerIndex = [reference, sortCondition, itemsInIndex, &bestCost, &bestIndex,
&coveredAttributes](std::shared_ptr<Index> const& idx) -> void {
for (auto const& idx : indexes) {
if (idx->sparse()) { if (idx->sparse()) {
// a sparse index may exclude some documents, so it can't be used to // a sparse index may exclude some documents, so it can't be used to
// get a sorted view of the ENTIRE collection // get a sorted view of the ENTIRE collection
continue; return;
} }
double sortCost = 0.0; double sortCost = 0.0;
size_t covered = 0; size_t covered = 0;
@ -2897,6 +2965,40 @@ bool transaction::Methods::getIndexForSortCondition(
coveredAttributes = covered; 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) { if (bestIndex != nullptr) {

View File

@ -24,6 +24,7 @@
#ifndef ARANGOD_TRANSACTION_METHODS_H #ifndef ARANGOD_TRANSACTION_METHODS_H
#define ARANGOD_TRANSACTION_METHODS_H 1 #define ARANGOD_TRANSACTION_METHODS_H 1
#include "Aql/IndexHint.h"
#include "Basics/Common.h" #include "Basics/Common.h"
#include "Basics/Exceptions.h" #include "Basics/Exceptions.h"
#include "Basics/Result.h" #include "Basics/Result.h"
@ -331,7 +332,7 @@ class Methods {
ENTERPRISE_VIRT std::pair<bool, bool> getBestIndexHandlesForFilterCondition( ENTERPRISE_VIRT std::pair<bool, bool> getBestIndexHandlesForFilterCondition(
std::string const&, arangodb::aql::Ast*, arangodb::aql::AstNode*, std::string const&, arangodb::aql::Ast*, arangodb::aql::AstNode*,
arangodb::aql::Variable const*, arangodb::aql::SortCondition const*, 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. /// @brief Gets the best fitting index for one specific condition.
/// Difference to IndexHandles: Condition is only one NARY_AND /// Difference to IndexHandles: Condition is only one NARY_AND
@ -339,10 +340,9 @@ class Methods {
/// Returns false if no index could be found. /// Returns false if no index could be found.
/// If it returned true, the AstNode contains the specialized condition /// If it returned true, the AstNode contains the specialized condition
ENTERPRISE_VIRT bool getBestIndexHandleForFilterCondition(std::string const&, ENTERPRISE_VIRT bool getBestIndexHandleForFilterCondition(
arangodb::aql::AstNode*&, std::string const&, arangodb::aql::AstNode*&,
arangodb::aql::Variable const*, arangodb::aql::Variable const*, size_t, aql::IndexHint const&, IndexHandle&);
size_t, IndexHandle&);
/// @brief Checks if the index supports the filter condition. /// @brief Checks if the index supports the filter condition.
/// note: the caller must have read-locked the underlying collection when /// note: the caller must have read-locked the underlying collection when
@ -362,7 +362,8 @@ class Methods {
ENTERPRISE_VIRT bool getIndexForSortCondition(std::string const&, ENTERPRISE_VIRT bool getIndexForSortCondition(std::string const&,
arangodb::aql::SortCondition const*, arangodb::aql::SortCondition const*,
arangodb::aql::Variable const*, arangodb::aql::Variable const*,
size_t, std::vector<IndexHandle>&, size_t, aql::IndexHint const&,
std::vector<IndexHandle>&,
size_t& coveredAttributes); size_t& coveredAttributes);
/// @brief factory for IndexIterator objects from AQL /// @brief factory for IndexIterator objects from AQL
@ -562,13 +563,14 @@ class Methods {
std::vector<std::shared_ptr<Index>> const& indexes, std::vector<std::shared_ptr<Index>> const& indexes,
arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference, arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference,
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection, 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; arangodb::aql::AstNode*& specializedCondition, bool& isSparse) const;
/// @brief findIndexHandleForAndNode, Shorthand which does not support Sort /// @brief findIndexHandleForAndNode, Shorthand which does not support Sort
bool findIndexHandleForAndNode(std::vector<std::shared_ptr<Index>> const& indexes, bool findIndexHandleForAndNode(std::vector<std::shared_ptr<Index>> const& indexes,
arangodb::aql::AstNode*& node, 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; transaction::Methods::IndexHandle& usedIndex) const;
/// @brief Get one index by id for a collection name, coordinator case /// @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_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_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_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_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_ACCESS_AFTER_MODIFICATION" : { "code" : 1579, "message" : "access after data-modification by %s" },
"ERROR_QUERY_FUNCTION_INVALID_NAME" : { "code" : 1580, "message" : "invalid user function name" }, "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 maxCollectionLen = String('Collection').length;
var maxUniqueLen = String('Unique').length; var maxUniqueLen = String('Unique').length;
var maxSparseLen = String('Sparse').length; var maxSparseLen = String('Sparse').length;
var maxNameLen = String('Name').length;
var maxTypeLen = String('Type').length; var maxTypeLen = String('Type').length;
var maxSelectivityLen = String('Selectivity').length; var maxSelectivityLen = String('Selectivity').length;
var maxFieldsLen = String('Fields').length; var maxFieldsLen = String('Fields').length;
@ -302,6 +303,10 @@ function printIndexes(indexes) {
if (l > maxIdLen) { if (l > maxIdLen) {
maxIdLen = l; maxIdLen = l;
} }
l = index.name.length;
if (l > maxNameLen) {
maxNameLen = l;
}
l = index.type.length; l = index.type.length;
if (l > maxTypeLen) { if (l > maxTypeLen) {
maxTypeLen = l; maxTypeLen = l;
@ -316,6 +321,7 @@ function printIndexes(indexes) {
} }
}); });
var line = ' ' + pad(1 + maxIdLen - String('By').length) + header('By') + ' ' + 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('Type') + pad(1 + maxTypeLen - 'Type'.length) + ' ' +
header('Collection') + pad(1 + maxCollectionLen - 'Collection'.length) + ' ' + header('Collection') + pad(1 + maxCollectionLen - 'Collection'.length) + ' ' +
header('Unique') + pad(1 + maxUniqueLen - 'Unique'.length) + ' ' + header('Unique') + pad(1 + maxUniqueLen - 'Unique'.length) + ' ' +
@ -344,6 +350,7 @@ function printIndexes(indexes) {
); );
line = ' ' + line = ' ' +
pad(1 + maxIdLen - String(indexes[i].node).length) + variable(String(indexes[i].node)) + ' ' + 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) + ' ' + keyword(indexes[i].type) + pad(1 + maxTypeLen - indexes[i].type.length) + ' ' +
collection(indexes[i].collection) + pad(1 + maxCollectionLen - indexes[i].collection.length) + ' ' + collection(indexes[i].collection) + pad(1 + maxCollectionLen - indexes[i].collection.length) + ' ' +
value(uniqueness) + pad(1 + maxUniqueLen - uniqueness.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::IndexNameInaccessible("inaccessible");
std::string const StaticStrings::IndexNamePrimary("primary"); 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 // HTTP headers
std::string const StaticStrings::Accept("accept"); std::string const StaticStrings::Accept("accept");
std::string const StaticStrings::AcceptEncoding("accept-encoding"); std::string const StaticStrings::AcceptEncoding("accept-encoding");

View File

@ -106,6 +106,17 @@ class StaticStrings {
static std::string const IndexNameInaccessible; static std::string const IndexNameInaccessible;
static std::string const IndexNamePrimary; 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 // HTTP headers
static std::string const Accept; static std::string const Accept;
static std::string const AcceptEncoding; 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_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_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_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_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." 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_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_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_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_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_ACCESS_AFTER_MODIFICATION, "access after data-modification by %s");
REG_ERROR(ERROR_QUERY_FUNCTION_INVALID_NAME, "invalid user function name"); 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. /// options specification.
constexpr int TRI_ERROR_QUERY_EXCEPTION_OPTIONS = 1576; 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 /// 1578: ERROR_QUERY_DISALLOWED_DYNAMIC_CALL
/// "disallowed dynamic call to '%s'" /// "disallowed dynamic call to '%s'"
/// Will be raised when a dynamic function call is made to a function that /// 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();