8000 add more indexHints: `disableIndex` and `maxProjections` (#15446) · strogo/arangodb@ee13389 · GitHub
[go: up one dir, main page]

Skip to content

Commit ee13389

Browse files
jsteemannmchacki
andauthored
add more indexHints: disableIndex and maxProjections (arangodb#15446)
Co-authored-by: Michael Hackstein <michael@arangodb.com>
1 parent c9f2e99 commit ee13389

22 files changed

+1242
-797
lines changed

CHANGELOG

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
11
devel
22
-----
33

4+
* Added `disableIndex` index hint for AQL FOR loops. This index hint disables
5+
the usage of any index (except geo or full text indexes) and will cause a
6+
full scan over the collection.
7+
In some circumstances a full scan can be more efficient than an index scan,
8+
for example if the index scan produces many matches (close to the number of
9+
documents in the collection) and the index is not fully covering the query.
10+
The `disableIndex` hint can be given per FOR loop in the query, e.g.:
11+
12+
FOR doc IN collection OPTIONS { disableIndex: true }
13+
RETURN doc.value
14+
15+
The default value of `disableIndex` is `false`.
16+
In case a different index hint is provided, `disableIndex: true` takes
17+
precendence and produces a warning about the ambiguous settings.
18+
19+
* Added `maxProjections` hint for AQL FOR loops. This hint can be used to set
20+
the maximum number of document attributes that are taken into account for
21+
using projections.
22+
23+
For example, in the following query, no projections will be used because the
24+
number of potential projection attributes (`value1`, value2`, `value3`) is
25+
higher than the maximum number of projection attributes set via the
26+
`maxProjections` option:
27+
28+
FOR doc IN collection OPTIONS { maxProjections: 2 }
29+
RETURN [ doc.value1, doc.value2, doc.value3 ]
30+
31+
The default value for `maxProjections` is `5`, which is compatible with the
32+
previous hard-coded default value.
33+
434
* Fixed potentially undefined behavior for exit code of arangodump.
535

636
* Ignore signals such as SIGPIPE in client tools.

arangod/Aql/ConditionFinder.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ bool ConditionFinder::before(ExecutionNode* en) {
169169
// copy over the read-own-writes flag from EnumerateCollectionNode
170170
// to IndexNode
171171
idx->setCanReadOwnWrites(node->canReadOwnWrites());
172+
// copy max number of projections
173+
idx->setMaxProjections(node->maxProjections());
172174
return idx;
173175
}));
174176
}

arangod/Aql/DocumentProducingNode.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,14 @@ using namespace arangodb::aql;
4141

4242
namespace {
4343
std::string_view const filterKey("filter");
44+
std::string_view const maxProjectionsKey("maxProjections");
4445
std::string_view const producesResultKey("producesResult");
4546
} // namespace
4647

4748
DocumentProducingNode::DocumentProducingNode(Variable const* outVariable)
48-
: _outVariable(outVariable), _count(false) {
49+
: _outVariable(outVariable),
50+
_count(false),
51+
_maxProjections(kMaxProjections) {
4952
TRI_ASSERT(_outVariable != nullptr);
5053
}
5154

@@ -54,7 +57,8 @@ DocumentProducingNode::DocumentProducingNode(ExecutionPlan* plan,
5457
: _outVariable(
5558
Variable::varFromVPack(plan->getAst(), slice, "outVariable")),
5659
_projections(arangodb::aql::Projections::fromVelocyPack(slice)),
57-
_count(false) {
60+
_count(false),
61+
_maxProjections(kMaxProjections) {
5862
TRI_ASSERT(_outVariable != nullptr);
5963

6064
VPackSlice p = slice.get(::filterKey);
@@ -70,6 +74,11 @@ DocumentProducingNode::DocumentProducingNode(ExecutionPlan* plan,
7074
slice, "readOwnWrites", false)
7175
? ReadOwnWrites::yes
7276
: ReadOwnWrites::no;
77+
78+
p = slice.get(::maxProjectionsKey);
79+
if (!p.isNone()) {
80+
setMaxProjections(p.getNumber<size_t>());
81+
}
7382
}
7483

7584
void DocumentProducingNode::cloneInto(ExecutionPlan* plan,
@@ -80,6 +89,7 @@ void DocumentProducingNode::cloneInto(ExecutionPlan* plan,
8089
}
8190
c.copyCountFlag(this);
8291
c.setCanReadOwnWrites(canReadOwnWrites());
92+
c.setMaxProjections(maxProjections());
8393
}
8494

8595
void DocumentProducingNode::toVelocyPack(arangodb::velocypack::Builder& builder,
@@ -108,6 +118,8 @@ void DocumentProducingNode::toVelocyPack(arangodb::velocypack::Builder& builder,
108118
}
109119
builder.add("readOwnWrites",
110120
VPackValue(_readOwnWrites == ReadOwnWrites::yes));
121+
122+
builder.add(::maxProjectionsKey, VPackValue(maxProjections()));
111123
}
112124

113125
Variable const* DocumentProducingNode::outVariable() const {

arangod/Aql/DocumentProducingNode.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ class DocumentProducingNode {
8585

8686
void setCanReadOwnWrites(ReadOwnWrites v) noexcept { _readOwnWrites = v; }
8787

88+
size_t maxProjections() const noexcept { return _maxProjections; }
89+
90+
void setMaxProjections(size_t value) noexcept { _maxProjections = value; }
91+
92+
// arbitrary default value for the maximum number of projected attributes
93+
static constexpr size_t kMaxProjections = 5;
94+
8895
protected:
8996
Variable const* _outVariable;
9097

@@ -99,6 +106,8 @@ class DocumentProducingNode {
99106
/// @brief Whether we should read our own writes performed by the current
100107
/// query. ATM this is only necessary for UPSERTS.
101108
ReadOwnWrites _readOwnWrites{ReadOwnWrites::no};
109+
110+
size_t _maxProjections{kMaxProjections};
102111
};
103112

104113
} // namespace aql

arangod/Aql/ExecutionNode.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ class EnumerateCollectionNode : public ExecutionNode,
682682
unsigned flags) const override final;
683683

684684
private:
685-
/// @brief whether or not we want random iteration
685+
/// @brief whether or not we want a random document from the collection
686686
bool _random;
687687

688688
/// @brief a possible hint from the user regarding which index to use

arangod/Aql/ExecutionPlan.cpp

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ std::unique_ptr<graph::BaseOptions> createTraversalOptions(
280280
value->isBoolValue()) {
281281
options->setRefactor(value);
282282
} else {
283-
ExecutionPlan::invalidOptionAttribute(ast->query(), "TRAVERSAL",
284-
name.data(), name.size());
283+
ExecutionPlan::invalidOptionAttribute(
284+
ast->query(), "unknown", "TRAVERSAL", name.data(), name.size());
285285
}
286286
}
287287
}
@@ -335,8 +335,9 @@ std::unique_ptr<graph::BaseOptions> createShortestPathOptions(
335335
} else if (name == StaticStrings::GraphRefactorFlag) {
336336
options->setRefactor(value->getBoolValue());
337337
} else {
338-
ExecutionPlan::invalidOptionAttribute(ast->query(), "SHORTEST_PATH",
339-
name.data(), name.size());
338+
ExecutionPlan::invalidOptionAttribute(ast->query(), "unknown",
339+
"SHORTEST_PATH", name.data(),
340+
name.size());
340341
}
341342
}
342343
}
@@ -345,6 +346,43 @@ std::unique_ptr<graph::BaseOptions> createShortestPathOptions(
345346
return options;
346347
}
347348

349+
// extract "maxProjections" from FOR options
350+
size_t extractMaxProjections(QueryContext& query, AstNode const* node) {
351+
if (node != nullptr && node->type == AstNodeType::NODE_TYPE_OBJECT) {
352+
for (size_t i = 0; i < node->numMembers(); i++) {
353+
AstNode const* child = node->getMember(i);
354+
355+
if (child->type == AstNodeType::NODE_TYPE_OBJECT_ELEMENT) {
356+
TRI_ASSERT(child->numMembers() > 0);
357+
std::string_view name(child->getStringView());
358+
if (name == arangodb::StaticStrings::IndexHintMaxProjections) {
359+
AstNode const* value = child->getMember(0);
360+
int64_t maxProjections = -1;
361+
362+
if (value->isNumericValue()) {
363+
if (value->isIntValue()) {
364+
maxProjections = value->getIntValue();
365+
} else if (value->isDoubleValue()) {
366+
maxProjections = static_cast<int64_t>(value->getDoubleValue());
367+
}
368+
}
369+
if (maxProjections >= 0) {
370+
// got a valid value
371+
return static_cast<size_t>(maxProjections);
372+
}
373+
// will raise a warning, which can optionally abort the query
374+
ExecutionPlan::invalidOptionAttribute(query, "invalid", "FOR",
375+
name.data(), name.size());
376+
break;
377+
}
378+
}
379+
}
380+
}
381+
382+
// default value
383+
return DocumentProducingNode::kMaxProjections;
384+
}
385+
348386
std::unique_ptr<Expression> createPruneExpression(ExecutionPlan* plan, Ast* ast,
349387
AstNode* node) {
350388
if (node->type == NODE_TYPE_NOP) {
@@ -438,9 +476,12 @@ ExecutionPlan::~ExecutionPlan() {
438476
}
439477

440478
void ExecutionPlan::invalidOptionAttribute(QueryContext& query,
479+
char const* errorReason,
441480
char const* operationName,
442481
char const* name, size_t length) {
443-
std::string msg("usage of unknown OPTIONS attribute '");
482+
std::string msg("usage of ");
483+
msg.append(errorReason);
484+
msg.append(" OPTIONS attribute '");
444485
msg.append(name, length);
445486
msg.append("' in ");
446487
msg.append(operationName);
@@ -937,7 +978,7 @@ ModificationOptions ExecutionPlan::parseModificationOptions(
937978
options.ignoreErrors = value->isTrue();
938979
} else {
939980
if (addWarnings) {
940-
invalidOptionAttribute(query, operationName, name.data(),
981+
invalidOptionAttribute(query, "unknown", operationName, name.data(),
941982
name.size());
942983
}
943984
}
@@ -981,8 +1022,8 @@ CollectOptions ExecutionPlan::createCollectOptions(AstNode const* node) {
9811022
}
9821023
}
9831024
if (!handled) {
984-
invalidOptionAttribute(_ast->query(), "COLLECT", name.data(),
985-
name.size());
1025+
invalidOptionAttribute(_ast->query(), "unknown", "COLLECT",
1026+
name.data(), name.size());
9861027
}
9871028
}
9881029
}
@@ -1091,6 +1132,11 @@ ExecutionNode* ExecutionPlan::fromNodeFor(ExecutionNode* previous,
10911132
ExecutionNode::castTo<EnumerateCollectionNode*>(en)->setCanReadOwnWrites(
10921133
ReadOwnWrites::yes);
10931134
}
1135+
size_t maxProjections = ::extractMaxProjections(_ast->query(), options);
1136+
auto* dn = dynamic_cast<DocumentProducingNode*>(en);
1137+
TRI_ASSERT(dn != nullptr);
1138+
dn->setMaxProjections(maxProjections);
1139+
10941140
} else if (expression->type == NODE_TYPE_VIEW) {
10951141
// second operand is a view
10961142
std::string const viewName = expression->getString();

arangod/Aql/ExecutionPlan.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ class ExecutionPlan {
308308

309309
/// @brief registers a warning for an invalid OPTIONS attribute
310310
static void invalidOptionAttribute(QueryContext& query,
311+
char const* errorReason,
311312
char const* operationName,
312313
char const* name, size_t length);
313314

arangod/Aql/IResearchViewNode.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ bool parseOptions(aql::QueryContext& query, LogicalView const& view,
518518
if (handler == Handlers.end()) {
519519
// no handler found for attribute
520520
aql::ExecutionPlan::invalidOptionAttribute(
521-
query, "FOR", attributeName.c_str(), attributeName.size());
521+
query, "unknown", "FOR", attributeName.c_str(), attributeName.size());
522522
continue;
523523
}
524524

0 commit comments

Comments
 (0)
0