1
0
Fork 0

added optimizer rule `sort-in-values`

This commit is contained in:
jsteemann 2015-12-13 23:33:44 +01:00
parent 1942ecc9ad
commit 600c48375d
33 changed files with 913 additions and 208 deletions

View File

@ -1,6 +1,16 @@
v2.8.0 (XXXX-XX-XX)
-------------------
* added AQL query optimizer rule "sort-in-values"
This rule pre-sorts the right-hand side operand of the `IN` and `NOT IN`
operators so the operation can use a binary search with logarithmic complexity
instead of a linear search. The rule is applied when the right-hand side
operand of an `IN` or `NOT IN` operator in a filter condition is a variable that
is defined in a different loop/scope than the operator itself. Additionally,
the filter condition must consist of solely the `IN` or `NOT IN` operation
in order to avoid any side-effects.
* changed collection status terminology in web interface for collections for
which an unload request has been issued from `in the process of being unloaded`
to `will be unloaded`.

View File

@ -343,6 +343,10 @@ The following optimizer rules may appear in the `rules` attribute of a plan:
* `move-filters-up`: will appear if a *FilterNode* was moved up in a plan. The
intention of this rule is to move filters up in the processing pipeline as far
as possible (ideally out of inner loops) so they filter results as early as possible.
* `sort-in-values`: will appear when the values used as right-hand side of an `IN`
operator will be pre-sorted using an extra function call. Pre-sorting the comparison
array allows using a binary search in-list lookup with a logarithmic complexity instead
of the default linear complexity in-list lookup.
* `remove-unnecessary-filters`: will appear if a *FilterNode* was removed or replaced.
*FilterNode*s whose filter condition will always evaluate to *true* will be
removed from the plan, whereas *FilterNode* that will never let any results pass

View File

@ -651,6 +651,7 @@ SHELL_SERVER_AQL = @top_srcdir@/js/server/tests/aql-arithmetic.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-remove-sort-rand.js \
@top_srcdir@/js/server/tests/aql-optimizer-rule-sort-in-values.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-stats-noncluster.js \

View File

