mirror of https://gitee.com/bigwinds/arangodb
Index hints (#8431)
This commit is contained in:
parent
29b941a677
commit
2eadab33e7
|
@ -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.
|
||||||
|
|
|
@ -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>]}
|
||||||
|
```
|
||||||
|
|
|
@ -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
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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" },
|
||||||
|
|
|
@ -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) + ' ' +
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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."
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
Loading…
Reference in New Issue