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
|
||||
the databases on restore.
|
||||
|
||||
* added index hints feature to AQL
|
||||
|
||||
* added "name" property for indices
|
||||
|
||||
If a name is not specified on index creation, one will be auto-generated.
|
||||
|
|
|
@ -33,7 +33,7 @@ required that *expression* returns an array in all cases. The empty array is
|
|||
allowed, too. The current array element is made available for further processing
|
||||
in the variable specified by *variableName*.
|
||||
|
||||
```
|
||||
```js
|
||||
FOR u IN users
|
||||
RETURN u
|
||||
```
|
||||
|
@ -52,7 +52,7 @@ placed in is closed.
|
|||
|
||||
Another example that uses a statically declared array of values to iterate over:
|
||||
|
||||
```
|
||||
```js
|
||||
FOR year IN [ 2011, 2012, 2013 ]
|
||||
RETURN { "year" : year, "isLeapYear" : year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }
|
||||
```
|
||||
|
@ -61,7 +61,7 @@ Nesting of multiple *FOR* statements is allowed, too. When *FOR* statements are
|
|||
nested, a cross product of the array elements returned by the individual *FOR*
|
||||
statements will be created.
|
||||
|
||||
```
|
||||
```js
|
||||
FOR u IN users
|
||||
FOR l IN locations
|
||||
RETURN { "user" : u, "location" : l }
|
||||
|
@ -72,3 +72,39 @@ In this example, there are two array iterations: an outer iteration over the arr
|
|||
traversed as many times as there are elements in the outer array. For each
|
||||
iteration, the current values of *users* and *locations* are made available for
|
||||
further processing in the variable *u* and *l*.
|
||||
|
||||
## Options
|
||||
|
||||
For collections and views, the `FOR` construct supports an optional `OPTIONS`
|
||||
suffix to modify behavior. The general syntax is:
|
||||
|
||||
```js
|
||||
FOR variableName IN expression OPTIONS {option: value, ...}
|
||||
```
|
||||
|
||||
### Index hints
|
||||
|
||||
For collections, index hints are provided though this inline options mechanism.
|
||||
Hints can be specified in two different formats.
|
||||
|
||||
The first format option is the simplest, just a single index name. This should
|
||||
be sufficient for many cases. Whenever there is a choice to potentially use an
|
||||
index for this `FOR` loop, the optimizer will first check if the specified index
|
||||
can be used. If so, it will use it, regardless of whether it would normally use
|
||||
a different index. If it cannot use that index, then it will fall back to its
|
||||
normal logic to select another index. If the optional `forceIndexHint: true` is
|
||||
specified, then it will not fall back, and instead generate an error.
|
||||
|
||||
```js
|
||||
OPTIONS {indexHint: 'byName'[, forceIndexHint: <boolean>]}
|
||||
```
|
||||
|
||||
The second is an array of index names, in order of preference. When specified
|
||||
this way, the optimizer will behave much in the same way as above, but will
|
||||
check the feasibility of each of the specified indices, in the order they are
|
||||
given, falling back to its normal logic or failing only if none of the specified
|
||||
indices are feasible.
|
||||
|
||||
```js
|
||||
OPTIONS {indexHint: ['byName', 'byColor'][, forceIndexHint: <boolean>]}
|
||||
```
|
||||
|
|
|
@ -262,6 +262,14 @@ name _cannot_ be changed after index creation. No two indices on the same
|
|||
collection may share the same name, but two indices on different collections
|
||||
may.
|
||||
|
||||
### Index Hints in AQL
|
||||
|
||||
Users may now take advantage of the `indexHint` inline query option to override
|
||||
the internal optimizer decision regarding which index to use to serve content
|
||||
from a given collection. The index hint works with the named indices feature
|
||||
above, making it easy to specify which index to use.
|
||||
|
||||
|
||||
Client tools
|
||||
------------
|
||||
|
||||
|
|
|
@ -462,12 +462,14 @@ std::pair<bool, bool> Condition::findIndexes(EnumerateCollectionNode const* node
|
|||
}
|
||||
if (_root == nullptr) {
|
||||
size_t dummy;
|
||||
return std::make_pair<bool, bool>(false, trx->getIndexForSortCondition(collectionName, sortCondition, reference,
|
||||
itemsInIndex, usedIndexes, dummy));
|
||||
return std::make_pair<bool, bool>(
|
||||
false, trx->getIndexForSortCondition(collectionName, sortCondition, reference, itemsInIndex,
|
||||
node->hint(), usedIndexes, dummy));
|
||||
}
|
||||
|
||||
return trx->getBestIndexHandlesForFilterCondition(collectionName, _ast, _root,
|
||||
reference, sortCondition, itemsInIndex,
|
||||
reference, sortCondition,
|
||||
itemsInIndex, node->hint(),
|
||||
usedIndexes, _isSorted);
|
||||
}
|
||||
|
||||
|
|
|
@ -1275,7 +1275,8 @@ EnumerateCollectionNode::EnumerateCollectionNode(ExecutionPlan* plan,
|
|||
: ExecutionNode(plan, base),
|
||||
DocumentProducingNode(plan, base),
|
||||
CollectionAccessingNode(plan, base),
|
||||
_random(base.get("random").getBoolean()) {}
|
||||
_random(base.get("random").getBoolean()),
|
||||
_hint(base) {}
|
||||
|
||||
/// @brief toVelocyPack, for EnumerateCollectionNode
|
||||
void EnumerateCollectionNode::toVelocyPackHelper(VPackBuilder& builder, unsigned flags) const {
|
||||
|
@ -1284,6 +1285,8 @@ void EnumerateCollectionNode::toVelocyPackHelper(VPackBuilder& builder, unsigned
|
|||
|
||||
builder.add("random", VPackValue(_random));
|
||||
|
||||
_hint.toVelocyPack(builder);
|
||||
|
||||
// add outvariable and projection
|
||||
DocumentProducingNode::toVelocyPack(builder);
|
||||
|
||||
|
@ -1325,7 +1328,7 @@ ExecutionNode* EnumerateCollectionNode::clone(ExecutionPlan* plan, bool withDepe
|
|||
}
|
||||
|
||||
auto c = std::make_unique<EnumerateCollectionNode>(plan, _id, _collection,
|
||||
outVariable, _random);
|
||||
outVariable, _random, _hint);
|
||||
|
||||
c->projections(_projections);
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
#include "Aql/CostEstimate.h"
|
||||
#include "Aql/DocumentProducingNode.h"
|
||||
#include "Aql/Expression.h"
|
||||
#include "Aql/IndexHint.h"
|
||||
#include "Aql/Variable.h"
|
||||
#include "Aql/WalkerWorker.h"
|
||||
#include "Aql/types.h"
|
||||
|
@ -637,11 +638,12 @@ class EnumerateCollectionNode : public ExecutionNode,
|
|||
/// @brief constructor with a vocbase and a collection name
|
||||
public:
|
||||
EnumerateCollectionNode(ExecutionPlan* plan, size_t id, aql::Collection const* collection,
|
||||
Variable const* outVariable, bool random)
|
||||
Variable const* outVariable, bool random, IndexHint const& hint)
|
||||
: ExecutionNode(plan, id),
|
||||
DocumentProducingNode(outVariable),
|
||||
CollectionAccessingNode(collection),
|
||||
_random(random) {}
|
||||
_random(random),
|
||||
_hint(hint) {}
|
||||
|
||||
EnumerateCollectionNode(ExecutionPlan* plan, arangodb::velocypack::Slice const& base);
|
||||
|
||||
|
@ -675,9 +677,15 @@ class EnumerateCollectionNode : public ExecutionNode,
|
|||
/// @brief enable random iteration of documents in collection
|
||||
void setRandom() { _random = true; }
|
||||
|
||||
/// @brief user hint regarding which index ot use
|
||||
IndexHint const& hint() const { return _hint; }
|
||||
|
||||
private:
|
||||
/// @brief whether or not we want random iteration
|
||||
bool _random;
|
||||
|
||||
/// @brief a possible hint from the user regarding which index to use
|
||||
IndexHint _hint;
|
||||
};
|
||||
|
||||
/// @brief class EnumerateListNode
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "Aql/ExecutionNode.h"
|
||||
#include "Aql/Expression.h"
|
||||
#include "Aql/Function.h"
|
||||
#include "Aql/IndexHint.h"
|
||||
#include "Aql/ModificationNodes.h"
|
||||
#include "Aql/NodeFinder.h"
|
||||
#include "Aql/OptimizerRulesFeature.h"
|
||||
|
@ -842,7 +843,8 @@ ExecutionNode* ExecutionPlan::fromNodeFor(ExecutionNode* previous, AstNode const
|
|||
|
||||
auto variable = node->getMember(0);
|
||||
auto expression = node->getMember(1);
|
||||
// TODO: process FOR options here if we want to use them later
|
||||
auto options = node->getMember(2);
|
||||
IndexHint hint(options);
|
||||
|
||||
// fetch 1st operand (out variable name)
|
||||
TRI_ASSERT(variable->type == NODE_TYPE_VARIABLE);
|
||||
|
@ -862,7 +864,7 @@ ExecutionNode* ExecutionPlan::fromNodeFor(ExecutionNode* previous, AstNode const
|
|||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
|
||||
"no collection for EnumerateCollection");
|
||||
}
|
||||
en = registerNode(new EnumerateCollectionNode(this, nextId(), collection, v, false));
|
||||
en = registerNode(new EnumerateCollectionNode(this, nextId(), collection, v, false, hint));
|
||||
#ifdef USE_IRESEARCH
|
||||
} else if (expression->type == NODE_TYPE_VIEW) {
|
||||
// second operand is a view
|
||||
|
|
|
@ -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;
|
||||
auto trx = _plan->getAst()->query()->trx();
|
||||
size_t coveredAttributes = 0;
|
||||
bool canBeUsed =
|
||||
trx->getIndexForSortCondition(enumerateCollectionNode->collection()->name(),
|
||||
&sortCondition, outVariable,
|
||||
enumerateCollectionNode->collection()->count(trx),
|
||||
usedIndexes, coveredAttributes);
|
||||
bool canBeUsed = trx->getIndexForSortCondition(
|
||||
enumerateCollectionNode->collection()->name(), &sortCondition,
|
||||
outVariable, enumerateCollectionNode->collection()->count(trx),
|
||||
enumerateCollectionNode->hint(), usedIndexes, coveredAttributes);
|
||||
if (canBeUsed) {
|
||||
// If this bit is set, then usedIndexes has length exactly one
|
||||
// and contains the best index found.
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "Aql/ExecutionNode.h"
|
||||
#include "Aql/ExecutionPlan.h"
|
||||
#include "Aql/Function.h"
|
||||
#include "Aql/IndexHint.h"
|
||||
#include "Aql/IndexNode.h"
|
||||
#include "Aql/Optimizer.h"
|
||||
#include "Aql/Query.h"
|
||||
|
@ -266,7 +267,7 @@ AstNode* replaceNearOrWithin(AstNode* funAstNode, ExecutionNode* calcNode,
|
|||
ExecutionNode* eEnumerate = plan->registerNode(
|
||||
// link output of index with the return node
|
||||
new EnumerateCollectionNode(plan, plan->nextId(), aqlCollection,
|
||||
enumerateOutVariable, false));
|
||||
enumerateOutVariable, false, IndexHint()));
|
||||
|
||||
//// build sort condition - DISTANCE(d.lat, d.long, param.lat, param.lon)
|
||||
auto* docRef = ast->createNodeReference(enumerateOutVariable);
|
||||
|
|
|
@ -227,6 +227,7 @@ SET(ARANGOD_SOURCES
|
|||
Aql/InAndOutRowExpressionContext.cpp
|
||||
Aql/IdExecutor.cpp
|
||||
Aql/IndexExecutor.cpp
|
||||
Aql/IndexHint.cpp
|
||||
Aql/IndexNode.cpp
|
||||
Aql/InputAqlItemRow.cpp
|
||||
Aql/LimitExecutor.cpp
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "Basics/tri-strings.h"
|
||||
#include "Cluster/ClusterComm.h"
|
||||
#include "Cluster/ClusterInfo.h"
|
||||
#include "ClusterMethods.h"
|
||||
#include "Graph/Traverser.h"
|
||||
#include "Indexes/Index.h"
|
||||
#include "RestServer/TtlFeature.h"
|
||||
|
|
|
@ -248,9 +248,10 @@ void BaseOptions::injectLookupInfoInList(std::vector<LookupInfo>& list,
|
|||
aql::AstNode* condition) {
|
||||
LookupInfo info;
|
||||
info.indexCondition = condition->clone(plan->getAst());
|
||||
bool res = _trx->getBestIndexHandleForFilterCondition(collectionName,
|
||||
info.indexCondition, _tmpVar,
|
||||
1000, info.idxHandles[0]);
|
||||
bool res =
|
||||
_trx->getBestIndexHandleForFilterCondition(collectionName, info.indexCondition,
|
||||
_tmpVar, 1000, aql::IndexHint(),
|
||||
info.idxHandles[0]);
|
||||
// Right now we have an enforced edge index which should always fit.
|
||||
if (!res) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
|
||||
|
|
|
@ -46,21 +46,25 @@ EdgeCollectionInfo::EdgeCollectionInfo(transaction::Methods* trx,
|
|||
auto var = _searchBuilder.getVariable();
|
||||
if (_dir == TRI_EDGE_OUT) {
|
||||
auto cond = _searchBuilder.getOutboundCondition();
|
||||
bool worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond,
|
||||
var, 1000, _forwardIndexId);
|
||||
bool worked =
|
||||
_trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var, 1000,
|
||||
aql::IndexHint(), _forwardIndexId);
|
||||
TRI_ASSERT(worked); // We always have an edge Index
|
||||
cond = _searchBuilder.getInboundCondition();
|
||||
worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var,
|
||||
1000, _backwardIndexId);
|
||||
1000, aql::IndexHint(),
|
||||
_backwardIndexId);
|
||||
TRI_ASSERT(worked); // We always have an edge Index
|
||||
} else {
|
||||
auto cond = _searchBuilder.getInboundCondition();
|
||||
bool worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond,
|
||||
var, 1000, _forwardIndexId);
|
||||
bool worked =
|
||||
_trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var, 1000,
|
||||
aql::IndexHint(), _forwardIndexId);
|
||||
TRI_ASSERT(worked); // We always have an edge Index
|
||||
cond = _searchBuilder.getOutboundCondition();
|
||||
worked = _trx->getBestIndexHandleForFilterCondition(_collectionName, cond, var,
|
||||
1000, _backwardIndexId);
|
||||
1000, aql::IndexHint(),
|
||||
_backwardIndexId);
|
||||
TRI_ASSERT(worked); // We always have an edge Index
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,8 +67,9 @@ void RestEdgesHandler::readCursor(aql::AstNode* condition, aql::Variable const*
|
|||
SingleCollectionTransaction& trx,
|
||||
std::function<void(LocalDocumentId const&)> const& cb) {
|
||||
transaction::Methods::IndexHandle indexId;
|
||||
bool foundIdx = trx.getBestIndexHandleForFilterCondition(collectionName, condition,
|
||||
var, 1000, indexId);
|
||||
bool foundIdx =
|
||||
trx.getBestIndexHandleForFilterCondition(collectionName, condition, var,
|
||||
1000, aql::IndexHint(), indexId);
|
||||
if (!foundIdx) {
|
||||
// Right now we enforce an edge index that can exactly! work on this
|
||||
// condition. So it is impossible to not find an index.
|
||||
|
|
|
@ -576,14 +576,16 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
|
|||
std::vector<std::shared_ptr<Index>> const& indexes,
|
||||
arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference,
|
||||
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection,
|
||||
std::vector<transaction::Methods::IndexHandle>& usedIndexes,
|
||||
aql::IndexHint const& hint, std::vector<transaction::Methods::IndexHandle>& usedIndexes,
|
||||
arangodb::aql::AstNode*& specializedCondition, bool& isSparse) const {
|
||||
std::shared_ptr<Index> bestIndex;
|
||||
double bestCost = 0.0;
|
||||
bool bestSupportsFilter = false;
|
||||
bool bestSupportsSort = false;
|
||||
|
||||
for (auto const& idx : indexes) {
|
||||
auto considerIndex = [&bestIndex, &bestCost, &bestSupportsFilter, &bestSupportsSort,
|
||||
&indexes, node, reference, itemsInCollection,
|
||||
sortCondition](std::shared_ptr<Index> const& idx) -> void {
|
||||
double filterCost = 0.0;
|
||||
double sortCost = 0.0;
|
||||
size_t itemsInIndex = itemsInCollection;
|
||||
|
@ -636,7 +638,7 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
|
|||
}
|
||||
|
||||
if (!supportsFilter && !supportsSort) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
double totalCost = filterCost;
|
||||
|
@ -666,6 +668,38 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
|
|||
bestSupportsFilter = supportsFilter;
|
||||
bestSupportsSort = supportsSort;
|
||||
}
|
||||
};
|
||||
|
||||
if (hint.type() == aql::IndexHint::HintType::Simple) {
|
||||
std::vector<std::string> const& hintedIndices = hint.hint();
|
||||
for (std::string const& hinted : hintedIndices) {
|
||||
std::shared_ptr<Index> matched;
|
||||
for (std::shared_ptr<Index> const& idx : indexes) {
|
||||
if (idx->name() == hinted) {
|
||||
matched = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched != nullptr) {
|
||||
considerIndex(matched);
|
||||
if (bestIndex != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hint.isForced() && bestIndex == nullptr) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(
|
||||
TRI_ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE,
|
||||
"could not use index hint to serve query; " + hint.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex == nullptr) {
|
||||
for (auto const& idx : indexes) {
|
||||
considerIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex == nullptr) {
|
||||
|
@ -681,13 +715,14 @@ std::pair<bool, bool> transaction::Methods::findIndexHandleForAndNode(
|
|||
}
|
||||
|
||||
bool transaction::Methods::findIndexHandleForAndNode(
|
||||
std::vector<std::shared_ptr<Index>> const& indexes,
|
||||
arangodb::aql::AstNode*& node, arangodb::aql::Variable const* reference,
|
||||
size_t itemsInCollection, transaction::Methods::IndexHandle& usedIndex) const {
|
||||
std::vector<std::shared_ptr<Index>> const& indexes, arangodb::aql::AstNode*& node,
|
||||
arangodb::aql::Variable const* reference, size_t itemsInCollection,
|
||||
aql::IndexHint const& hint, transaction::Methods::IndexHandle& usedIndex) const {
|
||||
std::shared_ptr<Index> bestIndex;
|
||||
double bestCost = 0.0;
|
||||
|
||||
for (auto const& idx : indexes) {
|
||||
auto considerIndex = [&bestIndex, &bestCost, itemsInCollection, &indexes, &node,
|
||||
reference](std::shared_ptr<Index> const& idx) -> void {
|
||||
size_t itemsInIndex = itemsInCollection;
|
||||
|
||||
// check if the index supports the filter expression
|
||||
|
@ -709,7 +744,7 @@ bool transaction::Methods::findIndexHandleForAndNode(
|
|||
<< ", node: " << node;
|
||||
|
||||
if (!supportsFilter) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// index supports the filter condition
|
||||
|
@ -721,6 +756,38 @@ bool transaction::Methods::findIndexHandleForAndNode(
|
|||
bestIndex = idx;
|
||||
bestCost = estimatedCost;
|
||||
}
|
||||
};
|
||||
|
||||
if (hint.type() == aql::IndexHint::HintType::Simple) {
|
||||
std::vector<std::string> const& hintedIndices = hint.hint();
|
||||
for (std::string const& hinted : hintedIndices) {
|
||||
std::shared_ptr<Index> matched;
|
||||
for (std::shared_ptr<Index> const& idx : indexes) {
|
||||
if (idx->name() == hinted) {
|
||||
matched = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched != nullptr) {
|
||||
considerIndex(matched);
|
||||
if (bestIndex != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hint.isForced() && bestIndex == nullptr) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(
|
||||
TRI_ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE,
|
||||
"could not use index hint to serve query; " + hint.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex == nullptr) {
|
||||
for (auto const& idx : indexes) {
|
||||
considerIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex == nullptr) {
|
||||
|
@ -2755,7 +2822,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
|
|||
std::string const& collectionName, arangodb::aql::Ast* ast,
|
||||
arangodb::aql::AstNode* root, arangodb::aql::Variable const* reference,
|
||||
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection,
|
||||
std::vector<IndexHandle>& usedIndexes, bool& isSorted) {
|
||||
aql::IndexHint const& hint, std::vector<IndexHandle>& usedIndexes, bool& isSorted) {
|
||||
// We can only start after DNF transformation
|
||||
TRI_ASSERT(root->type == arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_NARY_OR);
|
||||
auto indexes = indexesForCollection(collectionName);
|
||||
|
@ -2771,7 +2838,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
|
|||
auto node = root->getMemberUnchecked(i);
|
||||
arangodb::aql::AstNode* specializedCondition = nullptr;
|
||||
auto canUseIndex = findIndexHandleForAndNode(indexes, node, reference, sortCondition,
|
||||
itemsInCollection, usedIndexes,
|
||||
itemsInCollection, hint, usedIndexes,
|
||||
specializedCondition, isSparse);
|
||||
|
||||
if (canUseIndex.second && !canUseIndex.first) {
|
||||
|
@ -2817,7 +2884,7 @@ std::pair<bool, bool> transaction::Methods::getBestIndexHandlesForFilterConditio
|
|||
bool transaction::Methods::getBestIndexHandleForFilterCondition(
|
||||
std::string const& collectionName, arangodb::aql::AstNode*& node,
|
||||
arangodb::aql::Variable const* reference, size_t itemsInCollection,
|
||||
IndexHandle& usedIndex) {
|
||||
aql::IndexHint const& hint, IndexHandle& usedIndex) {
|
||||
// We can only start after DNF transformation and only a single AND
|
||||
TRI_ASSERT(node->type == arangodb::aql::AstNodeType::NODE_TYPE_OPERATOR_NARY_AND);
|
||||
if (node->numMembers() == 0) {
|
||||
|
@ -2829,7 +2896,8 @@ bool transaction::Methods::getBestIndexHandleForFilterCondition(
|
|||
|
||||
// Const cast is save here. Giving computeSpecialization == false
|
||||
// Makes sure node is NOT modified.
|
||||
return findIndexHandleForAndNode(indexes, node, reference, itemsInCollection, usedIndex);
|
||||
return findIndexHandleForAndNode(indexes, node, reference, itemsInCollection,
|
||||
hint, usedIndex);
|
||||
}
|
||||
|
||||
/// @brief Checks if the index supports the filter condition.
|
||||
|
@ -2872,20 +2940,20 @@ std::vector<std::vector<arangodb::basics::AttributeName>> transaction::Methods::
|
|||
bool transaction::Methods::getIndexForSortCondition(
|
||||
std::string const& collectionName, arangodb::aql::SortCondition const* sortCondition,
|
||||
arangodb::aql::Variable const* reference, size_t itemsInIndex,
|
||||
std::vector<IndexHandle>& usedIndexes, size_t& coveredAttributes) {
|
||||
aql::IndexHint const& hint, std::vector<IndexHandle>& usedIndexes,
|
||||
size_t& coveredAttributes) {
|
||||
// We do not have a condition. But we have a sort!
|
||||
if (!sortCondition->isEmpty() && sortCondition->isOnlyAttributeAccess() &&
|
||||
sortCondition->isUnidirectional()) {
|
||||
double bestCost = 0.0;
|
||||
std::shared_ptr<Index> bestIndex;
|
||||
|
||||
auto indexes = indexesForCollection(collectionName);
|
||||
|
||||
for (auto const& idx : indexes) {
|
||||
auto considerIndex = [reference, sortCondition, itemsInIndex, &bestCost, &bestIndex,
|
||||
&coveredAttributes](std::shared_ptr<Index> const& idx) -> void {
|
||||
if (idx->sparse()) {
|
||||
// a sparse index may exclude some documents, so it can't be used to
|
||||
// get a sorted view of the ENTIRE collection
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
double sortCost = 0.0;
|
||||
size_t covered = 0;
|
||||
|
@ -2897,6 +2965,40 @@ bool transaction::Methods::getIndexForSortCondition(
|
|||
coveredAttributes = covered;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto indexes = indexesForCollection(collectionName);
|
||||
|
||||
if (hint.type() == aql::IndexHint::HintType::Simple) {
|
||||
std::vector<std::string> const& hintedIndices = hint.hint();
|
||||
for (std::string const& hinted : hintedIndices) {
|
||||
std::shared_ptr<Index> matched;
|
||||
for (std::shared_ptr<Index> const& idx : indexes) {
|
||||
if (idx->name() == hinted) {
|
||||
matched = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched != nullptr) {
|
||||
considerIndex(matched);
|
||||
if (bestIndex != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hint.isForced() && bestIndex == nullptr) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(
|
||||
TRI_ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE,
|
||||
"could not use index hint to serve query; " + hint.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex == nullptr) {
|
||||
for (auto const& idx : indexes) {
|
||||
considerIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex != nullptr) {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#ifndef ARANGOD_TRANSACTION_METHODS_H
|
||||
#define ARANGOD_TRANSACTION_METHODS_H 1
|
||||
|
||||
#include "Aql/IndexHint.h"
|
||||
#include "Basics/Common.h"
|
||||
#include "Basics/Exceptions.h"
|
||||
#include "Basics/Result.h"
|
||||
|
@ -331,7 +332,7 @@ class Methods {
|
|||
ENTERPRISE_VIRT std::pair<bool, bool> getBestIndexHandlesForFilterCondition(
|
||||
std::string const&, arangodb::aql::Ast*, arangodb::aql::AstNode*,
|
||||
arangodb::aql::Variable const*, arangodb::aql::SortCondition const*,
|
||||
size_t, std::vector<IndexHandle>&, bool&);
|
||||
size_t, aql::IndexHint const&, std::vector<IndexHandle>&, bool&);
|
||||
|
||||
/// @brief Gets the best fitting index for one specific condition.
|
||||
/// Difference to IndexHandles: Condition is only one NARY_AND
|
||||
|
@ -339,10 +340,9 @@ class Methods {
|
|||
/// Returns false if no index could be found.
|
||||
/// If it returned true, the AstNode contains the specialized condition
|
||||
|
||||
ENTERPRISE_VIRT bool getBestIndexHandleForFilterCondition(std::string const&,
|
||||
arangodb::aql::AstNode*&,
|
||||
arangodb::aql::Variable const*,
|
||||
size_t, IndexHandle&);
|
||||
ENTERPRISE_VIRT bool getBestIndexHandleForFilterCondition(
|
||||
std::string const&, arangodb::aql::AstNode*&,
|
||||
arangodb::aql::Variable const*, size_t, aql::IndexHint const&, IndexHandle&);
|
||||
|
||||
/// @brief Checks if the index supports the filter condition.
|
||||
/// note: the caller must have read-locked the underlying collection when
|
||||
|
@ -362,7 +362,8 @@ class Methods {
|
|||
ENTERPRISE_VIRT bool getIndexForSortCondition(std::string const&,
|
||||
arangodb::aql::SortCondition const*,
|
||||
arangodb::aql::Variable const*,
|
||||
size_t, std::vector<IndexHandle>&,
|
||||
size_t, aql::IndexHint const&,
|
||||
std::vector<IndexHandle>&,
|
||||
size_t& coveredAttributes);
|
||||
|
||||
/// @brief factory for IndexIterator objects from AQL
|
||||
|
@ -562,13 +563,14 @@ class Methods {
|
|||
std::vector<std::shared_ptr<Index>> const& indexes,
|
||||
arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference,
|
||||
arangodb::aql::SortCondition const* sortCondition, size_t itemsInCollection,
|
||||
std::vector<transaction::Methods::IndexHandle>& usedIndexes,
|
||||
aql::IndexHint const& hint, std::vector<transaction::Methods::IndexHandle>& usedIndexes,
|
||||
arangodb::aql::AstNode*& specializedCondition, bool& isSparse) const;
|
||||
|
||||
/// @brief findIndexHandleForAndNode, Shorthand which does not support Sort
|
||||
bool findIndexHandleForAndNode(std::vector<std::shared_ptr<Index>> const& indexes,
|
||||
arangodb::aql::AstNode*& node,
|
||||
arangodb::aql::Variable const* reference, size_t itemsInCollection,
|
||||
arangodb::aql::Variable const* reference,
|
||||
size_t itemsInCollection, aql::IndexHint const& hint,
|
||||
transaction::Methods::IndexHandle& usedIndex) const;
|
||||
|
||||
/// @brief Get one index by id for a collection name, coordinator case
|
||||
|
|
|
@ -215,6 +215,7 @@
|
|||
"ERROR_QUERY_INVALID_AGGREGATE_EXPRESSION" : { "code" : 1574, "message" : "invalid aggregate expression" },
|
||||
"ERROR_QUERY_COMPILE_TIME_OPTIONS" : { "code" : 1575, "message" : "query options must be readable at query compile time" },
|
||||
"ERROR_QUERY_EXCEPTION_OPTIONS" : { "code" : 1576, "message" : "query options expected" },
|
||||
"ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE" : { "code" : 1577, "message" : "could not use forced index hint" },
|
||||
"ERROR_QUERY_DISALLOWED_DYNAMIC_CALL" : { "code" : 1578, "message" : "disallowed dynamic call to '%s'" },
|
||||
"ERROR_QUERY_ACCESS_AFTER_MODIFICATION" : { "code" : 1579, "message" : "access after data-modification by %s" },
|
||||
"ERROR_QUERY_FUNCTION_INVALID_NAME" : { "code" : 1580, "message" : "invalid user function name" },
|
||||
|
|
|
@ -294,6 +294,7 @@ function printIndexes(indexes) {
|
|||
var maxCollectionLen = String('Collection').length;
|
||||
var maxUniqueLen = String('Unique').length;
|
||||
var maxSparseLen = String('Sparse').length;
|
||||
var maxNameLen = String('Name').length;
|
||||
var maxTypeLen = String('Type').length;
|
||||
var maxSelectivityLen = String('Selectivity').length;
|
||||
var maxFieldsLen = String('Fields').length;
|
||||
|
@ -302,6 +303,10 @@ function printIndexes(indexes) {
|
|||
if (l > maxIdLen) {
|
||||
maxIdLen = l;
|
||||
}
|
||||
l = index.name.length;
|
||||
if (l > maxNameLen) {
|
||||
maxNameLen = l;
|
||||
}
|
||||
l = index.type.length;
|
||||
if (l > maxTypeLen) {
|
||||
maxTypeLen = l;
|
||||
|
@ -316,6 +321,7 @@ function printIndexes(indexes) {
|
|||
}
|
||||
});
|
||||
var line = ' ' + pad(1 + maxIdLen - String('By').length) + header('By') + ' ' +
|
||||
header('Name') + pad(1 + maxNameLen - 'Name'.length) + ' ' +
|
||||
header('Type') + pad(1 + maxTypeLen - 'Type'.length) + ' ' +
|
||||
header('Collection') + pad(1 + maxCollectionLen - 'Collection'.length) + ' ' +
|
||||
header('Unique') + pad(1 + maxUniqueLen - 'Unique'.length) + ' ' +
|
||||
|
@ -344,6 +350,7 @@ function printIndexes(indexes) {
|
|||
);
|
||||
line = ' ' +
|
||||
pad(1 + maxIdLen - String(indexes[i].node).length) + variable(String(indexes[i].node)) + ' ' +
|
||||
collection(indexes[i].name) + pad(1 + maxNameLen - indexes[i].name.length) + ' ' +
|
||||
keyword(indexes[i].type) + pad(1 + maxTypeLen - indexes[i].type.length) + ' ' +
|
||||
collection(indexes[i].collection) + pad(1 + maxCollectionLen - indexes[i].collection.length) + ' ' +
|
||||
value(uniqueness) + pad(1 + maxUniqueLen - uniqueness.length) + ' ' +
|
||||
|
|
|
@ -107,6 +107,17 @@ std::string const StaticStrings::IndexNameEdgeTo("edge_to");
|
|||
std::string const StaticStrings::IndexNameInaccessible("inaccessible");
|
||||
std::string const StaticStrings::IndexNamePrimary("primary");
|
||||
|
||||
// index hint strings
|
||||
std::string const StaticStrings::IndexHintAny("any");
|
||||
std::string const StaticStrings::IndexHintCollection("collection");
|
||||
std::string const StaticStrings::IndexHintHint("hint");
|
||||
std::string const StaticStrings::IndexHintDepth("depth");
|
||||
std::string const StaticStrings::IndexHintInbound("inbound");
|
||||
std::string const StaticStrings::IndexHintOption("indexHint");
|
||||
std::string const StaticStrings::IndexHintOptionForce("forceIndexHint");
|
||||
std::string const StaticStrings::IndexHintOutbound("outbound");
|
||||
std::string const StaticStrings::IndexHintWildcard("*");
|
||||
|
||||
// HTTP headers
|
||||
std::string const StaticStrings::Accept("accept");
|
||||
std::string const StaticStrings::AcceptEncoding("accept-encoding");
|
||||
|
|
|
@ -106,6 +106,17 @@ class StaticStrings {
|
|||
static std::string const IndexNameInaccessible;
|
||||
static std::string const IndexNamePrimary;
|
||||
|
||||
// index hint strings
|
||||
static std::string const IndexHintAny;
|
||||
static std::string const IndexHintCollection;
|
||||
static std::string const IndexHintHint;
|
||||
static std::string const IndexHintDepth;
|
||||
static std::string const IndexHintInbound;
|
||||
static std::string const IndexHintOption;
|
||||
static std::string const IndexHintOptionForce;
|
||||
static std::string const IndexHintOutbound;
|
||||
static std::string const IndexHintWildcard;
|
||||
|
||||
// HTTP headers
|
||||
static std::string const Accept;
|
||||
static std::string const AcceptEncoding;
|
||||
|
|
|
@ -258,6 +258,7 @@ ERROR_QUERY_MULTI_MODIFY,1573,"multi-modify query","Will be raised when an AQL q
|
|||
ERROR_QUERY_INVALID_AGGREGATE_EXPRESSION,1574,"invalid aggregate expression","Will be raised when an AQL query contains an invalid aggregate expression."
|
||||
ERROR_QUERY_COMPILE_TIME_OPTIONS,1575,"query options must be readable at query compile time","Will be raised when an AQL data-modification query contains options that cannot be figured out at query compile time."
|
||||
ERROR_QUERY_EXCEPTION_OPTIONS,1576,"query options expected","Will be raised when an AQL data-modification query contains an invalid options specification."
|
||||
ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE,1577,"could not use forced index hint","Will be raised when forceIndexHint is specified, and the hint cannot be used to serve the query."
|
||||
ERROR_QUERY_DISALLOWED_DYNAMIC_CALL,1578,"disallowed dynamic call to '%s'","Will be raised when a dynamic function call is made to a function that cannot be called dynamically."
|
||||
ERROR_QUERY_ACCESS_AFTER_MODIFICATION,1579,"access after data-modification by %s","Will be raised when collection data are accessed after a data-modification operation."
|
||||
|
||||
|
|
|
@ -214,6 +214,7 @@ void TRI_InitializeErrorMessages() {
|
|||
REG_ERROR(ERROR_QUERY_INVALID_AGGREGATE_EXPRESSION, "invalid aggregate expression");
|
||||
REG_ERROR(ERROR_QUERY_COMPILE_TIME_OPTIONS, "query options must be readable at query compile time");
|
||||
REG_ERROR(ERROR_QUERY_EXCEPTION_OPTIONS, "query options expected");
|
||||
REG_ERROR(ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE, "could not use forced index hint");
|
||||
REG_ERROR(ERROR_QUERY_DISALLOWED_DYNAMIC_CALL, "disallowed dynamic call to '%s'");
|
||||
REG_ERROR(ERROR_QUERY_ACCESS_AFTER_MODIFICATION, "access after data-modification by %s");
|
||||
REG_ERROR(ERROR_QUERY_FUNCTION_INVALID_NAME, "invalid user function name");
|
||||
|
|
|
@ -1148,6 +1148,12 @@ constexpr int TRI_ERROR_QUERY_COMPILE_TIME_OPTIONS
|
|||
/// options specification.
|
||||
constexpr int TRI_ERROR_QUERY_EXCEPTION_OPTIONS = 1576;
|
||||
|
||||
/// 1577: ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE
|
||||
/// "could not use forced index hint"
|
||||
/// Will be raised when forceIndexHint is specified, and the hint cannot be
|
||||
/// used to serve the query.
|
||||
constexpr int TRI_ERROR_QUERY_FORCED_INDEX_HINT_UNUSABLE = 1577;
|
||||
|
||||
/// 1578: ERROR_QUERY_DISALLOWED_DYNAMIC_CALL
|
||||
/// "disallowed dynamic call to '%s'"
|
||||
/// Will be raised when a dynamic function call is made to a function that
|
||||
|
|
|
@ -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