mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'aql-feature-index-or' of ssh://github.com/triAGENS/ArangoDB into aql-feature-index-or
This commit is contained in:
commit
7ab57d64ec
|
@ -39,6 +39,12 @@ v2.4.0 (XXXX-XX-XX)
|
||||||
v2.3.2 (XXXX-XX-XX)
|
v2.3.2 (XXXX-XX-XX)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
* fixed issue #1173: AQL Editor "Save current query" resets user password
|
||||||
|
|
||||||
|
* fixed missing makeDirectory when fetching a Foxx application from a zip file
|
||||||
|
|
||||||
|
* put in warning about default changed: fixed issue #1134: Change the default endpoint to localhost
|
||||||
|
|
||||||
* fixed issue #1163: invalid fullCount value returned from AQL
|
* fixed issue #1163: invalid fullCount value returned from AQL
|
||||||
|
|
||||||
* fixed range operator precedence
|
* fixed range operator precedence
|
||||||
|
@ -56,11 +62,14 @@ v2.3.2 (XXXX-XX-XX)
|
||||||
|
|
||||||
* fixed memleaks
|
* fixed memleaks
|
||||||
|
|
||||||
|
* added AQL optimizer rule for removing `INTO` from a `COLLECT` statement if not needed
|
||||||
|
|
||||||
* fixed issue #1131
|
* fixed issue #1131
|
||||||
|
|
||||||
This change provides the `KEEP` clause for `COLLECT ... INTO`. The `KEEP` clause
|
This change provides the `KEEP` clause for `COLLECT ... INTO`. The `KEEP` clause
|
||||||
allows controlling which variables will be kept in the variable created by `INTO`.
|
allows controlling which variables will be kept in the variable created by `INTO`.
|
||||||
|
|
||||||
|
* fixed issue #1147, must protect dispatcher ID for etcd
|
||||||
|
|
||||||
v2.3.1 (2014-11-28)
|
v2.3.1 (2014-11-28)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
|
@ -175,9 +175,10 @@ The *request* object inherits several attributes from the underlying Actions:
|
||||||
* *path*: request URI path, with potential [database name](../Glossary/README.html#database_name) stripped off.
|
* *path*: request URI path, with potential [database name](../Glossary/README.html#database_name) stripped off.
|
||||||
|
|
||||||
* *url*: request URI path + query string, with potential database name
|
* *url*: request URI path + query string, with potential database name
|
||||||
stripped off
|
stripped off
|
||||||
|
|
||||||
* *headers*: a JSON object with the request headers as key/value pairs
|
* *headers*: a JSON object with the request headers as key/value pairs.
|
||||||
|
**Note:** All header field names are lower-cased
|
||||||
|
|
||||||
* *cookies*: a JSON object with the request cookies as key/value pairs
|
* *cookies*: a JSON object with the request cookies as key/value pairs
|
||||||
|
|
||||||
|
|
|
@ -70,3 +70,10 @@ A Chef recipe is available from jbianquetti at:
|
||||||
|
|
||||||
https://github.com/jbianquetti/chef-arangodb
|
https://github.com/jbianquetti/chef-arangodb
|
||||||
|
|
||||||
|
!SECTION Using ansible
|
||||||
|
|
||||||
|
An [Ansible](http://ansible.com) role is available trough [Ansible-Galaxy](https://galaxy.ansible.com)
|
||||||
|
|
||||||
|
* Role on Ansible-Galaxy: https://galaxy.ansible.com/list#/roles/2344
|
||||||
|
* Source on Github: https://github.com/stackmagic/ansible-arangodb
|
||||||
|
|
||||||
|
|
|
@ -569,6 +569,7 @@ SHELL_SERVER_AQL = @top_srcdir@/js/server/tests/aql-arithmetic.js \
|
||||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-unnecessary-calculations.js \
|
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-unnecessary-calculations.js \
|
||||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-unnecessary-filters.js \
|
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-unnecessary-filters.js \
|
||||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-replace-or-with-in.js \
|
@top_srcdir@/js/server/tests/aql-optimizer-rule-replace-or-with-in.js \
|
||||||
|
@top_srcdir@/js/server/tests/aql-optimizer-rule-use-index-range.js \
|
||||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-use-index-for-sort.js \
|
@top_srcdir@/js/server/tests/aql-optimizer-rule-use-index-for-sort.js \
|
||||||
@top_srcdir@/js/server/tests/aql-optimizer-stats-noncluster.js \
|
@top_srcdir@/js/server/tests/aql-optimizer-stats-noncluster.js \
|
||||||
@top_srcdir@/js/server/tests/aql-parse.js \
|
@top_srcdir@/js/server/tests/aql-parse.js \
|
||||||
|
|
|
@ -368,10 +368,10 @@ AstNode* Ast::createNodeSort (AstNode const* list) {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
AstNode* Ast::createNodeSortElement (AstNode const* expression,
|
AstNode* Ast::createNodeSortElement (AstNode const* expression,
|
||||||
bool ascending) {
|
AstNode const* ascending) {
|
||||||
AstNode* node = createNode(NODE_TYPE_SORT_ELEMENT);
|
AstNode* node = createNode(NODE_TYPE_SORT_ELEMENT);
|
||||||
node->addMember(expression);
|
node->addMember(expression);
|
||||||
node->setBoolValue(ascending);
|
node->addMember(ascending);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -1091,9 +1091,6 @@ AstNode* Ast::clone (AstNode const* node) {
|
||||||
type == NODE_TYPE_FCALL) {
|
type == NODE_TYPE_FCALL) {
|
||||||
copy->setData(node->getData());
|
copy->setData(node->getData());
|
||||||
}
|
}
|
||||||
else if (type == NODE_TYPE_SORT_ELEMENT) {
|
|
||||||
copy->setBoolValue(node->getBoolValue());
|
|
||||||
}
|
|
||||||
else if (type == NODE_TYPE_VALUE) {
|
else if (type == NODE_TYPE_VALUE) {
|
||||||
switch (node->value.type) {
|
switch (node->value.type) {
|
||||||
case VALUE_TYPE_NULL:
|
case VALUE_TYPE_NULL:
|
||||||
|
|
|
@ -283,7 +283,7 @@ namespace triagens {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
AstNode* createNodeSortElement (AstNode const*,
|
AstNode* createNodeSortElement (AstNode const*,
|
||||||
bool);
|
AstNode const*);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief create an AST limit node
|
/// @brief create an AST limit node
|
||||||
|
|
|
@ -896,7 +896,7 @@ IndexRangeBlock::~IndexRangeBlock () {
|
||||||
if (_freeCondition && _condition != nullptr) {
|
if (_freeCondition && _condition != nullptr) {
|
||||||
delete _condition;
|
delete _condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_skiplistIterator != nullptr) {
|
if (_skiplistIterator != nullptr) {
|
||||||
TRI_FreeSkiplistIterator(_skiplistIterator);
|
TRI_FreeSkiplistIterator(_skiplistIterator);
|
||||||
}
|
}
|
||||||
|
@ -1888,6 +1888,7 @@ void IndexRangeBlock::readSkiplistIndex (size_t atMost) {
|
||||||
catch (...) {
|
catch (...) {
|
||||||
if (_skiplistIterator != nullptr) {
|
if (_skiplistIterator != nullptr) {
|
||||||
TRI_FreeSkiplistIterator(_skiplistIterator);
|
TRI_FreeSkiplistIterator(_skiplistIterator);
|
||||||
|
_skiplistIterator = nullptr;
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1198,6 +1198,14 @@ namespace triagens {
|
||||||
_reverse = value;
|
_reverse = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief getIndex, hand out the index used
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Index const* getIndex () {
|
||||||
|
return _index;
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// --SECTION-- private variables
|
// --SECTION-- private variables
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
|
@ -479,25 +479,58 @@ ExecutionNode* ExecutionPlan::fromNodeSort (ExecutionNode* previous,
|
||||||
|
|
||||||
try {
|
try {
|
||||||
size_t const n = list->numMembers();
|
size_t const n = list->numMembers();
|
||||||
|
elements.reserve(n);
|
||||||
|
|
||||||
for (size_t i = 0; i < n; ++i) {
|
for (size_t i = 0; i < n; ++i) {
|
||||||
auto element = list->getMember(i);
|
auto element = list->getMember(i);
|
||||||
TRI_ASSERT(element != nullptr);
|
TRI_ASSERT(element != nullptr);
|
||||||
TRI_ASSERT(element->type == NODE_TYPE_SORT_ELEMENT);
|
TRI_ASSERT(element->type == NODE_TYPE_SORT_ELEMENT);
|
||||||
TRI_ASSERT(element->numMembers() == 1);
|
TRI_ASSERT(element->numMembers() == 2);
|
||||||
|
|
||||||
auto expression = element->getMember(0);
|
auto expression = element->getMember(0);
|
||||||
|
auto ascending = element->getMember(1);
|
||||||
|
|
||||||
|
// get sort order
|
||||||
|
bool isAscending;
|
||||||
|
bool handled = false;
|
||||||
|
if (ascending->type == NODE_TYPE_VALUE) {
|
||||||
|
if (ascending->value.type == VALUE_TYPE_STRING) {
|
||||||
|
// special treatment for string values ASC/DESC
|
||||||
|
if (TRI_CaseEqualString(ascending->value.value._string, "ASC")) {
|
||||||
|
isAscending = true;
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
else if (TRI_CaseEqualString(ascending->value.value._string, "DESC")) {
|
||||||
|
isAscending = false;
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! handled) {
|
||||||
|
// if no sort order is set, ensure we have one
|
||||||
|
auto ascendingNode = ascending->castToBool(_ast);
|
||||||
|
if (ascendingNode->type == NODE_TYPE_VALUE &&
|
||||||
|
ascendingNode->value.type == VALUE_TYPE_BOOL) {
|
||||||
|
isAscending = ascendingNode->value.value._bool;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// must have an order
|
||||||
|
isAscending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (expression->type == NODE_TYPE_REFERENCE) {
|
if (expression->type == NODE_TYPE_REFERENCE) {
|
||||||
// sort operand is a variable
|
// sort operand is a variable
|
||||||
auto v = static_cast<Variable*>(expression->getData());
|
auto v = static_cast<Variable*>(expression->getData());
|
||||||
TRI_ASSERT(v != nullptr);
|
TRI_ASSERT(v != nullptr);
|
||||||
elements.push_back(std::make_pair(v, element->getBoolValue()));
|
elements.emplace_back(std::make_pair(v, isAscending));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// sort operand is some misc expression
|
// sort operand is some misc expression
|
||||||
auto calc = createTemporaryCalculation(expression);
|
auto calc = createTemporaryCalculation(expression);
|
||||||
temp.push_back(calc);
|
temp.push_back(calc);
|
||||||
elements.push_back(std::make_pair(calc->outVariable(), element->getBoolValue()));
|
elements.emplace_back(std::make_pair(calc->outVariable(), isAscending));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,11 +186,20 @@ int Optimizer::createPlans (ExecutionPlan* plan,
|
||||||
|
|
||||||
int res;
|
int res;
|
||||||
try {
|
try {
|
||||||
|
// all optimizer rule functions must obey the following guidelines:
|
||||||
|
// - the original plan passed to the rule function must be deleted if and only
|
||||||
|
// if it has not been added (back) to the optimizer (using addPlan).
|
||||||
|
// - if the rule throws, then the original plan will be deleted by the optimizer.
|
||||||
|
// thus the rule must not have deleted the plan itself or add it back to the
|
||||||
|
// optimizer
|
||||||
res = (*it).second.func(this, p, &(it->second));
|
res = (*it).second.func(this, p, &(it->second));
|
||||||
++_stats.rulesExecuted;
|
++_stats.rulesExecuted;
|
||||||
}
|
}
|
||||||
catch (...) {
|
catch (...) {
|
||||||
delete p;
|
if (! _newPlans.isContained(p)) {
|
||||||
|
// only delete the plan if not yet contained in _newPlans
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -276,12 +276,25 @@ namespace triagens {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief check if a plan is contained in the list
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
bool isContained (ExecutionPlan* plan) const {
|
||||||
|
for (auto p : list) {
|
||||||
|
if (p == plan) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief get a plan index pointing before the referenced rule, so it can be
|
/// @brief get a plan index pointing before the referenced rule, so it can be
|
||||||
/// re-executed
|
/// re-executed
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
static RuleLevel beforeRule(RuleLevel l) {
|
static RuleLevel beforeRule (RuleLevel l) {
|
||||||
return (RuleLevel) (l - 1);
|
return (RuleLevel) (l - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,6 +478,16 @@ namespace triagens {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief numberOfPlans, returns the current number of plans in the system
|
||||||
|
/// this should be called from rules, it will consider those that the
|
||||||
|
/// current rules has already added
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
size_t numberOfPlans () {
|
||||||
|
return _plans.size() + _newPlans.size() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief stealPlans, ownership of the plans is handed over to the caller,
|
/// @brief stealPlans, ownership of the plans is handed over to the caller,
|
||||||
/// the optimizer will forget about them!
|
/// the optimizer will forget about them!
|
||||||
|
|
|
@ -826,17 +826,29 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
|
||||||
std::unordered_set<VariableId> _varIds;
|
std::unordered_set<VariableId> _varIds;
|
||||||
bool _modified;
|
bool _modified;
|
||||||
bool _canThrow;
|
bool _canThrow;
|
||||||
|
// The following maps ids of EnumerateCollectionNodes in the original
|
||||||
|
// plan to an index in the (outer vector) of the _changes container.
|
||||||
|
std::unordered_map<size_t, size_t>& _changesPlaces;
|
||||||
|
// The outer vector is for the different ids of EnumerateCollectionNodes
|
||||||
|
// in the original plan that could be replaced. For each one, the pair
|
||||||
|
// contains the id of the node in the original plan and a vector
|
||||||
|
// that holds the possible replacements.
|
||||||
|
std::vector<std::pair<size_t, std::vector<ExecutionNode*>>>& _changes;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
FilterToEnumCollFinder (ExecutionPlan* plan,
|
FilterToEnumCollFinder (ExecutionPlan* plan,
|
||||||
Variable const* var)
|
Variable const* var,
|
||||||
|
std::unordered_map<size_t, size_t>& changesPlaces,
|
||||||
|
std::vector<std::pair<size_t, std::vector<ExecutionNode*>>>& changes)
|
||||||
: _rangeInfoMapVec(nullptr),
|
: _rangeInfoMapVec(nullptr),
|
||||||
_plan(plan),
|
_plan(plan),
|
||||||
_varIds(),
|
_varIds(),
|
||||||
_modified(false),
|
_modified(false),
|
||||||
_canThrow(false) {
|
_canThrow(false),
|
||||||
|
_changesPlaces(changesPlaces),
|
||||||
|
_changes(changes) {
|
||||||
|
|
||||||
_varIds.insert(var->id);
|
_varIds.insert(var->id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1128,12 +1140,21 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! isEmpty) {
|
if (! isEmpty) {
|
||||||
ExecutionNode* newNode = new IndexRangeNode(_plan,
|
std::unique_ptr<ExecutionNode> newNode
|
||||||
_plan->nextId(), node->vocbase(), node->collection(),
|
(new IndexRangeNode(_plan,
|
||||||
node->outVariable(), idx, indexOrCondition, false);
|
_plan->nextId(), node->vocbase(), node->collection(),
|
||||||
_plan->registerNode(newNode);
|
node->outVariable(), idx, indexOrCondition, false));
|
||||||
_plan->replaceNode(_plan->getNodeById(node->id()), newNode);
|
size_t place = node->id();
|
||||||
_modified = true;
|
std::unordered_map<size_t, size_t>::iterator it
|
||||||
|
= _changesPlaces.find(place);
|
||||||
|
if (it == _changesPlaces.end()) {
|
||||||
|
_changes.push_back(std::make_pair(place, std::vector<ExecutionNode*>()));
|
||||||
|
it = _changesPlaces.emplace(place, _changes.size()-1).first;
|
||||||
|
}
|
||||||
|
std::vector<ExecutionNode*>& vec = _changes[it->second].second;
|
||||||
|
vec.push_back(newNode.release());
|
||||||
|
// if all goes well, this node will be used, if an
|
||||||
|
// exception happens, the destructor will free it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1363,6 +1384,10 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
|
||||||
enumCollVar = nullptr;
|
enumCollVar = nullptr;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool enterSubquery (ExecutionNode* super, ExecutionNode* sub) final {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1373,23 +1398,178 @@ int triagens::aql::useIndexRangeRule (Optimizer* opt,
|
||||||
ExecutionPlan* plan,
|
ExecutionPlan* plan,
|
||||||
Optimizer::Rule const* rule) {
|
Optimizer::Rule const* rule) {
|
||||||
|
|
||||||
bool modified = false;
|
// The following maps ids of EnumerateCollectionNodes in the original
|
||||||
|
// plan to an index in the (outer vector) of the _changes container.
|
||||||
|
std::unordered_map<size_t, size_t> changesPlaces;
|
||||||
|
// The outer vector is for the different ids of EnumerateCollectionNodes
|
||||||
|
// in the original plan that could be replaced. For each one, the pair
|
||||||
|
// contains the id of the node in the original plan and a vector
|
||||||
|
// that holds the possible replacements.
|
||||||
|
std::vector<std::pair<size_t, std::vector<ExecutionNode*>>> changes;
|
||||||
|
|
||||||
std::vector<ExecutionNode*> nodes
|
auto cleanupChanges = [&] () -> void {
|
||||||
= plan->findNodesOfType(EN::FILTER, true);
|
for (auto& v : changes) {
|
||||||
|
for (ExecutionNode* n : v.second) {
|
||||||
for (auto n : nodes) {
|
delete n;
|
||||||
auto nn = static_cast<FilterNode*>(n);
|
}
|
||||||
auto invars = nn->getVariablesUsedHere();
|
}
|
||||||
TRI_ASSERT(invars.size() == 1);
|
changes.clear();
|
||||||
FilterToEnumCollFinder finder(plan, invars[0]);
|
changesPlaces.clear();
|
||||||
nn->walk(&finder);
|
};
|
||||||
if (finder.modified()) {
|
|
||||||
modified = true;
|
// These are all the FILTER nodes where we start:
|
||||||
|
std::vector<ExecutionNode*> nodes = plan->findNodesOfType(EN::FILTER, true);
|
||||||
|
|
||||||
|
bool modified = false;
|
||||||
|
// In the following loop we only collect changes, maybe we introduce some
|
||||||
|
// NoResultsNode, possibly in subqueries.
|
||||||
|
try {
|
||||||
|
for (auto n : nodes) {
|
||||||
|
auto nn = static_cast<FilterNode*>(n);
|
||||||
|
auto invars = nn->getVariablesUsedHere();
|
||||||
|
TRI_ASSERT(invars.size() == 1);
|
||||||
|
FilterToEnumCollFinder finder(plan, invars[0], changesPlaces, changes);
|
||||||
|
nn->walk(&finder);
|
||||||
|
modified |= finder.modified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (...) {
|
||||||
opt->addPlan(plan, rule->level, modified);
|
cleanupChanges();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First find out how many possibilities for plan changes we actually have:
|
||||||
|
size_t nrPlans = opt->numberOfPlans();
|
||||||
|
size_t possibilities = 1;
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < changes.size()) {
|
||||||
|
possibilities *= changes[i].second.size();
|
||||||
|
i++;
|
||||||
|
if (possibilities + nrPlans > 30) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will apply the first possible change for changes[i..changes.size()-1]
|
||||||
|
// and all possible changes for changes[0..i-1] and create all these plans.
|
||||||
|
// First make all the changes from i on in the original plan and those
|
||||||
|
// for which there is only one possibility:
|
||||||
|
try {
|
||||||
|
for (size_t j = 0; j < changes.size(); j++) {
|
||||||
|
std::vector<ExecutionNode*>& v = changes[j].second;
|
||||||
|
if (j >= i || v.size() == 1) {
|
||||||
|
size_t choice = 0;
|
||||||
|
if (v.size() > 1) {
|
||||||
|
// If in doubt, take a skiplist index:
|
||||||
|
for (size_t k = 0; k < v.size(); k++) {
|
||||||
|
auto n = static_cast<IndexRangeNode*>(v[k]);
|
||||||
|
if (n->getIndex()->type == TRI_IDX_TYPE_SKIPLIST_INDEX) {
|
||||||
|
choice = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t id = changes[j].first;
|
||||||
|
// Just in case:
|
||||||
|
if (! v.empty()) {
|
||||||
|
plan->registerNode(v[choice]);
|
||||||
|
plan->replaceNode(plan->getNodeById(id), v[choice]);
|
||||||
|
modified = true;
|
||||||
|
// Free the other nodes, if they are there:
|
||||||
|
for (size_t k = 0; k < v.size(); k++) {
|
||||||
|
if (k != choice) {
|
||||||
|
delete v[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.clear(); // take the new node away from changes such that
|
||||||
|
// cleanupChanges does not touch it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
cleanupChanges();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now see whether it is actually only one plan we make:
|
||||||
|
if (possibilities == 1) {
|
||||||
|
try {
|
||||||
|
opt->addPlan(plan, rule->level, modified);
|
||||||
|
cleanupChanges();
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
cleanupChanges();
|
||||||
|
}
|
||||||
|
return TRI_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have to create more than one plan, we have to use those from
|
||||||
|
// changes[0..i-1] which have more than one possibility. Note that those
|
||||||
|
// with exactly 1 possibility have already been done above. This amounts
|
||||||
|
// to doing a cartesian product, which we do recursively. The result will
|
||||||
|
// be in the todo variable:
|
||||||
|
|
||||||
|
std::function <void(size_t, size_t, std::vector<size_t>&)> doworkrecursive;
|
||||||
|
std::vector<std::vector<size_t>> todo;
|
||||||
|
std::vector<size_t> work;
|
||||||
|
|
||||||
|
|
||||||
|
doworkrecursive = [&doworkrecursive, &changes, &todo]
|
||||||
|
(size_t index, size_t limit, std::vector<size_t>& v) {
|
||||||
|
if (index >= limit) {
|
||||||
|
todo.push_back(v); // intentionally copy vector
|
||||||
|
}
|
||||||
|
else if (changes[index].second.size() < 2) {
|
||||||
|
doworkrecursive(index+1, limit, v);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (size_t l = 0; l < changes[index].second.size(); l++) {
|
||||||
|
v[index] = l;
|
||||||
|
doworkrecursive(index+1, limit, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// if we get here, we can choose between multiple plans...
|
||||||
|
TRI_ASSERT(possibilities != 1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
work.reserve(i);
|
||||||
|
for (size_t l = 0; l < i; l++) {
|
||||||
|
work.push_back(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
doworkrecursive(0, i, work);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
cleanupChanges();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we only have to go through todo and do what needs doing:
|
||||||
|
try {
|
||||||
|
for (auto const& v : todo) {
|
||||||
|
std::unique_ptr<ExecutionPlan> newPlan(plan->clone());
|
||||||
|
for (size_t l = 0; l < i; l++) {
|
||||||
|
if (changes[l].second.size() >= 2) {
|
||||||
|
ExecutionNode* newNode
|
||||||
|
= changes[l].second[v[l]]->clone(newPlan.get(), true, false);
|
||||||
|
newPlan->registerNode(newNode);
|
||||||
|
newPlan->replaceNode(newPlan->getNodeById(changes[l].first), newNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt->addPlan(newPlan.release(), rule->level, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
cleanupChanges();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupChanges();
|
||||||
|
// finally delete the original plan. all plans created in this rule will be better(tm)
|
||||||
|
delete plan;
|
||||||
|
|
||||||
return TRI_ERROR_NO_ERROR;
|
return TRI_ERROR_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -152,7 +152,7 @@ void Aqlerror (YYLTYPE* locp,
|
||||||
%type <strval> T_PARAMETER;
|
%type <strval> T_PARAMETER;
|
||||||
%type <node> sort_list;
|
%type <node> sort_list;
|
||||||
%type <node> sort_element;
|
%type <node> sort_element;
|
||||||
%type <boolval> sort_direction;
|
%type <node> sort_direction;
|
||||||
%type <node> collect_list;
|
%type <node> collect_list;
|
||||||
%type <node> collect_element;
|
%type <node> collect_element;
|
||||||
%type <node> optional_keep;
|
%type <node> optional_keep;
|
||||||
|
@ -406,13 +406,16 @@ sort_element:
|
||||||
|
|
||||||
sort_direction:
|
sort_direction:
|
||||||
/* empty */ {
|
/* empty */ {
|
||||||
$$ = true;
|
$$ = parser->ast()->createNodeValueBool(true);
|
||||||
}
|
}
|
||||||
| T_ASC {
|
| T_ASC {
|
||||||
$$ = true;
|
$$ = parser->ast()->createNodeValueBool(true);
|
||||||
}
|
}
|
||||||
| T_DESC {
|
| T_DESC {
|
||||||
$$ = false;
|
$$ = parser->ast()->createNodeValueBool(false);
|
||||||
|
}
|
||||||
|
| atomic_value {
|
||||||
|
$$ = $1;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
|
@ -1014,8 +1014,9 @@ bool RestDocumentHandler::checkDocument () {
|
||||||
/// of *true*.
|
/// of *true*.
|
||||||
///
|
///
|
||||||
/// The body of the response contains a JSON object with the information about
|
/// The body of the response contains a JSON object with the information about
|
||||||
/// the handle and the revision. The attribute *_id* contains the known
|
/// the handle and the revision. The attribute *_id* contains the known
|
||||||
/// *document-handle* of the updated document, the attribute *_rev*
|
/// *document-handle* of the updated document, *_key* contains the key which
|
||||||
|
/// uniquely identifies a document in a given collection, and the attribute *_rev*
|
||||||
/// contains the new document revision.
|
/// contains the new document revision.
|
||||||
///
|
///
|
||||||
/// If the document does not exist, then a *HTTP 404* is returned and the
|
/// If the document does not exist, then a *HTTP 404* is returned and the
|
||||||
|
@ -1249,7 +1250,8 @@ bool RestDocumentHandler::replaceDocument () {
|
||||||
///
|
///
|
||||||
/// The body of the response contains a JSON object with the information about
|
/// The body of the response contains a JSON object with the information about
|
||||||
/// the handle and the revision. The attribute *_id* contains the known
|
/// the handle and the revision. The attribute *_id* contains the known
|
||||||
/// *document-handle* of the updated document, the attribute *_rev*
|
/// *document-handle* of the updated document, *_key* contains the key which
|
||||||
|
/// uniquely identifies a document in a given collection, and the attribute *_rev*
|
||||||
/// contains the new document revision.
|
/// contains the new document revision.
|
||||||
///
|
///
|
||||||
/// If the document does not exist, then a *HTTP 404* is returned and the
|
/// If the document does not exist, then a *HTTP 404* is returned and the
|
||||||
|
@ -1679,9 +1681,10 @@ bool RestDocumentHandler::modifyDocumentCoordinator (
|
||||||
///
|
///
|
||||||
/// @RESTDESCRIPTION
|
/// @RESTDESCRIPTION
|
||||||
/// The body of the response contains a JSON object with the information about
|
/// The body of the response contains a JSON object with the information about
|
||||||
/// the handle and the revision. The attribute *_id* contains the known
|
/// the handle and the revision. The attribute *_id* contains the known
|
||||||
/// *document-handle* of the deleted document, the attribute *_rev*
|
/// *document-handle* of the deleted document, *_key* contains the key which
|
||||||
/// contains the document revision.
|
/// uniquely identifies a document in a given collection, and the attribute *_rev*
|
||||||
|
/// contains the new document revision.
|
||||||
///
|
///
|
||||||
/// If the *waitForSync* parameter is not specified or set to
|
/// If the *waitForSync* parameter is not specified or set to
|
||||||
/// *false*, then the collection's default *waitForSync* behavior is
|
/// *false*, then the collection's default *waitForSync* behavior is
|
||||||
|
|
|
@ -297,7 +297,7 @@ static v8::Handle<v8::Value> JS_NextGeneralCursor (v8::Arguments const& argv) {
|
||||||
try {
|
try {
|
||||||
TRI_general_cursor_row_t row = cursor->next(cursor);
|
TRI_general_cursor_row_t row = cursor->next(cursor);
|
||||||
|
|
||||||
if (row == 0) {
|
if (row == nullptr) {
|
||||||
value = v8::Undefined();
|
value = v8::Undefined();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -1161,9 +1161,9 @@ static int FillLookupSLOperator (TRI_index_operator_t* slOperator,
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// .............................................................................
|
// .............................................................................
|
||||||
// Note: this function will destroy the passed slOperator before it returns
|
// Note: this function will not destroy the passed slOperator before it returns
|
||||||
// Warning: who ever calls this function is responsible for destroying
|
// Warning: who ever calls this function is responsible for destroying
|
||||||
// TRI_skiplist_iterator_t* results
|
// the TRI_index_operator_t* and the TRI_skiplist_iterator_t* results
|
||||||
// .............................................................................
|
// .............................................................................
|
||||||
|
|
||||||
TRI_skiplist_iterator_t* TRI_LookupSkiplistIndex (TRI_index_t* idx,
|
TRI_skiplist_iterator_t* TRI_LookupSkiplistIndex (TRI_index_t* idx,
|
||||||
|
|
|
@ -47,6 +47,8 @@ double const ArangoClient::DEFAULT_CONNECTION_TIMEOUT = 3.0;
|
||||||
double const ArangoClient::DEFAULT_REQUEST_TIMEOUT = 300.0;
|
double const ArangoClient::DEFAULT_REQUEST_TIMEOUT = 300.0;
|
||||||
size_t const ArangoClient::DEFAULT_RETRIES = 2;
|
size_t const ArangoClient::DEFAULT_RETRIES = 2;
|
||||||
|
|
||||||
|
double const ArangoClient::LONG_TIMEOUT = 86400.0;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
bool _newLine () {
|
bool _newLine () {
|
||||||
|
@ -459,13 +461,19 @@ void ArangoClient::parse (ProgramOptions& options,
|
||||||
if (_serverOptions) {
|
if (_serverOptions) {
|
||||||
|
|
||||||
// check connection args
|
// check connection args
|
||||||
if (_connectTimeout <= 0) {
|
if (_connectTimeout < 0.0) {
|
||||||
LOG_FATAL_AND_EXIT("invalid value for --server.connect-timeout, must be positive");
|
LOG_FATAL_AND_EXIT("invalid value for --server.connect-timeout, must be >= 0");
|
||||||
|
}
|
||||||
|
else if (_connectTimeout == 0.0) {
|
||||||
|
_connectTimeout = LONG_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_requestTimeout <= 0) {
|
if (_requestTimeout < 0.0) {
|
||||||
LOG_FATAL_AND_EXIT("invalid value for --server.request-timeout, must be positive");
|
LOG_FATAL_AND_EXIT("invalid value for --server.request-timeout, must be positive");
|
||||||
}
|
}
|
||||||
|
else if (_requestTimeout == 0.0) {
|
||||||
|
_requestTimeout = LONG_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
// must specify a user name
|
// must specify a user name
|
||||||
if (_username.size() == 0) {
|
if (_username.size() == 0) {
|
||||||
|
|
|
@ -87,6 +87,12 @@ namespace triagens {
|
||||||
|
|
||||||
static double const DEFAULT_CONNECTION_TIMEOUT;
|
static double const DEFAULT_CONNECTION_TIMEOUT;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief default "long" timeout
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static double const LONG_TIMEOUT;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief ignore sequence used for prompt length calculation (starting point)
|
/// @brief ignore sequence used for prompt length calculation (starting point)
|
||||||
///
|
///
|
||||||
|
|
|
@ -288,7 +288,7 @@ function post_api_explain (req, res) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.plans) {
|
if (result.hasOwnProperty("plans")) {
|
||||||
result = {
|
result = {
|
||||||
plans: result.plans,
|
plans: result.plans,
|
||||||
warnings: result.warnings,
|
warnings: result.warnings,
|
||||||
|
|
|
@ -27,14 +27,11 @@
|
||||||
|
|
||||||
activeUser: 0,
|
activeUser: 0,
|
||||||
|
|
||||||
currentExtra: {},
|
|
||||||
|
|
||||||
parse: function(response) {
|
parse: function(response) {
|
||||||
var self = this, toReturn;
|
var self = this, toReturn;
|
||||||
|
|
||||||
_.each(response.result, function(val) {
|
_.each(response.result, function(val) {
|
||||||
if (val.user === self.activeUser) {
|
if (val.user === self.activeUser) {
|
||||||
self.currentExtra = val.extra;
|
|
||||||
try {
|
try {
|
||||||
if (val.extra.queries) {
|
if (val.extra.queries) {
|
||||||
toReturn = val.extra.queries;
|
toReturn = val.extra.queries;
|
||||||
|
@ -52,12 +49,8 @@
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var queries = [],
|
var returnValue = false;
|
||||||
returnValue1 = false,
|
var queries = [];
|
||||||
returnValue2 = false,
|
|
||||||
returnValue3 = false,
|
|
||||||
extraBackup = null,
|
|
||||||
self = this;
|
|
||||||
|
|
||||||
this.each(function(query) {
|
this.each(function(query) {
|
||||||
queries.push({
|
queries.push({
|
||||||
|
@ -66,33 +59,12 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
extraBackup = self.currentExtra;
|
// save current collection
|
||||||
extraBackup.queries = [];
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
cache: false,
|
|
||||||
type: "PUT",
|
|
||||||
async: false,
|
|
||||||
url: "/_api/user/" + this.activeUser,
|
|
||||||
data: JSON.stringify({
|
|
||||||
extra: extraBackup
|
|
||||||
}),
|
|
||||||
contentType: "application/json",
|
|
||||||
processData: false,
|
|
||||||
success: function() {
|
|
||||||
returnValue1 = true;
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
returnValue1 = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//save current collection
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
cache: false,
|
cache: false,
|
||||||
type: "PATCH",
|
type: "PATCH",
|
||||||
async: false,
|
async: false,
|
||||||
url: "/_api/user/" + this.activeUser,
|
url: "/_api/user/" + encodeURIComponent(this.activeUser),
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
extra: {
|
extra: {
|
||||||
queries: queries
|
queries: queries
|
||||||
|
@ -101,20 +73,14 @@
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
processData: false,
|
processData: false,
|
||||||
success: function() {
|
success: function() {
|
||||||
returnValue2 = true;
|
returnValue = true;
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
returnValue2 = false;
|
returnValue = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (returnValue1 === true && returnValue2 === true) {
|
return returnValue;
|
||||||
returnValue3 = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
returnValue3 = false;
|
|
||||||
}
|
|
||||||
return returnValue3;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
saveImportQueries: function(file, callback) {
|
saveImportQueries: function(file, callback) {
|
||||||
|
@ -128,7 +94,7 @@
|
||||||
cache: false,
|
cache: false,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
async: false,
|
async: false,
|
||||||
url: "query/upload/" + this.activeUser,
|
url: "query/upload/" + encodeURIComponent(this.activeUser),
|
||||||
data: file,
|
data: file,
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
processData: false,
|
processData: false,
|
||||||
|
|
|
@ -118,10 +118,18 @@ ArangoStatement.prototype.parse = function () {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
ArangoStatement.prototype.explain = function (options) {
|
ArangoStatement.prototype.explain = function (options) {
|
||||||
|
var opts = this._options || { };
|
||||||
|
if (typeof opts === 'object' && typeof options === 'object') {
|
||||||
|
Object.keys(options).forEach(function(o) {
|
||||||
|
// copy options
|
||||||
|
opts[o] = options[o];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var body = {
|
var body = {
|
||||||
query: this._query,
|
query: this._query,
|
||||||
bindVars: this._bindVars,
|
bindVars: this._bindVars,
|
||||||
options: options || { }
|
options: opts
|
||||||
};
|
};
|
||||||
|
|
||||||
var requestResult = this._database._connection.POST(
|
var requestResult = this._database._connection.POST(
|
||||||
|
@ -130,7 +138,7 @@ ArangoStatement.prototype.explain = function (options) {
|
||||||
|
|
||||||
arangosh.checkRequestResult(requestResult);
|
arangosh.checkRequestResult(requestResult);
|
||||||
|
|
||||||
if (options && options.allPlans) {
|
if (opts && opts.allPlans) {
|
||||||
return {
|
return {
|
||||||
plans: requestResult.plans,
|
plans: requestResult.plans,
|
||||||
warnings: requestResult.warnings,
|
warnings: requestResult.warnings,
|
||||||
|
|
|
@ -117,10 +117,18 @@ ArangoStatement.prototype.parse = function () {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
ArangoStatement.prototype.explain = function (options) {
|
ArangoStatement.prototype.explain = function (options) {
|
||||||
|
var opts = this._options || { };
|
||||||
|
if (typeof opts === 'object' && typeof options === 'object') {
|
||||||
|
Object.keys(options).forEach(function(o) {
|
||||||
|
// copy options
|
||||||
|
opts[o] = options[o];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var body = {
|
var body = {
|
||||||
query: this._query,
|
query: this._query,
|
||||||
bindVars: this._bindVars,
|
bindVars: this._bindVars,
|
||||||
options: options || { }
|
options: opts
|
||||||
};
|
};
|
||||||
|
|
||||||
var requestResult = this._database._connection.POST(
|
var requestResult = this._database._connection.POST(
|
||||||
|
@ -129,7 +137,7 @@ ArangoStatement.prototype.explain = function (options) {
|
||||||
|
|
||||||
arangosh.checkRequestResult(requestResult);
|
arangosh.checkRequestResult(requestResult);
|
||||||
|
|
||||||
if (options && options.allPlans) {
|
if (opts && opts.allPlans) {
|
||||||
return {
|
return {
|
||||||
plans: requestResult.plans,
|
plans: requestResult.plans,
|
||||||
warnings: requestResult.warnings,
|
warnings: requestResult.warnings,
|
||||||
|
|
|
@ -249,6 +249,29 @@ function StatementSuite () {
|
||||||
assertTrue(plan.hasOwnProperty("variables"));
|
assertTrue(plan.hasOwnProperty("variables"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test explain method
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testExplainAllPlansWithOptions : function () {
|
||||||
|
var st = db._createStatement({ query : "FOR i IN 1..10 RETURN i", options: { allPlans: true } });
|
||||||
|
var result = st.explain();
|
||||||
|
|
||||||
|
assertEqual([ ], result.warnings);
|
||||||
|
assertFalse(result.hasOwnProperty("plan"));
|
||||||
|
assertTrue(result.hasOwnProperty("plans"));
|
||||||
|
|
||||||
|
assertEqual(1, result.plans.length);
|
||||||
|
var plan = result.plans[0];
|
||||||
|
assertTrue(plan.hasOwnProperty("estimatedCost"));
|
||||||
|
assertTrue(plan.hasOwnProperty("rules"));
|
||||||
|
assertEqual([ ], plan.rules);
|
||||||
|
assertTrue(plan.hasOwnProperty("nodes"));
|
||||||
|
assertTrue(plan.hasOwnProperty("collections"));
|
||||||
|
assertEqual([ ], plan.collections);
|
||||||
|
assertTrue(plan.hasOwnProperty("variables"));
|
||||||
|
},
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief test explain method, bind variables
|
/// @brief test explain method, bind variables
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -344,6 +367,20 @@ function StatementSuite () {
|
||||||
assertTrue(plan.hasOwnProperty("variables"));
|
assertTrue(plan.hasOwnProperty("variables"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test explain
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testExplainWithOptions : function () {
|
||||||
|
var st = db._createStatement({ query : "for i in _users for j in _users return i", options: { allPlans: true, maxNumberOfPlans: 1 } });
|
||||||
|
var result = st.explain();
|
||||||
|
|
||||||
|
assertEqual([ ], result.warnings);
|
||||||
|
assertFalse(result.hasOwnProperty("plan"));
|
||||||
|
assertTrue(result.hasOwnProperty("plans"));
|
||||||
|
assertEqual(1, result.plans.length);
|
||||||
|
},
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief test execute method
|
/// @brief test execute method
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -400,7 +437,7 @@ function StatementSuite () {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
testExecuteExtra : function () {
|
testExecuteExtra : function () {
|
||||||
var st = db._createStatement({ query : "for i in 1..50 limit 1,2 return i", count: true, options: { fullCount: true } });
|
var st = db._createStatement({ query : "for i in 1..50 limit 1, 2 return i", count: true, options: { fullCount: true } });
|
||||||
var result = st.execute();
|
var result = st.execute();
|
||||||
|
|
||||||
assertEqual(2, result.count());
|
assertEqual(2, result.count());
|
||||||
|
|
|
@ -60,7 +60,15 @@ ArangoStatement.prototype.parse = function () {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
ArangoStatement.prototype.explain = function (options) {
|
ArangoStatement.prototype.explain = function (options) {
|
||||||
return AQL_EXPLAIN(this._query, this._bindVars, options || { });
|
var opts = this._options || { };
|
||||||
|
if (typeof opts === 'object' && typeof options === 'object') {
|
||||||
|
Object.keys(options).forEach(function(o) {
|
||||||
|
// copy options
|
||||||
|
opts[o] = options[o];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return AQL_EXPLAIN(this._query, this._bindVars, opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -70,11 +78,12 @@ ArangoStatement.prototype.explain = function (options) {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
ArangoStatement.prototype.execute = function () {
|
ArangoStatement.prototype.execute = function () {
|
||||||
var options = this._options || { };
|
var opts = this._options || { };
|
||||||
if (typeof options === 'object') {
|
if (typeof opts === 'object') {
|
||||||
options._doCount = this._doCount;
|
opts._doCount = this._doCount;
|
||||||
}
|
}
|
||||||
var result = AQL_EXECUTE(this._query, this._bindVars, options);
|
|
||||||
|
var result = AQL_EXECUTE(this._query, this._bindVars, opts);
|
||||||
|
|
||||||
return new GeneralArrayCursor(result.json, 0, null, result);
|
return new GeneralArrayCursor(result.json, 0, null, result);
|
||||||
};
|
};
|
||||||
|
|
|
@ -301,9 +301,9 @@ launchActions.startServers = function (dispatchers, cmd, isRelaunch) {
|
||||||
|
|
||||||
var url = endpointToURL(cmd.agency.endpoints[0])+"/v2/keys/"+
|
var url = endpointToURL(cmd.agency.endpoints[0])+"/v2/keys/"+
|
||||||
cmd.agency.agencyPrefix+"/";
|
cmd.agency.agencyPrefix+"/";
|
||||||
console.info("Downloading %sLaunchers/%s", url, cmd.name);
|
console.info("Downloading %sLaunchers/%s", url, encode(cmd.name));
|
||||||
var res = download(url+"Launchers/"+cmd.name,"",{method:"GET",
|
var res = download(url+"Launchers/"+encode(cmd.name),"",{method:"GET",
|
||||||
followRedirects:true});
|
followRedirects:true});
|
||||||
if (res.code !== 200) {
|
if (res.code !== 200) {
|
||||||
return {"error": true, "isStartServers": true, "suberror": res};
|
return {"error": true, "isStartServers": true, "suberror": res};
|
||||||
}
|
}
|
||||||
|
@ -620,8 +620,8 @@ upgradeActions.startServers = function (dispatchers, cmd, isRelaunch) {
|
||||||
|
|
||||||
var url = endpointToURL(cmd.agency.endpoints[0])+"/v2/keys/"+
|
var url = endpointToURL(cmd.agency.endpoints[0])+"/v2/keys/"+
|
||||||
cmd.agency.agencyPrefix+"/";
|
cmd.agency.agencyPrefix+"/";
|
||||||
console.info("Downloading %sLaunchers/%s", url, cmd.name);
|
console.info("Downloading %sLaunchers/%s", url, encode(cmd.name));
|
||||||
var res = download(url+"Launchers/"+cmd.name,"",{method:"GET",
|
var res = download(url+"Launchers/"+encode(cmd.name),"",{method:"GET",
|
||||||
followRedirects:true});
|
followRedirects:true});
|
||||||
if (res.code !== 200) {
|
if (res.code !== 200) {
|
||||||
return {"error": true, "isStartServers": true, "suberror": res};
|
return {"error": true, "isStartServers": true, "suberror": res};
|
||||||
|
|
|
@ -65,6 +65,50 @@ function ahuacatlBindTestSuite () {
|
||||||
internal.db._drop("UnitTestsAhuacatlNumbers");
|
internal.db._drop("UnitTestsAhuacatlNumbers");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test asc / desc variable
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testBindAscDesc1 : function () {
|
||||||
|
var expected = [ 1, 2, 3, 4, 6, 7, 9 ];
|
||||||
|
var actual = getQueryResults("FOR u IN @list SORT u @order RETURN u", { "list" : [ 9, 4, 3, 7, 2, 1, 6 ], "order" : "asc" });
|
||||||
|
|
||||||
|
assertEqual(expected, actual);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test asc / desc variable
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testBindAscDesc2 : function () {
|
||||||
|
var expected = [ 1, 2, 3, 4, 6, 7, 9 ].reverse();
|
||||||
|
var actual = getQueryResults("FOR u IN @list SORT u @order RETURN u", { "list" : [ 9, 4, 3, 7, 2, 1, 6 ], "order" : "DESC" });
|
||||||
|
|
||||||
|
assertEqual(expected, actual);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test asc / desc variable
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testBindAscDesc3 : function () {
|
||||||
|
var expected = [ 1, 2, 3, 4, 6, 7, 9 ];
|
||||||
|
var actual = getQueryResults("FOR u IN @list SORT u @order RETURN u", { "list" : [ 9, 4, 3, 7, 2, 1, 6 ], "order" : true });
|
||||||
|
|
||||||
|
assertEqual(expected, actual);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test asc / desc variable
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testBindAscDesc4 : function () {
|
||||||
|
var expected = [ 1, 2, 3, 4, 6, 7, 9 ].reverse();
|
||||||
|
var actual = getQueryResults("FOR u IN @list SORT u @order RETURN u", { "list" : [ 9, 4, 3, 7, 2, 1, 6 ], "order" : false });
|
||||||
|
|
||||||
|
assertEqual(expected, actual);
|
||||||
|
},
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief test a single bind variable
|
/// @brief test a single bind variable
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -246,6 +246,294 @@ function optimizerIndexesTestSuite () {
|
||||||
assertEqual(10, indexNodes);
|
assertEqual(10, indexNodes);
|
||||||
assertEqual(1, explain.stats.plansCreated);
|
assertEqual(1, explain.stats.plansCreated);
|
||||||
|
|
||||||
|
var results = AQL_EXECUTE(query);
|
||||||
|
assertEqual(0, results.stats.scannedFull);
|
||||||
|
assertNotEqual(0, results.stats.scannedIndex);
|
||||||
|
assertEqual([ [ [ 'test1' ], [ 'test2' ], [ 'test3' ], [ 'test4' ], [ 'test5' ], [ 'test6' ], [ 'test7' ], [ 'test8' ], [ 'test9' ], [ 'test10' ] ] ], results.json);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test index usage
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testMultipleSubqueriesMultipleIndexes : function () {
|
||||||
|
c.ensureHashIndex("value"); // now we have a hash and a skiplist index
|
||||||
|
var query = "LET a = (FOR x IN " + c.name() + " FILTER x.value == 1 RETURN x._key) " +
|
||||||
|
"LET b = (FOR x IN " + c.name() + " FILTER x.value == 2 RETURN x._key) " +
|
||||||
|
"LET c = (FOR x IN " + c.name() + " FILTER x.value == 3 RETURN x._key) " +
|
||||||
|
"LET d = (FOR x IN " + c.name() + " FILTER x.value == 4 RETURN x._key) " +
|
||||||
|
"LET e = (FOR x IN " + c.name() + " FILTER x.value == 5 RETURN x._key) " +
|
||||||
|
"LET f = (FOR x IN " + c.name() + " FILTER x.value == 6 RETURN x._key) " +
|
||||||
|
"LET g = (FOR x IN " + c.name() + " FILTER x.value == 7 RETURN x._key) " +
|
||||||
|
"LET h = (FOR x IN " + c.name() + " FILTER x.value == 8 RETURN x._key) " +
|
||||||
|
"LET i = (FOR x IN " + c.name() + " FILTER x.value == 9 RETURN x._key) " +
|
||||||
|
"LET j = (FOR x IN " + c.name() + " FILTER x.value == 10 RETURN x._key) " +
|
||||||
|
"RETURN [ a, b, c, d, e, f, g, h, i, j ]";
|
||||||
|
|
||||||
|
|
||||||
|
var explain = AQL_EXPLAIN(query);
|
||||||
|
var plan = explain.plan;
|
||||||
|
|
||||||
|
var walker = function (nodes, func) {
|
||||||
|
nodes.forEach(function(node) {
|
||||||
|
if (node.type === "SubqueryNode") {
|
||||||
|
walker(node.subquery.nodes, func);
|
||||||
|
}
|
||||||
|
func(node);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var indexNodes = 0, collectionNodes = 0;
|
||||||
|
walker(plan.nodes, function (node) {
|
||||||
|
if (node.type === "IndexRangeNode") {
|
||||||
|
++indexNodes;
|
||||||
|
if (indexNodes <= 5) {
|
||||||
|
assertEqual("hash", node.index.type);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEqual("skiplist", node.index.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node.type === "EnumerateCollectionNode") {
|
||||||
|
++collectionNodes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEqual(0, collectionNodes);
|
||||||
|
assertEqual(10, indexNodes);
|
||||||
|
assertEqual(32, explain.stats.plansCreated);
|
||||||
|
|
||||||
|
var results = AQL_EXECUTE(query);
|
||||||
|
assertEqual(0, results.stats.scannedFull);
|
||||||
|
assertNotEqual(0, results.stats.scannedIndex);
|
||||||
|
assertEqual([ [ [ 'test1' ], [ 'test2' ], [ 'test3' ], [ 'test4' ], [ 'test5' ], [ 'test6' ], [ 'test7' ], [ 'test8' ], [ 'test9' ], [ 'test10' ] ] ], results.json);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test index usage
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testMultipleSubqueriesHashIndexes : function () {
|
||||||
|
c.dropIndex(c.getIndexes()[1]); // drop skiplist index
|
||||||
|
c.ensureHashIndex("value");
|
||||||
|
var query = "LET a = (FOR x IN " + c.name() + " FILTER x.value == 1 RETURN x._key) " +
|
||||||
|
"LET b = (FOR x IN " + c.name() + " FILTER x.value == 2 RETURN x._key) " +
|
||||||
|
"LET c = (FOR x IN " + c.name() + " FILTER x.value == 3 RETURN x._key) " +
|
||||||
|
"LET d = (FOR x IN " + c.name() + " FILTER x.value == 4 RETURN x._key) " +
|
||||||
|
"LET e = (FOR x IN " + c.name() + " FILTER x.value == 5 RETURN x._key) " +
|
||||||
|
"LET f = (FOR x IN " + c.name() + " FILTER x.value == 6 RETURN x._key) " +
|
||||||
|
"LET g = (FOR x IN " + c.name() + " FILTER x.value == 7 RETURN x._key) " +
|
||||||
|
"LET h = (FOR x IN " + c.name() + " FILTER x.value == 8 RETURN x._key) " +
|
||||||
|
"LET i = (FOR x IN " + c.name() + " FILTER x.value == 9 RETURN x._key) " +
|
||||||
|
"LET j = (FOR x IN " + c.name() + " FILTER x.value == 10 RETURN x._key) " +
|
||||||
|
"RETURN [ a, b, c, d, e, f, g, h, i, j ]";
|
||||||
|
|
||||||
|
var explain = AQL_EXPLAIN(query);
|
||||||
|
var plan = explain.plan;
|
||||||
|
|
||||||
|
var walker = function (nodes, func) {
|
||||||
|
nodes.forEach(function(node) {
|
||||||
|
if (node.type === "SubqueryNode") {
|
||||||
|
walker(node.subquery.nodes, func);
|
||||||
|
}
|
||||||
|
func(node);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var indexNodes = 0, collectionNodes = 0;
|
||||||
|
walker(plan.nodes, function (node) {
|
||||||
|
if (node.type === "IndexRangeNode") {
|
||||||
|
++indexNodes;
|
||||||
|
assertEqual("hash", node.index.type);
|
||||||
|
}
|
||||||
|
else if (node.type === "EnumerateCollectionNode") {
|
||||||
|
++collectionNodes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEqual(0, collectionNodes);
|
||||||
|
assertEqual(10, indexNodes);
|
||||||
|
assertEqual(1, explain.stats.plansCreated);
|
||||||
|
|
||||||
|
var results = AQL_EXECUTE(query);
|
||||||
|
assertEqual(0, results.stats.scannedFull);
|
||||||
|
assertNotEqual(0, results.stats.scannedIndex);
|
||||||
|
assertEqual([ [ [ 'test1' ], [ 'test2' ], [ 'test3' ], [ 'test4' ], [ 'test5' ], [ 'test6' ], [ 'test7' ], [ 'test8' ], [ 'test9' ], [ 'test10' ] ] ], results.json);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test index usage
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testJoinMultipleIndexes : function () {
|
||||||
|
c.ensureHashIndex("value"); // now we have a hash and a skiplist index
|
||||||
|
var query = "FOR i IN " + c.name() + " FILTER i.value < 10 FOR j IN " + c.name() + " FILTER j.value == i.value RETURN j._key";
|
||||||
|
|
||||||
|
var explain = AQL_EXPLAIN(query);
|
||||||
|
var plan = explain.plan;
|
||||||
|
|
||||||
|
var collectionNodes = 0, indexNodes = 0;
|
||||||
|
plan.nodes.forEach(function(node) {
|
||||||
|
if (node.type === "IndexRangeNode") {
|
||||||
|
++indexNodes;
|
||||||
|
if (indexNodes === 1) {
|
||||||
|
// skiplist must be used for the first FOR
|
||||||
|
assertEqual("skiplist", node.index.type);
|
||||||
|
assertEqual("i", node.outVariable.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// second FOR should use a hash index
|
||||||
|
assertEqual("hash", node.index.type);
|
||||||
|
assertEqual("j", node.outVariable.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node.type === "EnumerateCollectionNode") {
|
||||||
|
++collectionNodes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEqual(0, collectionNodes);
|
||||||
|
assertEqual(2, indexNodes);
|
||||||
|
assertEqual(4, explain.stats.plansCreated);
|
||||||
|
|
||||||
|
var results = AQL_EXECUTE(query);
|
||||||
|
assertEqual(0, results.stats.scannedFull);
|
||||||
|
assertNotEqual(0, results.stats.scannedIndex);
|
||||||
|
assertEqual([ 'test0', 'test1', 'test2', 'test3', 'test4', 'test5', 'test6', 'test7', 'test8', 'test9' ], results.json);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test index usage
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testJoinRangesMultipleIndexes : function () {
|
||||||
|
c.ensureHashIndex("value"); // now we have a hash and a skiplist index
|
||||||
|
var query = "FOR i IN " + c.name() + " FILTER i.value < 5 FOR j IN " + c.name() + " FILTER j.value < i.value RETURN j._key";
|
||||||
|
|
||||||
|
var explain = AQL_EXPLAIN(query);
|
||||||
|
var plan = explain.plan;
|
||||||
|
|
||||||
|
var collectionNodes = 0, indexNodes = 0;
|
||||||
|
plan.nodes.forEach(function(node) {
|
||||||
|
if (node.type === "IndexRangeNode") {
|
||||||
|
++indexNodes;
|
||||||
|
// skiplist must be used for both FORs
|
||||||
|
assertEqual("skiplist", node.index.type);
|
||||||
|
if (indexNodes === 1) {
|
||||||
|
assertEqual("i", node.outVariable.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEqual("j", node.outVariable.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node.type === "EnumerateCollectionNode") {
|
||||||
|
++collectionNodes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEqual(0, collectionNodes);
|
||||||
|
assertEqual(2, indexNodes);
|
||||||
|
assertEqual(2, explain.stats.plansCreated);
|
||||||
|
|
||||||
|
var results = AQL_EXECUTE(query);
|
||||||
|
assertEqual(0, results.stats.scannedFull);
|
||||||
|
assertNotEqual(0, results.stats.scannedIndex);
|
||||||
|
assertEqual([ 'test0', 'test0', 'test1', 'test0', 'test1', 'test2', 'test0', 'test1', 'test2', 'test3' ], results.json);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test index usage
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testTripleJoin : function () {
|
||||||
|
c.ensureHashIndex("value"); // now we have a hash and a skiplist index
|
||||||
|
var query = "FOR i IN " + c.name() + " FILTER i.value == 4 FOR j IN " + c.name() + " FILTER j.value == i.value FOR k IN " + c.name() + " FILTER k.value < j.value RETURN k._key";
|
||||||
|
|
||||||
|
var explain = AQL_EXPLAIN(query);
|
||||||
|
var plan = explain.plan;
|
||||||
|
|
||||||
|
var collectionNodes = 0, indexNodes = 0;
|
||||||
|
plan.nodes.forEach(function(node) {
|
||||||
|
if (node.type === "IndexRangeNode") {
|
||||||
|
++indexNodes;
|
||||||
|
if (indexNodes === 1) {
|
||||||
|
assertEqual("hash", node.index.type);
|
||||||
|
assertEqual("i", node.outVariable.name);
|
||||||
|
}
|
||||||
|
else if (indexNodes === 2) {
|
||||||
|
assertEqual("hash", node.index.type);
|
||||||
|
assertEqual("j", node.outVariable.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEqual("skiplist", node.index.type);
|
||||||
|
assertEqual("k", node.outVariable.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node.type === "EnumerateCollectionNode") {
|
||||||
|
++collectionNodes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEqual(0, collectionNodes);
|
||||||
|
assertEqual(3, indexNodes);
|
||||||
|
assertEqual(12, explain.stats.plansCreated);
|
||||||
|
|
||||||
|
var results = AQL_EXECUTE(query);
|
||||||
|
assertEqual(0, results.stats.scannedFull);
|
||||||
|
assertNotEqual(0, results.stats.scannedIndex);
|
||||||
|
assertEqual([ 'test0', 'test1', 'test2', 'test3' ], results.json);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test index usage
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testSubqueryMadness : function () {
|
||||||
|
c.ensureHashIndex("value"); // now we have a hash and a skiplist index
|
||||||
|
var query = "LET a = (FOR x IN " + c.name() + " FILTER x.value == 1 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET b = (FOR x IN " + c.name() + " FILTER x.value == 2 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET c = (FOR x IN " + c.name() + " FILTER x.value == 3 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET d = (FOR x IN " + c.name() + " FILTER x.value == 4 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET e = (FOR x IN " + c.name() + " FILTER x.value == 5 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET f = (FOR x IN " + c.name() + " FILTER x.value == 6 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET g = (FOR x IN " + c.name() + " FILTER x.value == 7 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET h = (FOR x IN " + c.name() + " FILTER x.value == 8 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET i = (FOR x IN " + c.name() + " FILTER x.value == 9 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"LET j = (FOR x IN " + c.name() + " FILTER x.value == 10 FOR y IN " + c.name() + " FILTER y.value == x.value RETURN x._key) " +
|
||||||
|
"RETURN [ a, b, c, d, e, f, g, h, i, j ]";
|
||||||
|
|
||||||
|
var explain = AQL_EXPLAIN(query);
|
||||||
|
var plan = explain.plan;
|
||||||
|
|
||||||
|
var walker = function (nodes, func) {
|
||||||
|
nodes.forEach(function(node) {
|
||||||
|
if (node.type === "SubqueryNode") {
|
||||||
|
walker(node.subquery.nodes, func);
|
||||||
|
}
|
||||||
|
func(node);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var indexNodes = 0, collectionNodes = 0;
|
||||||
|
walker(plan.nodes, function (node) {
|
||||||
|
if (node.type === "IndexRangeNode") {
|
||||||
|
++indexNodes;
|
||||||
|
if (indexNodes < 5) {
|
||||||
|
assertEqual("hash", node.index.type);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEqual("skiplist", node.index.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node.type === "EnumerateCollectionNode") {
|
||||||
|
++collectionNodes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEqual(0, collectionNodes);
|
||||||
|
assertEqual(20, indexNodes);
|
||||||
|
assertEqual(36, explain.stats.plansCreated);
|
||||||
|
|
||||||
var results = AQL_EXECUTE(query);
|
var results = AQL_EXECUTE(query);
|
||||||
assertEqual(0, results.stats.scannedFull);
|
assertEqual(0, results.stats.scannedFull);
|
||||||
assertNotEqual(0, results.stats.scannedIndex);
|
assertNotEqual(0, results.stats.scannedIndex);
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
/*jshint strict: false, maxlen: 500 */
|
||||||
|
/*global require, assertEqual, assertTrue, AQL_EXPLAIN */
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief tests for optimizer rule use index-range
|
||||||
|
///
|
||||||
|
/// @file
|
||||||
|
///
|
||||||
|
/// DISCLAIMER
|
||||||
|
///
|
||||||
|
/// Copyright 2014-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 triAGENS GmbH, Cologne, Germany
|
||||||
|
///
|
||||||
|
/// @author Max Neunhoeffer
|
||||||
|
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var internal = require("internal");
|
||||||
|
var jsunity = require("jsunity");
|
||||||
|
var helper = require("org/arangodb/aql-helper");
|
||||||
|
var removeAlwaysOnClusterRules = helper.removeAlwaysOnClusterRules;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test suite
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function optimizerRuleUseIndexRangeTester () {
|
||||||
|
var ruleName = "use-index-range";
|
||||||
|
var collBaseName = "UTUseIndexRange";
|
||||||
|
var collNames = ["NoInd", "SkipInd", "HashInd", "BothInd"];
|
||||||
|
var collNoInd;
|
||||||
|
var collSkipInd;
|
||||||
|
var collHashInd;
|
||||||
|
var collBothInd;
|
||||||
|
|
||||||
|
// various choices to control the optimizer:
|
||||||
|
var paramNone = { optimizer: { rules: [ "-all" ] } };
|
||||||
|
var paramEnabled = { optimizer: { rules: [ "-all", "+" + ruleName ] } };
|
||||||
|
var paramAll = { optimizer: { rules: [ "+all" ] } };
|
||||||
|
var paramEnabledAllPlans = { optimizer: { rules: [ "-all", "+" + ruleName ]},
|
||||||
|
allPlans: true };
|
||||||
|
var paramAllAllPlans = { optimizer: { rules: [ "+all" ]}, allPlans: true };
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief set up
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
setUp : function () {
|
||||||
|
var n = collNames.map(function(x) { return collBaseName + x; });
|
||||||
|
var colls = [];
|
||||||
|
for (var i = 0; i < n.length; i++) {
|
||||||
|
var collName = n[i], coll;
|
||||||
|
internal.db._drop(collName);
|
||||||
|
coll = internal.db._create(collName);
|
||||||
|
for (var j = 0; j < 10; j++) {
|
||||||
|
coll.insert({"a":j, "s":"s"+j});
|
||||||
|
}
|
||||||
|
colls.push(coll);
|
||||||
|
}
|
||||||
|
collNoInd = colls[0];
|
||||||
|
collSkipInd = colls[1];
|
||||||
|
collSkipInd.ensureSkiplist("a");
|
||||||
|
collHashInd = colls[2];
|
||||||
|
collHashInd.ensureHashIndex("a");
|
||||||
|
collBothInd = colls[3];
|
||||||
|
collBothInd.ensureHashIndex("a");
|
||||||
|
collBothInd.ensureSkiplist("a");
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief tear down
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
tearDown : function () {
|
||||||
|
var n = collNames.map(function(x) { return collBaseName + x; });
|
||||||
|
for (var i = 0; i < n.length; i++) {
|
||||||
|
internal.db._drop(n[i]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test that rule has no effect when explicitly disabled
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testRuleDisabled : function () {
|
||||||
|
var queries = [
|
||||||
|
"FOR i IN UTUseIndexRangeNoInd FILTER i.a >= 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a >= 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeHashInd FILTER i.a == 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeBothInd FILTER i.a == 2 RETURN i",
|
||||||
|
];
|
||||||
|
|
||||||
|
queries.forEach(function(query) {
|
||||||
|
var result = AQL_EXPLAIN(query, { }, paramNone);
|
||||||
|
assertEqual([ ], removeAlwaysOnClusterRules(result.plan.rules), query);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test that rule has no effect
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testRuleNoEffect : function () {
|
||||||
|
var queries = [
|
||||||
|
"FOR i IN UTUseIndexRangeNoInd FILTER i.a >= 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeNoInd FILTER i.a == 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeHashInd FILTER i.a >= 2 RETURN i"
|
||||||
|
];
|
||||||
|
|
||||||
|
queries.forEach(function(query) {
|
||||||
|
var result = AQL_EXPLAIN(query, { }, paramEnabled);
|
||||||
|
assertEqual([ ], removeAlwaysOnClusterRules(result.plan.rules), query);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test that rule has an effect
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testRuleHasEffect : function () {
|
||||||
|
var queries = [
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a >= 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a == 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a < 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a < 2 && i.a > 0 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a < 2 && i.a > 3 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeHashInd FILTER i.a == 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeBothInd FILTER i.a == 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeBothInd FILTER i.a <= 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeBothInd FILTER i.a > 2 RETURN i"
|
||||||
|
];
|
||||||
|
|
||||||
|
queries.forEach(function(query) {
|
||||||
|
var result = AQL_EXPLAIN(query, { }, paramEnabled);
|
||||||
|
assertEqual([ ruleName ], removeAlwaysOnClusterRules(result.plan.rules),
|
||||||
|
query);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test that rule has an effect
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testRuleHasEffectWithAll : function () {
|
||||||
|
var queries = [
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a >= 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a == 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a < 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a < 2 && i.a > 0 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeSkipInd FILTER i.a < 2 && i.a > 3 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeHashInd FILTER i.a == 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeBothInd FILTER i.a == 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeBothInd FILTER i.a <= 2 RETURN i",
|
||||||
|
"FOR i IN UTUseIndexRangeBothInd FILTER i.a > 2 RETURN i"
|
||||||
|
];
|
||||||
|
|
||||||
|
queries.forEach(function(query) {
|
||||||
|
var result = AQL_EXPLAIN(query, { }, paramAll);
|
||||||
|
assertTrue(result.plan.rules.indexOf(ruleName) >= 0, query);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test that plan explosion does not happen
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testRuleHasNoPlanExplosion : function () {
|
||||||
|
var query =
|
||||||
|
"LET A = (FOR a IN UTUseIndexRangeBothInd FILTER a.a == 2 RETURN a) "+
|
||||||
|
"LET B = (FOR b IN UTUseIndexRangeBothInd FILTER b.a == 2 RETURN b) "+
|
||||||
|
"LET C = (FOR c IN UTUseIndexRangeBothInd FILTER c.a == 2 RETURN c) "+
|
||||||
|
"LET D = (FOR d IN UTUseIndexRangeBothInd FILTER d.a == 2 RETURN d) "+
|
||||||
|
"LET E = (FOR e IN UTUseIndexRangeBothInd FILTER e.a == 2 RETURN e) "+
|
||||||
|
"LET F = (FOR f IN UTUseIndexRangeBothInd FILTER f.a == 2 RETURN f) "+
|
||||||
|
"LET G = (FOR g IN UTUseIndexRangeBothInd FILTER g.a == 2 RETURN g) "+
|
||||||
|
"LET H = (FOR h IN UTUseIndexRangeBothInd FILTER h.a == 2 RETURN h) "+
|
||||||
|
"LET I = (FOR i IN UTUseIndexRangeBothInd FILTER i.a == 2 RETURN i) "+
|
||||||
|
"LET J = (FOR j IN UTUseIndexRangeBothInd FILTER j.a == 2 RETURN j) "+
|
||||||
|
"FOR k IN UTUseIndexRangeBothInd FILTER k.a == 2 "+
|
||||||
|
" RETURN {A:A, B:B, C:C, D:D, E:E, F:F, G:G, H:H, I:I, J:J, K:k}";
|
||||||
|
|
||||||
|
var result = AQL_EXPLAIN(query, { }, paramEnabledAllPlans);
|
||||||
|
assertTrue(result.plans.length < 40, query);
|
||||||
|
result = AQL_EXPLAIN(query, { }, paramAllAllPlans);
|
||||||
|
assertTrue(result.plans.length < 40, query);
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief test that plan explosion does not happen
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
testRuleMakesAll : function () {
|
||||||
|
var query =
|
||||||
|
"LET A = (FOR a IN UTUseIndexRangeBothInd FILTER a.a == 2 RETURN a) "+
|
||||||
|
"LET B = (FOR b IN UTUseIndexRangeBothInd FILTER b.a == 2 RETURN b) "+
|
||||||
|
"LET C = (FOR c IN UTUseIndexRangeBothInd FILTER c.a == 2 RETURN c) "+
|
||||||
|
"FOR k IN UTUseIndexRangeBothInd FILTER k.a == 2 "+
|
||||||
|
" RETURN {A:A, B:B, C:C, K:k}";
|
||||||
|
|
||||||
|
var result = AQL_EXPLAIN(query, { }, paramEnabledAllPlans);
|
||||||
|
assertTrue(result.plans.length === 16, query);
|
||||||
|
result = AQL_EXPLAIN(query, { }, paramAllAllPlans);
|
||||||
|
assertTrue(result.plans.length === 16, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @brief executes the test suite
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
jsunity.run(optimizerRuleUseIndexRangeTester);
|
||||||
|
|
||||||
|
return jsunity.done();
|
||||||
|
|
||||||
|
// Local Variables:
|
||||||
|
// mode: outline-minor
|
||||||
|
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
|
||||||
|
// End:
|
Loading…
Reference in New Issue