@ -799,6 +799,9 @@ AstNode* Ast::createNodeBinaryOperator (AstNodeType type,
node->addMember(lhs);
node->addMember(rhs);
// initialize sortedness information (currently used for the IN operator only)
node->setBoolValue(false);
return node;
}
@ -1935,6 +1938,19 @@ AstNode* Ast::clone (AstNode const* node) {
type == NODE_TYPE_EXPANSION) {
copy->setIntValue(node->getIntValue(true));
}
else if (type == NODE_TYPE_OPERATOR_BINARY_IN ||
type == NODE_TYPE_OPERATOR_BINARY_NIN) {
// copy sortedness information
copy->setBoolValue(node->getBoolValue());
}
else if (type == NODE_TYPE_ARRAY) {
if (node->isSorted()) {
copy->setFlag(DETERMINED_SORTED, VALUE_SORTED);
}
else {
copy->setFlag(DETERMINED_SORTED);
}
}
else if (type == NODE_TYPE_VALUE) {
switch (node->value.type) {
case VALUE_TYPE_NULL:
@ -2428,13 +2444,15 @@ AstNode* Ast::optimizeBinaryOperatorRelational (AstNode* node) {
}
if (! lhsIsConst) {
if (rhs->numMembers() >= 10 &&
if (rhs->numMembers() >= 8 &&
(node->type == NODE_TYPE_OPERATOR_BINARY_IN ||
node->type == NODE_TYPE_OPERATOR_BINARY_NIN)) {
// if the IN list contains a considerable amount of items, we will sort
// it, so we can find elements quicker later using a binary search
// note that sorting will also set a flag for the node
rhs->sort();
// set sortedness for IN/NIN operator node
node->setBoolValue(false);
}
return node;

View File

@ -34,6 +34,7 @@
#include "Aql/Query.h"
#include "Aql/Scopes.h"
#include "Aql/types.h"
#include "Basics/fasthash.h"
#include "Basics/JsonHelper.h"
#include "Basics/json-utilities.h"
#include "Basics/StringBuffer.h"
@ -562,6 +563,18 @@ AstNode::AstNode (Ast* ast,
setIntValue(JsonHelper::checkAndGetNumericValue<int64_t>(json.json(), "levels"));
break;
}
case NODE_TYPE_OPERATOR_BINARY_IN:
case NODE_TYPE_OPERATOR_BINARY_NIN: {
setBoolValue(JsonHelper::getBooleanValue(json.json(), "sorted", false));
break;
}
case NODE_TYPE_ARRAY: {
bool sorted = JsonHelper::getBooleanValue(json.json(), "sorted", false);
if (sorted) {
setFlag(DETERMINED_SORTED, VALUE_SORTED);
}
break;
}
case NODE_TYPE_OBJECT:
case NODE_TYPE_ROOT:
case NODE_TYPE_FOR:
@ -596,14 +609,11 @@ AstNode::AstNode (Ast* ast,
case NODE_TYPE_OPERATOR_BINARY_LE:
case NODE_TYPE_OPERATOR_BINARY_GT:
case NODE_TYPE_OPERATOR_BINARY_GE:
case NODE_TYPE_OPERATOR_BINARY_IN:
case NODE_TYPE_OPERATOR_BINARY_NIN:
case NODE_TYPE_OPERATOR_TERNARY:
case NODE_TYPE_SUBQUERY:
case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS:
case NODE_TYPE_INDEXED_ACCESS:
case NODE_TYPE_ITERATOR:
case NODE_TYPE_ARRAY:
case NODE_TYPE_RANGE:
case NODE_TYPE_NOP:
case NODE_TYPE_CALCULATED_OBJECT_ELEMENT:
@ -623,6 +633,7 @@ AstNode::AstNode (Ast* ast,
if (subNodes.isArray()) {
size_t const len = subNodes.size();
for (size_t i = 0; i < len; i++) {
Json subNode(subNodes.at(i));
int type = JsonHelper::checkAndGetNumericValue<int>(subNode.json(), "typeID");
@ -795,6 +806,55 @@ AstNode::~AstNode () {
// --SECTION-- public methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief computes a hash value for a value node
////////////////////////////////////////////////////////////////////////////////
uint64_t AstNode::hashValue (uint64_t hash) const {
if (type == NODE_TYPE_VALUE) {
switch (value.type) {
case VALUE_TYPE_NULL:
return fasthash64(static_cast<const void*>("null"), 4, hash);
case VALUE_TYPE_BOOL:
if (value.value._bool) {
return fasthash64(static_cast<const void*>("true"), 4, hash);
}
return fasthash64(static_cast<const void*>("false"), 5, hash);
case VALUE_TYPE_INT:
return fasthash64(static_cast<const void*>(&value.value._int), sizeof(value.value._int), hash);
case VALUE_TYPE_DOUBLE:
return fasthash64(static_cast<const void*>(&value.value._double), sizeof(value.value._double), hash);
case VALUE_TYPE_STRING:
return fasthash64(static_cast<const void*>(getStringValue()), getStringLength(), hash);
}
}
size_t const n = numMembers();
if (type == NODE_TYPE_ARRAY) {
hash = fasthash64(static_cast<const void*>("array"), 5, hash);
for (size_t i = 0; i < n; ++i) {
hash = getMemberUnchecked(i)->hashValue(hash);
}
return hash;
}
if (type == NODE_TYPE_OBJECT) {
hash = fasthash64(static_cast<const void*>("object"), 6, hash);
for (size_t i = 0; i < n; ++i) {
auto sub = getMemberUnchecked(i);
if (sub != nullptr) {
hash = fasthash64(static_cast<const void*>(sub->getStringValue()), sub->getStringLength(), hash);
hash = sub->getMember(0)->hashValue(hash);
}
}
return hash;
}
TRI_ASSERT(false);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief dump the node (for debugging purposes)
////////////////////////////////////////////////////////////////////////////////
@ -1065,6 +1125,13 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone,
// arguments are exported via node members
}
if (type == NODE_TYPE_ARRAY) {
if (hasFlag(DETERMINED_SORTED)) {
// transport information about a node's sortedness
TRI_Insert3ObjectJson(zone, node, "sorted", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, hasFlag(VALUE_SORTED)));
}
}
if (type == NODE_TYPE_VALUE) {
// dump value of "value" node
auto v = toJsonValue(zone);
@ -1083,6 +1150,11 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone,
}
}
if (type == NODE_TYPE_OPERATOR_BINARY_IN ||
type == NODE_TYPE_OPERATOR_BINARY_NIN) {
TRI_Insert3ObjectJson(zone, node, "sorted", TRI_CreateBooleanJson(TRI_UNKNOWN_MEM_ZONE, getBoolValue()));
}
if (type == NODE_TYPE_VARIABLE ||
type == NODE_TYPE_REFERENCE) {
auto variable = static_cast<Variable*>(getData());
@ -1133,16 +1205,14 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone,
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief iterates whether a node of type "searchType" can be found
////////////////////////////////////////////////////////////////////////////////
bool AstNode::containsNodeType (AstNodeType searchType) const {
if (type == searchType)
bool AstNode::containsNodeType (AstNodeType searchType) const {
if (type == searchType) {
return true;
}
// iterate sub-nodes
size_t const n = members.size();
@ -1150,10 +1220,10 @@ bool AstNode::containsNodeType (AstNodeType searchType) const {
if (n > 0) {
for (size_t i = 0; i < n; ++i) {
AstNode* member = getMemberUnchecked(i);
if (member != nullptr) {
if (member->containsNodeType(searchType)) {
return true;
}
if (member != nullptr &&
member->containsNodeType(searchType)) {
return true;
}
}
}
@ -1161,7 +1231,6 @@ bool AstNode::containsNodeType (AstNodeType searchType) const {
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief convert the node's value to a boolean value
/// this may create a new node or return the node itself if it is already a

View File

@ -265,6 +265,12 @@ namespace triagens {
public:
////////////////////////////////////////////////////////////////////////////////
/// @brief computes a hash value for a value node
////////////////////////////////////////////////////////////////////////////////
uint64_t hashValue (uint64_t) const;
////////////////////////////////////////////////////////////////////////////////
/// @brief dump the node (for debugging purposes)
////////////////////////////////////////////////////////////////////////////////
@ -941,6 +947,34 @@ namespace triagens {
};
int CompareAstNodes (AstNode const*, AstNode const*, bool);
////////////////////////////////////////////////////////////////////////////////
/// @brief less comparator for Ast value nodes
////////////////////////////////////////////////////////////////////////////////
template<bool useUtf8>
struct AstNodeValueLess {
inline bool operator() (AstNode const* lhs,
AstNode const* rhs) const {
return CompareAstNodes(lhs, rhs, useUtf8) < 0;
}
};
struct AstNodeValueHash {
inline size_t operator() (AstNode const* value) const {
return value->hashValue(0x12345678);
}
};
struct AstNodeValueEqual {
inline bool operator() (AstNode const* lhs,
AstNode const* rhs) const {
return CompareAstNodes(lhs, rhs, false) == 0;
}
};
}
}

View File

@ -245,12 +245,33 @@ bool ConditionPart::isCoveredBy (ConditionPart const& other) const {
}
}
}
return true;
}
else {
std::unordered_set<AstNode const*, AstNodeValueHash, AstNodeValueEqual> values(
512,
AstNodeValueHash(),
AstNodeValueEqual()
);
for (size_t i = 0; i < n2; ++i) {
values.emplace(other.valueNode->getMemberUnchecked(i));
}
for (size_t i = 0; i < n1; ++i) {
auto node = valueNode->getMemberUnchecked(i);
if (values.find(node) == values.end()) {
return false;
}
}
}
return true;
}
return false;
}
else if (isExpanded &&
if (isExpanded &&
other.isExpanded &&
operatorType == NODE_TYPE_OPERATOR_BINARY_IN &&
other.operatorType == NODE_TYPE_OPERATOR_BINARY_IN &&
@ -258,6 +279,7 @@ bool ConditionPart::isCoveredBy (ConditionPart const& other) const {
if (CompareAstNodes(other.valueNode, valueNode, false) == 0) {
return true;
}
return false;
}
@ -607,8 +629,8 @@ void Condition::normalize () {
/// @brief removes condition parts from another
////////////////////////////////////////////////////////////////////////////////
AstNode const* Condition::removeIndexCondition (Variable const* variable,
AstNode const* other) {
AstNode* Condition::removeIndexCondition (Variable const* variable,
AstNode* other) {
if (_root == nullptr || other == nullptr) {
return _root;
}

View File

@ -247,7 +247,7 @@ namespace triagens {
/// @brief return the condition root
////////////////////////////////////////////////////////////////////////////////
inline AstNode const* root () const {
inline AstNode* root () const {
return _root;
}
@ -323,8 +323,8 @@ namespace triagens {
/// @brief removes condition parts from another
////////////////////////////////////////////////////////////////////////////////
AstNode const* removeIndexCondition (Variable const*,
AstNode const*);
AstNode* removeIndexCondition (Variable const*,
AstNode*);
////////////////////////////////////////////////////////////////////////////////
/// @brief remove (now) invalid variables from the condition

View File

@ -1,7 +1,5 @@
/// @brief Infrastructure for ExecutionPlans
///
/// @file arangod/Aql/ExecutionNode.cpp
///
/// DISCLAIMER
///
/// Copyright 2010-2014 triagens GmbH, Cologne, Germany
@ -30,10 +28,10 @@
#include "Aql/ClusterNodes.h"
#include "Aql/Collection.h"
#include "Aql/ExecutionPlan.h"
#include "Aql/TraversalNode.h"
#include "Aql/IndexNode.h"
#include "Aql/ModificationNodes.h"
#include "Aql/SortNode.h"
#include "Aql/TraversalNode.h"
#include "Aql/WalkerWorker.h"
#include "Basics/StringBuffer.h"
@ -531,14 +529,14 @@ bool ExecutionNode::walk (WalkerWorker<ExecutionNode>* worker) {
}
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the node is in an inner loop
/// @brief get the surrounding loop
////////////////////////////////////////////////////////////////////////////////
bool ExecutionNode::isInInnerLoop () const {
ExecutionNode const* ExecutionNode::getLoop () const {
auto node = this;
while (node != nullptr) {
if (! node->hasDependency()) {
return false;
return nullptr;
}
node = node->getFirstDependency();
@ -550,15 +548,11 @@ bool ExecutionNode::isInInnerLoop () const {
type == INDEX ||
type == TRAVERSAL ||
type == ENUMERATE_LIST) {
// we are contained in an outer loop
return true;
// future potential optimization: check if the outer loop has 0 or 1
// iterations. in this case it is still possible to remove the sort
return node;
}
}
return false;
return nullptr;
}
// -----------------------------------------------------------------------------

View File

@ -1,8 +1,6 @@
////////////////////////////////////////////////////////////////////////////////
/// @brief Infrastructure for ExecutionPlans
///
/// @file arangod/Aql/ExecutionNode.h
///
/// DISCLAIMER
///
/// Copyright 2010-2014 triagens GmbH, Cologne, Germany
@ -29,12 +27,12 @@
#define ARANGODB_AQL_EXECUTION_NODE_H 1
#include "Basics/Common.h"
#include "Aql/Expression.h"
#include "Aql/types.h"
#include "Aql/Expression.h"
#include "Aql/Variable.h"
#include "Aql/WalkerWorker.h"
#include "Basics/JsonHelper.h"
#include "lib/Basics/json-utilities.h"
#include "Basics/json-utilities.h"
#include "VocBase/voc-types.h"
#include "VocBase/vocbase.h"
@ -282,6 +280,15 @@ namespace triagens {
typedef std::vector<std::pair<std::string, bool>> IndexMatchVec;
////////////////////////////////////////////////////////////////////////////////
/// @brief make a new node the (only) parent of the node
////////////////////////////////////////////////////////////////////////////////
void setParent (ExecutionNode* p) {
_parents.clear();
_parents.emplace_back(p);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replace a dependency, returns true if the pointer was found and
/// replaced, please note that this does not delete oldNode!
@ -703,7 +710,15 @@ namespace triagens {
/// @brief whether or not the node is in an inner loop
////////////////////////////////////////////////////////////////////////////////
bool isInInnerLoop () const;
bool isInInnerLoop () const {
return getLoop() != nullptr;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the surrounding loop
////////////////////////////////////////////////////////////////////////////////
ExecutionNode const* getLoop () const;
// -----------------------------------------------------------------------------
// --SECTION-- protected functions

View File

@ -1791,7 +1791,8 @@ void ExecutionPlan::replaceNode (ExecutionNode* oldNode,
oldNode->removeDependency(x);
}
auto oldNodeParents = oldNode->getParents(); // Intentional copy
// Intentional copy
auto oldNodeParents = oldNode->getParents();
for (auto* oldNodeParent : oldNodeParents) {
if (! oldNodeParent->replaceDependency(oldNode, newNode)){

View File

@ -108,7 +108,7 @@ static void RegisterWarning (triagens::aql::Ast const* ast,
////////////////////////////////////////////////////////////////////////////////
Expression::Expression (Ast* ast,
AstNode const* node)
AstNode* node)
: _ast(ast),
_executor(_ast->query()->executor()),
_node(node),
@ -325,7 +325,7 @@ bool Expression::findInArray (AqlValue const& left,
size_t const n = right.arraySize();
if (n > 3 && node->getMember(1)->isSorted()) {
if (n > 3 && (node->getBoolValue() || node->getMember(1)->isSorted())) {
// node values are sorted. can use binary search
size_t l = 0;
size_t r = n - 1;
@ -570,55 +570,6 @@ bool Expression::isConstant () const {
return _node->isConstant();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief this gives you ("variable.access", "Reference")
/// call isAttributeAccess in advance to ensure no exceptions.
////////////////////////////////////////////////////////////////////////////////
std::pair<std::string, std::string> Expression::getAttributeAccess () {
if (_type == UNPROCESSED) {
analyzeExpression();
}
if (_type != SIMPLE &&
_type != ATTRIBUTE) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"getMultipleAttributes works only on simple expressions or attribute accesses!");
}
auto expNode = _node;
std::vector<std::string> attributeVector;
if (expNode->type != triagens::aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"getMultipleAttributes works only on simple expressions or attribute accesses(2)!");
}
while (expNode->type == triagens::aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
attributeVector.emplace_back(std::string(expNode->getStringValue(), expNode->getStringLength()));
expNode = expNode->getMember(0);
}
std::string attributeVectorStr;
for (auto oneAttr = attributeVector.rbegin();
oneAttr != attributeVector.rend();
++oneAttr) {
if (! attributeVectorStr.empty()) {
attributeVectorStr.push_back('.');
}
attributeVectorStr += *oneAttr;
}
if (expNode->type != triagens::aql::NODE_TYPE_REFERENCE) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"getMultipleAttributes works only on simple expressions or attribute accesses(3)!");
}
auto variable = static_cast<Variable*>(expNode->getData());
return std::make_pair(variable->name, attributeVectorStr);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief stringify an expression
/// note that currently stringification is only supported for certain node types

View File

@ -83,7 +83,7 @@ namespace triagens {
////////////////////////////////////////////////////////////////////////////////
Expression (Ast*,
AstNode const*);
AstNode*);
////////////////////////////////////////////////////////////////////////////////
/// @brief constructor, using JSON
@ -106,7 +106,15 @@ namespace triagens {
/// @brief get the underlying AST node
////////////////////////////////////////////////////////////////////////////////
inline AstNode const* node () {
inline AstNode const* node () const {
return _node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the underlying AST node
////////////////////////////////////////////////////////////////////////////////
inline AstNode* nodeForModification () const {
return _node;
}
@ -245,13 +253,6 @@ namespace triagens {
bool isConstant () const;
////////////////////////////////////////////////////////////////////////////////
/// @brief this gives you ("variable.access", "Reference")
/// call isAttributeAccess in advance to ensure no exceptions.
////////////////////////////////////////////////////////////////////////////////
std::pair<std::string, std::string> getAttributeAccess();
////////////////////////////////////////////////////////////////////////////////
/// @brief stringify an expression
/// note that currently stringification is only supported for certain node types
@ -525,7 +526,7 @@ namespace triagens {
/// @brief the AST node that contains the expression to execute
////////////////////////////////////////////////////////////////////////////////
AstNode const* _node;
AstNode* _node;
////////////////////////////////////////////////////////////////////////////////
/// @brief a v8 function that will be executed for the expression

View File

@ -1789,7 +1789,7 @@ AqlValue Functions::SortedUnique (triagens::aql::Query* query,
if (! value.isArray()) {
// not an array
RegisterWarning(query, "SORTED_UNIQUE", TRI_ERROR_QUERY_ARRAY_EXPECTED);
// this is an internal function - do NOT issue a warning here
return AqlValue(new Json(Json::Null));
}
@ -4246,25 +4246,36 @@ AqlValue Functions::Range (triagens::aql::Query* query,
double from = ValueToNumber(leftJson.json(), unused);
double to = ValueToNumber(rightJson.json(), unused);
double step = 0;
double step = 0.0;
if (n == 3) {
auto const stepJson = ExtractFunctionParameter(trx, parameters, 2, false);
step = ValueToNumber(stepJson.json(), unused);
}
if ( step == 0 ||
(from < to && step < 0) ||
(from > to && step > 0)) {
else {
// no step specified
if (from <= to) {
step = 1.0;
}
else {
step = -1.0;
}
}
if ( step == 0.0 ||
(from < to && step < 0.0) ||
(from > to && step > 0.0)) {
RegisterWarning(query, "RANGE", TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return AqlValue(new Json(Json::Null));
}
Json result(Json::Array);
if (from < to) {
for (; from <= to; from += step) {
if (step < 0.0 && to <= from) {
for (; from >= to; from += step) {
result.add(Json(from));
}
}
else {
for (; from >= to; from += step) {
for (; from <= to; from += step) {
result.add(Json(from));
}
}

View File

@ -146,7 +146,7 @@ int IndexBlock::initialize () {
auto ast = en->_plan->getAst();
// instantiate expressions:
auto instantiateExpression = [&] (size_t i, size_t j, size_t k, AstNode const* a) -> void {
auto instantiateExpression = [&] (size_t i, size_t j, size_t k, AstNode* a) -> void {
// all new AstNodes are registered with the Ast in the Query
std::unique_ptr<Expression> e(new Expression(ast, a));

View File

@ -1,8 +1,6 @@
////////////////////////////////////////////////////////////////////////////////
/// @brief infrastructure for query optimizer
///
/// @file arangod/Aql/Optimizer.cpp
///
/// DISCLAIMER
///
/// Copyright 2010-2014 triagens GmbH, Cologne, Germany
@ -416,8 +414,12 @@ void Optimizer::setupRules () {
splitFiltersRule_pass1,
true);
#endif
registerRule("sort-in-values",
sortInValuesRule,
sortInValuesRule_pass1,
true);
// determine the "right" type of AggregateNode and
// add a sort node for each COLLECT (may be removed later)
// this rule cannot be turned off (otherwise, the query result might be wrong!)

View File

@ -1,8 +1,6 @@
////////////////////////////////////////////////////////////////////////////////
/// @brief infrastructure for query optimizer
///
/// @file arangod/Aql/Optimizer.h
///
/// DISCLAIMER
///
/// Copyright 2010-2014 triagens GmbH, Cologne, Germany
@ -90,7 +88,7 @@ namespace triagens {
// split and-combined filters into multiple smaller filters
splitFiltersRule_pass1 = 110,
// move calculations up the dependency chain (to pull them out of
// inner loops etc.)
moveCalculationsUpRule_pass1 = 120,
@ -98,6 +96,9 @@ namespace triagens {
// move filters up the dependency chain (to make result sets as small
// as possible as early as possible)
moveFiltersUpRule_pass1 = 130,
// sort IN values
sortInValuesRule_pass1 = 135,
// remove calculations that are repeatedly used in a query
removeRedundantCalculationsRule_pass1 = 140,

View File

@ -31,7 +31,6 @@
#include "Aql/AggregationOptions.h"
#include "Aql/ClusterNodes.h"
#include "Aql/ConditionFinder.h"
#include "Aql/TraversalConditionFinder.h"
#include "Aql/ExecutionEngine.h"
#include "Aql/ExecutionNode.h"
#include "Aql/Function.h"
@ -40,6 +39,7 @@
#include "Aql/ModificationNodes.h"
#include "Aql/SortCondition.h"
#include "Aql/SortNode.h"
#include "Aql/TraversalConditionFinder.h"
#include "Aql/Variable.h"
#include "Aql/types.h"
#include "Basics/json-utilities.h"
@ -52,6 +52,119 @@ using EN = triagens::aql::ExecutionNode;
// --SECTION-- rules for the optimizer
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief adds a SORT operation for IN right-hand side operands
////////////////////////////////////////////////////////////////////////////////
int triagens::aql::sortInValuesRule (Optimizer* opt,
ExecutionPlan* plan,
Optimizer::Rule const* rule) {
bool modified = false;
std::vector<ExecutionNode*> nodes(std::move(plan->findNodesOfType(EN::FILTER, true)));
for (auto const& n : nodes) {
// filter nodes always have one input variable
auto varsUsedHere = n->getVariablesUsedHere();
TRI_ASSERT(varsUsedHere.size() == 1);
// now check who introduced our variable
auto variable = varsUsedHere[0];
auto setter = plan->getVarSetBy(variable->id);
if (setter == nullptr ||
setter->getType() != EN::CALCULATION) {
// filter variable was not introduced by a calculation.
continue;
}
// filter variable was introduced a CalculationNode. now check the expression
auto s = static_cast<CalculationNode*>(setter);
auto filterExpression = s->expression();
auto inNode = filterExpression->nodeForModification();
TRI_ASSERT(inNode != nullptr);
// check the filter condition
if ((inNode->type != NODE_TYPE_OPERATOR_BINARY_IN && inNode->type != NODE_TYPE_OPERATOR_BINARY_NIN) ||
inNode->canThrow() ||
! inNode->isDeterministic()) {
// we better not tamper with this filter
continue;
}
auto rhs = inNode->getMember(1);
if (rhs->type != NODE_TYPE_REFERENCE) {
continue;
}
auto loop = n->getLoop();
if (loop == nullptr) {
// FILTER is not used inside a loop. so it will be used at most once
// not need to sort the IN values then
continue;
}
variable = static_cast<Variable const*>(rhs->getData());
setter = plan->getVarSetBy(variable->id);
if (setter == nullptr ||
setter->getType() != EN::CALCULATION) {
// variable itself was not introduced by a calculation.
continue;
}
if (loop == setter->getLoop()) {
// the FILTER and its value calculation are contained in the same loop
// this means the FILTER will be executed as many times as its value
// calculation. sorting the IN values will not provide a benefit here
continue;
}
auto ast = plan->getAst();
auto args = ast->createNodeArray();
args->addMember(static_cast<CalculationNode*>(setter)->expression()->node());
auto sorted = ast->createNodeFunctionCall("SORTED_UNIQUE", args);
auto outVar = ast->variables()->createTemporaryVariable();
ExecutionNode* calculationNode = nullptr;
auto expression = new Expression(ast, sorted);
try {
calculationNode = new CalculationNode(plan, plan->nextId(), expression, outVar);
}
catch (...) {
delete expression;
throw;
}
plan->registerNode(calculationNode);
// make the new node a parent of the original calculation node
calculationNode->addDependency(setter);
auto const& oldParents = setter->getParents();
TRI_ASSERT(! oldParents.empty());
calculationNode->addParent(oldParents[0]);
oldParents[0]->removeDependencies();
oldParents[0]->addDependency(calculationNode);
setter->setParent(calculationNode);
// finally adjust the variable inside the IN calculation
inNode->changeMember(1, ast->createNodeReference(outVar));
// set sortedness bit for the IN operator
inNode->setBoolValue(true);
modified = true;
}
if (modified) {
plan->findVarUsage();
}
opt->addPlan(plan, rule, modified);
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief remove redundant sorts
/// this rule modifies the plan in place:
@ -1330,7 +1443,7 @@ int triagens::aql::splitFiltersRule (Optimizer* opt,
continue;
}
std::vector<AstNode const*> stack{ expression->node() };
std::vector<AstNode*> stack{ expression->nodeForModification() };
while (! stack.empty()) {
auto current = stack.back();
@ -2057,7 +2170,7 @@ int triagens::aql::removeFiltersCoveredByIndexRule (Optimizer* opt,
std::unordered_set<ExecutionNode*> toUnlink;
bool modified = false;
std::vector<ExecutionNode*> nodes(std::move(plan->findNodesOfType(EN::FILTER, true)));
for (auto const& node : nodes) {
auto fn = static_cast<FilterNode const*>(node);
// find the node with the filter expression

View File

@ -29,8 +29,7 @@
#ifndef ARANGOD_AQL_OPTIMIZER_RULES_H
#define ARANGOD_AQL_OPTIMIZER_RULES 1
#include <Basics/Common.h>
#include "Basics/Common.h"
#include "Aql/Optimizer.h"
namespace triagens {
@ -40,6 +39,12 @@ namespace triagens {
// --SECTION-- rules for the optimizer
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief adds a SORT operation for IN right-hand side operands
////////////////////////////////////////////////////////////////////////////////
int sortInValuesRule (Optimizer*, ExecutionPlan*, Optimizer::Rule const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief remove redundant sorts
/// this rule modifies the plan in place:

View File

@ -62,7 +62,6 @@ TraversalBlock::TraversalBlock (ExecutionEngine* engine,
triagens::arango::traverser::TraverserOptions opts;
ep->fillTraversalOptions(opts);
auto edgeColls = ep->edgeColls();
auto ast = ep->_plan->getAst();
for (auto& map : *_expressions) {
@ -93,15 +92,16 @@ TraversalBlock::TraversalBlock (ExecutionEngine* engine,
_resolver = new CollectionNameResolver(_trx->vocbase());
if (triagens::arango::ServerState::instance()->isCoordinator()) {
_traverser.reset(new triagens::arango::traverser::ClusterTraverser(
edgeColls,
ep->edgeColls(),
opts,
std::string(_trx->vocbase()->_name, strlen(_trx->vocbase()->_name)),
_resolver,
_expressions
));
} else {
}
else {
std::vector<TRI_document_collection_t*> edgeCollections;
for (auto const& coll : edgeColls) {
for (auto const& coll : ep->edgeColls()) {
TRI_voc_cid_t cid = _resolver->getCollectionId(coll);
edgeCollections.push_back(_trx->documentCollection(cid));
}

View File

@ -96,7 +96,7 @@ static bool checkPathVariableAccessFeasible (CalculationNode const* cn,
static bool extractSimplePathAccesses (AstNode const* node,
TraversalNode* tn,
Ast* ast) {
Ast* ast) {
std::vector<AstNode const*> currentPath;
std::vector<std::vector<AstNode const*>> paths;
@ -141,8 +141,8 @@ static bool extractSimplePathAccesses (AstNode const* node,
}
if (compareNode != nullptr) {
AstNode const * pathAccessNode;
AstNode const * filterByNode;
AstNode const* pathAccessNode;
AstNode* filterByNode;
bool flipOperator = false;
if (compareNode->getMember(0) == accessNodeBranch) {
@ -160,7 +160,6 @@ static bool extractSimplePathAccesses (AstNode const* node,
}
if (accessNodeBranch->isSimple() && filterByNode->isDeterministic()) {
currentPath.clear();
clonePath.clear();
filterByNode->findVariableAccess(currentPath, clonePath, tn->pathOutVariable());

View File

@ -175,27 +175,12 @@ TraversalNode::TraversalNode (ExecutionPlan* plan,
_minDepth(minDepth),
_maxDepth(maxDepth),
_direction(direction),
_condition(nullptr)
{
_condition(nullptr) {
for (auto& it : edgeColls) {
_edgeColls.push_back(it);
}
}
int TraversalNode::checkIsOutVariable(size_t variableId) {
if (_vertexOutVariable != nullptr && _vertexOutVariable->id == variableId) {
return 0;
}
if (_edgeOutVariable != nullptr && _edgeOutVariable->id == variableId) {
return 1;
}
if (_pathOutVariable != nullptr && _pathOutVariable->id == variableId) {
return 2;
}
return -1;
}
TraversalNode::TraversalNode (ExecutionPlan* plan,
triagens::basics::Json const& base)
: ExecutionNode(plan, base),
@ -255,6 +240,19 @@ TraversalNode::TraversalNode (ExecutionPlan* plan,
}
}
int TraversalNode::checkIsOutVariable (size_t variableId) const {
if (_vertexOutVariable != nullptr && _vertexOutVariable->id == variableId) {
return 0;
}
if (_edgeOutVariable != nullptr && _edgeOutVariable->id == variableId) {
return 1;
}
if (_pathOutVariable != nullptr && _pathOutVariable->id == variableId) {
return 2;
}
return -1;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief toJson, for TraversalNode
////////////////////////////////////////////////////////////////////////////////
@ -301,13 +299,13 @@ void TraversalNode::toJsonHelper (triagens::basics::Json& nodes,
json("pathOutVariable", pathOutVariable()->toJson());
}
if (_expressions.size() > 0) {
if (! _expressions.empty()) {
triagens::basics::Json expressionObject = triagens::basics::Json(triagens::basics::Json::Object,
_expressions.size());
for (auto const & map : _expressions) {
for (auto const& map : _expressions) {
triagens::basics::Json expressionArray = triagens::basics::Json(triagens::basics::Json::Array,
map.second.size());
for (auto const & x : map.second) {
for (auto const& x : map.second) {
triagens::basics::Json exp(zone, triagens::basics::Json::Object);
auto tmp = dynamic_cast<SimpleTraverserExpression*>(x);
if (tmp != nullptr) {
@ -395,23 +393,23 @@ void TraversalNode::setCondition(triagens::aql::Condition* condition){
Ast::getReferencedVariables(condition->root(), varsUsedByCondition);
for (auto const& oneVar : varsUsedByCondition) {
if ((_vertexOutVariable != nullptr && oneVar->id != _vertexOutVariable->id) &&
(_edgeOutVariable != nullptr && oneVar->id != _edgeOutVariable->id) &&
(_pathOutVariable != nullptr && oneVar->id != _pathOutVariable->id) &&
(_inVariable != nullptr && oneVar->id != _inVariable->id)) {
if ((_vertexOutVariable != nullptr && oneVar->id != _vertexOutVariable->id) &&
(_edgeOutVariable != nullptr && oneVar->id != _edgeOutVariable->id) &&
(_pathOutVariable != nullptr && oneVar->id != _pathOutVariable->id) &&
(_inVariable != nullptr && oneVar->id != _inVariable->id)) {
_conditionVariables.push_back(oneVar);
_conditionVariables.emplace_back(oneVar);
}
}
_condition = condition;
}
void TraversalNode::storeSimpleExpression(bool isEdgeAccess,
size_t indexAccess,
AstNodeType comparisonType,
AstNode const* varAccess,
AstNode const* compareTo) {
void TraversalNode::storeSimpleExpression (bool isEdgeAccess,
size_t indexAccess,
AstNodeType comparisonType,
AstNode const* varAccess,
AstNode* compareTo) {
auto it = _expressions.find(indexAccess);
if (it == _expressions.end()) {

View File

@ -43,18 +43,17 @@ namespace triagens {
class SimpleTraverserExpression : public triagens::arango::traverser::TraverserExpression {
public:
triagens::aql::AstNode const* toEvaluate;
triagens::aql::AstNode* toEvaluate;
triagens::aql::Expression* expression;
SimpleTraverserExpression (
bool isEdgeAccess,
triagens::aql::AstNodeType comparisonType,
triagens::aql::AstNode const* varAccess,
triagens::aql::AstNode const* ptoEvaluate
) : triagens::arango::traverser::TraverserExpression(isEdgeAccess,
SimpleTraverserExpression (bool isEdgeAccess,
triagens::aql::AstNodeType comparisonType,
triagens::aql::AstNode const* varAccess,
triagens::aql::AstNode* toEvaluate)
: triagens::arango::traverser::TraverserExpression(isEdgeAccess,
comparisonType,
varAccess),
toEvaluate(ptoEvaluate),
toEvaluate(toEvaluate),
expression(nullptr) {
}
@ -354,13 +353,13 @@ namespace triagens {
/// @brief which variable? -1 none, 0 Edge, 1 Vertex, 2 path
////////////////////////////////////////////////////////////////////////////////
int checkIsOutVariable(size_t variableId);
int checkIsOutVariable (size_t variableId) const;
////////////////////////////////////////////////////////////////////////////////
/// @brief check whether an access is inside the specified range
////////////////////////////////////////////////////////////////////////////////
bool isInRange(uint64_t thisIndex, bool isEdge) {
bool isInRange (uint64_t thisIndex, bool isEdge) const {
if (isEdge) {
return (thisIndex < _maxDepth);
}
@ -371,7 +370,7 @@ namespace triagens {
/// @brief check whecher min..max actualy span a range
////////////////////////////////////////////////////////////////////////////////
bool isRangeValid() {
bool isRangeValid() const {
return _maxDepth >= _minDepth;
}
@ -379,11 +378,11 @@ namespace triagens {
/// @brief Remember a simple comparator filter
////////////////////////////////////////////////////////////////////////////////
void storeSimpleExpression(bool isEdgeAccess,
size_t indexAccess,
AstNodeType comparisonType,
AstNode const* varAccess,
AstNode const* compareTo);
void storeSimpleExpression (bool isEdgeAccess,
size_t indexAccess,
AstNodeType comparisonType,
AstNode const* varAccess,
AstNode* compareTo);
////////////////////////////////////////////////////////////////////////////////
/// @brief Returns a regerence to the simple traverser expressions
@ -465,7 +464,6 @@ namespace triagens {
std::vector<std::string> _edgeColls;
////////////////////////////////////////////////////////////////////////////////
/// @brief our graph...
////////////////////////////////////////////////////////////////////////////////

View File

@ -95,8 +95,9 @@ void ExampleMatcher::fillExampleDefinition (v8::Isolate* isolate,
if (pid == 0) {
// Internal attributes do have pid == 0.
if (strncmp("_", *keyStr, 1) == 0) {
string const key(*keyStr, (size_t) keyStr.length());
char const* p = *keyStr;
if (*p == '_') {
string const key(p, keyStr.length());
string keyVal = TRI_ObjectToString(val);
if (TRI_VOC_ATTRIBUTE_KEY == key) {
def._internal.insert(make_pair(internalAttr::key, DocumentId(0, keyVal)));
@ -153,16 +154,15 @@ void ExampleMatcher::fillExampleDefinition (TRI_json_t const* example,
CollectionNameResolver const* resolver,
ExampleDefinition& def) {
if ( TRI_IsStringJson(example) ) {
if (TRI_IsStringJson(example)) {
// Example is an _id value
char const* _key = strchr(example->_value._string.data, '/');
if (_key != nullptr) {
_key += 1;
def._internal.insert(make_pair(internalAttr::key, DocumentId(0, _key)));
return;
} else {
THROW_ARANGO_EXCEPTION(TRI_RESULT_ELEMENT_NOT_FOUND);
}
}
THROW_ARANGO_EXCEPTION(TRI_RESULT_ELEMENT_NOT_FOUND);
}
TRI_vector_t objects = example->_value._objects;
@ -181,8 +181,8 @@ void ExampleMatcher::fillExampleDefinition (TRI_json_t const* example,
if (pid == 0) {
// Internal attributes do have pid == 0.
if (strncmp("_", keyStr, 1) == 0) {
string const key(keyStr);
if (*keyStr == '_') {
string const key(keyStr, keyObj->_value._string.length - 1);
auto jsonValue = static_cast<TRI_json_t const*>(TRI_AtVector(&objects, i + 1));
if (! TRI_IsStringJson(jsonValue)) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_TYPE_ERROR);
@ -231,7 +231,7 @@ void ExampleMatcher::fillExampleDefinition (TRI_json_t const* example,
}
}
}
catch (bad_alloc&) {
catch (std::bad_alloc const&) {
ExampleMatcher::cleanup();
throw;
}

View File

@ -46,8 +46,8 @@ const std::string graphs = "_graphs";
/// @brief Load a graph from the _graphs collection; local and coordinator way
////////////////////////////////////////////////////////////////////////////////
triagens::aql::Graph* triagens::arango::lookupGraphByName (TRI_vocbase_t* vocbase, std::string name) {
triagens::aql::Graph *g = nullptr;
triagens::aql::Graph* triagens::arango::lookupGraphByName (TRI_vocbase_t* vocbase, std::string const& name) {
triagens::aql::Graph* g = nullptr;
if (ServerState::instance()->isCoordinator()) {
triagens::rest::HttpResponse::HttpResponseCode responseCode;

View File

@ -29,17 +29,15 @@
#ifndef ARANGODB_GRAPHS_LOOKUP_H
#define ARANGODB_GRAPHS_LOOKUP_H 1
#include "vocbase.h"
#include "VocBase/vocbase.h"
namespace triagens {
namespace aql {
class Graph;
}
namespace arango {
// -----------------------------------------------------------------------------
// --SECTION-- Factory for graphs
// -----------------------------------------------------------------------------
@ -51,7 +49,7 @@ namespace triagens {
/// The caller has to take care for the memory.
////////////////////////////////////////////////////////////////////////////////
triagens::aql::Graph * lookupGraphByName (TRI_vocbase_t*, std::string);
triagens::aql::Graph* lookupGraphByName (TRI_vocbase_t*, std::string const&);
} // namespace arango
} // namespace triagens

View File

@ -68,7 +68,7 @@ namespace triagens {
key(key) {
}
bool operator== (const VertexId& other) const {
bool operator== (VertexId const& other) const {
if (cid == other.cid) {
return strcmp(key, other.key) == 0;
}
@ -140,7 +140,6 @@ namespace triagens {
bool recursiveCheck (triagens::aql::AstNode const*,
DocumentAccessor&) const;
// Required when creating this expression without AST
std::vector<std::unique_ptr<triagens::aql::AstNode const>> _nodeRegister;
std::vector<std::string*> _stringRegister;
@ -226,25 +225,20 @@ namespace triagens {
bool usesPrune;
TraverserOptions () :
direction(TRI_EDGE_OUT),
minDepth(1),
maxDepth(1),
usesPrune(false)
{ };
usesPrune(false) {
}
void setPruningFunction (
std::function<bool (const TraversalPath* path)> callback
) {
void setPruningFunction (std::function<bool(TraversalPath const* path)> const& callback) {
pruningFunction = callback;
usesPrune = true;
}
bool shouldPrunePath (
const TraversalPath* path
) {
if (!usesPrune) {
bool shouldPrunePath (TraversalPath const* path) {
if (! usesPrune) {
return false;
}
return pruningFunction(path);
@ -357,7 +351,7 @@ namespace triagens {
////////////////////////////////////////////////////////////////////////////////
bool hasMore () {
return !_done;
return ! _done;
}
protected:
@ -430,7 +424,7 @@ namespace std {
template<>
struct less<triagens::arango::traverser::VertexId> {
public:
bool operator() (const triagens::arango::traverser::VertexId& lhs, const triagens::arango::traverser::VertexId& rhs) {
bool operator() (triagens::arango::traverser::VertexId const& lhs, triagens::arango::traverser::VertexId const& rhs) {
if (lhs.cid != rhs.cid) {
return lhs.cid < rhs.cid;
}

View File

@ -469,7 +469,7 @@ function processQuery (query, explain) {
if (node.hasOwnProperty("subNodes")) {
if (node.subNodes.length > 20) {
// print only the first 20 values from the array
return "[ " + node.subNodes.slice(0, 20).map(buildExpression).join(", ") + " ... ]";
return "[ " + node.subNodes.slice(0, 20).map(buildExpression).join(", ") + ", ... ]";
}
return "[ " + node.subNodes.map(buildExpression).join(", ") + " ]";
}
@ -514,8 +514,14 @@ function processQuery (query, explain) {
case "modulus":
return buildExpression(node.subNodes[0]) + " % " + buildExpression(node.subNodes[1]);
case "compare not in":
if (node.sorted) {
return buildExpression(node.subNodes[0]) + " not in " + annotation("/* sorted */") + " " + buildExpression(node.subNodes[1]);
}
return buildExpression(node.subNodes[0]) + " not in " + buildExpression(node.subNodes[1]);
case "compare in":
if (node.sorted) {
return buildExpression(node.subNodes[0]) + " in " + annotation("/* sorted */") + " " + buildExpression(node.subNodes[1]);
}
return buildExpression(node.subNodes[0]) + " in " + buildExpression(node.subNodes[1]);
case "compare ==":
return buildExpression(node.subNodes[0]) + " == " + buildExpression(node.subNodes[1]);

View File

@ -468,7 +468,7 @@ function processQuery (query, explain) {
if (node.hasOwnProperty("subNodes")) {
if (node.subNodes.length > 20) {
// print only the first 20 values from the array
return "[ " + node.subNodes.slice(0, 20).map(buildExpression).join(", ") + " ... ]";
return "[ " + node.subNodes.slice(0, 20).map(buildExpression).join(", ") + ", ... ]";
}
return "[ " + node.subNodes.map(buildExpression).join(", ") + " ]";
}
@ -513,8 +513,14 @@ function processQuery (query, explain) {
case "modulus":
return buildExpression(node.subNodes[0]) + " % " + buildExpression(node.subNodes[1]);
case "compare not in":
if (node.sorted) {
return buildExpression(node.subNodes[0]) + " not in " + annotation("/* sorted */") + " " + buildExpression(node.subNodes[1]);
}
return buildExpression(node.subNodes[0]) + " not in " + buildExpression(node.subNodes[1]);
case "compare in":
if (node.sorted) {
return buildExpression(node.subNodes[0]) + " in " + annotation("/* sorted */") + " " + buildExpression(node.subNodes[1]);
}
return buildExpression(node.subNodes[0]) + " in " + buildExpression(node.subNodes[1]);
case "compare ==":
return buildExpression(node.subNodes[0]) + " == " + buildExpression(node.subNodes[1]);

View File

@ -2036,6 +2036,118 @@ function ahuacatlFunctionsTestSuite () {
assertQueryWarningAndNull(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN RANGE(-1, 1, -1)");
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx1 : function () {
var expected = [ [ 1 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(1, 1))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx2 : function () {
var expected = [ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(1, 10))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx3 : function () {
var expected = [ [ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(-1, 10))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx4 : function () {
var expected = [ [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(10, -1))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx5 : function () {
var expected = [ [ 0 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(0, 0))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx6 : function () {
var expected = [ [ 10 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(10, 10, 5))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx7 : function () {
var expected = [ [ 10, 15, 20, 25, 30 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(10, 30, 5))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx8 : function () {
var expected = [ [ 30, 25, 20, 15, 10 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(30, 10, -5))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxx9 : function () {
var expected = [ [ 3.4, 4.6, 5.8, 7.0, 8.2 ] ];
var actual = getQueryResults("RETURN NOOPT(RANGE(3.4, 8.9, 1.2))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range function
////////////////////////////////////////////////////////////////////////////////
testRangeCxxInvalid : function () {
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NOOPT(RANGE())");
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NOOPT(RANGE(1))");
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NOOPT(RANGE(1, 2, 3, 4))");
assertEqual([ [ 0, 1, 2 ] ], getQueryResults("RETURN NOOPT(RANGE(null, 2))"));
assertEqual([ [ 0 ] ], getQueryResults("RETURN NOOPT(RANGE(null, null))"));
assertEqual([ [ 0, 1, 2 ] ], getQueryResults("RETURN NOOPT(RANGE(false, 2))"));
assertEqual([ [ 1, 2 ] ], getQueryResults("RETURN NOOPT(RANGE(true, 2))"));
assertEqual([ [ 1 ] ], getQueryResults("RETURN NOOPT(RANGE(1, true))"));
assertEqual([ [ 1, 0 ] ], getQueryResults("RETURN NOOPT(RANGE(1, [ ]))"));
assertEqual([ [ 1, 0 ] ], getQueryResults("RETURN NOOPT(RANGE(1, { }))"));
assertQueryWarningAndNull(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NOOPT(RANGE(1, 1, 0))");
assertQueryWarningAndNull(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NOOPT(RANGE(-1, -1, 0))");
assertQueryWarningAndNull(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NOOPT(RANGE(1, -1, 1))");
assertQueryWarningAndNull(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NOOPT(RANGE(-1, 1, -1))");
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test minus function
////////////////////////////////////////////////////////////////////////////////
@ -3103,6 +3215,56 @@ function ahuacatlFunctionsTestSuite () {
assertEqual([ false ], getQueryResults("RETURN HAS({ }, { })"));
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test has function
////////////////////////////////////////////////////////////////////////////////
testHasCxx1 : function () {
var expected = [ true, true, true, false, true ];
var actual = getQueryResults("FOR u IN [ { \"the fox\" : true }, { \"the fox\" : false }, { \"the fox\" : null }, { \"the FOX\" : true }, { \"the FOX\" : true, \"the fox\" : false } ] return NOOPT(HAS(u, \"the fox\"))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test has function
////////////////////////////////////////////////////////////////////////////////
testHasCxx2 : function () {
var expected = [ false, false, false ];
var actual = getQueryResults("FOR u IN [ { \"the foxx\" : { \"the fox\" : false } }, { \" the fox\" : false }, null ] return NOOPT(HAS(u, \"the fox\"))");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test has function
////////////////////////////////////////////////////////////////////////////////
testHasCxx3 : function () {
var expected = [ [ "test2", [ "other" ] ] ];
var actual = getQueryResults("LET doc = { \"_id\": \"test/76689250173\", \"_rev\": \"76689250173\", \"_key\": \"76689250173\", \"test1\": \"something\", \"test2\": { \"DATA\": [ \"other\" ] } } FOR attr IN ATTRIBUTES(doc) LET prop = doc[attr] FILTER NOOPT(HAS(prop, 'DATA')) RETURN [ attr, prop.DATA ]");
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test has function
////////////////////////////////////////////////////////////////////////////////
testHasCxxInvalid : function () {
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NOOPT(HAS())");
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NOOPT(HAS({ }))");
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NOOPT(HAS({ }, \"fox\", true))");
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS(false, \"fox\"))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS(3, \"fox\"))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS(\"yes\", \"fox\"))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS([ ], \"fox\"))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS({ }, null))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS({ }, false))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS({ }, true))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS({ }, 1))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS({ }, [ ]))"));
assertEqual([ false ], getQueryResults("RETURN NOOPT(HAS({ }, { }))"));
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test attributes function
////////////////////////////////////////////////////////////////////////////////

View File

@ -1,5 +1,5 @@
/*jshint globalstrict:false, strict:false, sub: true, maxlen: 500 */
/*global assertEqual, assertFalse, assertTrue, AQL_EXECUTE */
/*global assertEqual, assertFalse, assertTrue, AQL_EXECUTE, AQL_EXPLAIN */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for multi-modify operations
@ -108,6 +108,16 @@ function ahuacatlMultiModifySuite () {
assertEqual(1, c1.count());
assertTrue(c1.exists("1"));
assertEqual(2009, c3.count());
// check that 'readCompleteInput' option is set
var nodes = AQL_EXPLAIN(q, { "@cn": cn1, "@e": cn3 }).plan.nodes, found = false;
nodes.forEach(function(node) {
if (node.type === 'RemoveNode') {
assertTrue(node.modificationFlags.readCompleteInput);
found = true;
}
});
assertTrue(found);
},
testTraversalAfterModification : function () {
@ -281,6 +291,26 @@ function ahuacatlMultiModifySuite () {
assertQueryError(errors.ERROR_QUERY_ACCESS_AFTER_MODIFICATION.code, q, {"@cn": cn1 });
},
testMultiRemoveLoopSameCollectionWithRead : function () {
AQL_EXECUTE("FOR i IN 1..2010 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn1 });
var q = "FOR doc IN @@cn1 INSERT { _key: doc._key } INTO @@cn2 REMOVE doc IN @@cn1";
var actual = AQL_EXECUTE(q, { "@cn1": cn1, "@cn2": cn2 });
assertEqual([ ], actual.json);
assertEqual(4020, actual.stats.writesExecuted);
assertEqual(0, c1.count());
assertEqual(2010, c2.count());
// check that 'readCompleteInput' option is set
var nodes = AQL_EXPLAIN(q, { "@cn1": cn1, "@cn2": cn2 }).plan.nodes, found = false;
nodes.forEach(function(node) {
if (node.type === 'RemoveNode') {
assertTrue(node.modificationFlags.readCompleteInput);
found = true;
}
});
assertTrue(found);
},
testMultiRemoveLoopOtherCollection : function () {
AQL_EXECUTE("FOR i IN 1..10 INSERT { _key: CONCAT('test', i) } INTO @@cn", { "@cn" : cn2 });
var q = "FOR i IN 1..10 INSERT { value: i } INTO @@cn1 REMOVE { _key: CONCAT('test', i) } INTO @@cn2";

View File

@ -0,0 +1,262 @@
/*jshint globalstrict:false, strict:false, maxlen: 500 */
/*global assertEqual, assertNotEqual, assertTrue, AQL_EXPLAIN, AQL_EXECUTE */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for optimizer rules
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 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 Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
var helper = require("org/arangodb/aql-helper");
var isEqual = helper.isEqual;
var getQueryMultiplePlansAndExecutions = helper.getQueryMultiplePlansAndExecutions;
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function optimizerRuleTestSuite () {
var ruleName = "sort-in-values";
// various choices to control the optimizer:
var paramNone = { optimizer: { rules: [ "-all" ] } };
var paramEnabled = { optimizer: { rules: [ "-all", "+" + ruleName ] } };
var paramDisabled = { optimizer: { rules: [ "+all", "-" + ruleName ] } };
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief set up
////////////////////////////////////////////////////////////////////////////////
setUp : function () {
},
////////////////////////////////////////////////////////////////////////////////
/// @brief tear down
////////////////////////////////////////////////////////////////////////////////
tearDown : function () {
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test that rule has no effect when explicitly disabled
////////////////////////////////////////////////////////////////////////////////
testRuleDisabled : function () {
var queries = [
"LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN values RETURN i",
"LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a NOT IN values RETURN i",
"LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER LENGTH(i.a) >= 3 FILTER i.a IN values RETURN i",
"LET values = NOOPT(RANGE(1, 100)) FOR i IN 1..100 FILTER i IN values RETURN i"
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query, { }, paramNone);
assertEqual([ ], result.plan.rules);
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test that rule has no effect
////////////////////////////////////////////////////////////////////////////////
testRuleNoEffect : function () {
var queryList = [
"LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a == 'foobar' && i.a IN values RETURN i",
"LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a == 'foobar' || i.a IN values RETURN i",
"LET values = SPLIT('foo,bar,foobar,qux', ',') FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN values RETURN i",
"LET values = SPLIT('foo,bar,foobar,qux', ',') FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a NOT IN values RETURN i",
"FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN SPLIT('foo,bar,foobar,qux', ',') RETURN i",
"FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a NOT IN SPLIT('foo,bar,foobar,qux', ',') RETURN i",
"LET values = RANGE(1, 100) FOR i IN 1..100 FILTER i IN values RETURN i",
"FOR i IN 1..100 FILTER i IN RANGE(1, 100) RETURN i",
"FOR i IN 1..100 FILTER i IN NOOPT(RANGE(1, 100)) RETURN i"
];
queryList.forEach(function(query) {
var result = AQL_EXPLAIN(query, { }, paramEnabled);
assertEqual([ ], result.plan.rules, query);
var allresults = getQueryMultiplePlansAndExecutions(query, {});
for (var j = 1; j < allresults.results.length; j++) {
assertTrue(isEqual(allresults.results[0],
allresults.results[j]),
"whether the execution of '" + query +
"' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) +
" Should be: '" + JSON.stringify(allresults.results[0]) +
"' but is: " + JSON.stringify(allresults.results[j]) + "'"
);
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test that rule has an effect
////////////////////////////////////////////////////////////////////////////////
testRuleHasEffect : function () {
var queries = [
"LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN values RETURN i",
"LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a NOT IN values RETURN i",
"LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER LENGTH(i.a) >= 3 FILTER i.a IN values RETURN i",
"LET values = NOOPT(RANGE(1, 100)) FOR i IN 1..100 FILTER i IN values RETURN i"
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query, { }, paramEnabled);
assertEqual([ ruleName ], result.plan.rules, query);
var allresults = getQueryMultiplePlansAndExecutions(query, {});
for (var j = 1; j < allresults.results.length; j++) {
assertTrue(isEqual(allresults.results[0],
allresults.results[j]),
"whether the execution of '" + query +
"' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) +
" Should be: '" + JSON.stringify(allresults.results[0]) +
"' but is: " + JSON.stringify(allresults.results[j]) + "'"
);
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test generated plans
////////////////////////////////////////////////////////////////////////////////
testPlans : function () {
var query = "LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN values RETURN i";
var actual = AQL_EXPLAIN(query, null, paramEnabled);
var nodes = helper.getLinearizedPlan(actual).reverse();
assertEqual("ReturnNode", nodes[0].type);
assertEqual("FilterNode", nodes[1].type);
assertEqual(nodes[2].outVariable.id, nodes[1].inVariable.id);
// the filter calculation
assertEqual("CalculationNode", nodes[2].type);
assertEqual("compare in", nodes[2].expression.type);
assertTrue(nodes[2].expression.sorted);
assertEqual("reference", nodes[2].expression.subNodes[1].type);
assertEqual("simple", nodes[2].expressionType);
var varId = nodes[2].expression.subNodes[1].id;
assertEqual("EnumerateListNode", nodes[3].type);
assertEqual("CalculationNode", nodes[4].type);
// new calculation
assertEqual("CalculationNode", nodes[5].type);
assertEqual("function call", nodes[5].expression.type);
assertEqual("SORTED_UNIQUE", nodes[5].expression.name);
assertEqual(nodes[5].outVariable.id, varId);
// original calculation
assertEqual("CalculationNode", nodes[6].type);
assertEqual("function call", nodes[6].expression.type);
assertEqual("NOOPT", nodes[6].expression.name);
assertNotEqual(nodes[6].outVariable.id, varId);
assertEqual("SingletonNode", nodes[7].type);
var allresults = getQueryMultiplePlansAndExecutions(query, {});
for (var j = 1; j < allresults.results.length; j++) {
assertTrue(isEqual(allresults.results[0],
allresults.results[j]),
"whether the execution of '" + query +
"' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) +
" Should be: '" + JSON.stringify(allresults.results[0]) +
"' but is: " + JSON.stringify(allresults.results[j]) + "'"
);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test results
////////////////////////////////////////////////////////////////////////////////
testResults : function () {
var numbers = [ ], strings = [ ], reversed = [ ];
for (var i = 1; i <= 100; ++i) {
numbers.push(i);
strings.push("test" + i);
reversed.push("test" + (101 - i));
}
var queries = [
[ "LET values = NOOPT(RANGE(1, 100)) FOR i IN 1..100 FILTER i IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(numbers) + ") FOR i IN 1..100 FILTER i IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(numbers) + ") FOR i IN 1..100 FILTER i NOT IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(strings) + ") FOR i IN 1..100 FILTER i IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(strings) + ") FOR i IN 1..100 FILTER i NOT IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(strings) + ") FOR i IN 1..100 FILTER CONCAT('test', i) IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(strings) + ") FOR i IN 1..100 FILTER CONCAT('test', i) NOT IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(numbers) + ") FOR i IN 1..100 FILTER CONCAT('test', i) IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(numbers) + ") FOR i IN 1..100 FILTER CONCAT('test', i) NOT IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER CONCAT('test', i) IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER CONCAT('test', i) NOT IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER i IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER i NOT IN values RETURN i", numbers ]
];
queries.forEach(function(query) {
var planDisabled = AQL_EXPLAIN(query[0], { }, paramDisabled);
var planEnabled = AQL_EXPLAIN(query[0], { }, paramEnabled);
var resultDisabled = AQL_EXECUTE(query[0], { }, paramDisabled).json;
var resultEnabled = AQL_EXECUTE(query[0], { }, paramEnabled).json;
assertTrue(isEqual(resultDisabled, resultEnabled), query[0]);
assertTrue(planDisabled.plan.rules.indexOf(ruleName) === -1, query[0]);
assertTrue(planEnabled.plan.rules.indexOf(ruleName) !== -1, query[0]);
assertEqual(resultDisabled, query[1], query[0]);
assertEqual(resultEnabled, query[1], query[0]);
var allresults = getQueryMultiplePlansAndExecutions(query[0], {});
for (var j = 1; j < allresults.results.length; j++) {
assertTrue(isEqual(allresults.results[0],
allresults.results[j]),
"whether the execution of '" + query[0] +
"' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) +
" Should be: '" + JSON.stringify(allresults.results[0]) +
"' but is: " + JSON.stringify(allresults.results[j]) + "'"
);
}
});
}
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suite
////////////////////////////////////////////////////////////////////////////////
jsunity.run(optimizerRuleTestSuite);
return jsunity.done();
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End:

View File

@ -652,10 +652,10 @@ static uint64_t FastHashJsonRecursive (uint64_t hash,
}
}
}
return hash; // never reached
}
////////////////////////////////////////////////////////////////////////////////
/// @brief compute a hash value for a JSON document, using fasthash64.
/// This is slightly faster than the FNV-based hashing