mirror of https://gitee.com/bigwinds/arangodb
added optimizer rule `sort-in-values`
This commit is contained in:
parent
1942ecc9ad
commit
600c48375d
10
CHANGELOG
10
CHANGELOG
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)){
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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!)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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...
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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:
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue